There are many articles that discuss the concept of binding and explain how to use static resources and dynamic resources to bind properties. These concepts use the data binding expressions provided by WPF. In this article, let's study the different types of data binding expressions provided by WPF.
introduce
Data binding is a powerful technology that allows data to flow between UI elements and business models. When the data in the business model changes, it will automatically reflect the changes to the UI elements.
Models | Description |
---|---|
OneWay | Source → Destination |
TwoWay | Source ←→ Destination |
OneWayToSource | Source ← Destination |
OneTime | Source → Destination (only once) |
This can be achieved through different types of data binding expressions provided by WPF.
The types of data binding expressions are as follows.
- DataContext binding
- RelativeSource binding
- Collection current item binding
1. DataContext binding
DataContext is a dependent property, which is the default source of binding. DataContext inherits along the logical tree. Therefore, if you set a DataContext to control all child elements in the logical tree, it will also reference the same DataContext unless and until another source is explicitly specified.
Let's take an example to understand it in more detail.
1.1 create a class Book, as shown below.
public class Book { public string Name { get; set; } public string Author { get; set; } }
1.2 add a XAML file DataContextBinding.XAML and place four textblocks, as shown below.
<Grid VerticalAlignment="Center"> <Grid.RowDefinitions> <RowDefinition Height="40" /> <RowDefinition Height="40" /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" /> </Grid.ColumnDefinitions> <TextBlock Text="Book Name:" FontWeight="Bold" /> <TextBlock Grid.Column="1" /> <TextBlock Text="Author:" FontWeight="Bold" Grid.Row="1" /> <TextBlock Grid.Row="1" Grid.Column="1" /> </Grid>
Now, let's see how to use this DataContext property to display data.
It has two uses, as shown below.
- 1. Use {Binding} expression
Used to bind DataContext directly.
Create an instance of class Book, initialize its properties, and assign the Name property of the class to the DataContext property of Window.
public partial class DataContextBinding: Window { public DataContextBinding() { InitializeComponent(); //Create the instance Book book = new Book(); //initialize the properties book.Name = "Computer Networking"; //Assign the Property as DataContext this.DataContext = book.Name; } }
Because DataContext inherits along the logical tree and data book, name is bound to the Control Window. All child elements of Window will also reference the same object (book.Name).
To display data, bind DataContext to Textblock, as shown below.
<TextBlock Text="Book Name:" FontWeight="Bold"/> <TextBlock Text="{Binding}" Grid.Column="1" />
output
- Use the {Binding Property} expression
Bind the properties of Datacontext.
Create an instance of the class book, initialize its properties, and assign the instance of the class (Book) to the DataContext property of Window.
Book book = new Book(); //initialize the properties book.Name = "Computer Networking"; book.Author = "James F. Kurose"; //Assign the instance as DataContext this.DataContext = book;
Now, let's look at the output.
Since the Binding expression {Binding} is used to bind a DataContext object of type Book, the ToString () method is called and the data is displayed as a string. In order to display data in the correct format, we must bind the properties of the data object to TextBlock, as shown below:
<TextBlock Text="Book Name:" FontWeight="Bold"/> <TextBlock Text="{Binding Name}" Grid.Column="1" /> <TextBlock Text="Author:" FontWeight="Bold" Grid.Row="1" /> <TextBlock Text="{Binding Author}" Grid.Row="1" Grid.Column="1"/>
The binding expression {Binding Name} is used to bind the Name property of DataContext binding.
output
2. RelativeSource binding
RelativeSource is a property that sets the binding source with a relative relationship to bind the target. This extension is mainly used when one attribute of an element must be bound to another attribute of the same element.
There are four types of relativesources, as shown below.
- Self
- FindAncestor
- TemplatedParent
- PreviousData
Let's discuss it in detail one by one.
2.1 Self
Self is used in scenes where the binding source and the binding target are the same. An attribute of an object is bound to another attribute of the same object.
For example, let's take an ellipse with the same height and width.
Add the code given below to the XAML file. The width attribute is bound relative to the height attribute.
<Grid> <Ellipse Fill="Black" Height="100" Width="{Binding RelativeSource={RelativeSource Self},Path=Height}"> </Ellipse> </Grid>
output
If you change the height of the ellipse, the width will also change relatively.
2.2 FindAncestor
As the name suggests, use this option when the binding source is one of the ancestors (parents) of the binding target. Using the FindAncestor extension, you can find ancestors at any level.
Let's take an example to understand it more clearly.
step
Create a XAML that represents the logical tree of the elements given below.
<Grid Name="Parent_3"> <StackPanel Name="Parent_2"> <Border Name="Parent_1"> <StackPanel x:Name="Parent_0" Orientation="Vertical"> <Button></Button> </StackPanel> </Border> </StackPanel> </Grid>
Now, let's use the findancester extension to bind the ancestor's Name attribute to the Content attribute of the child element button.
<Grid Name="Parent_3"> <StackPanel Name="Parent_2" HorizontalAlignment="Center" VerticalAlignment="Center" Width="100"> <Border Name="Parent_1"> <StackPanel x:Name="Parent_0" Orientation="Vertical"> <Button Height="50" Content="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type StackPanel}, AncestorLevel=2},Path=Name}"></Button> </StackPanel> </Border> </StackPanel> </Grid>
output
The AncestorType is "StackPanel" and the AcestorLevel is "2", and the content attribute of the button is bound with the Name attribute (Parent_2) of the StackPanel.
2.3 TemplatedParent
TemplatedParent is a property that enables you to create a control template that contains a small number of unknown values. These values depend on the properties of the control to which the ControlTemplate is applied.
Let's take an example to understand it in more detail
step
- Create a ControlTemplate for the button, as shown below.
<Window.Resources> <ControlTemplate x:Key="template"> <Canvas> <Ellipse Height="110" Width="155" Fill="Black"/> <Ellipse Height="100" Width="150" Fill="{Binding RelativeSource={RelativeSource TemplatedParent},Path=Background}"> </Ellipse> <ContentPresenter Margin="35" Content="{Binding RelativeSource={RelativeSource TemplatedParent},Path=Content}"/> </Canvas> </ControlTemplate> </Window.Resources>
In the code given above, the Fill property of the ellipse and the Content property of the ContentPresenter depend on the property value of the control to which this template will be applied.
- Add a button and apply a template to it.
<Button Margin="50" Background="Beige" Template="{StaticResource template}" Height="0" Content="Click me" FontSize="22"> </Button>
When applying a template, the Background (Beige) of the button is bound relative to the Fill attribute of the ellipse, and the Content (Click me) is bound relative to the Content attribute of the ContentPresenter. The dependent value takes effect and gives the following output.
output
2.4 PreviousData
This is the least used way. This occurs when the data is analyzed, and we need to represent the change of the value relative to the previous data.
Let's take an example to understand it in more detail.
step
- Create a Data class and implement the INotifyPropertyChanged interface, as shown below
public class Data: INotifyPropertyChanged { public int DataValue { get; set; } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged(string PropertyName) { if (null != PropertyChanged) { PropertyChanged(this, new PropertyChangedEventArgs(PropertyName)); } } }
- Create a list of Data types and specify it as DataContext.
public RelativeSourcePreviousData() { InitializeComponent(); List < Data > data = new List < Data > (); data.Add(new Data() { DataValue = 60 }); data.Add(new Data() { DataValue = 100 }); data.Add(new Data() { DataValue = 120 }); this.DataContext = data; }
- Add ItemsControl to the XAML file.
<ItemsControl ItemsSource="{Binding}"></ItemsControl>
- Create an ItemsPanel template for it, as follows.
<ItemsControl ItemsSource="{Binding}"> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <StackPanel Orientation="Vertical" /> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> </ItemsControl>
- Now, in order to display the data correctly, create a DataTemplate, as shown below.
<ItemsControl.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal"> <Grid Margin="30,20,0,0"> <Rectangle Width="80" Height="{Binding DataValue}" Fill="Blue" /> <TextBlock Foreground="White" Margin="35,0,0,0" Text="{Binding DataValue}"></TextBlock> </Grid> <TextBlock Margin="30,20,0,0" Text="Previous Data:"></TextBlock> <TextBlock VerticalAlignment="Center" Margin="5,20,0,0" Text="{Binding RelativeSource={RelativeSource PreviousData}, Path=DataValue}" /> </StackPanel> </DataTemplate> </ItemsControl.ItemTemplate>
output
The height of the blue box is the value of the item in the list, and the old data is displayed on the right. The first value of this item is "60". Therefore, the first item has no old value.
3. Collection current item binding
Used when working with collections. Using this binding expression, you can easily read the properties of the SelectedItem. Slashes are special operators that handle the current item in a collection.
Three expressions are given below.
- {Binding / }
- {Binding Collection / }
- {Binding Collection / Property}
3.1 {Binding / }
This expression is used to bind the current item in the DataContext.
Let's take an example:
In the example given below, DataContext is a collection of countries of string type and bound with Listbox.
step
- Create a Countries class and add a GetCountriesName() method that returns a collection of Countries of string data type, as shown below.
public class Countries { public static List <string> GetCountriesName() { List <string> countries = new List <string> (); foreach(CultureInfo culture in CultureInfo.GetCultures(CultureTypes.SpecificCultures)) { RegionInfo country = new RegionInfo(culture.LCID); if (!countries.Contains(country.EnglishName)) countries.Add(country.EnglishName); } countries.Sort(); return countries; } }
- Add a XAMl file, a ListBox and TextBlock, as shown below.
<DockPanel Name="Collection"> <ListBox ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="True"> </ListBox> <TextBlock DockPanel.Dock="Top" /> </DockPanel>
- Create an instance of the class Countries and specify the Countries collection as DataContext.
public CurrentItemCollection() { InitializeComponent(); Countries countries = new Countries(); this.DataContext = countries.GetCountriesName() }
- Bind the Text property of TextBlock to bind it to the currently selected item of the collection, as shown below.
<TextBlock DockPanel.Dock="Top" Text="{Binding /}" />
output
Once the list item is selected, it displays the selected country on the right.
3.2 {Binding Collection /}
This expression is used to bind the current item of the collection property in the DataContext.
For example,
DataContext is the Countries class
The Collection property is courrieslist, which is bound to the ListBox.
step
- Use a similar country class created above, but slightly different. Create a method with a return type of RegionInfo.
public static List <RegionInfo> GetCountries() { List <RegionInfo> countries = new List <RegionInfo> (); foreach(CultureInfo culture in CultureInfo.GetCultures(CultureTypes.SpecificCultures)) { RegionInfo country = new RegionInfo(culture.LCID); if (countries.Where(p => p.Name == country.Name).Count() == 0) countries.Add(country); } return countries.OrderBy(p => p.EnglishName).ToList(); }
- Add the CountriesList property of RegionInfo type.
private List <RegionInfo> countries = null; public List <RegionInfo> CountriesList { get { if (countries == null) countries = GetCountries(); return countries; } }
The following is a screenshot of the values in the CountriesList collection.
- Specify the class Countries as DataContext, and bind the Listbox to the CountriesList property of DataContext.
<Window.Resources> <vm:Countries x:Key="Countries"></vm:Countries> </Window.Resources> <Grid> <DockPanel Name="Collection" DataContext="{StaticResource Countries}"> <ListBox ItemsSource="{Binding CountriesList}" IsSynchronizedWithCurrentItem="True"> <ListBox.ItemTemplate> <DataTemplate> <TextBlock Text="{Binding EnglishName}"></TextBlock> </DataTemplate> </ListBox.ItemTemplate> </ListBox> </DockPanel> </Grid>
- To calculate the current item of the CountriesList property, bind the Text property of TextBlock, as shown below.
<TextBlock DockPanel.Dock="Top" Text="{Binding CountriesList/}" HorizontalAlignment="Center" FontSize="16" VerticalAlignment="Center" />
output
The right side displays the current item (CountriesList) of the collection in the DataContext (CountriesList).
3.3 {Binding Collection / Property}
This expression is used to bind the properties of the current item of the collection in the DataContext.
For example, if you must evaluate the specific properties of the current item in the CountriesList collection.
In this example, I want to display the value of the property "EnglishName".
To do this, bind the Text property of TextBlock, as shown below.
<TextBlock DockPanel.Dock="Top" Text="{Binding CountriesList/EnglishName}" />
output
Now, when an item in the list is selected, it displays the value of the property "EnglishName".
conclusion
I've covered all the data binding expressions in detail. I hope this helps you understand the concept of binding and the expressions provided by WPF.