I have a tabControl that is bound to an observable collection.
In the headerTemplate, I would like to bind to a string property, and in the contentTemplate I have placed a user-control.
Here's the code for the MainWindow.xaml:
<Grid>
<Grid.Resources>
<DataTemplate x:Key="contentTemplate">
<local:UserControl1 />
</DataTemplate>
<DataTemplate x:Key="itemTemplate">
<Label Content="{Binding Path=Name}" />
</DataTemplate>
</Grid.Resources>
<TabControl IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Path=Pages}"
ItemTemplate="{StaticResource itemTemplate}"
ContentTemplate="{StaticResource contentTemplate}"/>
</Grid>
And its code behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainWindowViewModel();
}
}
public class MainWindowViewModel
{
public ObservableCollection<PageViewModel> Pages { get; set; }
public MainWindowViewModel()
{
this.Pages = new ObservableCollection<PageViewModel>();
this.Pages.Add(new PageViewModel("first"));
this.Pages.Add(new PageViewModel("second"));
}
}
public class PageViewModel
{
public string Name { get; set; }
public PageViewModel(string name)
{
this.Name = name;
}
}
So the problem in this scenario (having specified an itemTemplate as well as a controlTemplate) is that I only get one instance for the user-control, where I want to have an instance for each item that is bound to.
Try this:
<TabControl IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding Pages}">
<TabControl.Resources>
<DataTemplate x:Key="contentTemplate" x:Shared="False">
<local:UserControl1/>
</DataTemplate>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Header" Value="{Binding Name}"/>
<Setter Property="ContentTemplate" Value="{StaticResource contentTemplate}"/>
</Style>
</TabControl.Resources>
</TabControl>
Try setting
x:Shared="False"
When set to false, modifies Windows Presentation Foundation (WPF) resource retrieval behavior such that requests for a resource will create a new instance for each request, rather than sharing the same instance for all requests.
You need to override the Equals() Method of your PageViewModel class.
public override bool Equals(object obj)
{
if (!(obj is PageViewModel)) return false;
return (obj as PageViewModel).Name == this.Name;
}
Something like this should work.
Now it is looking for the same property of the value Name. Otherwise you could also add a ID Property which is unique.
Related
If I build a custom control with some controls inside it (witch also have some bindings), how can I remove the binding parts from the custom control XAML (like Text="{Binding Path=Name}" and ItemsSource="{Binding}") to make the control reusable? My guess is to create some dependency properties but I don't know how to do this and what makes it harder for me is that some bindings are inside the DataTemplate of the custom control and I can't get the instances by GetTemplateChild().
Here is a my code:
Custom Control:
public class CustomListBox : Control
{
static CustomListBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomListBox), new FrameworkPropertyMetadata(typeof(CustomListBox)));
}
}
Generics.xaml:
<Style TargetType="{x:Type local:CustomListBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomListBox}">
<ListBox x:Name="MainListBox" ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="true">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox x:Name="BindingTextBox" Text="{Binding Path=Name}"></TextBox>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
MainWindow.xaml:
<StackPanel>
<local:CustomListBox x:Name="BindingCustomListBox"></local:CustomListBox>
</StackPanel>
MainWindow.xaml.cs And Person(Sample Data) Class:
public partial class MainWindow : Window
{
public ObservableCollection<Person> PersonList { get; set; }
public MainWindow()
{
InitializeComponent();
PersonList = new ObservableCollection<Person>
{
new Person{ Name = "Person1" },
new Person{ Name = "Person2" }
};
BindingCustomListBox.DataContext = PersonList;
}
}
public class Person
{
public string Name { get; set; }
}
by removing binding parts I mean moving from custom control to Window.xaml or where ever the user wants to use the control.
I hope it's clear enough.
And an ItemsSource property to your control:
public class CustomListBox : Control
{
static CustomListBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomListBox), new FrameworkPropertyMetadata(typeof(CustomListBox)));
}
public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(CustomListBox));
}
Bind to it in your template:
<Style TargetType="{x:Type local:CustomListBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomListBox}">
<ListBox x:Name="MainListBox" ItemsSource="{TemplateBinding ItemsSource}" IsSynchronizedWithCurrentItem="true">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox x:Name="BindingTextBox" Text="{Binding Name}"></TextBox>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
...and in your view:
<local:CustomListBox x:Name="BindingCustomListBox" ItemsSource="{Binding PersonList}" />
I found a solution though not sure if a better one exists(I didn't find any after 3 days). first I added a dependency property (NameBindingStr) to enable users to define the PropertyPath of the binding:
public class CustomListBox : Control
{
static CustomListBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomListBox), new FrameworkPropertyMetadata(typeof(CustomListBox)));
}
public static readonly DependencyProperty NameBindingStrProperty =
DependencyProperty.Register(
"NameBindingStr", typeof(string), typeof(CustomListBox),
new FrameworkPropertyMetadata(""));
public string NameBindingStr
{
get { return (string)GetValue(NameBindingStrProperty); }
set { SetValue(NameBindingStrProperty, value); }
}
}
And the XAML for the custom control:
<Style TargetType="{x:Type local:CustomListBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomListBox}">
<ListBox x:Name="MainListBox" ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="true">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<local:BindTextBox TextBindingPath="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:CustomListBox}}, Path=NameBindingStr, Mode=TwoWay}"></local:BindTextBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
for the custom control's items to bind I inherited BindTextBox from TextBox:
public class BindTextBox : TextBox
{
static BindTextBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(BindTextBox), new FrameworkPropertyMetadata(typeof(BindTextBox)));
}
public static readonly DependencyProperty TextBindingPathProperty =
DependencyProperty.Register(
"TextBindingPath", typeof(string), typeof(BindTextBox),
new FrameworkPropertyMetadata("", new PropertyChangedCallback(OnTextBindingPathChanged)));
public string TextBindingPath
{
get { return (string)GetValue(TextBindingPathProperty); }
set { SetValue(TextBindingPathProperty, value); }
}
private static void OnTextBindingPathChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
BindTextBox elem = obj as BindTextBox;
var newTextBinding = new Binding((string)args.NewValue);
newTextBinding.Mode = BindingMode.TwoWay;
BindingOperations.SetBinding(elem, TextProperty, newTextBinding);
}
}
XAML:
<Style TargetType="{x:Type local:BindTextBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:BindTextBox}">
<TextBox x:Name="TemplateTextBox" Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Text, Mode=TwoWay}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The MainWindow.xaml.cs is not changed and I won't type it again(can be found in the question). I have to recall that my goal was to let the user to easily set the binding path. Now the the custom control can be used by one single code:
<local:CustomListBox x:Name="BindingCustomListBox" NameBindingStr="Name"></local:CustomListBox>
Works perfect.
Totally i am new to WPF, I need to solve the problem. Could anybody give me a sample xaml code without using code-behind.
Based on the questType (Check,radio, combo) Control need to be created based on the ObserverableCollection.
public class Order
{
public int OrderCode {get;set;}
public string Description {get;set;}
public ObserverableCollection<question> Questions{get;set;}
}
public class question
{
public string questType {get;set;}
public string Question {get;set;}
public ObserverableCollection<Answer> Answers {get;set;}
}
public class Answer
{
public string Ans{get; set;}
}
Based on the questType (Check,radio, combo)
Control need to be created based on the ObserverableCollection.
example:
1001 Pencil Gender? oMale oFemale oOther
[]Checkbox1 []Checkbox2
1002 Pen Fasting? oYes oNo
Here is how I would do it:
Code behind:
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Windows;
namespace Ans
{
public class Order
{
public int OrderCode { get; set; }
public string Description { get; set; }
public ObservableCollection<Question> Questions { get; set; }
}
public class Question
{
public string questType { get; set; }
public string Label { get; set; }
public ObservableCollection<Answer> Answers { get; set; }
}
public class Answer
{
public string Ans { get; set; }
public bool IsSelected { get; set; }
}
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
#region Order
/// <summary>
/// Order Dependency Property
/// </summary>
public static readonly DependencyProperty OrderProperty =
DependencyProperty.Register("Order", typeof(Order), typeof(MainWindow),
new FrameworkPropertyMetadata((Order)null));
/// <summary>
/// Gets or sets the Order property. This dependency property
/// indicates ....
/// </summary>
public Order Order
{
get { return (Order)GetValue(OrderProperty); }
set { SetValue(OrderProperty, value); }
}
#endregion
public MainWindow()
{
InitializeComponent();
Order = new Order()
{
Questions = new ObservableCollection<Question>()
{
new Question()
{
questType = "Combo",
Label = "Combo",
Answers = new ObservableCollection<Answer>()
{
new Answer(){Ans = "Female"},
new Answer(){Ans = "Male"}
}
},
new Question()
{
questType = "Check",
Label = "Multi",
Answers = new ObservableCollection<Answer>()
{
new Answer(){Ans = "Female"},
new Answer(){Ans = "Male"}
}
},
new Question()
{
questType = "Radio",
Label = "Radio",
Answers = new ObservableCollection<Answer>()
{
new Answer(){Ans = "Female"},
new Answer(){Ans = "Male"}
}
}
}
};
DataContext = this;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
foreach(Question q in Order.Questions)
{
Console.WriteLine( q.Label + " : " + string.Join(", " , q.Answers.Where(a=>a.IsSelected).Select(a=>a.Ans)) );
}
}
}
}
XAML:
<Window x:Class="Ans.MainWindow"
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:Ans"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<DataTemplate x:Key="ComboQuestion">
<ComboBox ItemsSource="{Binding Answers}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Ans}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
<ComboBox.ItemContainerStyle>
<Style TargetType="{x:Type ComboBoxItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
</DataTemplate>
<DataTemplate x:Key="CheckQuestion">
<ItemsControl ItemsSource="{Binding Answers}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Ans}" IsChecked="{Binding IsSelected, Mode=TwoWay}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
<DataTemplate x:Key="RadioQuestion">
<ItemsControl ItemsSource="{Binding Answers}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<RadioButton Content="{Binding Ans}" IsChecked="{Binding IsSelected, Mode=TwoWay}" GroupName="{Binding DataContext.Label, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</Window.Resources>
<Grid>
<ItemsControl ItemsSource="{Binding Order.Questions}" Grid.IsSharedSizeScope="True">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="Label"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Label}"/>
<ContentControl x:Name="ccQuestion" Grid.Column="1" Content="{Binding}" Margin="10"/>
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding questType}" Value="Combo">
<Setter TargetName="ccQuestion" Property="ContentTemplate" Value="{StaticResource ComboQuestion}"/>
</DataTrigger>
<DataTrigger Binding="{Binding questType}" Value="Check">
<Setter TargetName="ccQuestion" Property="ContentTemplate" Value="{StaticResource CheckQuestion}"/>
</DataTrigger>
<DataTrigger Binding="{Binding questType}" Value="Radio">
<Setter TargetName="ccQuestion" Property="ContentTemplate" Value="{StaticResource RadioQuestion}"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button Content="Order" Click="Button_Click" VerticalAlignment="Bottom"/>
</Grid>
</Window>
The only thing I've added to your model is IsSelected property that allows to know if this answer was selected.
The other important thing is the Radios. Their GroupName property defines the scope. So if no GroupName is set then when clicking on a radio in one question it will unselect radio in another question. I used Question label in my solution however it only works if labels are unique.
Another point is that data triggers are OK if you have 3-5 question types and if thay are only based on the questionType. However for more complex scenarios you can look for ItemTemplateSelector. It allows to write C# code that will select the template based on each item in ItemsControl.
how can I bind the Content of a ContentControl to an ObservableCollection.
The control should show an object as content only if the ObservableColelction contains exactly one object (the object to be shown).
Thanks,
Walter
This is easy. Just use this DataTemplate:
<DataTemplate x:Key="ShowItemIfExactlyOneItem">
<ItemsControl x:Name="ic">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate><Grid/></ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Count}" Value="1">
<Setter TargetName="ic" Property="ItemsSource" Value="{Binding}" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
This is used as the ContentTemplate of your ContentControl. For example:
<Button Content="{Binding observableCollection}"
ContentTemplate="{StaticResource ShowItemIfExactlyOneItem}" />
That's all you need to do.
How it works: The template normally contains an ItemsControl with no items, which is invisible and has no size. But if the ObservableCollection that is set as Content ever has exactly one item in it (Count==1), the trigger fires and sets the ItemsSource of the ItmesControl, causing the single item to display using a Grid for a panel. The Grid template is required because the default panel (StackPanel) does not allow its content to expand to fill the available space.
Note: If you also want to specify a DataTemplate for the item itself rather than using the default template, set the "ItemTemplate" property of the ItemsControl.
+1, Good question :)
You can bind the ContentControl to an ObservableCollection<T> and WPF is smart enough to know that you are only interested in rendering one item from the collection (the 'current' item)
(Aside: this is the basis of master-detail collections in WPF, bind an ItemsControl and a ContentControl to the same collection, and set the IsSynchronizedWithCurrentItem=True on the ItemsControl)
Your question, though, asks how to render the content only if the collection contains a single item... for this, we need to utilize the fact that ObservableCollection<T> contains a public Count property, and some judicious use of DataTriggers...
Try this...
First, here's my trivial Model object, 'Customer'
public class Customer
{
public string Name { get; set; }
}
Now, a ViewModel that exposes a collection of these objects...
public class ViewModel
{
public ViewModel()
{
MyCollection = new ObservableCollection<Customer>();
// Add and remove items to check that the DataTrigger fires correctly...
MyCollection.Add(new Customer { Name = "John Smith" });
//MyCollection.Add(new Customer { Name = "Mary Smith" });
}
public ObservableCollection<Customer> MyCollection { get; private set; }
}
Set the DataContext in the Window to be an instance of the VM...
public Window1()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
and here's the fun bit: the XAML to template a Customer object, and set a DataTrigger to remove the 'Invalid Count' part if (and only if) the Count is equal to 1.
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate x:Name="template">
<Grid>
<Grid Background="AliceBlue">
<TextBlock Text="{Binding Name}" />
</Grid>
<Grid x:Name="invalidCountGrid" Background="LightGray" Visibility="Visible">
<TextBlock
VerticalAlignment="Center" HorizontalAlignment="Center"
Text="Invalid Count" />
</Grid>
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Count}" Value="1">
<Setter TargetName="invalidCountGrid" Property="Visibility" Value="Collapsed" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<ContentControl
Margin="30"
Content="{Binding MyCollection}" />
</Window>
UPDATE
To get this dynamic behaviour working, there is another class that will help us... the CollectionViewSource
Update your VM to expose an ICollectionView, like:
public class ViewModel
{
public ViewModel()
{
MyCollection = new ObservableCollection<Customer>();
CollectionView = CollectionViewSource.GetDefaultView(MyCollection);
}
public ObservableCollection<Customer> MyCollection { get; private set; }
public ICollectionView CollectionView { get; private set; }
internal void Add(Customer customer)
{
MyCollection.Add(customer);
CollectionView.MoveCurrentTo(customer);
}
}
And in the Window wire a button Click event up to the new 'Add' method (You can use Commanding if you prefer, this is just as effective for now)
private void Button_Click(object sender, RoutedEventArgs e)
{
_viewModel.Add(new Customer { Name = "John Smith" });
}
Then in the XAML, without changing the Resource at all - make this the body of your Window:
<StackPanel>
<TextBlock Height="20">
<TextBlock.Text>
<MultiBinding StringFormat="{}Count: {0}">
<Binding Path="MyCollection.Count" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<Button Click="Button_Click" Width="80">Add</Button>
<ContentControl
Margin="30" Height="120"
Content="{Binding CollectionView}" />
</StackPanel>
So now, the Content of your ContentControl is the ICollectionView, and you can tell WPF what the current item is, using the MoveCurrentTo() method.
Note that, even though ICollectionView does not itself contain properties called 'Count' or 'Name', the platform is smart enough to use the underlying data source from the CollectionView in our Bindings...
I just noticed some strange behaviour of WPF's TreeView. I added both ItemContainerStyle to bind to "IsSelected" of my ViewModel and an ItemsTemplated for custom display of my data. But now the user cannot change the selected node anymore. For testing purposes I created a similar UI using ListView and Expander. This version works as excepted. Any tips why TreeView does fail?
<TreeView ItemsSource="{Binding ElementName=frame, Path=list}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}" >
<Setter Property="IsSelected" Value="{Binding Mode=TwoWay, Path=IsSelected}"/>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate>
<TreeViewItem Header="{Binding}">
<TextBlock Text="{Binding Path= Item.SomeData}"/>
</TreeViewItem>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
EDIT: My data are not hierachical. I just want to get the "collapse" feature on displaying a list. Item.SomeData is not a list. Display of data is as desired. Only selection by mouse fails!
alt text http://img682.imageshack.us/img682/3702/bildy.png
TreeViews work differently. The Items inside a HierarchicalDataTemplate are TreeViewItems and any control you specify inside the HierarchicalDataTemplate will function as its Header. So, basically you are specifying that the Items in your TreeView are TreeViewItems with TreeViewItems as their headers! Instead try this:
<HierarchicalDataTemplate ItemsSource="{Binding Items}">
<Label Content="{Binding}"/>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path= Item.SomeData}"/>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
EDIT: I could not reproduce a DataSource that produces the properties you want to bind to, so I wrote some simple code of my own that shows how it all works. Hopefully you will be able to adapt it to your needs:
<TreeView ItemsSource="{Binding}" Name="Tree">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}" >
<Setter Property="IsSelected" Value="{Binding Mode=TwoWay, Path=IsSelected}"/>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Items}">
<Label Content="{Binding Name}"/>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path= SomeData}"/>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
namespace TreeViewSpike
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
List = new List<ItemList>
{
new ItemList
{
Name = "MyList",
Items = new List<Item> {new Item("1"),
new Item("2")}
},
new ItemList
{
Name = "MySecondList",
Items = new List<Item> {new Item("3"),
new Item("4")}
}
};
Tree.DataContext = List;
List[1].IsSelected = true;
}
public List<ItemList> List { get; set; }
}
public class ItemList: INotifyPropertyChanged
{
public string Name{ get; set;}
private bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
_isSelected = value;
if (PropertyChanged != null)
PropertyChanged(this,
new PropertyChangedEventArgs("IsSelected"));
if(_isSelected)
MessageBox.Show(Name + " selected");
}
}
public List<Item> Items { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
}
public class Item
{
public string SomeData { get; set; }
public Item(string data)
{
SomeData = data;
}
}
}
I am trying to populate a treeview using mvvm but the tree does not display any data.
I have a Employee list which is a property in my vm which contains he employee data.
the xaml is as follows.
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="FontWeight" Value="Normal" />
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding EmpList}" >
<TextBlock Text="{Binding EmpName}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Is there anything i am missing here.
thanks
Hi Ian's suggested article indeed is a great read!
The trick is that you should specify how the Treeview shows its items through type specific (Hierarchical)DataTemplates. You specify these datatemplates in the Treeview's resources (or higher up the visual tree if you want to reuse them in more treeviews).
I tried to simulate what you want:
<Window x:Class="TreeViewSelection.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TreeViewSelection"
Title="Window1" Height="300" Width="300">
<StackPanel>
<TreeView ItemsSource="{Binding Enterprises}">
<TreeView.Resources>
<!-- template for showing the Enterprise's properties
the ItemsSource specifies what the next nested level's
datasource is -->
<HierarchicalDataTemplate DataType="{x:Type local:Enterprise}"
ItemsSource="{Binding EmpList}">
<Label Content="{Binding EntName}"/>
</HierarchicalDataTemplate>
<!-- the template for showing the Employee's properties-->
<DataTemplate DataType="{x:Type local:Employee}">
<Label Content="{Binding EmpName}"/>
</DataTemplate>
</TreeView.Resources>
</TreeView>
</StackPanel>
</Window>
using System.Collections.ObjectModel;
using System.Windows;
namespace TreeViewSelection
{
public partial class Window1 : Window
{
public ObservableCollection<Enterprise> Enterprises { get; set; }
public Window1()
{
InitializeComponent();
Enterprises = new ObservableCollection<Enterprise>
{
new Enterprise("Sweets4Free"),
new Enterprise("Tires4Ever")
};
DataContext = this;
}
}
public class Enterprise : DependencyObject
{
public string EntName { get; set; }
public ObservableCollection<Employee> EmpList { get; set; }
public Enterprise(string name)
{
EntName = name;
EmpList = new ObservableCollection<Employee>
{
new Employee("John Doe"),
new Employee("Sylvia Smith")
};
}
}
public class Employee : DependencyObject
{
public string EmpName { get; set; }
public Employee(string name)
{
EmpName = name;
}
}
}
Check out Josh Smith's article on exactly this topic... Helped me no end!
p.s. Looks like you're missing the DataType property on the HierarchicalDataTemplate, e.g.
<HierarchicalDataTemplate
DataType="{x:Type local:ItemType}"
ItemsSource="{Binding ...}" >