I have TreeView structure defined as below:
<TreeView ItemsSource="{Binding RootCollection}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding ChildNodes}">
<TextBlock Foreground="Green" Text="{Binding Text}" />
<HierarchicalDataTemplate.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding ChildNodes}">
<TextBlock Foreground="Black" Text="{Binding Text}" />
</HierarchicalDataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
as you can see root elements have green foreground and all childrens are black
Now I want to change the foreground color of childrens from black to red on those childnodes whose parents have "Failed" property set to true.
For example if RootCollection[0].Failed = true then all RootCollection[0].ChildNodes on treeview should become red (rootnode stays green, and there wouldn't be any grandchildnodes in this case so it doesn't matter what will happen to them).
I tried setting DataTrigger Styles wherever I could and tried binding to RelativeSource in many different ways but I couldn't handle it.
Any help will be appreciated :)
DataContext is something like this:
public class MyTreeNode : INotifyPropertyChanged
{
private string text;
private ObservableCollection<MyTreeNode> childNodes;
private bool failed;
public string Text
{
get
{
return this.text;
}
set
{
this.text = value;
Changed("Text");
}
}
public bool Failed
{
get
{
return this.failed;
}
set
{
this.failed = value;
Changed("Text");
}
}
public ObservableCollection<MyTreeNode> ChildNodes
{
get
{
return this.childNodes;
}
set
{
this.childNodes = value;
Changed("ChildNodes");
}
}
private void string Changed(string propertyName)
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
public ObservableCollection<MyTreeNode> RootCollection { get; set; }
public MyClass()
{
this.RootCollection = new ObservableCollection<MyTreeNode>
{
new MyTreeNode
{
Text = "first",
Failed = true,
ChildNodes = new ObservableCollection<MyTreeNode>
{
new MyTreeNode { Text = "first first" },
new MyTreeNode { Text = "first second" }
}
},
new MyTreeNode
{
Text = "second",
ChildNodes = new ObservableCollection<MyTreeNode>
{
new MyTreeNode { Text = "second first" }
}
}
};
}
it's simplified because my real code uses many custom classes.
Here's view structure from Snoop:
http://i.imgur.com/XMF6rLN.png
(I need to set property marked in blue based on property marked in red)
I can't check if this works at the moment, so it might not, but it looks about right. Try this:
<HierarchicalDataTemplate ItemsSource="{Binding ChildNodes}">
<TextBlock Text="{Binding Text}">
<TextBlock.Style>
<Style>
<Setter Property="Foreground" Value="Black" />
<Style.Triggers>
<DataTrigger Binding="{Binding Failed, RelativeSource={
RelativeSource AncestorType={x:Type TreeViewItem}, AncestorLevel=1}}" Value="True">
<Setter Property="Foreground" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</HierarchicalDataTemplate>
If it doesn't work, you could experiment a little with the AncestorLevel property, setting it to 2 or 3, or even removing it. Let me know how it goes.
you can use template selector instead
//you put this in your App.xaml
<Application.Resources>
<HierarchicalDataTemplate x:Key="failed" ItemsSource="{Binding ChildNodes}">
<TextBlock Name="pTxt" Foreground="Red" Text="{Binding Text}" >
</TextBlock>
<HierarchicalDataTemplate.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding ChildNodes}">
<TextBlock Foreground="Black" Text="{Binding Text}" />
</HierarchicalDataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate x:Key="succeded" ItemsSource="{Binding ChildNodes}">
<TextBlock Name="pTxt" Foreground="Green" Text="{Binding Text}" >
</TextBlock>
<HierarchicalDataTemplate.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding ChildNodes}">
<TextBlock Foreground="Black" Text="{Binding Text}" />
</HierarchicalDataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</Application.Resources>
// and here in your Window.cs
public class ResourceInstDataTemplateSelector : DataTemplateSelector
{
public override DataTemplate
SelectTemplate(object item, DependencyObject container)
{
FrameworkElement element = container as FrameworkElement;
if (element != null && item != null && item is MyTreeNode)
{
MyTreeNode treeNode = item as MyTreeNode;
DataTemplate temp = null;
if (treeNode.Failed)
temp = App.Current.Resources["failed"] as HierarchicalDataTemplate;
else
temp = App.Current.Resources["succeded"] as HierarchicalDataTemplate;
return temp;
}
return null;
}
}
// and this in you window.xaml.cs
<TreeView Name="treeView" ItemsSource="{Binding RootCollection}">
<TreeView.ItemTemplateSelector>
<myApp:ResourceInstDataTemplateSelector></myApp:ResourceInstDataTemplateSelector>
</TreeView.ItemTemplateSelector>
</TreeView>
I tried it and it's work perfectly
Hope this help
Related
I have ComboBox with custom ComboBoxItem. ComboBoxItem contains TextBlock and CheckBox. CheckBox bind to bool property in my ViewModel. When i click to CheckBox, all works fine, but when i click to TextBlock - ComboBox Closing! I need 'MultiSelection' mode inside my ComboBox
xaml:
<ComboBox ItemsSource="{Binding Elements}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding IsSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<TextBlock Text="{Binding Caption}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
VM:
public class SomeClass : INotifyPropertyChanged
{
private string m_caption;
private bool m_isSelected;
public string Caption
{
get { return m_caption; }
set
{
if (m_caption != value)
{
m_caption = value;
RaisePropertyChanged("Caption");
}
}
}
public bool IsSelected
{
get { return m_isSelected; }
set
{
if (m_isSelected != value)
{
m_isSelected = value;
RaisePropertyChanged("IsSelected");
}
}
}
public SomeClass(string _caption)
{
if (string.IsNullOrWhiteSpace(_caption))
m_caption = Guid.NewGuid().ToString();
Caption = _caption;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string _property)
{
PropertyChangedEventHandler hanlder = PropertyChanged;
if (hanlder != null)
{
hanlder.Invoke(null, new PropertyChangedEventArgs(_property));
}
}
}
Without a lot of tweaking, you can't really display a multi-selection in a closed combobox, so I guess this part is not really needed. So I propose you use a button and a popup, where the popup is containing a listview with multi selection enabled for your items.
As Button, I use DropDownButton from Extended WPF Toolkit, you can also implement it differently.
Introducing the Extended WPF Toolkit namespace:
xmlns:xt="http://schemas.xceed.com/wpf/xaml/toolkit"
The actual thing:
<xt:DropDownButton Content="Elements Selection" VerticalAlignment="Top" HorizontalAlignment="Left" MinWidth="100">
<xt:DropDownButton.DropDownContent>
<ListView ItemsSource="{Binding Elements}" SelectionMode="Multiple" MinWidth="100">
<ListView.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource AncestorType=ListViewItem}}">
<CheckBox.Content>
<TextBlock Text="{Binding Caption}"/>
</CheckBox.Content>
</CheckBox>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</Style>
</ListView.ItemContainerStyle>
</ListView>
</xt:DropDownButton.DropDownContent>
</xt:DropDownButton>
Another important little detail to reflect the property changes in WPF: your property changed notifications need a source instead of null!
protected void RaisePropertyChanged(string _property)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler.Invoke(this, new PropertyChangedEventArgs(_property));
}
}
Bind the Content property of the CheckBox to your Caption property and use an ItemContainerStyle that stretches the ComboBoxItem container:
<ComboBox ItemsSource="{Binding Elements}">
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ComboBox.ItemContainerStyle>
<ComboBox.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Content="{Binding Caption}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
I'm binding to a ListBox, and within the ListBox, another ListBox.
My model looks like
public interface ICriteriaDetail
{
string Title { get; }
int NumberOfEvents { get; }
ICriteriaDetail ChildCriteria { get; }
}
So, I'm using a recursive approach for the children (not a list).
When I bind to my ListView, I get the items in a list, one under the other.
The issue is, the child item is not showing at all!
The dummy data
public static IEnumerable<ICriteriaDetail> GetCriteriaList()
{
var list = new List<CompoundCriteria.Ui.Model.Interfaces.ICriteriaDetail>();
list.Add(GetCriteriaDetail("My Title", 5, false));
list.Add(GetCriteriaDetail("Other", 3, false));
list.Add(GetCriteriaDetail("Biggy", 8, true));
var child = new CompoundCriteria.Ui.Model.CriteriaDetail.CriteriaDetail("childAgain", 43, null);
var child2 = new CompoundCriteria.Ui.Model.CriteriaDetail.CriteriaDetail("childAgainAgain", 13, child);
list.Add(new CompoundCriteria.Ui.Model.CriteriaDetail.CriteriaDetail("Really big", 86, new CompoundCriteria.Ui.Model.CriteriaDetail.CriteriaDetail("smaller", 15, child2)));
return list;
}
private static ICriteriaDetail GetCriteriaDetail(string title, int events, bool hasChild)
{
if (!hasChild)
return new CompoundCriteria.Ui.Model.CriteriaDetail.CriteriaDetail(title, events, null);
var child = new CompoundCriteria.Ui.Model.CriteriaDetail.CriteriaDetail("child" + title, 13 + events, null);
return new CompoundCriteria.Ui.Model.CriteriaDetail.CriteriaDetail(title, events, child);
}
The ViewModel
public MainWindowViewModel()
{
this.Criterias = DatasourceMockup.GetCriteriaList();
}
private IEnumerable<ICriteriaDetail> _criterias;
public IEnumerable<ICriteriaDetail> Criterias
{
get { return _criterias; }
set { _criterias = value; }
}
And the ListBox in the XAML
<ListBox ItemsSource="{Binding Criterias}" >
<ListBox.Resources>
<ControlTemplate x:Key="con">
<StackPanel Orientation="Horizontal">
<StackPanel Margin="15">
<TextBlock Text="{Binding Title}" />
<TextBlock Text="Events: ">
<Run Text="{Binding NumberOfEvents}" />
</TextBlock>
</StackPanel>
<ListBox ItemsSource="{Binding ChildCriteria} Visibility="{Binding ChildCriteria, Converter={StaticResource HideIfNull}}">">
<ListBox.Resources>
<DataTemplate DataType="{x:Type cDetail:CriteriaDetail}">
<TextBlock Text="{Binding Title}" />
</DataTemplate>
</ListBox.Resources>
</ListBox>
</StackPanel>
</ControlTemplate>
<HierarchicalDataTemplate DataType="{x:Type cDetail:CriteriaDetail}">
<Control Template="{StaticResource con}" />
</HierarchicalDataTemplate>
</ListBox.Resources>
</ListBox>
After reading TreeView, HierarchicalDataTemplate and recursive Data it appears that HierarchicalDataTemplate will suffice (no need for a DataTemplate as well) but, I'm lost as to why I'm not seeing the result I expected (even if the children are not in the desired place, I'd still hope to see them)
EDIT
I have just added a converter which makes the ListBox (the one inside the ControlTemplate) hidden if the bound data is null. I have an outline (an empty ListBox) for just 1 of the 3 (which is correct) but it doesn't bind any of the content (the Title). The output window shows nothing of any use...
Please note, there is no limit of chidren. In the example above, there is a parent child relationship, but it could be parent-child-child-child etc
1) It is hard to understand your purpose , why ListBox, if you have ChildCriteria in model, not IEnumerable<ICriteriaDetail>?
2) Also you have binding errors, i changed xaml to
<ListBox ItemsSource="{Binding Criterias}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<StackPanel Margin="15">
<TextBlock Text="{Binding Title}" />
<TextBlock Text="Events: ">
<Run Text="{Binding NumberOfEvents}" />
</TextBlock>
</StackPanel>
<TextBox Text="{Binding Path=DataContext.ChildCriteria.Title,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type ListBoxItem},
AncestorLevel=1}}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
and got
EDIT1:
1) I changed data model to
public class CriteriaDetail : ICriteriaDetail
{
public CriteriaDetail(string title, int numberOfEvents, IEnumerable<ICriteriaDetail> childCriteria)
{
Title = title;
NumberOfEvents = numberOfEvents;
ChildCriteria = childCriteria;
}
public string Title { get; set; }
public int NumberOfEvents { get; set; }
public IEnumerable<ICriteriaDetail> ChildCriteria { get; set; }
}
2) Changed xaml to
<TreeView ItemsSource="{Binding Criterias}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding ChildCriteria}">
<TextBlock Foreground="Red" Text="{Binding Title}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
3) Changed GetCriteriaDetail to
private static ICriteriaDetail GetCriteriaDetail(string title, int events, bool hasChild)
{
if (!hasChild)
return new CriteriaDetail(title, events, null);
var child3 = new CriteriaDetail("child3" + title, 13 + events, null);
var child2 = new CriteriaDetail("child2" + title, 13 + events, new ICriteriaDetail[] { child3 });
var child1 = new CriteriaDetail("child1" + title, 13 + events, new ICriteriaDetail[] { child2 });
return new CriteriaDetail(title, events, new ICriteriaDetail[] {child1});
}
and got this. I think it looks more like you want
I am currently following this guide for setting up a TreeView with checkboxes. In my code, the tree "FooViewModel" is initiated in my MainViewModel and bound to the TreeView as an ItemsSource. I want to be able to subscribe to some event in the MainViewModel that will trigger when something is checked or unchecked. That way I can iterate through the "FooViewModel" and check which nodes have IsChecked = True. How do I create this event binding?
This is the code I have:
<Style x:Key="TreeViewItemStyle" TargetType="TreeViewItem">
<Setter Property="IsExpanded" Value="False" />
<Setter Property="IsSelected" Value="{Binding IsInitiallySelected, Mode=OneTime}" />
<Setter Property="KeyboardNavigation.AcceptsReturn" Value="True" />
<Setter Property="xn:VirtualToggleButton.IsVirtualToggleButton" Value="True" />
<Setter Property="xn:VirtualToggleButton.IsChecked" Value="{Binding IsChecked}" />
<Setter Property="Focusable" Value="False" />
</Style>
<xn:TreeView ItemsSource="{Binding CollectionFooViewModel}" ItemContainerStyle="{StaticResource TreeViewItemStyle}">
<xn:TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children, Mode=OneTime}">
<StackPanel Orientation="Horizontal">
<CheckBox Focusable="False" IsChecked="{Binding IsChecked}" VerticalAlignment="Center"/>
<ContentPresenter Content="{Binding Name, Mode=OneTime}" Margin="2,0"/>
</StackPanel>
</HierarchicalDataTemplate>
</xn:TreeView.ItemTemplate>
</xn:TreeView>
I figured if there's a way to bind "IsChecked" to two properties (one in FooViewModel, another in MainViewModel) I would have my answer.
Lots of ways to achieve this. One would be some kind of a pub/sub (messaging) implementation or maybe just bunch of Action delegates? Something like...
MainWindow
<Window x:Class="WpfApplication1.View.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Height="300"
Width="250">
<TreeView ItemsSource="{Binding CollectionFooViewModel}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children, Mode=OneTime}">
<StackPanel Orientation="Horizontal">
<CheckBox Focusable="False"
IsChecked="{Binding IsChecked}"
VerticalAlignment="Center"/>
<ContentPresenter Content="{Binding Name, Mode=OneTime}"
Margin="2,0"/>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Window>
DataContext
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
Action<MyItem> action = item => Console.WriteLine(#"MyItem was clicked");
CollectionFooViewModel = new ObservableCollection<MyItem>()
{
new MyItem()
{
Name = "MyItem1",
Children = new List<MyItem>()
{
new MyItem()
{
Name = "MySubItem1",
IsChecked = false,
Action = item => Console.WriteLine(#"{0} invoked action", item.Name)
},
new MyItem()
{
Name = "MySubItem2",
IsChecked = true,
Action = item => Console.WriteLine(#"{0} state is {1} ", item.Name, item.IsChecked)
},
},
Action = action
}
};
}
public ObservableCollection<MyItem> CollectionFooViewModel { get; set; }
}
public class MyItem : ViewModelBase
{
private bool _isChecked;
public string Name { get; set; }
public bool IsChecked
{
get { return _isChecked; }
set
{
_isChecked = value;
if (Action != null)
Action.BeginInvoke(this, null, null);
}
}
public IEnumerable<MyItem> Children { get; set; }
public Action<MyItem> Action { get; set; }
}
Which gives you the following...
...and spits this out to console when clicked in order.
MyItem was clicked
MySubItem1 invoked action
MySubItem2 state is False
Of course, in your case, you might want to pass concrete method to delegate.
Try adding "OnPropertyEventChanged" method call in the setter for your model, here is an example of what I mean:
https://msdn.microsoft.com/en-us/library/ms743695%28v=vs.110%29.aspx
My problem:
I have a listbox with owners of dogs, and i have a listbox with dogs. I want to modify the dogs listbox itemtemplate as the following: DogName(textblock)+DogKind(textblock)+Owners(combobox).The first two was successful, but i cant add the existing owners to the combobox. If i give a name to my combobox like :
<ComboBox x:Name="mycombo" />
i cant see the mycombo variable in the c# code.
The XAML:
<Window x:Class="CodeFirst.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sajat="clr-namespace:CodeFirst"
Title="MainWindow" Height="557.638" Width="721.294"
>
<Grid x:Name="grid1">
<ListBox x:Name="listbox2" HorizontalAlignment="Left" Height="313" Margin="338,10,0,0" VerticalAlignment="Top" Width="250">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}"/>
<TextBlock Text=", "/>
<TextBlock Text="{Binding Path=Kind}"/>
<ComboBox />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
How can i give the itemsource to the combobox, or how can i reach to add the owners?
If you use the DataContext, you can set the Binding like this:
<ComboBox ItemsSource="{Binding Path=DataContext.MyItemsSource, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"></ComboBox>
First of all, in order to work with WPF or other XAML-based technologies, you must understand that
UI is not Data. Data is Data. UI is UI.
This means that you should not manipulate any ComboBox or any other UI elements in code, in order to populate them with data, but instead create a ViewModel and bind these objects to that.
In this example, the Window itself is used as ViewModel because it's a simple example, but you should consider moving all application logic to a separate class:
<Window x:Class="MiscSamples.UIisNotData"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="UIisNotData" Height="300" Width="300">
<UniformGrid Rows="1" Columns="2">
<DockPanel>
<TextBlock Text="Owners:" DockPanel.Dock="Top" FontWeight="Bold" TextAlignment="Center" Margin="2"/>
<Button Content="Add" Width="80" DockPanel.Dock="Bottom" Margin="2" Click="AddOwner"/>
<ListBox ItemsSource="{Binding Owners}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding Name}" x:Name="block"/>
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" Visibility="Collapsed" x:Name="box"/>
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource FindAncestor, AncestorType=ListBoxItem}}" Value="True">
<Setter TargetName="block" Property="Visibility" Value="Collapsed"/>
<Setter TargetName="box" Property="Visibility" Value="Visible"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DockPanel>
<DockPanel>
<TextBlock Text="Dogs:" DockPanel.Dock="Top" FontWeight="Bold" TextAlignment="Center" Margin="2"/>
<ListBox ItemsSource="{Binding Dogs}" HorizontalContentAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate>
<DockPanel>
<ComboBox ItemsSource="{Binding DataContext.Owners, RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}"
SelectedItem="{Binding Owner}" DisplayMemberPath="Name"
DockPanel.Dock="Right" Width="100"/>
<TextBlock>
<Run Text="{Binding Name}"/>
<Run Text=", "/>
<Run Text="{Binding Kind}"/>
</TextBlock>
</DockPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DockPanel>
</UniformGrid>
</Window>
Code Behind (This code should be placed in a ViewModel):
public partial class UIisNotData : Window
{
public ObservableCollection<Owner> Owners { get; set; }
public ObservableCollection<string> Kinds { get; set; }
public ObservableCollection<Dog> Dogs { get; set; }
public UIisNotData()
{
InitializeComponent();
Owners = new ObservableCollection<Owner>
{
new Owner() {Name = "Jack"},
new Owner() {Name = "Mike"},
new Owner() {Name = "Kirk"},
new Owner() {Name = "John"},
};
Kinds = new ObservableCollection<string>
{
"Affenpinscher",
"Afghan Hound",
"Airedale Terrier",
"Akita"
//.. All the rest of dog Breeds taken from http://www.petmd.com/dog/breeds?breed_list=az#.UVsQKpPcmQo
};
Dogs = new ObservableCollection<Dog>
{
new Dog() {Name = "Bobby", Kind = Kinds[0], Owner = Owners[0]},
new Dog() {Name = "Fido", Kind = Kinds[1], Owner = Owners[1]},
new Dog() {Name = "Toby", Kind = Kinds[2], Owner = Owners[2]}
};
DataContext = this;
}
private void AddOwner(object sender, RoutedEventArgs e)
{
Owners.Add(new Owner(){Name = "New Owner"});
}
}
Data Model:
public class Owner : PropertyChangedBase
{
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged("Name");
}
}
}
public class Dog: PropertyChangedBase
{
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged("Name");
}
}
private Owner _owner;
public Owner Owner
{
get { return _owner; }
set
{
_owner = value;
OnPropertyChanged("Owner");
}
}
private string _kind;
public string Kind
{
get { return _kind; }
set
{
_kind = value;
OnPropertyChanged("Kind");
}
}
}
PropertyChangedBase Class:
public class PropertyChangedBase:INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Result:
There are 3 important aspects you need to consider about this example:
I am in no way manipulating UI elements in code. That's completely unnecessary most of the time in WPF.
The classes from the Data Model implement INotifyPropertyChanged in order to support 2-way binding in WPF.
The Collections are of type ObservableCollection<T> in order to support automatic notification when elements are added/removed from the collection (in order to automatically update the ListBoxes, etc).
Another thing you may notice is that the XAML elements in my example have no specific size or Margin values. Things like Margin="338,10,0,0" is usually what you get from the Visual Studio designer and indicates a poorly structured layout. I recommend you look at the Layout elements in WPF (DockPanel, StackPanel, Grid, UniformGrid, WrapPanel, etc), and start coding the XAML yourself instead of using the designer. This will allow a much higher level of scalability and will also save you from the nuances of Fixed-position elements.
Sorry for the vague description, I can't think of a better way to put it.
Let's say that my ViewModel has a property as follows:
public List<MyClass> SubSystems { get; set; }
and the SubSystems class:
public class SubSystem
{
public string Name { get; set; }
public bool IsSelected { get; set; }
}
In the view, I'd like to bind the SubSystems property to, what I think would be, a list of checkboxes where the IsChecked and Name properties of the CheckBox is bound to the their respective properties, IsChecked for IsSelected and Content for Name.
I know I can make a ListBox in the XAML, but I'm not sure how I'd go about doing this using binding and a collection..
Thanks for the help!
Edit -
Here's the XAML:
<GroupBox Header="Sub-Systems" Grid.Column="0" Grid.Row="0" Margin="5">
<Grid>
<Grid.Resources>
<DataTemplate x:Key="checkBox">
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding IsSelected}" />
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</Grid.Resources>
<ListBox ItemTemplate="{StaticResource checkBox}" ItemsSource="{Binding SubSystems}" />
</Grid>
</GroupBox>
Edit #2 -
Just to clarify, all of the examples populate the box, but none of the examples are breaking on the breakpoints in the setters.
I think that instead of a ListBox, you probably want an ItemsControl. ListBoxes assume that you want to select one of the SubSystem but really, you just want to arrange the items with data templates:
<ItemsControl ItemsSource="{Binding SubSystems}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Checkbox IsChecked="{Binding IsSelected, Mode=TwoWay}" Content="{Binding Name}" />
</DataTemplate>
<ItemsControl.ItemTemplate>
</ItemsControl>
How about this:
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked={Binding IsSelected, Mode=TwoWay} /><TextBlock Text={Binding Name} />
</StackPanel>
</DataTemplate>
Do you mean something like this?
SubSystem class
public class SubSystem : INotifyPropertyChanged
{
private string mName;
private Boolean mIsSelected = false;
public SubSystem()
{
}
public SubSystem(string name, Boolean isSelected)
{
this.Name = name;
this.IsSelected = isSelected;
}
public string Name
{
get { return mName; }
set
{
if (mName != value)
{
mName = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Name"));
}
}
}
public Boolean IsSelected
{
get { return mIsSelected; }
set
{
if (mIsSelected != value)
{
mIsSelected = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("IsSelected"));
}
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
ViewModel
ObservableCollection<SubSystem> mSubSystems = new ObservableCollection<SubSystem>();
public ObservableCollection<SubSystem> SubSystems
{
get { return mSubSystems; }
set { mSubSystems = value; }
}
View
<ListBox x:Name="lstSubsystems" ItemsSource="{Binding SubSystems}">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsSelected}">
<ContentPresenter Content="{Binding Name}" />
</CheckBox>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Hope that helps,
Wts
Modify the ListBox.ItemTemplate to use a checkbox, and bind the CheckBox.IsChecked to SubSystem.IsSelected and CheckBox.Content to SubSystem.Name:
XAML:
<ListBox ItemsSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsSelected}" Content="{Binding Name}" Margin="5" Focusable="False" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
C#:
private void window1_Loaded(object sender, RoutedEventArgs e)
{
this.SubSystems = new List<SubSystem>();
this.SubSystems.Add(new SubSystem() { Name = "SubSystem 1", IsSelected = false });
this.SubSystems.Add(new SubSystem() { Name = "SubSystem 2", IsSelected = false });
this.SubSystems.Add(new SubSystem() { Name = "SubSystem 3", IsSelected = true });
this.SubSystems.Add(new SubSystem() { Name = "SubSystem 4", IsSelected = false });
this.SubSystems.Add(new SubSystem() { Name = "SubSystem 5", IsSelected = true });
this.DataContext = this.SubSystems;
}
And make sure you set Focusable="False" to the CheckBoxes or else your users will be able to tab into them.
EDIT:
Also from what you added you might be missing the ElementName property (if SubSystems is NOT the DataContext of your window, you need to specify where the SubSystems property is coming from with the ElementName binding property):
<ListBox ItemTemplate="{StaticResource checkBox}" ItemsSource="{Binding ElementName=window1, Path=SubSystems}" />