I'm having a weird problem that I can't figure out where it's coming from.
I'm binding a TreeView in WPF to an object structure. At the beginning, the TreeView is completely empty, then I create the root with empty children (from a user action) and then the children are added (also from a user action).
For some reason that I don't understand, when I add the children of the root (Parents in my example), the arrow is not added to the root element. The parents are there, because when I double-click the root, it displays the parents, but the arrow is not displayed.
At the same time, the parents have "Children" and for those there is no issue, the arrow is displayed and everything is fine.
Here's the (simplified) XAML I used to display the TreeView:
<Grid>
<Grid.Resources>
<DataTemplate x:Key="ChildTemplate"
DataType="dom:Child">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Child: " />
<TextBlock Text="{Binding Date}" />
</StackPanel>
</DataTemplate>
<HierarchicalDataTemplate x:Key="ParentTemplate"
ItemsSource="{Binding Path=Children}"
ItemTemplate="{StaticResource ChildTemplate}"
DataType="dom:Parent">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Parent: " />
<TextBlock Text="{Binding Point, StringFormat='point: {0:N2}'}" />
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate x:Key="RootTemplate"
ItemsSource="{Binding Path=Parents}"
ItemTemplate="{StaticResource ParentTemplate}"
DataType="vm:RootViewModel">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Root: " />
<TextBlock Text="{Binding Name}" Margin="5 0" />
</StackPanel>
</HierarchicalDataTemplate>
</Grid.Resources>
<TreeView ItemsSource="{Binding Roots}" ItemTemplate="{StaticResource RootTemplate}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}" BasedOn="{StaticResource MetroTreeViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
</Grid>
In terms of ViewModel, there is nothing particular. The ViewModel has a collection of Root elements (called Roots, which is bound to the ItemSource). And this Roots class has a collection of Parent called Parents which have a collection of Child called Children. Parents implement INotifyPropertyChanged which notifies when I add a child, same thing for Root.
This should really be closed, but just in case someone else looks at this:
Joel Lucsy comment solved the issue.
In addition: read only observable collection on msdn.
Related
I have a treeview control that picks item controls dynamically through a template selector. I have bound the property IsExpanded on my model to IsExpanded on TreeViewItem using a Setter. (I know this is hooked up, because if I set IsExpanded to true in the model constructor, the entire tree is expanded, as expected.)
Here's the problem. After the I load the tree, just the root node is visible (as expected), but if I set IsExpanded on a node to true, the tree is supposed to expand to the node that changed, but it does not. (I've put debug markers in to make sure the property is actually changing. )
Here is my xaml:
<Window.Resources>
<HierarchicalDataTemplate x:Key="RegularNodeTemplate"
ItemsSource="{Binding Path=Children}" >
<StackPanel Orientation="Horizontal">
<Border Width="8" Height="15" >
<Label Content="*" Padding="0" HorizontalAlignment="Right" Visibility="{Binding ModifiedCueVisibility}" />
</Border>
<TextBlock Text="{Binding Path=ModelDisplayName}"/>
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate x:Key="RootNodeTemplate"
ItemsSource="{Binding Path=Children}" >
<Grid>
<StackPanel Orientation="Horizontal">
<TextBlock FontWeight="Bold" Text="{Binding Path=ModelDisplayName}"/>
</StackPanel>
</Grid>
</HierarchicalDataTemplate>
<local:ManifestNodeTemplateSelector x:Key="manifestNodeTemplateSelector"/>
</Window.Resources>
<Grid>
<TreeView Name="TheManifestTreeView" Grid.Row="0" ItemsSource="{Binding ManifestRoot}"
ItemTemplateSelector="{StaticResource manifestNodeTemplateSelector}"
SelectedItemChanged="TreeView_SelectedItemChanged" >
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsExpanded" Value="{Binding IsExpanded}"/>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
</Grid>
Here is The code for IsExpanded:
private bool _isExpanded;
public bool IsExpanded
{
get => _isExpanded;
set
{
_isExpanded = value;
NotifyPropertyChanged("IsExpanded");
}
}
My problem is that I was expecting the tree to automatically expand to a leaf node if that node expanded. If I want the tree to expand to a node, I need to walk up the parentage and expand those.
It seems like ItemContainerStyle only applies for first level of TreeViewItem.Try to put the style inside TreeView.Resources instead
<TreeView.Resources>
<Style TargetType="TreeViewItem">
<Setter Property="IsExpanded" Value="{Binding IsExpanded}"/>
</Style>
</TreeView.Resources>
I tried to implement treeview with 4 levels using WPF/C#.Net 4.0.It loads all 4 levels but can't select 4th level and when selecting 3rd level it select group with 4th level.
Continent->Country->District->Artifacts is one structure but there is another one Continent->Products->Artifacts
Resource DataTemplates->
<DataTemplate x:Key="DistrictTemplates">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=ArtifactName}" />
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="CountryTemplate">
<TreeViewItem ItemsSource="{Binding Path=District}"
ItemTemplate=" {StaticResource DistrictTemplates}"
Header="{Binding Path=Code}">
</TreeViewItem>
</DataTemplate>
TreeView code->
<TreeView Name="treeExplorer" MouseDoubleClick="TreeView_MouseDoubleClick" SelectedItemChanged="treeExplorer_SelectedItemChanged">
<TreeViewItem Name="tviDefinition" IsExpanded="True" Header="Continent">
<TreeViewItem ItemsSource="{Binding Path=Country}" ItemTemplate="{StaticResource CountryTemplate}" Header="Country" />
</TreeViewItem>
</TreeView>
there are some other treeItems as well.I can't use inline template inside the TreeView.Resources and also im confused if can use this HierarchicalDataTemplate sine i cant call Country.Districts.ArtifactName and got two hierarchies but I can call Country.Districts() and then Districts has code property and using code i can find Artifacts.And im using datatemplates inside usercontrol.resources
How would I be able to do this?
Here's something I've written that goes 4 levels deep. It's a TreeView which shows HL7 message structure. For a quick background HL7 is a field separated message. You have a message. Each message has a Segment. Each Segment has at least one field. A field could have multiple components. A component can have subcomponents. This tree displays the HL7 message structure, where each level is a part of the HL7 message format.
For example, if there is PID segment in the message the tree would like this:
PID
...PID.1
...PID.2
......PID.2.1
......PID.2.2
......PID.2.3
......PID.2.4
.........PID.2.4.1
.........PID.2.4.2
etc...
Here is the XAML:
<TreeView x:Name="hl7Structure" ItemsSource="{Binding Path=MessageSegments}" IsEnabled="True">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type MyNamespace:MessageSegment}" ItemsSource="{Binding Path=Fields}">
<TextBox x:Name="segmentName" BorderBrush="Transparent" BorderThickness="0" Text="{Binding Path=Name}" FocusVisualStyle="{x:Null}" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type MyNamespace:MessageField}" ItemsSource="{Binding Path=Components}">
<TextBlock x:Name="fieldName" Text="{Binding Path=Name}" ToolTip="{Binding Path=Info}" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type MyNamespace:MessageComponent}" ItemsSource="{Binding Path=Subcomponents}">
<TextBlock x:Name="componentName" Text="{Binding Path=Name}" ToolTip="{Binding Path=Info}" />
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type MyNamespace:MessageSubcomponent}">
<TextBlock x:Name="subComponentName" Text="{Binding Path=Name}" ToolTip="{Binding Path=Info}" />
</DataTemplate>
</TreeView.Resources>
</TreeView>
Now the explanation of how it works. I have a base object that each HL7 message piece inherits. The ItemsSource of the TreeView is bound to that collection. Since there are 4 levels, where 3 show hierarchy and one that does not, there are 3 HierarchicalDataTemplates and 1 DataTemplate.
Think of it this way...The HL7 Message Segment, Field, and Components are tree nodes because they have children. The HL7 subcomponent is a leaf, because it has none. Each tree node gets a HierarchicalDataTemplate, but each leaf just gets a DataTemplate.
Each of HierarchicalDataTemplates know what object type to display by using the DataType property. Here is where I tell the control, the child type it's displaying. This allows me to use the base type collection and then display all the child types at their appropriate node levels.
Hope this helps.
Finally I managed to solve this.Thanks Josh and everyone.
Resource DataTemplates->
<DataTemplate x:Key="DistrictTemplates">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=ArtifactName}" />
</StackPanel>
</DataTemplate>
<HierarchicalDataTemplate x:Key="CountryTemplate" DataType="Continent.Countries" ItemsSource="{Binding Path=District}" ItemTemplate="{StaticResource DistrictTemplates}">
<TextBlock Text="{Binding Path=Code}"/>
</HierarchicalDataTemplate>
TreeView code->
<TreeView Name="treeExplorer" MouseDoubleClick="TreeView_MouseDoubleClick" SelectedItemChanged="treeExplorer_SelectedItemChanged">
<TreeViewItem Name="tviDefinition" IsExpanded="True" Header="Continent">
<TreeViewItem ItemsSource="{Binding Path=Country}" ItemTemplate="{StaticResource CountryTemplate}" Header="Countries" />
</TreeViewItem>
</TreeView>
I have a datagrid nested inside the ItemTemplate of a ListBox. I'm trying to display a tree like data structure using this. My classes are as follows.
The object in my data context contains a List<Section> named Sections, my ListBox is bound to this. Each Section contains a List<Item> named Items, the DataGrid in eac ItemTemplate is bound to this.
When I run the app, I get a null reference exception from the XAML at the line with the binding. Is there a better/alternative way of doing this, or am I missing a trick with the binding?
<Window.Resources>
<CollectionViewSource x:Key="SectionSource" /><!-- this is initialized and filled with an ObservableCollection<Section> Sections when the window loads-->
</Window.Resources>
<ListBox x:Name="lstIngredients" ItemsSource="{Binding Source={StaticResource SectionSource}}">
<ListBox.ItemTemplate>
<DataTemplate>
<DataTemplate.Resources>
<CollectionViewSource x:Key="itemsSource" Source="{Binding Items}"/>
</DataTemplate.Resources>
<DataGrid x:Name="dgItems" IsReadOnly="false" AutoGenerateColumns="False" SelectionMode="Single" SelectionUnit="FullRow" IsSynchronizedWithCurrentItem="True"
DataContext="{Binding}"
ItemsSource="{Binding Source={StaticResource Items}}"
EnableRowVirtualization="false"
VirtualizingStackPanel.VirtualizationMode="Standard"
<DataGrid.Columns>
<DataGridTemplateColumn Width="2*" Header="{lex:LocText ChickenPing.Shared:Strings:Measurement}">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock x:Name="quantity" Text="{Binding Measurement}" TextTrimming="CharacterEllipsis" TextAlignment="Left"/>
<!-- Null reference on this line caused by the binding. If I set this to any DependencyProperty on an Item object, I get a null reference-->
</DataTemplate>
This need to be path
ItemsSource="{Binding Source={StaticResource Items}}"
ItemsSource="{Binding Path=PropertyThatIsCollection}"
And delete the DataContext line
I eventually tracked this down to an event which was set in one of the TemplateColumns. Switching the event from
<TextBlock x:Name="quantity" Text="{Binding Measurement}" GotFocus="txt_GotFocus" />
to
<Style x:Key="FocusableTextbox" TargetType="{x:Type TextBox}">
<EventSetter Event="GotFocus" Handler="txt_GotFocus" />
</Style>
...
<TextBlock x:Name="quantity" Text="{Binding Measurement}" Style={StaticResource FocusableTextbox} />
I have a tree view with bind to some ObservableCollection, which is filled with some asyncronous function. The problem is that it's not getting updated on UI after the asyncronous command worked and updated the source collection (added some child nodes or something like that). My XAML looks like this:
<StackPanel>
<StackPanel.Resources>
<HierarchicalDataTemplate x:Key="CheckBoxItemTemplate"
ItemsSource="{Binding Children, Mode=TwoWay}">
<StackPanel Orientation="Horizontal">
<CheckBox Focusable="False" IsChecked="{Binding IsChecked, Mode=TwoWay}"
VerticalAlignment="Center" />
<ContentPresenter Content="{Binding Node.Caption, Mode=OneWay}" />
</StackPanel>
</HierarchicalDataTemplate>
</StackPanel.Resources>
<TreeView Style="{DynamicResource FormItem}" ItemsSource="{Binding Nodes, Mode=TwoWay}"
ItemTemplate="{StaticResource CheckBoxItemTemplate}" >
</TreeView>
</StackPanel>
Any suggestions?
Are you sure you're updating your UI on the correct thread?
Sorry for bothering you, guys. It was a silly mistake of mine. I just didn't set DataContexts of one control and of the window using this control. So it turned out that they had different contexts, because my ViewModel isn't a singleton. I should have been more careful about it.
I've got a set of ViewModels that I'm binding to the ItemsSource property of a TabControl. Let's call those ViewModels AViewModel, BViewModel, and CViewModel. Each one of those needs to have a different ItemTemplate (for the header; because they each need to show a different icon) and a different ContentTemplate (because they have very different interaction models).
What I'd like is something like this:
Defined in Resource.xaml files somewhere:
<DataTemplate x:Key="ItemTemplate" DataType="{x:Type AViewModel}">
...
</DataTemplate>
<DataTemplate x:Key="ItemTemplate" DataType="{x:Type BViewModel}">
...
</DataTemplate>
<DataTemplate x:Key="ItemTemplate" DataType="{x:Type CViewModel}">
...
</DataTemplate>
<DataTemplate x:Key="ContentTemplate" DataType="{x:Type AViewModel}">
...
</DataTemplate>
<DataTemplate x:Key="ContentTemplate" DataType="{x:Type BViewModel}">
...
</DataTemplate>
<DataTemplate x:Key="ContentTemplate" DataType="{x:Type CViewModel}">
...
</DataTemplate>
Defined separately:
<TabControl ItemTemplate="[ Some way to select "ItemTemplate" based on the type ]"
ContentTemplate="[ Some way to select "ContentTemplate" based on the type ]"/>
Now, I know that realistically, each time I define a DataTemplate with the same key the system is just going to complain. But, is there something I can do that's similar to this that will let me put a DataTemplate into a TabControl based on a name and a DataType?
The easiest way would be to use the automatic template system, by including the DataTemplates in the resources of a ContentControl. The scope of the templates are limited to the element they reside within!
<TabControl ItemsSource="{Binding TabViewModels}">
<TabControl.ItemTemplate>
<DataTemplate>
<ContentControl Content="{Binding}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type AViewModel}">
...
</DataTemplate>
<DataTemplate DataType="{x:Type BViewModel}">
...
</DataTemplate>
<DataTemplate DataType="{x:Type CViewModel}">
...
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.Resources>
<DataTemplate DataType="{x:Type AViewModel}">
...
</DataTemplate>
<DataTemplate DataType="{x:Type BViewModel}">
...
</DataTemplate>
<DataTemplate DataType="{x:Type CViewModel}">
...
</DataTemplate>
</TabControl.Resources>
</TabControl>
You can remove the x:Key :) This will automatically apply the template when the given type is encountered (probably one of the most powerful and underused features of WPF, imo.
This Dr. WPF article goes over DataTemplates pretty well. The section you'll want to pay attention to is "Defining a Default Template for a Given CLR Data Type".
http://www.drwpf.com/blog/Home/tabid/36/EntryID/24/Default.aspx
If this doesn't help your situation, you might be able to do something close to what you are looking for using a Style (ItemContainerStyle) and setting the content and header based on the type using a data trigger.
The sample below hinges on your ViewModel having a property called "Type" defined pretty much like this (easily put in a base ViewModel if you have one):
public Type Type
{
get { return this.GetType(); }
}
So as long as you have that, this should allow you to do anything you want. Note I have "A Header!" in a textblock here, but that could easily be anything (icon, etc).
I've got it in here two ways... one style applies templates (if you have a significant investment in these already) and the other just uses setters to move the content to the right places.
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300"
xmlns:local="clr-namespace:WpfApplication1">
<Window.Resources>
<CompositeCollection x:Key="MyCollection">
<local:AViewModel Header="A Viewmodel" Content="A Content" />
<local:BViewModel Header="B ViewModel" Content="B Content" />
</CompositeCollection>
<DataTemplate x:Key="ATypeHeader" DataType="{x:Type local:AViewModel}">
<WrapPanel>
<TextBlock>A Header!</TextBlock>
<TextBlock Text="{Binding Header}" />
</WrapPanel>
</DataTemplate>
<DataTemplate x:Key="ATypeContent" DataType="{x:Type local:AViewModel}">
<StackPanel>
<TextBlock>Begin "A" Content</TextBlock>
<TextBlock Text="{Binding Content}" />
</StackPanel>
</DataTemplate>
<Style x:Key="TabItemStyle" TargetType="TabItem">
<Style.Triggers>
<!-- Template Application Approach-->
<DataTrigger Binding="{Binding Path=Type}" Value="{x:Type local:AViewModel}">
<Setter Property="HeaderTemplate" Value="{StaticResource ATypeHeader}" />
<Setter Property="ContentTemplate" Value="{StaticResource ATypeContent}" />
</DataTrigger>
<!-- Just Use Setters Approach -->
<DataTrigger Binding="{Binding Path=Type}" Value="{x:Type local:BViewModel}">
<Setter Property="Header">
<Setter.Value>
<WrapPanel>
<TextBlock Text="B Header!"></TextBlock>
<TextBlock Text="{Binding Header}" />
</WrapPanel>
</Setter.Value>
</Setter>
<Setter Property="Content" Value="{Binding Content}" />
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<TabControl ItemContainerStyle="{StaticResource TabItemStyle}" ItemsSource="{StaticResource MyCollection}" />
</Grid>
HTH, Anderson
One way would be to use DataTemplateSelectors and have each one resolve the resource from a separate ResourceDictionary.
In this example I use DataTemplates in the resources section of my TabControl for each view model I want to display in the tab items.
In this case I map ViewModelType1 to View1 and ViewModelType2 to View2.
The view models will be set as DataContext object of the views automatically.
For displaying the tab item header, I use an ItemTemplate.
The view models I bind to are of different types, but derive from a common base class ChildViewModel that has a Title property. So I can set up a binding to pick up the title to display it in the tab item header.
In addition I display a "Close" Button in the tab item header. If you do not need that, just remove the button from the example code so you just have the header text.
The contents of the tab items are rendered with a simple ItemTemplate which displays the view in a content control with Content="{Binding}".
<UserControl ...>
<UserControl.DataContext>
<ContainerViewModel></ContainerViewModel>
</UserControl.DataContext>
<TabControl ItemsSource="{Binding ViewModels}"
SelectedItem="{Binding SelectedViewModel}">
<TabControl.Resources>
<DataTemplate DataType="{x:Type ViewModelType1}">
<View1/>
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModelType2}">
<View2/>
</DataTemplate>
</TabControl.Resources>
<TabControl.ItemTemplate>
<DataTemplate>
<DockPanel>
<TextBlock Text="{Binding Title}" />
<Button DockPanel.Dock="Right" Margin="5,0,0,0"
Visibility="{Binding RemoveButtonVisibility}"
Command="{Binding DataContext.CloseItemCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TypeOfContainingView}}}"
>
<Image Source="/Common/Images/ActiveClose.gif"></Image>
</Button>
</DockPanel>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<ContentControl Content="{Binding}"/>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</UserControl>
The user control which contains the tab control has a container view model of type ContainerViewModel as DataContext. Here I have a collection of all the view models displayed in the tab control. I also have a property for the currently selected view model (tab item).
This is a shortened version of my container view model (I skipped the change notification part).
public class ContainerViewModel
{
/// <summary>
/// The child view models.
/// </summary>
public ObservableCollection<ChildViewModel> ViewModels {get; set;}
/// <summary>
/// The currently selected child view model.
/// </summary>
public ChildViewModel SelectedViewModel {get; set;}
}
Josh Smith uses exactly this technique (of driving a tab control with a view model collection) in his excellent article and sample project WPF Apps With The Model-View-ViewModel Design Pattern. In this approach, because each item in the VM collection has a corresponding DataTemplate linking the View to the VM Type (by omitting the x:Key as Anderson Imes correctly notes), each tab can have a completely different UI. See the full article and source code for details.
The key parts of the XAML are:
<DataTemplate DataType="{x:Type vm:CustomerViewModel}">
<vw:CustomerView />
</DataTemplate>
<DataTemplate x:Key="WorkspacesTemplate">
<TabControl
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding}"
ItemTemplate="{StaticResource ClosableTabItemTemplate}"
Margin="4"
/>
There is one downside - driving a WPF TabControl from an ItemsSource has performance issues if the UI in the tabs is big/complex and therefore slow to draw (e.g., datagrids with lots of data). For more on this issue, search SO for "WPF VirtualizingStackPanel for increased performance".