Content
Introduction
In the three parts of this series of articles we touched the issue of Binding
s without an extensive investigation, except where it was needed in the discussion at hand. Remember how we used them with DataTrigger
s to specify the property we want the trigger to react to, and in templates to reach out from our Template
to the templated Control
I will (try to) correct this inbalance in this article.
Mind however that I will be talking exclusively about bindings in the light of what we are discussing: visual customization of WPF controls. This means objects which are instances of BindingBase
or its descendants, but not CommandBinding
s
You can find the other parts of this series here:
- WPF Custom Visualization Part 1 of some: Styling
- WPF Custom Visualization Part 2 of some: Styling with Triggers
- WPF Custom Visualization Intermezzo: Resources
- WPF Custom Visualization Intermezzo 2: Binding (this article)
- WPF Custom Visualization Part 3 of N: Templating
- WPF Custom Visualization Part 4 of some: Addorners
Binding in WPF: what are the variables?
A Binding
allows, as used in Trigger
s and Template
s, to be notified of changes in the properties of an object, get the value of that property and apply it to the property of another object. They are the mediator between the source and the target.
So, we will need two pieces of information:
- A source for the binding, defining the property to monitor
- A target for the binding, defining who to notify.
Of course, there is no magic: as mentioned above the Binding
simply acts as a mediator between the source and the target and thus somehow will have to get notified of any changes in the source to be able to forward them to the target. Thus, regular .NET properties won't work.
But let's just find out how to do it:
Basic Binding in WPF: a simple binding.
Concepts
A binding allows changes in the value of a property on one object to be propagated to the property of another object without any further intervention. This notification can be bidirectional: changes in the other object's property can be propagated back to the first property. It will be clear that the Binding
must somehow be notified of changes in the properties to be able to propagate them.
Thus we need:
- A source property: is defined by setting properties on the binding itself
- A target property: is the property on which the binding is applied
- A notification mechanism: either you implement INotifyPropertyChanged or use Dependency Properties
How to do it?
A very basic binding and the one you've probably seen the most is the following:
public partial class BindingToNotifyPropertyChangedProperties : Window
{
public BindingToNotifyPropertyChangedProperties()
{
InitializeComponent();
var dataContext = new SomeNotifyPropertyChangedImplementingClass();
dataContext.NotifyingProperty = "Notifying Property value from start";
this.DataContext = dataContext;
}
private void ButtonSet_Click(object sender, RoutedEventArgs e)
{
var dataContext = (this.DataContext as SomeNotifyPropertyChangedImplementingClass).NotifyingProperty = "Notifying Property new value";
}
private void ButtonGet_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show((this.DataContext as SomeNotifyPropertyChangedImplementingClass).NotifyingProperty);
}
}
<TextBlock Grid.Row="0" Text="{Binding Path=NotifyingProperty}"/>
<Button Grid.Row="1" Content="Change the notifying property to another value" Click="ButtonSet_Click" />
<Button Grid.Row="2" Content="Get the notifying property" Click="ButtonGet_Click" />
The above definitions will result in following visuals:
<img src="1034113/SourceDataContextBinding.gif" />
Allthough this looks like a very simple syntax, there is allready a lot going on here:
- The target of the binding is the property on which it is defined, in this case the
TextBlock
's Text
property
- The source object of our binding is not explicitely defined here: if we do not specify anything then the default is the
DataContext
of the target object.
- The source property is defined in the Path variable: the NotifyingProperty of the
DataContext
object's class type, thus SomeNotifyPropertyChangedImplementingClass
A first remark here: there is no error if you bind to a non existing property. Thus, WPF will not walk the object hierarchy in search for an object implementing the property, neither will it throw an exception.
Notice how in the below definitions, the chkPropertyNotPresent
Checkbox
has a DataContext
which does not have a property IsChecked. Also notice how this code just compiles AND runs without any compiler errors or exceptions. And allthough the CheckBox
itself does support the IsChecked
property, our binding does NOT use it: the Content
property is empty: there is no text beside the Checkbox
. Contrast this with the Checkbox
bound to the DataContext
with an IsChecked
property.
public partial class BindingToNotifyPropertyChangedProperties : Window
{
public BindingToNotifyPropertyChangedProperties()
{
InitializeComponent();
chkPropertyNotPresent.DataContext = new SomeNotifyPropertyChangedImplementingClass();
chkPropertyIsPresent.DataContext = new IsCheckedDataContext();
}
private void ToggleCheckBoxIsChecked_Click(object sender, RoutedEventArgs e)
{
chkPropertyNotPresent.IsChecked = !chkPropertyNotPresent.IsChecked;
}
private void ToggleDataContextIsChecked_Click(object sender, RoutedEventArgs e)
{
(chkPropertyIsPresent.DataContext as IsCheckedDataContext).IsChecked = !(chkPropertyIsPresent.DataContext as IsCheckedDataContext).IsChecked;
}
}
-->
<CheckBox Name="chkPropertyNotPresent" Grid.Row="3" Content="{Binding Path=IsChecked}" IsChecked="False" />
<Button Grid.Row="4" Content="Toggle the IsChecked of the above CheckBox" Click="ToggleCheckBoxIsChecked_Click" />
-->
<CheckBox Name="chkPropertyIsPresent" Grid.Row="5" Content="{Binding Path=IsChecked}" IsChecked="False" />
<Button Grid.Row="6" Content="Toggle the DataContext IsChecked property in the above CheckBox" Click="ToggleDataContextIsChecked_Click" />
The above definitions will result in following visuals:
<img src="1034113/SourceDataContextNonExidtingPropertyBinding.gif" />
The above binding works in both directions: the value of the DataContext
property is shown in the TextBox
, but if we change the value in the TextBox, then our source property also gets updated. You can see this by clicking the "Change the notifying property to another value" button and by changing the text and then click the "Get the notifying property" button.
This works for two reasons:
- The target property is a Dependency Property: this makes sure the
DataContext
is notified of changes in our TextBox
control.
- The source property is backed by a INotifyPropertyChanged mechanism: this makes sure the
TextBox
control is notified of changes in the DataContext
.
The fact that the target property is a Dependency Property is a requirement of the Binding
: no other properties can be specified as the target, not even INotifyPropertyChanged backed properties.
Following will not work:
-->
<local:SomeNotifyPropertyChangedImplementingClass x:Key="theSource" NotifyingProperty="{Binding Source=chkPropertyIsPresent, Path=IsChecked}" />
If the DataContext
did not implement the INotifyProeprtyChanged
interface then changes in the source property would not propagate to the TextBox
:
public class SomeMuteDataClass
{
public string MuteProperty
{
get;
set;
}
}
public partial class BindingToMuteProperties : Window
{
public BindingToMuteProperties()
{
InitializeComponent();
var dataContext = new SomeMuteDataClass();
dataContext.MuteProperty = "Mute Property value from start";
this.DataContext = dataContext;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var dataContext = (this.DataContext as SomeMuteDataClass).MuteProperty = "Mute Property new value";
}
}
<TextBlock Grid.Row="0" TextWrapping="Wrap" Text="{Binding Path=MuteProperty}" />
<Button Grid.Row="1" Content="Change the mute property to another value" Click="Button_Click" />
The above definitions will result in following visuals:
<img src="1034113/SourceDataContextMutePropertyBinding.gif" />
It is of course possible to specify a Dependency Property as the source of a binding:
<TextBox Name="SrcTextBox" Text="type here to change text on button"/>
<Button Content="{Binding ElementName=SrcTextBox, Path=Text}" />
The above definitions will result in following visuals:
<img src="1034113/SourceDataContextDependencyPropertyBinding.gif" />
But I don't care about the DataContext: How to specify another Source
Concepts
Binding the DataContext
is very nice and also very usefull when you are using the MVVM pattern, but there will be use cases where you want to bind other sources. So, what are the other sources you can specify?
- using the Source property
- using the RelativeSource property
- using the ElementName property
But for each there are multiple ways of doing things.
So let's get in...
How to do it?
By using the Source
property on the Binding
, you can directly specify the object acting as the source. You can do this by either providing the instance inline, or using the Resource
syntax.
Following is an example of specifying the value for the Source
property inline:
<TextBlock Grid.Row="1" HorizontalAlignment="Left" TextWrapping="Wrap" VerticalAlignment="Top">
<TextBlock.Text>
<Binding Path="NotifyingProperty">
<Binding.Source>
<local:SomeNotifyPropertyChangedImplementingClass NotifyingProperty="Inline defined object" />
</Binding.Source>
</Binding>
</TextBlock.Text>
</TextBlock>
The above definitions will result in following visuals:
<img src="1034113/BindingSourceInline.gif" />
Following is an example of specifying the Source
property as a Resource
:
<Window.Resources>
<local:SomeNotifyPropertyChangedImplementingClass x:Key="theSource" NotifyingProperty="Object defined in Resource section" />
</Window.Resources>
<Grid>
<TextBlock Grid.Row="2" HorizontalAlignment="Left" TextWrapping="Wrap" VerticalAlignment="Top" Text="{Binding Path=NotifyingProperty, Source={StaticResource theSource}}"/>
</Grid>
The above definitions will result in following visuals:
<img src="1034113/BindingSourceResource.gif" />
By using the RelativeSource
we can get at objects relative in the visual tree to the Binding's target object. For this, the syntax provides a Mode
property allowing you to navigate the tree. This property can have following values:
- Self: the object on which the binding is defined
-
- FindAncestor: allows access to a parent object in the visual tree by specifying its type.
-
- PreviousData: used in a collection, allows access at the previous item in the collection
- <!-- !!! NOG EENS ONDERZOEKEN !!! -->
- TemplatedParent: allows access from within a template to the control on which the template is applied
-
Most of these Mode
's have a shortcut notation which I will demonstrate in the following examples. In practice, this is the result of static properties on the RelativeSource
class which return a kind of pre-configured RelativeSource
objects with their Mode
property set to a specific value. In the examples I start with the shortcut notation because you will most likely use it the most yourself, but also see it used the most.
Ok, so lets start with mode Self
:
-->
<TextBlock Text="{Binding Path=VerticalAlignment, RelativeSource={RelativeSource Self}}" />
-->
<TextBlock Text="{Binding Path=VerticalAlignment, RelativeSource={RelativeSource Mode=Self}}" />
The above definitions will result in following visuals:
<img src="1034113/BindingModeSelf.gif" />
There is not much to say here: you can see that the Binding
uses the value of the VerticalAlignment
property of the target object, this is the TextBlock
itself. Contrast this with the beginning of the article where we used the DataContext
.
Next is the FindAncestor
mode:
-->
<GroupBox Grid.Row="5" Header="Demo for Ancestor binding: level 2 above the bound element">
<StackPanel>
<TextBlock>Just a textblock control to have something inside the GroupBox</TextBlock>
<GroupBox HorizontalAlignment="Right" Header="Demo for Ancestor binding: level 1b above the bound element">
</GroupBox>
<GroupBox HorizontalAlignment="Right" Header="Demo for Ancestor binding: level 1a above the bound element">
<StackPanel>
<TextBlock>Just a textblock control to have something inside the GroupBox</TextBlock>
<TextBlock Text="{Binding Path=Header, RelativeSource={RelativeSource AncestorType={x:Type GroupBox}}}" />
<TextBlock Text="{Binding Path=Header, RelativeSource={RelativeSource AncestorType={x:Type GroupBox}, AncestorLevel=2}}" />
-->
-->
-->
<GroupBox HorizontalAlignment="Left" Header="{Binding Path=HorizontalAlignment, RelativeSource={RelativeSource AncestorType={x:Type GroupBox}}}" />
<GroupBox HorizontalAlignment="Left" Header="{Binding Path=HorizontalAlignment, RelativeSource={RelativeSource Self}}" />
</StackPanel>
</GroupBox>
</StackPanel>
</GroupBox>
-->
-->
The above definitions will result in following visuals:
<img src="1034113/BindingModeFindAncestor.gif" />
Allthough the idea will be clear, there are a few things to notice here.
If you don't specify a level, then the first object of the specified type which is a parent of the binding target is used as the source for the binding. If you want to climb higher up the tree you'll need to specify the AncestorLevel
. If the target object is of this same type it is NOT used as the source for the binding. If you want that one you'll need to use the RelativeSource
Self
. Specifying an AncestorLevel
of 0 will not help you neither as this value is not allowed: AncestorLevel
must be 1 or more and thus starts immediately above the binding target object.
Specifying the combination AncestorType
, AncestorLevel
also walks the element TREE. As you can see from the above you cannot use it to get at sibling elements. Thus in the above XAML AncestorLevel=2
gets you at the GroupBox
with Header
"Demo for Ancestor binding: level 2 above the bound element" and NOT at the group GroupBox
with Header
"Demo for Ancestor binding: level 1b above the bound element".
There is also the possibility to get at the templated control from within a template. I'm not sure if I should bother you with this now, as we haven't yet discussed templates. I am planning a next article on the subject, so feel free to skip this section for now and return to it later. For those who know templates you can continue reading.
A most basic example:
<Button Grid.Row="6" Background="Red">
<Button.Template>
<ControlTemplate TargetType="{x:Type Button}">
<Grid>
<Ellipse Name="el1" Fill="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Background}" Width="10" Height="10">
</Ellipse>
</Grid>
</ControlTemplate>
</Button.Template>
</Button>
-->
-->
The above definitions will result in following visuals:
<img src="1034113/BindingModeTemplatedParent.gif" />
Nothing exciting here: by specifying the TemplatedParent
source, you have access to the templated object from within the template and you can bind to its properties.
There are few permutations possible on the above syntax.
You can also use a specific type of Binding
: the TemplateBinding
.
-->
<Button Grid.Row="8" Background="Red">
<Button.Template>
<ControlTemplate TargetType="{x:Type Button}">
<Grid>
<Ellipse Name="el1" Fill="{TemplateBinding Property=Background}" Width="10" Height="10">
</Ellipse>
</Grid>
</ControlTemplate>
</Button.Template>
</Button>
Allthough the endresult looks the same for both syntaxes, there is a difference: the TemplateBinding
is evaluated at compile time. If you specify any non-existing properties in the path, then the xaml will not compile. If you use the RelativeSource TemplatedParent
your xaml will compile and even execute. The only way to know something is wrong is looking at the visual studio Output pane.
-->
-->
-->
-->
<Button Grid.Row="9" Background="Red">
<Button.Template>
<ControlTemplate TargetType="{x:Type Button}">
<Grid>
<Ellipse Name="el1" Fill="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=NonExistingProperty}" Width="10" Height="10">
</Ellipse>
</Grid>
</ControlTemplate>
</Button.Template>
</Button>
Related to the AncestorType
there is an alternative you can use:
<Window.Resources>
<ControlTemplate x:Key="MyButtonTemplate" TargetType="{x:Type Button}">
<Grid>
-->
<Ellipse Name="el1" Fill="{Binding RelativeSource={RelativeSource AncestorType={x:Type Button}}, Path=Background}" Width="10" Height="10">
</Ellipse>
</Grid>
</ControlTemplate>
</Window.Resources>
<GroupBox Grid.Row="7" Header="Binding the templated parent through the AncestorType">
<StackPanel>
<Button Background="Blue">
<Button.Template>
<ControlTemplate TargetType="{x:Type Button}">
<Grid>
-->
<Ellipse Name="el1" Fill="{Binding RelativeSource={RelativeSource AncestorType={x:Type Button}}, Path=Background}" Width="10" Height="10">
</Ellipse>
</Grid>
</ControlTemplate>
</Button.Template>
</Button>
-->
<Button Background="Green" Template="{StaticResource MyButtonTemplate}">
</Button>
</StackPanel>
</GroupBox>
The above definitions will result in following visuals:
<img src="1034113/BindingSourceTemplatedParentThroughAncestorType.gif" />
Untill now, we could either bind to an object defined directly in the binding, or defined in a way relative to the binding. But what if there is no exact relation? What if you want to specify a source somewhere random in the visual tree? For this, there is the ElementName
specifier.
-->
<GroupBox x:Name="source1" Grid.Row="10" Header="Demo for ElementName property: toplevel">
<StackPanel>
<GroupBox x:Name="source3" Header="Out of the hierarchy">
<GroupBox x:Name="source31" Header="Out of the hierarchy and nested">
<StackPanel>
<TextBlock Text="Just to have something inside" />
</StackPanel>
</GroupBox>
</GroupBox>
<GroupBox x:Name="source2" Header="Demo for ElementName property: level 1 above the bound element">
<StackPanel>
<TextBlock Text="{Binding ElementName=source1, Path=Header}" />
<TextBlock Text="{Binding ElementName=source2, Path=Header}" />
<TextBlock Text="{Binding ElementName=source3, Path=Header}" />
<TextBlock Text="{Binding ElementName=source31, Path=Header}" />
<TextBlock Text="{Binding ElementName=source4, Path=Header}" />
</StackPanel>
</GroupBox>
<GroupBox x:Name="source4" Header="Declared after the referencing">
<StackPanel>
<TextBlock Text="Just to have something inside" />
</StackPanel>
</GroupBox>
</StackPanel>
</GroupBox>
The above definitions will result in following visuals:
<img src="1034113/BindingSourceElementName.gif" />
I tried to incorporate all possible permutations in the above example:
source2
: Some toplevel element still in the parent-child tree relation
source1
: Some toplevel element with similar elements inbetween (source2
) still in the parent-child tree relation
source3
: Out of the hierarchy
source31
: Out of the hierarchy and nested
source4
: Declared after target object
Following will probably not come as a surprise, but you cannot get at an element defined in another window:
<Window x:Class="Bindings.CrossingWindowBoundaries"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="CrossingWindowBoundaries" Height="300" Width="300">
<Grid>
<Button Name="BtnInOtherWindow" Background="Yellow">A button in another window</Button>
</Grid>
</Window>
<GroupBox x:Name="source2" Header="Demo for ElementName property: level 1 above the bound element">
<StackPanel>
-->
<Button Background="{Binding ElementName=BtnInOtherWindow, Path=Background}" >Getting at a button in another window</Button>
</StackPanel>
</GroupBox>
The above definitions will result in following visuals:
<img src="1034113/BindingSourceElementNameInOtherWindow.gif" />
As you can see, the target of the binding is not yellow.
We can also use the ElementName
with Templates
.
<Window.Resources>
-->
<Style x:Key="GettingInStyle" TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Grid>
<Ellipse Fill="{Binding ElementName=theSource, Path=Background}" Stroke="{TemplateBinding BorderBrush}"/>
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
-->
<Style x:Key="GettingOutStyle" TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Grid>
<Ellipse x:Name="GetOutNameInStyle" Fill="Red" Stroke="{TemplateBinding BorderBrush}"/>
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<GroupBox Header="Demo for ElementName property: usage inside Template">
<StackPanel>
<TextBlock x:Name="theSource" Text="A named source for the bindings" Background="Red"></TextBlock>
<Button>
<Button.Content>
<TextBlock Text="{Binding ElementName=theSource, Path=Text}"></TextBlock>
</Button.Content>
</Button>
-->
<Button>
<Button.Template>
<ControlTemplate>
<TextBlock x:Name="GetOutNameInline" Background="Green" Text="{Binding ElementName=theSource, Path=Text}"></TextBlock>
</ControlTemplate>
</Button.Template>
</Button>
-->
<Button Style="{StaticResource GettingInStyle}" >Getting in from the template</Button>
-->
<Button Background="{Binding ElementName=GetOutNameInStyle, Path=Fill}" >Getting out from a template in the resourcesection</Button>
<Button Background="{Binding ElementName=GetOutNameInline, Path=Fill}" >Getting out from a template defined inline</Button>
</StackPanel>
</GroupBox>
The above definitions will result in following visuals:
<img src="1034113/BindingSourceElementNameWithTemplates.gif" />
Ok, what do we have here? The first use case is getting at a named object from within a template. The second and third Button
in the above example demonstrate this for an inline defined Template
and a Template
defined in the resource section. A second use case is getting at a named element inside a Template
. Unfortunately, this second use case is not possible. If you think about this it is to be expected: a template can be instantiated inside multiple controls and which instance should the binding use as a source?
But I don't care about that Property: How to specify the path
Concepts
Binding to Properties
is nice, but there will be times when you want to bind other things, like nested Properties
, items in a collection, collections, etc... So, what is possible?
- We are able to get at simple
Properties
(that's what we've been doing until now)
- We can bind to nested
Properties
- We can bind to collections (which will be discussed in the section about the
ItemsControl
)
- We can bind to items in a collection, even multi-dimensional collections
- We can bind to the current item in a view of a collection
How to do it?
We will skip binding regular properties because that is what we have been doing all the time, so it should be clear by now how to do this.
So, let's fast forward to binding nested properties:
public partial class SpecifyingThePath : Window
{
SpecifyingThePathDataContext dataContext = new SpecifyingThePathDataContext();
public SpecifyingThePath()
{
dataContext.SubclassProperty = new SpecifyingThePathDataContext.SubClass();
dataContext.SubclassProperty.SubNotifyingProperty = "Sub initial value";
dataContext.NotifyPropertyChanged = true;
dataContext.SubclassProperty.NotifyPropertyChanged = true;
dataContext.MultiDimensionalArrayProperty[1, 2] = "Value at [1,2]";
this.DataContext = dataContext;
InitializeComponent();
CheckMainClassNotification.IsChecked = dataContext.NotifyPropertyChanged;
CheckSubClassNotification.IsChecked = dataContext.SubclassProperty.NotifyPropertyChanged;
}
private void ButtonSet_Click(object sender, RoutedEventArgs e)
{
dataContext.SubclassProperty.SubNotifyingProperty = "Sub new value";
}
private void ButtonGet_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(dataContext.SubclassProperty.SubNotifyingProperty);
}
private void CheckSubClassNotification_Click(object sender, RoutedEventArgs e)
{
dataContext.SubclassProperty.NotifyPropertyChanged = CheckSubClassNotification.IsChecked.Value;
}
private void CheckMainClassNotification_Click(object sender, RoutedEventArgs e)
{
dataContext.NotifyPropertyChanged = CheckMainClassNotification.IsChecked.Value;
}
private void ButtonSetObject_Click(object sender, RoutedEventArgs e)
{
dataContext.SubclassProperty = new SpecifyingThePathDataContext.SubClass() { SubNotifyingProperty = "new Object" };
}
}
-->
<TextBox Text="{Binding Path=SubclassProperty.SubNotifyingProperty}" />
<CheckBox Name="CheckSubClassNotification" Content="Enable NotifyPropertyChanged on SubClass" Click="CheckSubClassNotification_Click" />
<CheckBox Name="CheckMainClassNotification" Content="Enable NotifyPropertyChanged on SpecifyingThePathDataContext" Click="CheckMainClassNotification_Click" />
<Button Content="Set subproperty to value 'Sub new value'" Click="ButtonSet_Click" />
<Button Content="Get subproperty value" Click="ButtonGet_Click" />
<Button Content="Set complete object to new value" Click="ButtonSetObject_Click" />
The above definitions will result in following visuals:
<img src="1034113/BindingPathNestedProperties.gif" />
There is nothing much surprising here I think, though I would like to digg a little deeper on how this works. With the CheckBoxes
you can regulate if the PropertyChanged
event is called on the main type or on the type of the property. You will notice that the Binding
class is intelligent enough to bind to the type of the property and not the main class, which was to be expected: the object owning the property doesn't really change. The last button allows you to replace the object in the main property. As you can see from the above animated gif, the Binding
class can also handle these changes.
Next is binding to a single element in a collection.
public partial class SpecifyingThePath : Window
{
SpecifyingThePathDataContext dataContext = new SpecifyingThePathDataContext();
public SpecifyingThePath()
{
dataContext.SimpleObservableCollectionProperty = new ObservableCollection<string>();
dataContext.SimpleObservableCollectionProperty.Add("Value at index 0");
dataContext.SimpleObservableCollectionProperty.Add("Value at index 1");
dataContext.SimpleObservableCollectionProperty.Add("Value at index 2");
this.DataContext = dataContext;
InitializeComponent();
}
private void ButtonChangeObservableColl_Click(object sender, RoutedEventArgs e)
{
dataContext.SimpleObservableCollectionProperty[1] = "New Value at index 1";
}
private void ButtonDeleteObservableColl_Click(object sender, RoutedEventArgs e)
{
dataContext.SimpleObservableCollectionProperty.RemoveAt(1);
}
-->
<TextBox Grid.Row="5" Text="{Binding Path=SimpleObservableCollectionProperty[1]}" />
-->
<Button Grid.Row="6" Content="Observable: Change value at index 1" Click="ButtonChangeObservableColl_Click" />
<Button Grid.Row="7" Content="Observable: Delete value at index 1" Click="ButtonDeleteObservableColl_Click" />
The above definitions will result in following visuals:
<img src="1034113/BindingPathObservableCollection.gif" />
Again, nothing surprising here: you're binding the element in the collection whose index you specified in the Binding
definition. And because we're binding an ObservableCollection
if we add or delete items, our binding is updated. If we were to use a regular list, our initial binding would work but adding or deleting would not update the binding. This is similar to the mute properties of the previous section: again, if there is no notification mechanism, how is to binding to now anything changed?
Binding a regular List
is demonstrated in the following code:
public partial class SpecifyingThePath : Window
{
SpecifyingThePathDataContext dataContext = new SpecifyingThePathDataContext();
public SpecifyingThePath()
{
dataContext.SimpleMuteCollectionProperty = new List<string>();
dataContext.SimpleMuteCollectionProperty.Add("Value at index 0");
dataContext.SimpleMuteCollectionProperty.Add("Value at index 1");
dataContext.SimpleMuteCollectionProperty.Add("Value at index 2");
this.DataContext = dataContext;
InitializeComponent();
}
private void ButtonChangeMuteColl_Click(object sender, RoutedEventArgs e)
{
dataContext.SimpleMuteCollectionProperty[1] = "New Mute Value at index 1";
}
private void ButtonDeleteMuteColl_Click(object sender, RoutedEventArgs e)
{
dataContext.SimpleMuteCollectionProperty.RemoveAt(1);
}
-->
<TextBox Text="{Binding Path=SimpleMuteCollectionProperty[1]}" />
<Button Content="Mute: Change value at index 1" Click="ButtonChangeMuteColl_Click" />
<Button Content="Mute: Delete value at index 1" Click="ButtonDeleteMuteColl_Click" />
The above definitions will result in following visuals:
<img src="1034113/BindingPathList.gif" />
In the above examples the elements in the Collection
where simple values: in this case string
values. But of course those can be class-type values and then we can bind to properties of an element in the collection:
dataContext.ComplexObservableCollectionProperty = new ObservableCollection<specifyingthepathdatacontext.subclass>();
dataContext.ComplexObservableCollectionProperty.Add(new SpecifyingThePathDataContext.SubClass() { SubNotifyingProperty = "Class value at index 0", NotifyPropertyChanged = true });
dataContext.ComplexObservableCollectionProperty.Add(new SpecifyingThePathDataContext.SubClass() { SubNotifyingProperty = "Class value at index 1", NotifyPropertyChanged = true });
dataContext.ComplexObservableCollectionProperty.Add(new SpecifyingThePathDataContext.SubClass() { SubNotifyingProperty = "Class value at index 2", NotifyPropertyChanged = true });
private void ButtonChangeObservableColComplexChange_Click(object sender, RoutedEventArgs e)
{
dataContext.ComplexObservableCollectionProperty[1] = new SpecifyingThePathDataContext.SubClass() { SubNotifyingProperty = "New Class value at index 1", NotifyPropertyChanged = true };
}
private void ButtonChangeObservableCollComplexMove_Click(object sender, RoutedEventArgs e)
{
dataContext.ComplexObservableCollectionProperty.Move(0, 1);
}
private void ButtonChangeObservableCollComplexPropChange_Click(object sender, RoutedEventArgs e)
{
dataContext.ComplexObservableCollectionProperty[1].SubNotifyingProperty = "New Property value at index 1";
}
</specifyingthepathdatacontext.subclass>
<TextBox Text="{Binding Path=ComplexObservableCollectionProperty[1].SubNotifyingProperty}" />
-->
<Button Content="Observable: Change object at index 1" Click="ButtonChangeObservableColComplexChange_Click" />
-->
<Button Content="Observable: Move object at index 0 to index 1" Click="ButtonChangeObservableCollComplexMove_Click" />
-->
<Button Content="Observable: Changeproeprty at index 1" Click="ButtonChangeObservableCollComplexPropChange_Click" />
The above definitions will result in following visuals:
<img src="1034113/BindingPathObservableCollectionClassType.gif" />
The result was to be expected as is the propagation of changes in the collection or the property. The Binding
class is intelligent enough to monitor both changes in both the collection itself and in the property of the element in the collection it is bound to.
Besides binding to Array
s and ObservableCollection
s, there is a third type of container you can bind to: CollectionView
. I know: purists will say this is not really a container but a view on a container. And they are right. However I made this simplification because the class CollectionView
is a subject on its own and I just want to discuss it here with respect to the Path
syntax. We will meet it again when we discuss binding the ItemsControl
public partial class SpecifyingThePath : Window
{
SpecifyingThePathDataContext dataContext = new SpecifyingThePathDataContext();
public SpecifyingThePath()
{
dataContext.SimpleCollectionView = (CollectionView)CollectionViewSource.GetDefaultView(dataContext.SimpleMuteCollectionProperty);
dataContext.SimpleCollectionView.MoveCurrentToFirst();
List<SpecifyingThePathDataContext.SubClass> complexList = new List<SpecifyingThePathDataContext.SubClass>();
List<string> simpleList0 = new List<string>();
simpleList0.Add("Nested Value at index 0.0");
simpleList0.Add("Nested Value at index 0.1");
simpleList0.Add("Nested Value at index 0.2");
complexList.Add(new SpecifyingThePathDataContext.SubClass()
{
SubNotifyingProperty = "Class value at index 0",
NotifyPropertyChanged = true,
SubSimpleCollectionView = (CollectionView)CollectionViewSource.GetDefaultView(simpleList0)
});
List<string> simpleList1 = new List<string>();
simpleList1.Add("Nested Value at index 1.0");
simpleList1.Add("Nested Value at index 1.1");
simpleList1.Add("Nested Value at index 1.2");
complexList.Add(new SpecifyingThePathDataContext.SubClass()
{
SubNotifyingProperty = "Class value at index 1",
NotifyPropertyChanged = true,
SubSimpleCollectionView = (CollectionView)CollectionViewSource.GetDefaultView(simpleList1)
});
List<string> simpleList2 = new List<string>();
simpleList2.Add("Nested Value at index 2.0");
simpleList2.Add("Nested Value at index 2.1");
simpleList2.Add("Nested Value at index 2.2");
complexList.Add(new SpecifyingThePathDataContext.SubClass()
{
SubNotifyingProperty = "Class value at index 2",
NotifyPropertyChanged = true,
SubSimpleCollectionView = (CollectionView)CollectionViewSource.GetDefaultView(simpleList2)
});
dataContext.ComplexCollectionView = (CollectionView)CollectionViewSource.GetDefaultView(complexList);
dataContext.ComplexCollectionView.MoveCurrentToFirst();
this.DataContext = dataContext;
InitializeComponent();
}
private void ButtonMoveCurrentSimpleView(object sender, RoutedEventArgs e)
{
dataContext.SimpleCollectionView.MoveCurrentToNext();
}
private void ButtonMoveCurrentComplexView(object sender, RoutedEventArgs e)
{
dataContext.ComplexCollectionView.MoveCurrentToNext();
}
private void ButtonMoveNestedCurrentComplexView(object sender, RoutedEventArgs e)
{
(dataContext.ComplexCollectionView.CurrentItem as SpecifyingThePathDataContext.SubClass).SubSimpleCollectionView.MoveCurrentToNext();
}
}
<TextBox Text="{Binding SimpleCollectionView/}" />
<Button Content="Move CurrentItem to next item" Click="ButtonMoveCurrentSimpleView" />
<TextBox Text="{Binding ComplexCollectionView/SubNotifyingProperty}" />
<TextBox Text="{Binding ComplexCollectionView/SubSimpleCollectionView/}" />
<Button Content="Move CurrentItem to next item" Click="ButtonMoveCurrentComplexView" />
<Button Content="Move Nested CurrentItem to next item" Click="ButtonMoveNestedCurrentComplexView" />
The above definitions will result in following visuals:
<img src="1034113/BindingPathCollectionViewClassType.gif" />
So, what is going on here?
We are creating CollectionView
s on the collections which we want to get values from. A property of a CollectionView
is, among others like sortation, filtering, that it can have a current item. See in the above code how we set this CurrentItem
the the first item by calling the method MoveCurrentToFirst()
of the CollectionView
. Then by clicking the any of the Button
s we call MoveCurrentToNext()
which moves the CurrentItem
.
But I don't want my property updated: Specify the direction using the Mode
Concepts
Until now we've been using two way bindings: the value from the source is transferred to the target, but any changes made to the target are also propagated to the source. This is because our properties are defined with both setters and getters and the default Mode
of the Binding
is TwoWay
.
But what if we have a read-only property? Or the target is a read-only property?
You can define the direction of the Binding
with the Mode
property:
- TwoWay: changes in both target and source are propagated
- OneWay: only changes in the source are propagated: for readonly source properties
- OneWayToSource: only changes in the target are propagated to the source, but not the other way around.
- OneTime: the source value is read only once and then never again
How to do it?
The most common case is the TwoWay
binding mode:
<TextBox TextWrapping="Wrap" Text="{Binding Mode=TwoWay, Path=TwoWayBindingProperty}"/>
<Button Content="Set the datacontext property to 'Some Text'" Click="TwoWaySetPropertyValue_Click"/>
<Button Content="Get the datacontext property" Click="TwoWayGetPropertyValue_Click"/>
<TextBox TextWrapping="Wrap" Text="{Binding Path=TwoWayBindingProperty}"/>
public BindingMode()
{
InitializeComponent();
m_dataContext = new BindingModeDataContext();
m_dataContext.TwoWayBindingProperty = "TwoWay initial value";
DataContext = m_dataContext;
}
private void TwoWaySetPropertyValue_Click(object sender, RoutedEventArgs e)
{
m_dataContext.TwoWayBindingProperty = "TwoWay: Some Text";
}
private void TwoWayGetPropertyValue_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(m_dataContext.TwoWayBindingProperty);
}
The above definitions will result in following visuals:
<img src="1034113/BindingModeTwoWay.gif" />
No surprises here: updating the control propagates to the bound property of the datacontext and vice versa: updates to the bound property of the datacontext propagate to the target. TwoWay
mode is the default if you don't specify anything.
Trying to set a TwoWay
binding on a readonly property will fail:
class BindingModeDataContext : INotifyPropertyChanged
{
string m_readOnlyProperty;
public string ReadOnlyProperty
{
get { return m_readOnlyProperty; }
}
}
<TextBox TextWrapping="Wrap" Text="{Binding Mode=TwoWay, Path=ReadOnlyProperty}"/>
The code will compile but on execution you get an Exception
with following message: A TwoWay or OneWayToSource binding cannot work on the read-only property 'ReadOnlyProperty' of type 'Bindings.BindingModeDataContext'.
This can be solved with the OneWay
mode:
class BindingModeDataContext : INotifyPropertyChanged
{
string m_oneWayBindingProperty;
public string OneWayBindingProperty
{
get { return m_oneWayBindingProperty; }
}
}
<TextBox TextWrapping="Wrap" Text="{Binding Mode=OneWay, Path=OneWayBindingProperty}"/>
The above definitions will result in following visuals:
<img src="1034113/BindingModeOneWay.gif" />
This shows what was to be expected: the target shows the value of the source and because we have no setter on the source we also cannot update it.
But what if we define a OneWay
binding on a INotifyPropertyChanged backed property with getter and setter?
class BindingModeDataContext : INotifyPropertyChanged
{
string m_oneWayBindingPropertyFullProp;
public string OneWayBindingPropertyFullProp
{
get { return m_oneWayBindingPropertyFullProp; }
set
{
m_oneWayBindingPropertyFullProp = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("OneWayBindingPropertyFullProp"));
}
}
}
public partial class BindingMode : Window
{
private void OneWayFullPropSetPropertyValue_Click(object sender, RoutedEventArgs e)
{
m_dataContext.OneWayBindingPropertyFullProp = "OneWay: Some Text";
}
private void OneWayFullPropGetPropertyValue_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(m_dataContext.OneWayBindingPropertyFullProp);
}
}
<TextBox TextWrapping="Wrap" Text="{Binding Mode=OneWay, Path=OneWayBindingPropertyFullProp}"/>
<Button Content="Set the datacontext property to 'OneWay: Some Text'" Click="OneWayFullPropSetPropertyValue_Click"/>
<Button Content="Get the datacontext property" Click="OneWayFullPropGetPropertyValue_Click"/>
The above definitions will result in following visuals:
<img src="1034113/BindingModeOneWayWithFullProperty.gif" />
The above may come as a bit of a surprise: we are using the OneWay
binding, but if we bind a property with setter and INotifyPropertyChanged
notification and set the property from code, then the target of the binding is updated! So, the meaning of OneWay
here is: changing the target does not update the source.
If you do not want changes in the source to propagate to the target you should use the OneWayToSource
setting.
class BindingModeDataContext : INotifyPropertyChanged
{
string m_oneWayToSourceBindingProperty;
public string OneWayToSourceBindingProperty
{
get { return m_oneWayToSourceBindingProperty; }
set
{
m_oneWayToSourceBindingProperty = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("OneWayToSourceBindingProperty"));
}
}
}
public partial class BindingMode : Window
{
private void OneWayToSourceSetPropertyValue_Click(object sender, RoutedEventArgs e)
{
m_dataContext.OneWayToSourceBindingProperty = "OneWayToSource: Some Text";
}
private void OneWayToSourceGetPropertyValue_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(m_dataContext.OneWayToSourceBindingPropertyGetter);
}
}
<TextBox TextWrapping="Wrap" Text="{Binding Mode=OneWayToSource, Path=OneWayToSourceBindingProperty}"/>
<Button Content="Set the datacontext property to 'OneWayToSource: Some Text'" Click="OneWayToSourceSetPropertyValue_Click"/>
<Button Content="Get the datacontext property" Click="OneWayToSourceGetPropertyValue_Click"/>
The above definitions will result in following visuals:
<img src="1034113/BindingModeOneWayToSource.gif" />
You might be tempted to think you can use the OneWayToSource
mode for readonly target properties, but you would be wrong. The following will not compile:
<TextBox TextWrapping="Wrap" Text="This doesn't work" ActualWidth="{Binding Mode=OneWayToSource, Path=OneWayToSourceBindingProperty}"/>
The last possible value for the Mode
property is OneTime
.
And yes, again no surprises, that is just what it does: put the source value in the target property and then never update anything again in neither direction, no mather what kind of property, mute or INotifyPropertyChanged
backed
class BindingModeDataContext : INotifyPropertyChanged
{
string m_oneTimeBindingProperty;
public string OneTimeBindingProperty
{
get { return m_oneTimeBindingProperty; }
set
{
m_oneTimeBindingProperty = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("OneTimeBindingProperty"));
}
}
}
public partial class BindingMode : Window
{
private void OneTimeSetPropertyValue_Click(object sender, RoutedEventArgs e)
{
m_dataContext.OneTimeBindingProperty = "OneTime: Some Text";
}
private void OneTimeGetPropertyValue_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(m_dataContext.OneTimeBindingProperty);
}
}
<TextBox TextWrapping="Wrap" Text="{Binding Mode=OneTime, Path=OneTimeBindingProperty}"/>
<Button Content="Set the datacontext property to 'OneTime: Some Text'" Click="OneTimeSetPropertyValue_Click"/>
<Button Content="Get the datacontext property" Click="OneTimeGetPropertyValue_Click"/>
The above definitions will result in following visuals:
<img src="1034113/BindingModeOneTime.gif" />
But I don't want that value now, I want it when ...: UpdateSourceTrigger
Concepts
Until now we have not explicitely defined when the propagation should take place. Most examples until now where binding to the Text
property of a TextBox
control. And this might have given you the false impression that the binding is always updated when the control looses the focus. But that isn't thru! In fact, most bindings update when the property changes its value.
You can define in the Binding
which trigger it should use to propagate changes:
- Default: the default of the target property. And because target properties of
Binding
s can only be Dependency Properties, this is defined in the definition of the property.
- LostFocus: the value is propagated when the control on which the target property is defined loses it's focus.
- PropertyChanged: the value is propagated from the moment the property changes.
- Explicit: you will have to trigger the propagation yourself from inside the code
How to do it?
We've been using the default setting all along: if you don't specify anything that is what you are using. You can of course also explicitely specify it.
<GroupBox Grid.Row="0" Header="Unspecified on textbox">
<StackPanel>
<TextBox Name="TextSource" Text="TextBox"/>
<TextBox Background="Green" Text="{Binding ElementName=TextSource, Path=Text}"/>
</StackPanel>
</GroupBox>
<GroupBox Grid.Row="1" Header="Unspecified on slider">
<StackPanel>
<Slider x:Name="Slider0" Background="Green" Value="{Binding ElementName=SliderSource,Path=Text}"/>
<TextBox x:Name="SliderSource" Text="6"/>
</StackPanel>
</GroupBox>
<GroupBox Grid.Row="2" Header="Default on textbox">
<StackPanel>
<TextBox Name="TextSourceDef" Text="TextBox"/>
<TextBox Background="Green" Text="{Binding ElementName=TextSourceDef, Path=Text, UpdateSourceTrigger=Default}"/>
</StackPanel>
</GroupBox>
<GroupBox Grid.Row="3" Header="Default on slider">
<StackPanel>
<Slider Background="Green" Value="{Binding ElementName=SliderSourceDef,Path=Text, UpdateSourceTrigger=Default}"/>
<TextBox x:Name="SliderSourceDef" Text="6"/>
</StackPanel>
</GroupBox>
The above definitions will result in following visuals:
<img src="1034113/BindingUpdateSourceTriggerDefault.gif" />
I've been using two controls here with a different trigger on the target property of the Binding
. The TextBox
has a default of LostFocus
while the Slider
has a default of PropertyChanged
The visual result of this is that, if you don't specify anything and thus use the default, for the TextBox
the value is propagated only when it looses focus, and for the Slider
it is propagated while you are dragging the slider.
The interesting thing to notice here is if you explicitely set the UpdateSourceTrigger
to something different then the default.
<GroupBox Grid.Row="4" Header="LostFocus on textbox">
<StackPanel>
<TextBox Name="TextSource1" Text="TextBox"/>
<TextBox Background="Green" Text="{Binding ElementName=TextSource1, Path=Text, UpdateSourceTrigger=LostFocus}"/>
</StackPanel>
</GroupBox>
<GroupBox Grid.Row="5" Header="PropertyChanged on textbox">
<StackPanel>
<TextBox Name="TextSource2" Text="TextBox"/>
<TextBox Background="Green" Text="{Binding ElementName=TextSource2, Path=Text, UpdateSourceTrigger=PropertyChanged}"/>
</StackPanel>
</GroupBox>
<GroupBox Grid.Row="6" Header="LostFocus on slider">
<StackPanel>
<Slider Background="Green" Value="{Binding ElementName=SliderSource1, Path=Text, UpdateSourceTrigger=LostFocus}"/>
<TextBox x:Name="SliderSource1" Text="6"/>
</StackPanel>
</GroupBox>
<GroupBox Grid.Row="7" Header="PropertyChanged on slider">
<StackPanel>
<Slider Background="Green" Value="{Binding ElementName=SliderSource2, Path=Text, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox x:Name="SliderSource2" Text="6"/>
</StackPanel>
</GroupBox>
The above definitions will result in following visuals:
<img src="1034113/BindingUpdateSourceTriggerSpecified.gif" />
Also notice how defining this property only influences the trigger for propagation of values from the target to the source and not the other way around.
A last possibility is to specify Explicit
as the trigger. By doing this you say you will decide when to trigger the propagation. You do this by calling the UpdateSource()
method on the binding:
private void ButtonTriggerText_Click(object sender, RoutedEventArgs e)
{
BindingExpression binding = TextEplicit.GetBindingExpression(TextBox.TextProperty);
binding.UpdateSource();
}
private void ButtonTriggerSlider_Click(object sender, RoutedEventArgs e)
{
BindingExpression binding = SliderExplicit.GetBindingExpression(Slider.ValueProperty);
binding.UpdateSource();
}
<GroupBox Grid.Row="8" Header="Explicit on textbox">
<StackPanel>
<TextBox Name="TextSource3" Text="TextBox"/>
<TextBox Background="LightGreen" Name="TextEplicit" Text="{Binding ElementName=TextSource3, Path=Text, UpdateSourceTrigger=Explicit}"/>
<Button Content="Trigger binding" Click="ButtonTriggerText_Click" />
</StackPanel>
</GroupBox>
<GroupBox Grid.Row="9" Header="Explicit on slider">
<StackPanel>
<Slider Background="LightGreen" Name="SliderExplicit" Value="{Binding ElementName=SliderSource3, Path=Text, UpdateSourceTrigger=Explicit}"/>
<TextBox x:Name="SliderSource3" Text="6"/>
<Button Content="Trigger binding" Click="ButtonTriggerSlider_Click" />
</StackPanel>
</GroupBox>
The above definitions will result in following visuals:
<img src="1034113/BindingUpdateSourceTriggerExplicit.gif" />
But I want more ... properties I mean: MultiBinding
Concepts
You may come to a use case in which you need to bind to multiple source properties. An example (allthough some will argue a bad one) may be if you want to show a concatenated value of several source properties, or for example a calculation on multiple source properties.
For this, one can use the MultiBinding
. A MultiBinding
needs at least the following:
- Multiple
Binding
s
- An implementation of
IMultiValueConverter
which converts the source values into a single result value
As stated above, some will probably argue the examples are bad and if you are really into MVVM then indeed it can be discussed if MultiBinding
is the way to go, or if one should calculate the property directly in the viewmodel. The reason I included the discussion of the MultiBinding
is simply a matter of completeness.
How to do it?
A basic example of a MultiBinding
is the following:
class MyMultiBindingConverter : IMultiValueConverter
{
static string[] stringSeparators = new string[] { "_" };
bool m_allowConvertBack = true;
public string InstanceId { get; set; }
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
Debug.WriteLine("MyMultiBindingConverter.Convert[" + (string.IsNullOrEmpty(InstanceId) ? "Global" : InstanceId) + "]");
string one = values[0] as string;
string two = values[1] as string;
string three = values[2] as string;
Debug.WriteLine("MyMultiBindingConverter.Convert[" + (string.IsNullOrEmpty(InstanceId) ? "Global" : InstanceId) + "]"
+ "[1:" + (string.IsNullOrEmpty(one) ? "NULL" : one) + "]"
+ "[2:" + (string.IsNullOrEmpty(two) ? "NULL" : two) + "]"
+ "[3:" + (string.IsNullOrEmpty(three) ? "NULL" : three) + "]"
);
return (string.IsNullOrEmpty(one)?"NULL":one)
+ "_" + (string.IsNullOrEmpty(two) ? "NULL" : two)
+ "_" + (string.IsNullOrEmpty(three) ? "NULL" : three);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
string valueToConvert = value as string;
Debug.WriteLine("MyMultiBindingConverter.ConvertBack[" + (string.IsNullOrEmpty(InstanceId) ? "Global" : InstanceId) + "]");
Debug.WriteLine("MyMultiBindingConverter.ConvertBack[" + (string.IsNullOrEmpty(InstanceId) ? "Global" : InstanceId) + "]"
+ "[in:" + (string.IsNullOrEmpty(valueToConvert) ? "NULL" : valueToConvert) + "]"
);
if (m_allowConvertBack)
{
object[] result = valueToConvert.Split(stringSeparators, StringSplitOptions.None).Select(x => (x=="NULL"?null:x)).ToArray();
Debug.WriteLine("MyMultiBindingConverter.ConvertBack[" + (string.IsNullOrEmpty(InstanceId) ? "Global" : InstanceId) + "]Object[" + result.Count() + "]");
return result;
}
else
{
throw new NotImplementedException();
}
}
}
<Window.Resources>
<local:MyMultiBindingConverter x:Key="myConverter"/>
</Window.Resources>
<TextBox Name="TB1" Text="Text1"/>
<TextBox Name="TB2" Text="Text2" />
<TextBox Name="TB3" Text="Text3" />
<TextBox>
<TextBox.Text>
<MultiBinding Converter="{StaticResource myConverter}">
<Binding ElementName="TB1" Path="Text" />
<Binding ElementName="TB2" Path="Text" />
<Binding ElementName="TB3" Path="Text" />
</MultiBinding>
</TextBox.Text>
</TextBox>
The above definitions will result in following visuals:
<img src="1034113/MultiBinding.gif" />
To be honest, above is not the most simple use case. Below is more simple:
<TextBox Name="TB1" Text="Text1"/>
<TextBox Name="TB2" Text="Text2" />
<TextBox Name="TB3" Text="Text3" />
<TextBlock>
<TextBlock.Text>
-->
<MultiBinding StringFormat="{}{0}, {1}, {2}">
<Binding ElementName="TB1" Path="Text" />
<Binding ElementName="TB2" Path="Text" />
<Binding ElementName="TB3" Path="Text" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
However, if you concatenate values, most of the time you will want your output text formatted if some values aren't present and this is not possible with the StringFormat
syntax. You will need a more intelligent solution for that which requires yout to use a IMultiValueConverter
:
class IntelligentMultiBindingConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
string one = values[0] as string;
string two = values[1] as string;
string three = values[2] as string;
string result = one;
if (string.IsNullOrEmpty(result) && !string.IsNullOrEmpty(two))
{
result = two;
}
else if (!string.IsNullOrEmpty(result) && !string.IsNullOrEmpty(two))
{
result = result + ", "+ two;
}
if (string.IsNullOrEmpty(result) && !string.IsNullOrEmpty(three))
{
result = three;
}
else if (!string.IsNullOrEmpty(result) && !string.IsNullOrEmpty(three))
{
result = result + ", " + three;
}
return result;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
<Window.Resources>
<local:IntelligentMultiBindingConverter x:Key="intelligentConverter"/>
</Window.Resources>
<TextBox Name="TB1" Text="Text1"/>
<TextBox Name="TB2" Text="Text2" />
<TextBox Name="TB3" Text="Text3" />
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource intelligentConverter}">
<Binding ElementName="TB1" Path="Text" />
<Binding ElementName="TB2" Path="Text" />
<Binding ElementName="TB3" Path="Text" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
Of course, we can combine this with previous learned stuff:
What happens if we try to set the UpdateSourceTrigger
?
Well, we cannot set it on any of the inner bindings. Following will compile but throw an exception on execution:
<TextBox Name="USTTB1" Text="Text1"/>
<TextBox Name="USTTB2" Text="Text2" />
<TextBox Name="USTTB3" Text="Text3" />
-->
-->
It was of course to be expected: remember how the UpdateSourceTrigger
defines on when to update the SOURCE if you change the value in the TARGET. But we have three sources here, so what we are saying is that we want one source to change at a different rate hen the others when modifying the target. That would of course lead to inconsistend results.
But nothing keeps us from setting it on the MultiBinding
:
<TextBox Name="USTTB1" Text="Text1"/>
<TextBox Name="USTTB2" Text="Text2" />
<TextBox Name="USTTB3" Text="Text3" />
<TextBox>
<TextBox.Text>
<MultiBinding Converter="{StaticResource myConverter}" UpdateSourceTrigger="LostFocus">
<Binding ElementName="USTTB1" Path="Text" />
<Binding ElementName="USTTB2" Path="Text"/>
<Binding ElementName="USTTB3" Path="Text" />
</MultiBinding>
</TextBox.Text>
</TextBox>
<TextBox>
<TextBox.Text>
<MultiBinding Converter="{StaticResource myConverter}" UpdateSourceTrigger="PropertyChanged">
<Binding ElementName="USTTB1" Path="Text" />
<Binding ElementName="USTTB2" Path="Text"/>
<Binding ElementName="USTTB3" Path="Text" />
</MultiBinding>
</TextBox.Text>
</TextBox>
The above definitions will result in following visuals:
<img src="1034113/UpdateSourceOnMultiBinding.gif" />
Contrary to the UpdateSourceTrigger
, a Mode
can be specified on an inner binding. And what is more, this seems to work also.
<GroupBox Header="OneWay on inner Binding">
<StackPanel>
<TextBox Name="MD1WITB1" Text="Text1"/>
<TextBox Name="MD1WITB2" Text="Text2" />
<TextBox Name="MD1WITB3" Text="Text3" />
<TextBox>
<TextBox.Text>
<MultiBinding Converter="{StaticResource myConverter}">
<Binding ElementName="MD1WITB1" Path="Text" />
<Binding ElementName="MD1WITB2" Path="Text" Mode="OneWay"/>
<Binding ElementName="MD1WITB3" Path="Text" />
</MultiBinding>
</TextBox.Text>
</TextBox>
</StackPanel>
</GroupBox>
The above definitions will result in following visuals:
<img src="1034113/ModeOneWayOnInnerBinding.gif" />
But how then does it know what you did? Well, it sequentially calls the ConvertBack
and Convert
methods on your IMultiValueConverter
implementing class:
<img src="1034113/VSOutputWindowForInnerMode.png" />
And on after calling your ConvertBack
method, it doesn't forward the returned value at the index of the inner Binding
you put the Mode
property on. So it then calls the ConvertBack
method with the new value of the Binding
s not specifying a Mode
and the old unchanged value of the Binding
which does set the Mode
. No Hocus Pocus here.
And of course we can also set it on the MultiBinding
:
<GroupBox Header="OneWay on MultiBinding">
<StackPanel>
<TextBox Name="MD1WMTB1" Text="Text1"/>
<TextBox Name="MD1WMTB2" Text="Text2" />
<TextBox Name="MD1WMTB3" Text="Text3" />
<TextBox>
<TextBox.Text>
<MultiBinding Converter="{StaticResource myConverter}" Mode="OneWay">
<Binding ElementName="MD1WMTB1" Path="Text" />
<Binding ElementName="MD1WMTB2" Path="Text"/>
<Binding ElementName="MD1WMTB3" Path="Text" />
</MultiBinding>
</TextBox.Text>
</TextBox>
</StackPanel>
</GroupBox>
The above definitions will result in following visuals:
<img src="1034113/ModeOneWayOnMultiBinding.gif" />
There is a thing to notice here: when we specified the Mode
on one of the inner bindings and changed the target value, the original value was restored (notice how, if we change "Text2", in the target Textbox
, after tabbing out of it the value is restored to "Text2") This does not happen when specifying the Mode
on the MultiBinding
itself!
We can do the same with a OneWayToSource
Mode
. Let's try it.
Setting the Mode
on an inner Binding
:
<local:MyMultiBindingConverter x:Key="oneWayToSourceOnInnerBinding2Converter" InstanceId="OneWayToSourceOnInnerBinding2"/>
<GroupBox Header="OneWayToSource on second inner Binding">
<StackPanel>
<TextBox Name="MD1SITB1" Text="Text1"/>
<TextBox Name="MD1SITB2" Text="Text2" />
<TextBox Name="MD1SITB3" Text="Text3" />
<TextBox>
<TextBox.Text>
<MultiBinding Converter="{StaticResource oneWayToSourceOnInnerBinding2Converter}">
<Binding ElementName="MD1SITB1" Path="Text" />
<Binding ElementName="MD1SITB2" Path="Text" Mode="OneWayToSource" />
<Binding ElementName="MD1SITB3" Path="Text" />
</MultiBinding>
</TextBox.Text>
</TextBox>
</StackPanel>
</GroupBox>
The above definitions will result in following visuals:
<img src="1034113/ModeOneWayToSourceOnInnerBinding.gif" />
There are a few things to notice here which, after some thinking, are not that surprising. First, when we open the window for the first time, the TextBox
on which the MultiBinding
is defined has a center value of NULL. After all, we defined a OneWayToSource
on the second inner Binding
which has the second TextBox
as its source, so the value of the second TextBox
is not forwarded to the converter, hence, the converter replaces it with the string NULL. Then, when we change the value in the second TextBox
, still nothing is happening because of the OneWayToSource
on the second inner Binding
. Only when we change the NULL in the composite value, does it end up in the second TextBox
. The other TextBox
s behave as usual.
But I want this first and if I can't then ...: PriorityBinding
Concepts
Where the MultiBinding
allows you to bind to multiple sources and perform some action on those sources, the PriorityBinding
also allows you to bind multiple sources, but it chooses a single value from those sources to propagate to the target.
Which source is eventually shown depends on its priority in the binding: that is its order in the list of sources, and if any of the property preceeding it provide a valid value or not.
The MSDN website on PriorityBinding
discusses when a source value is considered valid:
A binding returns a value successfully if:
- The path to the binding source resolves successfully.
- The value converter, if any, is able to convert the resulting value.
- The resulting value is valid for the target property.
The value DependencyProperty.UnsetValue is not considered a successful return value.
How to do it?
A simple PriorityBinding
is the following:
<TextBox Name="TB11" Text="Text1" />
<TextBox Name="TB12" Text="Text2" />
<TextBox>
<TextBox.Text>
<PriorityBinding>
<Binding ElementName="TB11" Path="Text" />
<Binding ElementName="TB12" Path="Text" />
</PriorityBinding>
</TextBox.Text>
</TextBox>
The above definitions will result in following visuals:
<img src="1034113/PriorityBinding.gif" />
As you can see, the value of the TextBox
TB11 is shown in the target of the binding
Note also that making the TextBox
empty does not make the target use the second source because an empty source is not considered an unsuccessfull value.
To be able to experiment with this I constructed the following example:
class SimpleValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
switch (value.ToString().ToLower())
{
case "unset":
return DependencyProperty.UnsetValue;
case "null":
return null;
case "exception":
throw new Exception();
case "donothing":
return Binding.DoNothing;
default:
return "CONVERTED: " + value.ToString();
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (false)
{
return value;
}
else
{
throw new NotImplementedException();
}
}
}
<Window.Resources>
<local:SimpleValueConverter x:Key="myConverter"/>
</Window.Resources>
<TextBox Name="TB21" Text="Text1" />
<TextBox Name="TB22" Text="Text2" />
<TextBox>
<TextBox.Text>
<PriorityBinding>
<Binding ElementName="TB21" Path="Text" Converter="{StaticResource myConverter}" />
<Binding ElementName="TB22" Path="Text" />
</PriorityBinding>
</TextBox.Text>
</TextBox>
The above definitions will result in following visuals:
<img src="1034113/PriorityBindingWithConverter.gif" />
This is very much the smae example, except that we are using a converter now. With this converter we can experiment with different settings and returnvalues.
As you can see from this, the only value which makes the PriorityBinding
use the second binding is if the coverter returns DependencyProperty.UnsetValue
. Neither Binding.DoNothing
or throwing an Exception
are considered an unsuccessfull return value.
If you specify a non existing property as the source, then also the next source is selected (if that is a legal one of course):
<TextBox Name="TB31" Text="Text1" />
<TextBox Name="TB32" Text="Text2" />
<TextBox>
<TextBox.Text>
<PriorityBinding>
-->
<Binding ElementName="TB31" Path="PropertyWhichDoesNotExist" />
<Binding ElementName="TB32" Path="Text" />
</PriorityBinding>
</TextBox.Text>
</TextBox>
The above definitions will result in following visuals:
<img src="1034113/PriorityBindingWithNonExistingProperty.gif" />
Another case where the next binding will be considered is if the binding returns a value which is not bij default convertable to the target property:
<TextBox Name="TB41" Text="Text1" HorizontalAlignment="Right" Background="Blue" />
<TextBox Name="TB42" Text="Text2" HorizontalAlignment="Left" Background="Green" />
<TextBox Text="Take the alignment">
<TextBox.HorizontalAlignment>
<PriorityBinding>
-->
<Binding ElementName="TB41" Path="Background" />
<Binding ElementName="TB42" Path="HorizontalAlignment" />
</PriorityBinding>
</TextBox.HorizontalAlignment>
</TextBox>
The above definitions will result in following visuals:
<img src="1034113/PriorityBindingWithInvalidValue.gif" />
A Brush
is not a valid value for the HorizontalAlignment
property, so the first binding is skipped and the target is bound to the HorizontalAlignment
property of the TextBox
named TB42.
All this is done at runtime, as the following example shows:
private void ChangeObjectPropertyToBrush_Click(object sender, RoutedEventArgs e)
{
m_dataContext.ObjectProperty = Brushes.Blue;
}
private void ChangeObjectPropertyToAlignment_Click(object sender, RoutedEventArgs e)
{
m_dataContext.ObjectProperty = HorizontalAlignment.Right;
}
<TextBox Name="TB41" Text="Text1" HorizontalAlignment="Right" Background="Blue" />
<TextBox Name="TB42" Text="Text2" HorizontalAlignment="Left" Background="Green" />
<TextBox Text="Dynamic">
<TextBox.HorizontalAlignment>
<PriorityBinding>
<Binding Path="ObjectProperty" />
<Binding ElementName="TB42" Path="HorizontalAlignment" />
</PriorityBinding>
</TextBox.HorizontalAlignment>
</TextBox>
<Button Click="ChangeObjectPropertyToBrush_Click">Change property to Brush</Button>
<Button Click="ChangeObjectPropertyToAlignment_Click">Change property to Alignment</Button>
The above definitions will result in following visuals:
<img src="1034113/PriorityBindingWithInvalidValueDynamic.gif" />
As you can see from the above example, when you click the change property to brush button, then the Dynamic TextBox
takes the value of the TextBox
TB42 for its HorizontalAlignment
property. The first Binding
of the PriorityBinding
returns a value of the wrong type, so it is skipped. The second Binding
does provide a value of the correct type, so it is applied to to the target property. But if we click the Change property to Alignment button,then the first Binding
does provide a valid value, so it is applied.
But I have multiple items in my source ...: ItemsControl
Concepts
The ItemsControl
has in this series always been something special, and this is no exception.
On the surface al looks very similar to regular Binding
we've been using untill now, and from a binding-syntax perspective it is. However, under the hood all kinds of things are happening:
- To have the control update on adding and deleting items, you must implement
INotifyCollectionChanged
- Internally, the binding is actually done on a
CollectionView
giving you some extras:
- Notification of changes in the
CurrentItem
through the SelectedItem
property
- Synchronization with the
CurrentItem
through the IsSynchronizedWithCurrentItem
property
- .
So, let's get in and see how to do all this.
How to do it?
Let's start with something obvious: binding to an ObservableCollection
:
CollectionDataContext m_dataContext;
public MainWindow()
{
m_dataContext = new CollectionDataContext();
m_dataContext.ObservableSource = new ObservableCollection<string>();
m_dataContext.ObservableSource.Add("Observable Value 1");
m_dataContext.ObservableSource.Add("Observable Value 2");
m_dataContext.ObservableSource.Add("Observable Value 3");
m_dataContext.ObservableSource.Add("Observable Value 4");
this.DataContext = m_dataContext;
InitializeComponent();
}
private void AddToObservableSource(object sender, RoutedEventArgs e)
{
m_dataContext.ObservableSource.Add("Added value");
}
private void RemoveFromObservableSource(object sender, RoutedEventArgs e)
{
m_dataContext.ObservableSource.RemoveAt(0);
}
private void ChangeInObservableSource(object sender, RoutedEventArgs e)
{
m_dataContext.ObservableSource[0] = "Changed value";
}
<GroupBox Header="Simple Observable Collection">
<StackPanel>
<ItemsControl ItemsSource="{Binding Path=ObservableSource}" />
-->
<Button Click="AddToObservableSource">Add to ObservableSource</Button>
<Button Click="RemoveFromObservableSource">Remove from ObservableSource</Button>
<Button Click="ChangeInObservableSource">Change in ObservableSource</Button>
</StackPanel>
</GroupBox>
The above definitions will result in following visuals:
<img src="1034113/ItemsControlObservableCollectionSimple.gif" />
Nothing special here, except perhaps we're binding a INotifyCollectionChanged
implementing object. Classes imlementing this interface provide notification of changes in the collection, thus notification of add, remove and change operations. And that is exactly what we are doing in de Click
handlers of the Button
s. Notice however that we are binding to an ObservableCollection
of String
s, so the fact that we are seeing a change in clicking the change button is because we are replacing the value in the collection. I'll come back to this later.
If we use a Converter
on the Binding
it is applied on the ObservableCollection
and not on the items in the collecion, which is not that surprising. However, if for some reason you want to use a converter on the items, you'll need to define a ItemsTemplate
in which you define the converter, see also this StackOverflow question. This is demonstrated in the follwowing example:
class PrependingValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return "Prepended - " + value;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value;
}
}
<GroupBox Header="Simple Observable Collection">
<StackPanel>
-->
<ItemsControl ItemsSource="{Binding Path=ObservableSource, Converter={StaticResource myConverter}}" />
-->
<ItemsControl ItemsSource="{Binding Path=ObservableSource}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={StaticResource prependingConverter}}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</GroupBox>
The above definitions will result in following visuals:
<img src="1034113/ItemsControlObservableCollectionConverter.gif" />
As mentioned above, you only see changes in the collection because it implements INotifyCollectionChanged
. If you bind a regular List
your ItemsControl
will not be updated on changes in the List
.
m_dataContext.ListSource = new List<string>();
m_dataContext.ListSource.Add("List Value 1");
m_dataContext.ListSource.Add("List Value 2");
m_dataContext.ListSource.Add("List Value 3");
m_dataContext.ListSource.Add("List Value 4");
<GroupBox Header="Simple List Collection">
<StackPanel>
<ItemsControl ItemsSource="{Binding Path=ListSource}"/>
-->
<Button Click="AddToListSource">Add to ListSource</Button>
<Button Click="RemoveFromListSource">Remove from ListSource</Button>
<Button Click="ChangeInListSource">Change in ListSource</Button>
</StackPanel>
</GroupBox>
The above definitions will result in following visuals:
<img src="1034113/ItemsControlList.gif" />
Of course, we can also bind to collections of complex objects. There will be a lot of times you will use Template
s to display the items in the collection. I will not discuss this here, because a later article will be about using Template
s to customize visualization, but the ItemsControl provides some properties which allow you to display members of the items without needing to use Template
s.
class ComplexClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string MuteProperty
{
get;
set;
}
string m_notifyingProperty;
public string NotifyingProperty
{
get { return m_notifyingProperty; }
set
{
m_notifyingProperty = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("NotifyingProperty"));
}
}
}
m_dataContext.ComplexObservableSource = new ObservableCollection<ComplexClass>();
m_dataContext.ComplexObservableSource.Add(new ComplexClass() { MuteProperty = "Mute 1", NotifyingProperty = "Notify 1" });
m_dataContext.ComplexObservableSource.Add(new ComplexClass() { MuteProperty = "Mute 2", NotifyingProperty = "Notify 2" });
m_dataContext.ComplexObservableSource.Add(new ComplexClass() { MuteProperty = "Mute 3", NotifyingProperty = "Notify 3" });
m_dataContext.ComplexObservableSource.Add(new ComplexClass() { MuteProperty = "Mute 4", NotifyingProperty = "Notify 4" });
private void AddToComplexObservableSource(object sender, RoutedEventArgs e)
{
m_dataContext.ComplexObservableSource.Add(new ComplexClass() { MuteProperty = "Added mute value", NotifyingProperty = "Added notifying value" });
}
private void RemoveFromComplexObservableSource(object sender, RoutedEventArgs e)
{
m_dataContext.ComplexObservableSource.RemoveAt(0);
}
private void ChangeItemInComplexObservableSource(object sender, RoutedEventArgs e)
{
m_dataContext.ComplexObservableSource[0] = new ComplexClass() { MuteProperty = "Changed Mute", NotifyingProperty = "Changed Notify" };
}
private void ChangeMuteInComplexObservableSource(object sender, RoutedEventArgs e)
{
m_dataContext.ComplexObservableSource[0].MuteProperty = "Changed mute value";
}
private void ChangeNotifyingInComplexObservableSource(object sender, RoutedEventArgs e)
{
m_dataContext.ComplexObservableSource[0].NotifyingProperty = "Changed notifying value";
}
<GroupBox Header="Complex Observable Collection">
<StackPanel>
<ItemsControl ItemsSource="{Binding Path=ComplexObservableSource}" DisplayMemberPath="MuteProperty" />
<ItemsControl ItemsSource="{Binding Path=ComplexObservableSource}" DisplayMemberPath="NotifyingProperty" />
-->
<ItemsControl ItemsSource="{Binding Path=ComplexObservableSource}" DisplayMemberPath="NotifyingProperty" ItemStringFormat="Formatted - {0}" />
-->
<Button Click="AddToComplexObservableSource">Add to ComplexObservableSource</Button>
<Button Click="RemoveFromComplexObservableSource">Remove from ComplexObservableSource</Button>
<Button Click="ChangeItemInComplexObservableSource">Change item in ComplexObservableSource</Button>
<Button Click="ChangeMuteInComplexObservableSource">Change Mute in ComplexObservableSource</Button>
<Button Click="ChangeNotifyingInComplexObservableSource">Change Notifying in ComplexObservableSource</Button>
-->
<ItemsControl ItemsSource="{Binding Path=ComplexObservableSource}" ItemStringFormat="Formatted - {MuteProperty}" />
</StackPanel>
</GroupBox>
The above definitions will result in following visuals:
<img src="1034113/ItemsControlObservableCollectionComplex.gif" />
That is a lot, but most of it is well known stuff. First things first: we are using the DisplayMemberPath
on the ItemsControl
and as such it shows the value of the property with this name of the items class. Again, we have a INotifyCollectionChanged
implementing collection, so addition, removal and changing of members in the collection updates our ItemsControl
. Notice however how using the DisplayMemberPath
on a property backed by INotifyPropertyChanged
also updates our visuals when this property is changed, but not if the property is a mute property. DisplayMemberPath
effectively results in a Binding
on the item listening for changes. So if we click the Change Mute button the visuals don't change and if we click the Change Notifying button the visuals do change. Notice also how the operations on the collection also update what is shown in the ItemsControl
with a DisplayMemberPath
to the mute property. This is not surprising: the ItemsControl
is notified of changes at a position in the collections and simply reads the complete object at that position. No need for INotifyPropertyChanged
here.
But I have multiple items in my source of which I want one selected: ListBox
Concepts
ItemsControl
is a big subject and the class also has some derivatives, like the System.Windows.Controls.Primitives.Selector
which allows for selection of the items in the ItemsSource
. Remember how previously we discussed the CollectionView
? This class comes into play here also.
As previously stated, the CollectionView
has a CurrentItem
property which can act as the source for the SelectedItem
property of the Selector
.
Let's try a few things to see how this all works
How to do it?
Again, a most basic example:
private void GetSelectedItemNoSync(object sender, RoutedEventArgs e)
{
if (ListSyncNoSyncing.SelectedItem == null)
MessageBox.Show("There is no current Item");
else
MessageBox.Show((ListSyncNoSyncing.SelectedItem as ComplexClass).NotifyingProperty);
}
private void GetCurrentItemNoSync(object sender, RoutedEventArgs e)
{
ICollectionView view = CollectionViewSource.GetDefaultView(m_dataContext.ComplexObservableSource);
if(view.CurrentItem == null)
MessageBox.Show("There is no current Item");
else
MessageBox.Show((view.CurrentItem as ComplexClass).NotifyingProperty);
}
private void GetSelectedItemWtSync(object sender, RoutedEventArgs e)
{
if (ListSyncWtSyncing.SelectedItem == null)
MessageBox.Show("There is no current Item");
else
MessageBox.Show((ListSyncWtSyncing.SelectedItem as ComplexClass).NotifyingProperty);
}
private void GetCurrentItemWtSync(object sender, RoutedEventArgs e)
{
ICollectionView view = CollectionViewSource.GetDefaultView(m_dataContext.ComplexObservableSource);
if (view.CurrentItem == null)
MessageBox.Show("There is no current Item");
else
MessageBox.Show((view.CurrentItem as ComplexClass).NotifyingProperty);
}
<GroupBox Header="Simple Observable Collection: What is Synchronization">
<StackPanel>
-->
<Label Content="Listbox with No Sync" />
<ListBox Name="ListSyncNoSyncing" ItemsSource="{Binding Path=ComplexObservableSource}" DisplayMemberPath="NotifyingProperty" />
<Button Click="GetSelectedItemNoSync">Show Selected Item on Not Synced</Button>
<Button Click="GetCurrentItemNoSync">Show Current Item on Not Synced</Button>
<Label Content="Listbox with Sync" />
<ListBox Name="ListSyncWtSyncing" ItemsSource="{Binding Path=ComplexObservableSource}" DisplayMemberPath="NotifyingProperty" IsSynchronizedWithCurrentItem="True" />
<Button Click="GetSelectedItemWtSync">Show Selected Item with Syncing</Button>
<Button Click="GetCurrentItemWtSync">Show Current Item with Syncing</Button>
<ListBox Name="ListSyncWtSyncing2" ItemsSource="{Binding Path=ComplexObservableSource}" DisplayMemberPath="NotifyingProperty" IsSynchronizedWithCurrentItem="True" />
</StackPanel>
</GroupBox>
The above definitions will result in following visuals:
<img src="1034113/ItemsControlObservableCollectionSyncExplain.gif" />
So, if you set the IsSynchronizedWithCurrentItem = True
, then the SelectedItem
property of the ListBox
is equal to the CurrentItem
of the default view of the collection acting as the ItemsSource
of the ListView
. This becomes clear in a few scenario's:
- If, upon opening the window, you select in the ListSyncNoSyncing
ListBox
items by clicking on the ListBox
and then click the Show Current Item on Not Synced Button
, you will see the message "There is no current Item". However, if you click the Show Selected Item on Not Synced Button
you will see the selected item in a messagebox.
- If you now select an item in the ListSyncWtSyncing
ListBox
and then click the Show Current Item on Synced Button
, you will see the item you selected in the ListSyncWtSyncing ListBox
: the CurrentItem
on the default view is independent of the ListBox
. Notice also how the selection of the ListSyncWtSyncing2 ListBox
also changes: this ListBox
is also synchronized with the CurrentItem
on the default view.
Notice also how this has nothing to do with what we display, or the type of property of what we display. With two ListBox
es, each set to a different DisplayMemberPath
are kept synchronized when selecting different items:
<GroupBox Header="Simple Observable Collection: Synchronization">
<StackPanel>
-->
<ListBox ItemsSource="{Binding Path=ComplexObservableSource}" DisplayMemberPath="MuteProperty" IsSynchronizedWithCurrentItem="True" />
<ListBox ItemsSource="{Binding Path=ComplexObservableSource}" DisplayMemberPath="NotifyingProperty" IsSynchronizedWithCurrentItem="True" />
</StackPanel>
</GroupBox>
The above definitions will result in following visuals:
<img src="1034113/ItemsControlObservableCollectionSyncDiffDisplayMember.gif" />
Also notice how this has nothing to do with the type of collection the ListBox
is bound to: this also works with a List
. All this is implemented by using the ICollectionView
and has nothing to do with the collection itself. After all, we're not changing anything in the collection itself, but are maintaining information perpendicular to the collection functionality.
<GroupBox Header="Simple List Collection: Synchronization">
<StackPanel>
-->
<ListBox ItemsSource="{Binding Path=ComplexListSource}" DisplayMemberPath="MuteProperty" IsSynchronizedWithCurrentItem="True" />
<ListBox ItemsSource="{Binding Path=ComplexListSource}" DisplayMemberPath="NotifyingProperty" IsSynchronizedWithCurrentItem="True" />
</StackPanel>
</GroupBox>
The above definitions will result in following visuals:
<img src="1034113/ItemsControlListSync.gif" />
It will of course be no surprise that, when binding to the SelectedItem
and then changing it in code, will maintain it in all synchronized ListBox
es:
ComplexClass m_selectedInObservable;
public ComplexClass SelectedInObservable
{
get { return m_selectedInObservable; }
set
{
m_selectedInObservable = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("SelectedInObservable"));
}
}
private void SetSelectedInObservable(object sender, RoutedEventArgs e)
{
m_dataContext.SelectedInObservable = m_dataContext.ComplexObservableSource[2];
}
<GroupBox Header="Simple Observable Collection: Synchronization and SelectedItem">
<StackPanel>
-->
<ListBox ItemsSource="{Binding Path=ComplexObservableSource}" SelectedItem="{Binding Path=SelectedInObservable}" DisplayMemberPath="MuteProperty" IsSynchronizedWithCurrentItem="True" />
<ListBox ItemsSource="{Binding Path=ComplexObservableSource}" DisplayMemberPath="NotifyingProperty" IsSynchronizedWithCurrentItem="True" />
<Button Content="Set Selected to second item in collection" Click="SetSelectedInObservable" />
</StackPanel>
</GroupBox>
The above definitions will result in following visuals:
<img src="1034113/ItemsControlObservableCollectionSyncSetInCode.gif" />
And finally it also works when using the SelectedValuePath
property instead of the SelectedItem
:
string m_selectedValueInObservable;
public string SelectedValueInObservable
{
get { return m_selectedValueInObservable; }
set
{
m_selectedValueInObservable = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("SelectedValueInObservable"));
}
}
private void SetSelectedValueInObservable(object sender, RoutedEventArgs e)
{
m_dataContext.SelectedValueInObservable = m_dataContext.ComplexObservableSource[2].NotifyingProperty;
}
<GroupBox Header="Simple Observable Collection: Synchronization and SelectedValue">
<StackPanel>
-->
<ListBox ItemsSource="{Binding Path=ComplexObservableSource}" SelectedValue="{Binding SelectedValueInObservable}" SelectedValuePath="NotifyingProperty" DisplayMemberPath="MuteProperty" IsSynchronizedWithCurrentItem="True" />
<ListBox ItemsSource="{Binding Path=ComplexObservableSource}" DisplayMemberPath="NotifyingProperty" IsSynchronizedWithCurrentItem="True" />
-->
<Button Content="Set Selected to a property of second item in collection" Click="SetSelectedValueInObservable" />
</StackPanel>
</GroupBox>
The above definitions will result in following visuals:
<img src="1034113/ItemsControlObservableCollectionSyncSelectedValuePathSetInCode.gif" />
A lot allready has been written about binding in WPF and I hold no illusion as to have written something new here. I wrote this article partly for myself as explaining something to someone else always helps me to understand the subject myself. Also, allthough a lot has been written, I wanted to add a twist: supply examples on what works, but also on what doesn't. Hence some xml in comments which wouldn't compile. But I invite you to uncomment these sections to see what happens.
Anyway, I hope you learned something by reading this article, I know I did explaining the concepts.