Who can explain to me how to use two different datacontext ?
this is my file.xaml.cs :
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new string[] { "Female", "Male", "Animal", "Safe", "Soft", "Hard", "Space", "Landscape", "Outside", "Inside",
"City", "France", "Flower", "Sunset", "Sky", "Fireworks", "Spring", "Winter", "Summer", "Fall", "Christmas", "Halloween",
"Ghost", "Demon", "Angel", "Watermelon", "Storm", "Waterfall", "Night", "Sun","Moon", "Dog", "Cat", "Food", "Cheese",
"Kancolle", "IT", "UFO", "Travel", "Sport", "Nightmare"};
}
and here my file.xaml :
<ScrollViewer HorizontalAlignment="Left" Height="170" VerticalAlignment="Top" Width="97" Margin="10,149,0,0">
<ListBox ItemsSource="{Binding .}">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Path=.}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</ScrollViewer>
Everything work well here, but i would like add an second ScrollViewer exactly like the first one, but with another content. so the datacontexte need to be different for each of them.
thank you for giving me a little of your time.
You don't need to set different DataContexts.
Create two collection properties in your MainWindow class and set the DataContext to the window instance.
public IEnumerable<string> Collection1 { get; }
public IEnumerable<string> Collection2 { get; }
public MainWindow()
{
InitializeComponent();
Collection1 = new string[] { ... };
Collection2 = new string[] { ... };
DataContext = this;
}
Bind the ListBox's ItemSource to the collection properties:
<ListBox ItemsSource="{Binding Collection1}" ...>
...
<ListBox ItemsSource="{Binding Collection2}" ...>
Related
I have a listbox and its data template. But in its DataTemplate i have CheckedCombobox DataTemplate. Wanted to know how to set the bindings. I have tried below things to get which are items checked in child element by each listbox item. Below is the code which is not working.
<ListBox
Name="ListLayers"
ItemsSource="{Binding LstDragList}"
Height="123"
Width="283"
SelectedIndex="{Binding SelectedRow, UpdateSourceTrigger=PropertyChanged, Mode=OneWayToSource}"
>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<ComboBox
Name="childlayers"
Style="{StaticResource EditableCboStyle}"
ItemsSource="{Binding lstLayerModel}"
Text="{Binding SelectedLayers, UpdateSourceTrigger=PropertyChanged}"
ItemContainerStyle="{DynamicResource ComboboxItemContainerStyle}"
Width="200"
IsEditable="True"
IsReadOnly="False"
PreviewMouseLeftButtonDown="ComboBox_PreviewMouseLeftButtonDown"
>
<ComboBox.ItemTemplate>
<DataTemplate DataType="{x:Type local:Model}">
<CheckBox
VerticalAlignment="Center"
VerticalContentAlignment="Center"
IsChecked="{Binding IsChecked}"
Content="{Binding DisplayLayer}"
/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Models:
public class DragList : ObservableObject
{
public DragList()
{
_selectedLayers = string.Empty;
}
public string FileName { get; set; }
public ObservableCollection<Model> lstLayerModel { get; set; }
public string Text { get; set; }
private string _selectedLayers;
public string SelectedLayers
{
get { return _selectedLayers; }
set { SetAndNotify(ref _selectedLayers, value, () => this.SelectedLayers); }
}
private int _selectedLayerInx;
public int SelectedLayerInx
{
get { return _selectedLayerInx; }
set { SetAndNotify(ref _selectedLayerInx, value, () => this.SelectedLayerInx); }
}
}
my constructor viewmodel:
public ViewModel()
{
_lstLayers = new ObservableCollection<Model>();
mCheckedItems = new ObservableCollection<Model>();
_tempDragList = new List<DragList>();
_lstLayers.CollectionChanged += _lstLayers_CollectionChanged;
_lstLayers.Add(new Model
{
LayerName = "All",
LayerNumber = "",
IsChecked = true
});
_lstLayers.Add(new Model { LayerName = "Layer one", LayerNumber = "1", IsChecked = false });
_lstLayers.Add(new Model { LayerName = "Layer two", LayerNumber = "2", IsChecked = false });
_lstLayers.Add(new Model { LayerName = "Layer three", LayerNumber = "3", IsChecked = false });
_lstDragList = new List<DragList>();
_lstDragList.Add(new DragList { FileName = "Test", lstLayerModel = _lstLayers });
_lstDragList.Add(new DragList { FileName = "Test1", lstLayerModel = _lstLayers });
_tempDragList = _lstDragList;
}
It's because you need to use RelativeSource on the inner bindings to get the correct DataContext like so:
{Binding DataContext.SelectedRow, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBox}}}
There are many other articles on RelativeSource here on SO that should provide you with that you need.
I have a grid displaying the contents of an observableCollection of Persons, and two textboxes showing the properties of the selected row. A master-detail view if you will.
When assigning the observablecollection to the datacontext you can simply do this:
<Grid>
<Grid Background="Gray">
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition Height="30"></RowDefinition>
</Grid.RowDefinitions>
<igWPF:XamDataGrid Grid.Row="0" DataSource="{Binding}" IsSynchronizedWithCurrentItem="True" />
<StackPanel Grid.Row="1" Orientation="Horizontal">
<TextBox Height="21" Width="100" Margin="5,0,5,0" Text="{Binding Name}"></TextBox>
<TextBox Height="21" Width="100" Text="{Binding Age}"></TextBox>
</StackPanel>
</Grid>
</Grid>
The IsSynchronizedWithCurrentItem-property makes sure the selected item in the grid is the one that is treated in the textboxes.
I was wondering if it is possible to do this exact thing when the observablecollection is not in the datacontext directly, but rather is located in a viewmodel (which is assigned to the datacontext of the window).
public class TestViewModel: DependencyObject
{
public TestViewModel(){
Results = new ObservableCollection<Person>();
Results.Add(new Person { Age = "23", Name = "Able" });
Results.Add(new Person { Age = "25", Name = "Baker" });
}
public ObservableCollection<TagDlgmtEntity> Results
{
get { return (ObservableCollection<Person>)GetValue(ResultsProperty); }
set { SetValue(ResultsProperty, value); }
}
public static readonly DependencyProperty ResultsProperty =
DependencyProperty.Register("Results", typeof(ObservableCollection<Person>), typeof(TestViewModel), new PropertyMetadata(null));
}
I would like not to reassign a datacontext on a lower level in the visual tree, or bind with the selectedItem property of the grid.
Is it possible to use this mechanism this way?
Thanks!
Yes, that's possible. Declare your binding like this:
DataSource="{Binding Results}"
Example ViewModel
Assumption: ViewModelBase implements INotifyPropertyChanged (see my comment on the question)
public class MainWindowViewModel : ViewModelBase
{
private readonly List<Person> _results;
public MainWindowViewModel()
{
_results = new List<Person>
{
new Person {Age = "23", Name = "Able"},
new Person {Age = "25", Name = "Baker"}
};
ResultsView = CollectionViewSource.GetDefaultView(_results);
ResultsView.CurrentChanged += (sender, args) => RaisePropertyChanged(() => Name);
}
public ICollectionView ResultsView { get; private set; }
public string Name
{
get
{
return ((Person) ResultsView.CurrentItem).Name;
}
}
}
I have a class ShipmentsCollection that inherits ObservableCollection which contains shipment objects (entities). This is displayed in a listbox on my ShipmentsView UserControl. My intent is to allow a user to type into a textbox above the list and filter the list with items that contain that string, as well as filter based on several checkbox and radiobutton based options (Delivery status and orderby direction).
I have tried this several ways, but none seem very elegant or really functional. Things I have tried follows:
Put ShipmentsCollection into a CollectionViewSource and filtered via predicate. Could not figure out a good way to make the filter auto update based on user typing or option change.
Refactored as a Class that Inherits collectionViewSource and tried to declare directly in XAML but got the following error: "The specified named connection is either not found in the configuration, not intended to be used with the EntityClient provider, or not valid". Tried fixing but could not find solution that worked.
Refactored to inherit from CollectionView, implemented filter logic, in event handler in codebehind. Still trying to figure out how I can get the filter string to the event handler without naming the filtertext textbox control.
Anyone got some good ideas in regard to implementing this functionality in an MVVM design pattern. I expect to have at most 200 objects in the list, so it will not be an enormous filter operation.
Cory
Your first option would be the one I would suggest. To get the auto-filter to work based on typing, I'd do something like a SearchString property in my ViewModel, bind the textbox text to that, and set the UpdateSourceTrigger in the binding to PropertyChanged so it will call the SearchString PropertyChanged event every time a key is typed instead of waiting until the box loses focus.
XAML:
<TextBox Text="{Binding SearchString, UpdateSourceTrigger=PropertyChanged}" />
ViewModel: With the above property set to PropertyChanged, the "Set" method gets called anytime a key is typed instead of just when the textbox loses focus.
private string _searchString;
public string SearchString
{
get { return _searchString; }
set
{
if (_searchString != value)
{
_searchString = value;
OnPropertyChanged("SearchString");
}
}
}
I know this question is closed and old. but for someone like me searching for dynamic filtering, can refer to the following link
https://github.com/lokeshlal/WPFDynamicFilters
the above example creates filters for each entity based on the attribute defined on property of entity model.
As an example:
Define an attribute for filters
public class FilterAttribute : Attribute
{
public FilterAttribute() { }
public string FilterLabel { get; set; }
public object FilterValue { get; set; }
public string FilterKey { get; set; }
public Type FilterDataType { get; set; }
public bool IsDropDown { get; set; }
public string DropDownList { get; set; }
public List<object> ObjectDropDownList { get; set; }
}
Apply the above attribute in model properties
public class GridModel
{
[Filter(FilterLabel = "Id",
FilterKey = "Id",
IsDropDown = false,
FilterDataType = typeof(int))]
public int Id { get; set; }
[Filter(FilterLabel = "Name",
FilterKey = "Name",
IsDropDown = false,
FilterDataType = typeof(string))]
public string Name { get; set; }
[Filter(FilterLabel = "Country",
FilterKey = "Country",
IsDropDown = true,
FilterDataType = typeof(int),
DropDownList = "Country")]
public string Country { get; set; }
[Filter(FilterLabel = "Address",
FilterKey = "Address",
IsDropDown = false,
FilterDataType = typeof(string))]
public string Address { get; set; }
}
Define the model that will bind to the drop down type
public class Country
{
public int Id { get; set; } // id will be used for value
public string Name { get; set; } // Name will be used for display value
}
ViewModel of actual View
public class FilterViewModel
{
public ICommand CheckFiltersCommand { get; set; }
public FilterViewModel()
{
CheckFiltersCommand = new DelegateCommand(GetFilters);
GridSource = new List<GridModel>();
GridSource.Add(new GridModel() { Id = 1, Name = "Name1", Country = "Denmark" });
GridSource.Add(new GridModel() { Id = 2, Name = "Name2", Country = "India" });
GridSource.Add(new GridModel() { Id = 3, Name = "Name3", Country = "Australia" });
GridSource.Add(new GridModel() { Id = 4, Name = "Name4", Country = "India" });
GridSource.Add(new GridModel() { Id = 5, Name = "Name5", Country = "Australia" });
GridSource.Add(new GridModel() { Id = 6, Name = "Name6", Country = "Hongkong" });
FilterControlViewModel = new FilterControlViewModel();
FilterControlViewModel.FilterDetails = new List<FilterAttribute>();
foreach (var property in typeof(GridModel).GetProperties())
{
if (property.GetCustomAttributes(true).Where(attr => attr.GetType() == typeof(FilterAttribute)).Any())
{
var attribute = (FilterAttribute)property.GetCustomAttributes(true).Where(attr => attr.GetType() == typeof(FilterAttribute)).First();
FilterControlViewModel.FilterDetails.Add(attribute);
}
}
}
private void GetFilters()
{
FilterCollection = new Dictionary<string, object>();
foreach (var filter in FilterControlViewModel.FilterDetails)
{
if (filter.IsDropDown)
{
if (filter.FilterValue != null)
FilterCollection.Add(filter.FilterKey, filter.FilterValue.GetType().GetProperty("Id").GetValue(filter.FilterValue));
}
else
{
FilterCollection.Add(filter.FilterKey, filter.FilterValue);
}
}
MessageBox.Show(string.Join(", ", FilterCollection.Select(m => m.Key + ":" + Convert.ToString(m.Value)).ToArray()));
}
public List<GridModel> GridSource { get; set; }
public Dictionary<string, object> FilterCollection { get; set; }
public FilterControlViewModel FilterControlViewModel { get; set; }
}
In the above view model 'FilterControlViewModel' property will iterate all property of model and collect the filter information of the properties.
This same property will be assigned to the user control as explained in xaml file below
<Window x:Class="WPFDynamicFilters.GridWithFilters"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WPFDynamicFilters"
mc:Ignorable="d"
Title="gridwithfilters" Height="481.239" Width="858.171">
<Grid>
<Grid HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" x:Name="FilterGrid" Height="209" Width="830">
<Border BorderThickness="1" BorderBrush="Gold"/>
<local:Filter DataContext="{Binding FilterControlViewModel, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Margin="0,0,0,10" />
</Grid>
<DataGrid x:Name="DataGrid" ItemsSource="{Binding GridSource}" HorizontalAlignment="Left" Margin="10,294,0,0" VerticalAlignment="Top" Height="146" Width="830"/>
<Button x:Name="button" Content="Check Filters" HorizontalAlignment="Left" Margin="10,245,0,0" VerticalAlignment="Top" Width="110" Command="{Binding CheckFiltersCommand}"/>
</Grid>
</Window>
Filter control will take all the attributes and render the control using itemscontrol
<UserControl x:Class="WPFDynamicFilters.Filter"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WPFDynamicFilters"
mc:Ignorable="d"
d:DesignHeight="40"
>
<UserControl.Resources>
<DataTemplate x:Key="TStringTemplate">
<StackPanel FlowDirection="LeftToRight">
<TextBlock Text="{Binding FilterKey}" />
<TextBox x:Name="TxtFieldValue"
Text="{Binding FilterValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
RenderTransformOrigin="-9.3,0.5" Width="200" FontSize="16" TextAlignment="Left" VerticalAlignment="Center"/>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="TIntegerTemplate">
<StackPanel FlowDirection="LeftToRight">
<TextBlock Text="{Binding FilterKey}" />
<TextBox x:Name="IntFieldValue"
Text="{Binding FilterValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
RenderTransformOrigin="-9.3,0.5" Width="200" FontSize="16" TextAlignment="Left" VerticalAlignment="Center"/>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="TDropDownTemplate">
<StackPanel FlowDirection="LeftToRight">
<TextBlock Text="{Binding FilterKey}" />
<ComboBox
SelectedItem="{Binding FilterValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectedIndex="{Binding FilterValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ItemsSource="{Binding ObjectDropDownList, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
DisplayMemberPath="Name"
RenderTransformOrigin="-9.3,0.5" Width="200" FontSize="16" />
</StackPanel>
</DataTemplate>
<local:FilterTemplateSelector x:Key="FilterTemplateSelector"
StringTemplate="{StaticResource TStringTemplate}"
IntegerTemplate="{StaticResource TIntegerTemplate}"
DropDownTemplate="{StaticResource TDropDownTemplate}"
/>
</UserControl.Resources>
<Grid>
<ItemsControl ItemsSource="{Binding FilterDetails}" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="3" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ContentControl
Content="{Binding}"
HorizontalAlignment="Left"
ContentTemplateSelector="{StaticResource FilterTemplateSelector}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</UserControl>
Finally define the template selector
public class FilterTemplateSelector : DataTemplateSelector
{
public DataTemplate StringTemplate { get; set; }
public DataTemplate IntegerTemplate { get; set; }
public DataTemplate DropDownTemplate { get; set; }
public override DataTemplate SelectTemplate(object item,
DependencyObject container)
{
var filter = (item as FilterAttribute);
if (filter == null) return StringTemplate;
if (!filter.IsDropDown)
{
switch (filter.FilterDataType.Name.ToLower())
{
case "int32":
case "int64":
return IntegerTemplate;
case "string":
return StringTemplate;
}
}
else
{
// display drop down
switch (filter.DropDownList)
{
case "Country":
filter.ObjectDropDownList = GetDropDown.GetCountries().ToList<object>();
break;
}
return DropDownTemplate;
}
return StringTemplate;
}
}
This problem has been solved before, but I'm just not getting it with examples I'm finding online.
I have a class, lets say 'ClassA', this class has 2 string properties, 'Property1' and 'Property2' as well as an IEnumerable where 'ClassB' also has 2 properties. The list of ClassB will all be displayed in a nested treeview
I want these displayed in a treeview like so:
-ClassA[0]
ClassA.Property1
ClassA.Property2
-ClassA.ClassB Title
ClassB[0]
ClassB[1]
Etc.
+ClassA[1]
+ClassB[2]
It is my understanding that the way to accomplish this is to use HierarchicalDataTemplates however all examples I can find only tell me how to do:
-ClassA[0]
-ClassA.ClassB Title
ClassB[0]
ClassB[1]
Etc.
+ClassA[1]
+ClassB[2]
I cant figure out how to get the properties of ClassA in the template. Im thinking it'd be a DataTemplate on type ClassA but something isnt clicking.
Any help is greatly appreciated.
Thanks!
Well, I answered my own question, but I dont think it's the right way to go about this.
I used an itemtemplate on the treeview and then created another treeview inside of that template with another itemtemplate on it.
I can however, understand this when I look at it vs looking at the HierarchicalDataTemplates.
WPF:
<TreeView HorizontalAlignment="Left" Name="treeView1" VerticalAlignment="Top">
<TreeView.ItemTemplate>
<DataTemplate>
<TreeViewItem Header="{Binding FileName}">
<TextBlock Text="{Binding MetaData1}"/>
<TextBlock Text="{Binding MetaData2}"/>
<TreeViewItem ItemsSource="{Binding Mappings}" Header="Mappings">
<TreeViewItem.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="17"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Original}" Grid.Column="0"/>
<TextBlock Text="->" Grid.Column="1" Margin="3,0,3,0"/>
<TextBlock Text="{Binding Mapping}" Grid.Column="2"/>
</Grid>
</DataTemplate>
</TreeViewItem.ItemTemplate>
</TreeViewItem>
</TreeViewItem>
</DataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Classes:
public class ClassA
{
public string MetaData1 { get; set; }
public string MetaData2 { get; set; }
public string FileName { get; set; }
public List<ClassB> Mappings { get; set; }
}
public class ClassB
{
public string Original { get; set; }
public string Mapping { get; set; }
}
Quick Implementation of my data structure:
new List<ClassA>
{
new ClassA
{
FileName = "ClassA 1",
MetaData1 = "Prop 1",
MetaData2 = "Prop 2",
Mappings = new List<ClassB>
{
new ClassB
{
Original = "BProp 1",
Mapping = "BProp 2"
}
}
},
new ClassA
{
FileName = "ClassA 2",
MetaData1 = "Prop 1",
MetaData2 = "Prop 2",
Mappings = new List<ClassB>
{
new ClassB
{
Original = "BProp 1",
Mapping = "BProp 2"
}
}
}
};
If anyone knows how I should have done this better (with HierachicalDataTemplates and DataTemplates Im open to seeing that code and improving upon this.
I'm adding TreeViewItems manually in code behind and would like to use a DataTemplate to display them but can't figure out how to. I'm hoping to do something like this but the items are displayed as empty headers. What am I doing wrong?
XAML
<Window x:Class="TreeTest.WindowTree"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="WindowTree" Height="300" Width="300">
<Grid>
<TreeView Name="_treeView">
<TreeView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" />
<TextBlock Text="{Binding Path=Age}" />
</StackPanel>
</DataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Grid>
</Window>
Behind code
using System.Windows;
using System.Windows.Controls;
namespace TreeTest
{
public partial class WindowTree : Window
{
public WindowTree()
{
InitializeComponent();
TreeViewItem itemBob = new TreeViewItem();
itemBob.DataContext = new Person() { Name = "Bob", Age = 34 };
TreeViewItem itemSally = new TreeViewItem();
itemSally.DataContext = new Person() { Name = "Sally", Age = 28 }; ;
TreeViewItem itemJoe = new TreeViewItem();
itemJoe.DataContext = new Person() { Name = "Joe", Age = 15 }; ;
itemSally.Items.Add(itemJoe);
_treeView.Items.Add(itemBob);
_treeView.Items.Add(itemSally);
}
}
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
}
Your ItemTemplate is trying to render a "Name" and "Age" property in TextBlocks, but TreeViewItem doesn't have an "Age" property and you aren't setting its "Name".
Because you're using an ItemTemplate, there's no need to add TreeViewItems to the tree. Instead, add your Person instances directly:
_treeView.Items.Add(new Person { Name = "Sally", Age = 28});
The problem, of course, is that your underlying object ("Person") doesn't have any concept of hierarchy, so there's no simple way to add "Joe" to "Sally". There are a couple of more complex options:
You could try handling the TreeView.ItemContainerGenerator.StatusChanged event and wait for the "Sally" item to be generated, then get a handle to it and add Joe directly:
public Window1()
{
InitializeComponent();
var bob = new Person { Name = "Bob", Age = 34 };
var sally = new Person { Name = "Sally", Age = 28 };
_treeView.Items.Add(bob);
_treeView.Items.Add(sally);
_treeView.ItemContainerGenerator.StatusChanged += (sender, e) =>
{
if (_treeView.ItemContainerGenerator.Status != GeneratorStatus.ContainersGenerated)
return;
var sallyItem = _treeView.ItemContainerGenerator.ContainerFromItem(sally) as TreeViewItem;
sallyItem.Items.Add(new Person { Name = "Joe", Age = 15 });
};
}
Or, a better solution, you could introduce the hierarchy concept into your "Person" object and use a HierarchicalDataTemplate to define the TreeView hierarchy:
XAML:
<Window x:Class="TreeTest.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="WindowTree" Height="300" Width="300">
<Grid>
<TreeView Name="_treeView">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Subordinates}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" />
<TextBlock Text="{Binding Path=Age}" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Grid>
</Window>
CODE:
using System.Collections.Generic;
using System.Windows;
namespace TreeTest
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
var bob = new Person { Name = "Bob", Age = 34 };
var sally = new Person { Name = "Sally", Age = 28 };
_treeView.Items.Add(bob);
_treeView.Items.Add(sally);
sally.Subordinates.Add(new Person { Name = "Joe", Age = 15 });
}
}
public class Person
{
public Person()
{
Subordinates = new List<Person>();
}
public string Name { get; set; }
public int Age { get; set; }
public List<Person> Subordinates { get; private set; }
}
}
This is a more "data-oriented" way to display your hierarchy and a better approach IMHO.
It will work if you pull your DataTemplate out of the TreeView and put it into Window.Resources. Like this:
<Window.Resources>
<DataTemplate DataType={x:type Person}>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" />
<TextBlock Text="{Binding Path=Age}" />
</StackPanel>
</DataTemplate>
</Window.Resources>
Don't forget to add the right namespace before Person.