- The position of Data Binding in WPF
- Binding Foundation
- Source and path of Binding
- Extend the control as a Binding source and Binding tag
- Control the Binding direction and data update
- Path of Binding
- Binding without Path
- Several methods of specifying Source for Binding
- Binding without Source -- use DataContext as the Source of binding
- Use the collection object as the ItemsSource of the list control
- Use ADO Net object as the source of Binding
- Use XML data as the Binding source
- Use LINQ search results as the source of Binding
- Use the ObjectDataProvider object as the source of the Binding
- RelativeSource using Binding
- Data conversion and verification by Binding
- MultiBinding
- reference material
The operation mechanism of Windows GUI is to use message to drive the program forward. The main source of message is the user's operation (such as clicking the mouse and pressing the button). The message will be translated by windows and delivered to the target program, and then processed by the program. The program is driven by events from the UI (i.e. encapsulated messages), which is referred to as "message driven" or "event driven". Because most messages and events come from the UI, they are collectively referred to as "UI drivers". Using "UI driver" to develop programs is "GUI for GUI". Simply to realize the GUI of the program, it has deviated from the essence of the program - data addition algorithm.
The position of Data Binding in WPF
In general, applications have a three-tier structure:
- Data storage layer: composed of database and file system;
- Data processing layer: also known as logic layer, the algorithms related to business logic and used for processing data are concentrated here;
- Data display layer: display the processed data to the user through the visual interface or to other applications through other kinds of interfaces (interface and interface are both interface in English). It is also necessary to collect the user's operations and feed them back to the logic layer.
As a special display layer technology, WPF's gorgeous appearance and animation are only its surface phenomenon. WPF introduces the concept of Data Binding and its supporting Dependency Property system and DataTemplate to help programmers fix the focus of thinking on the logic layer and make the presentation layer always subordinate to the logic layer.
The communication between the display layer and the logic layer is realized by Data Binding. The processed data will be automatically sent to the user interface for display, and the data modified by the user will be automatically sent back to the logic layer. Once the data is processed, it will be sent to the user interface. Use the user interface of the processed data driver to display the data in the form of text, graphics and animation - this is "data driven UI".
After Data Binding optimization, all algorithms related to business logic are located in the data logic layer. The logic layer becomes an independent and complete system, while the user interface layer does not contain any code, completely depends on and belongs to the data logic layer.
Binding Foundation
Binding: transliteration of "binding", which means "binding", "association" and "bonding" in English. Binding pays more attention to expressing its relevance like a bridge.
Comparing binding to a data bridge, its two ends are the Source and Target of binding. Where data comes from is the Source. Binding is a bridge in the middle. Binding aims at where data is going. In general, the binding Source is the object of the logical layer, and the binding Target is the control object of the UI layer.
First, create a Student class. The instance of the class is used as the data source. The code is as follows:
public class Student : INotifyPropertyChanged { private string name; public string Name { get { return name; } set { name = value; if (PropertyChanged != null) { this.PropertyChanged.Invoke(this, new PropertyChangedEventArgs("Name")); } } } public event PropertyChangedEventHandler PropertyChanged; }
Prepare a TextBox and Button with the following code:
<StackPanel Margin="0,0,2,0"> <TextBox x:Name="textBoxName" BorderBrush="Black" Margin="5"/> <Button Content="Add Age" Margin="5" Click="Button_Click"/> </StackPanel>
Use Binding to connect the data source and UI elements. The C# code is as follows:
public partial class MainWindow : Window { Student stu = null; public MainWindow() { InitializeComponent(); //Prepare data source stu = new Student(); //Prepare Binding Binding binding = new Binding(); binding.Source = stu; binding.Path = new PropertyPath("Name"); //Using Binding to connect data source and Binding target BindingOperations.SetBinding(this.textBoxName,TextBox.TextProperty,binding); //or this.textBoxName.SetBinding(TextBox.TextProperty, binding); } private void Button_Click(object sender, RoutedEventArgs e) { stu.Name += "Name"; } }
The above code can be simplified as follows:
public partial class MainWindow : Window { Student stu = null; public MainWindow() { InitializeComponent(); //Three in one operation this.textBoxName.SetBinding(TextBox.TextProperty,new Binding("Name") { Source = stu = new Student()}); } private void Button_Click(object sender, RoutedEventArgs e) { stu.Name += "Name"; } }
The element on the UI cares about which attribute value changes. This attribute is called the binding Path. Binding is an automatic mechanism. When the value changes, the property should be able to notify the binding and let the binding pass the change to the UI element. A PropertyChanged event needs to be triggered in the set statement of the property. This event does not need to be declared by itself. Let the class as the data source implement system INotifyPropertyChanged interface in componentmodel namespace. Binding will automatically listen for PropertyChanged events from this interface.
The Binding model is as follows:
Source and path of Binding
The source of Binding is also the source of data. As long as it is an object and exposes its own data through Property, it can be used as the source of Binding. If you want the object as the Binding source to have the ability to automatically notify the Binding that its own Property value has changed, you need to let the class implement the INotifyPropertyChanged interface and fire the PropertyChanged event in the set statement of the Property.
In addition to using this object as a data source, there are:
- Use your own container or child element as the source
- Use one control as a data source for another control
- Use the collection as the data source of ItemsControl
- Use XML as the data source of TreeView or Menu
- Associate multiple controls to a "data commanding point", or even don't specify a data source for Binding and let it find it by itself
Extend the control as a Binding source and Binding tag
In order to make UI elements produce some linkage effects, Binding will be used to establish associations between controls. Next, the Text property of a TextBox is associated with the Value property of the Slider. The code is as follows:
<StackPanel> <TextBox x:Name="textBox1" Text="{Binding Path=Value, ElementName=slider1}" BorderBrush="Black" Margin="5"/> <Slider x:Name="slider1" Maximum="100" Minimum="0" Margin="5"/> </StackPanel>
The above code uses the Binding tag extension syntax:
<TextBox x:Name="textBox1" Text="{Binding Path=Value, ElementName=slider1}" BorderBrush="Black" Margin="5"/>
Equivalent C# codes are as follows:
this.textBox1.SetBinding(TextBox.TextProperty, new Binding("Value") { ElementName = "slider1" }); //Generally, the ElementName attribute of Binding is not used, but the object is directly assigned to the Source attribute of Binding to facilitate reconstruction this.textBox1.SetBinding(TextBox.TextProperty, new Binding("Value") {Source=slider1});
The constructor of Binding class itself can receive Path as a parameter, which is often written as:
<TextBox x:Name="textBox1" Text="{Binding Value, ElementName=slider1}" BorderBrush="Black" Margin="5"/>
Control the Binding direction and data update
The attribute controlling the Binding data flow direction is Mode. Its type is BindingMode enumeration. The values can be:
- TwoWay: causes the other party to be automatically updated when changing the source attribute or target attribute, bidirectional mode.
- OneWay: update the binding target (target) when changing the binding source (source), and one-way communication from the source to the target.
- OnTime: when the application starts or the data context changes, the Binding target is updated, and the data is read only once when the Binding relationship is established.
- OneWayToSource: when the target property changes, update the source property and one-way communication from the target to the source.
- Default: use the default Mode value of the Binding target, and the Binding Mode will be determined according to the actual situation of the target (if it is editable, it adopts two-way Mode, such as TextBox.Text property; if it is read-only, it adopts one-way Mode, such as TextBlock.Text).
The property that controls the update of Binding data is UpdateSourceTrigger. Its type is UpdateSourceTrigger enumeration, and its values are:
- PropertyChanged: the binding source is updated whenever the binding target property changes.
- LostFocus: the binding source is updated whenever the binding target element loses focus.
- Explicit: updates the binding source only when the UpdateSource() method is called.
- Default: the default UpdateSource trigger value of the binding target property. The default value of most dependent properties is PropertyChanged, while the default value of Text property is LostFocus.
Binding also has two bool type properties: NotifyOnSourceUpdated and NotifyOnTargetUpdated. If set to true, binding will fire the corresponding SourceUpdated event and TargetUpdated event when the source or target is updated. In practice, we can listen to these two events to find out which data or controls have been updated.
Path of Binding
The object as the Binding source may have many properties. The Path property of the Binding needs to specify which property Value the Binding needs to pay attention to. For example, the Slider control object is used as the source and its Value property is used as the Path. Although Path is represented as a string in XAML code or in the constructor parameter list of Binding class, the actual type of Path is PropertyPath.
The association is on the property of the Binding source
XAML syntax is as follows:
<TextBox x:Name="textBox1" Text="{Binding Path=Value,ElementName=sliderl}"/>
The equivalent C# code is as follows:
Binding binding = new Binding(){ Path = new PropertyPath("Value"), Source = this.slider1 }; this.textBox1.SetBinding(TextBox.TextProperty, binding);
The constructor using Binding is abbreviated as:
Binding binding = new Binding() { Path = new PropertyPath("Value"), Source = this.slider1 }; this.textBox1.SetBinding(TextBox.TextProperty, binding);
Multilevel path of Binding
Binding supports multi-level paths (generally speaking, it means "clicking" all the way). For example, let one TextBox display the text length of another TextBox. The XAML code is as follows:
<StackPanel> <TextBox x:Name="textBox1" BorderBrush="Black" Margin="5"/> <TextBox x:Name="textBox2" Text="{ Binding Path=Text.Length, ElementName=textBox1, Mode=OneWay}" BorderBrush="Black" Margin="5"/> </StackPanel>
The equivalent C# code is:
this.textBox2.SetBinding(TextBox.TextProperty, new Binding("Text.Length") { Source = this.textBox1, Mode =BindingMode.OneWay });
The effect is as follows:
Use indexer as Path
Indexer of collection type, also known as parameterized property, can also be used as Path. If one TextBox displays the fourth character of another TextBox, the XAML code is as follows:
<!--XAML The editor will think this is the wrong syntax, but it does not affect compilation--> <TextBox x:Name="textBox2" Text="{Binding Path=Text.[3],ElementName=textBox1,Mode=OneWay}" BorderBrush="Black" Margin="5"/>
The equivalent C# code is:
//You can put the "." between Text and [3] If omitted, it can work correctly this.textBox2.SetBinding(TextBox.TextProperty, new Binding("Text.[3]") { Source = this.textBox1, Mode =BindingMode.OneWay });
The effect is as follows:
Use a collection or DataView as the Binding source
When using a collection or DataView as the Binding source, if you want to use its default element as Path, you need to use this syntax:
List<string> stringList = new List<string>() { "Tim", "Tom", "Blog" }; textBox1.SetBinding(TextBox.TextProperty, new Binding("/") { Source = stringList }); textBox2.SetBinding(TextBox.TextProperty, new Binding("/Length") { Source = stringList, Mode = BindingMode.OneWay }); textBox3.SetBinding(TextBox.TextProperty, new Binding("/[2]") { Source = stringList, Mode = BindingMode.OneWay }); //Equivalent code textBox1.SetBinding(TextBox.TextProperty, new Binding("[0]") { Source = stringList }); textBox2.SetBinding(TextBox.TextProperty, new Binding("[0].Length") { Source = stringList, Mode = BindingMode.OneWay }); textBox3.SetBinding(TextBox.TextProperty, new Binding("[0].[2]") { Source = stringList, Mode = BindingMode.OneWay });
The effect is as follows:
If the attribute of a set element is still a set and you want to treat the element in the child set as a Path, you can use the syntax of multi-level slashes (that is, go all the way down). The code is as follows:
//Related types class City { public string Name { get; set; } } class Province { public string Name { get; set; } public List<City> CityList { get; set; } } class Country { public string Name { get; set; } public List<Province> ProvinceList { get; set; } } //Binding List<Country> countryList = new List<Country> { new Country() { Name = "China", ProvinceList = new List<Province>() { new Province() { Name = "Sichuan", CityList = new List<City>() { new City(){ Name = "Chengdu" } } } } } }; textBox1.SetBinding(TextBox.TextProperty, new Binding("/Name") { Source = countryList }); textBox2.SetBinding(TextBox.TextProperty, new Binding("/ProvinceList/Name") { Source = countryList }); textBox3.SetBinding(TextBox.TextProperty, new Binding("/ProvinceList/CityList/Name") { Source = countryList }); //Equivalent C# code textBox1.SetBinding(TextBox.TextProperty, new Binding("[0].Name") { Source = countryList }); textBox2.SetBinding(TextBox.TextProperty, new Binding("[0].ProvinceList[0].Name") { Source = countryList }); textBox3.SetBinding(TextBox.TextProperty, new Binding("[0].ProvinceList[0].CityList[0].Name") { Source = countryList });
The effect is as follows:
Binding without Path
Sometimes I see some paths in the code as a "." Or there is no Path Binding at all, which is a special case - the Binding source itself is data and does not need Path to indicate it. For example, instances of basic types such as string and int are data themselves. It is impossible to indicate which property of the instance is used to access the data. In this case, you only need to set the value of Path to "." That's it.
Note: in XAML code, this "." You can omit it, but you can't omit it in C# code.
The code is as follows:
<StackPanel> <StackPanel.Resources> <sys:String x:Key="myString"> Bodhi has no trees, and the mirror is not a platform. There's nothing in the world, where can I get dust. </sys:String> </StackPanel.Resources> <TextBlock x:Name="textBlock1" TextWrapping="Wrap" Margin="5" FontSize="16" Text="{Binding Path=.,Source={StaticResource ResourceKey=myString}}"/> </StackPanel>
The effect is as follows:
The above code can be abbreviated as follows:
<--Form 1-!> Text="{Binding .,Source={StaticResource ResourceKey=myString}}" <--Form 2-!> Text="{Binding Source={StaticResource ResourceKey=myString}}"
The equivalent C# code is as follows:
string myString = "Bodhi has no trees, and the mirror is not a platform. There's nothing in the world, where can I get dust."; //Form 1 textBlock1.SetBinding(TextBlock.TextProperty, new Binding(".") { Source = myString }); //Form 2 is easily misunderstood as not specifying Path textBlock1.SetBinding(TextBlock.TextProperty, new Binding() { Source = myString });
Several methods of specifying Source for Binding
The source of Binding is the source of data. As long as an object contains data and can expose the data through attributes, it can be used as the source of Binding. Common methods are:
- Specify a single object of normal CLR type as source: including NET Framework has its own type of object and user-defined type of object (the INotifyPropertyChanged interface needs to be implemented). There are two methods: assign the object to binding Source property or assign the Name of the object to binding Elementname.
- Specify the common CLR collection type object as Source: including array, List, ObservableCollection and other collection types (generally associate the ItemsSource property of the ItemsControl derived control with a collection object).
- Put ADO Net data object is specified as Source: including DataTable, DataView and other objects.
- Use XmlDataProvider to specify XML data as Source: XML can be used to represent a single data object or collection, and the XML data of tree structure can be specified as the Source to the Binding associated with cascading WPF controls (such as TreeView and Menu).
- Specify the Dependency Object as the Source: the Dependency Object can be used as the target or Source of Binding, which may form a Binding chain, and the dependency attribute in the Dependency Object can be used as the Path of Binding.
- Specify the DataContext of the container as the Source (the default behavior of WPF Data Binding): create a Binding that only sets the Path and does not set the Source (only determines the property and does not determine the object). The Binding will automatically regard the DataContext of the control as its own Source (look for the object with the property specified by Path from the outer layer along the control tree).
- Specify Source through ElementName: in C# code, you can directly assign the object as Source to Binding, but XAML cannot access the object, so you can only use the Name attribute of the object to find the object.
- Specify the Source relatively through the RelativeSource property of Binding: this method is needed when the control needs to pay attention to a value of its own, its own container or its own internal element.
- Specify the ObjectDataProvider object as the Source: when the data of the data Source is exposed to the outside world not through attributes but through methods, these two objects can be used to wrap the data Source and then specify them as the Source.
- Take the data object retrieved using LINQ as the source of Binding: the result of LINQ query is an IEnumerable object.
Binding without Source -- use DataContext as the Source of binding
The DataContext property is defined in the FrameworkElement class (the base class of WPF controls). All WPF controls (including container controls) have this property. The UI layout of WPF is a tree structure, and each node is a control - each node of the UI element tree has a DataContext. When a Binding only knows its own Path but not its own Soruce, it will go all the way along the UI element tree to the root of the tree. When passing a node, it is necessary to see whether the DataContext of the node has the attribute specified by the Path:
- If so, take this object as your own Source;
- If not, keep looking;
- If it is not found at the root of the tree, the Binding has no Source, so it will not get data.
The following is a simple example. The code is as follows:
//Create a class named Student with three attributes: Id, Name and Age public class Student { public int Id { get; set; } public string Name { get; set; } public int Age { get; set; } }
<StackPanel Background="LightBlue"> <StackPanel.DataContext> <local:Student Id="6" Age="29" Name="Tim"/> </StackPanel.DataContext> <Grid> <StackPanel> <TextBox Text="{Binding Path=Id}" Margin="5"/> <TextBox Text="{Binding Path=Name}" Margin="5"/> <TextBox Text="{Binding Path=Age}" Margin="5"/> </StackPanel> </Grid> </StackPanel>
The tree view of UI layout is as follows:
The Text of the three textboxes obtains the value through Binding, but only specifies the Path for the Binding without specifying the Source. It can be abbreviated as:
<TextBox Text="{Binding Id}" Margin="5"/> <TextBox Text="{Binding Name}" Margin="5"/> <TextBox Text="{Binding Age}" Margin="5"/>
The effect is as follows:
When a DataContext is a simple type object, Path can be set to "." Or omit to write, you may see a Binding with "neither Path nor Source":
<StackPanel> <StackPanel.DataContext> <sys:String>Hello DataContext!</sys:String> </StackPanel.DataContext> <Grid> <StackPanel> <TextBlock Text="{Binding}" Margin="5"/> <TextBlock Text="{Binding}" Margin="5"/> <TextBlock Text="{Binding}" Margin="5"/> </StackPanel> </Grid> </StackPanel>
DataContext is a "dependent property". When there is no explicit assignment for a dependent property of the control, the control will "borrow" the property value of its own container as its own property value. "Binding up the UI element tree" is just an illusion. In fact, attribute values are passed down the UI element tree.
In practice, the usage of DataContext is very flexible, such as:
- When multiple controls on the UI focus on the same object with Binding, you might as well use DataContext.
- When the object as the Source cannot be accessed directly - for example, when the control in form B wants to take the control in form A (private access level) as its own Binding Source, the control (or the value of the control) can be used as the DataContext (public access level) of form A to expose the data.
Use the collection object as the ItemsSource of the list control
The List controls in WPF derive from the ItemsControl class and inherit the ItemsSource property. The ItemsSource property can receive an instance of an IEnumerable interface derived class as its own value (all collections that can be iterated through implement this interface, including arrays, lists, etc.). Each derived class of ItemsControl has its own Item Container (Item Container, such as ListBoxItem of ListBox and ComboBoxItem of ComboBox). As long as the ItemsSource attribute value is set for an ItemsControl object, the ItemsControl object will automatically iterate over the data elements, prepare an Item Container for each data element, and use Binding to establish an association between the Item Container and the data element.
Using DisplayMemberPath
XAML code is as follows:
<StackPanel> <TextBlock Text="Student ID:" FontWeight="Bold" Margin="5"/> <TextBox x:Name="textBoxId" Margin="5"/> <TextBlock Text="Student List:" FontWeight="Bold" Margin="5"/> <ListBox x:Name="listBoxStudents" Height="110" Margin="5"/> </StackPanel>
The C# code is as follows:
//Prepare data source List<Student> stuList = new List<Student>() { new Student(){Id=0,Name="Tim",Age=29}, new Student(){Id=1,Name="Tom",Age=28}, new Student(){Id=2,Name="Kyle",Age=27}, new Student(){Id=3,Name="Tony",Age=26}, new Student(){Id=4,Name="Vina",Age=25}, new Student(){Id=5,Name="Mike",Age=24}, }; //Setting Binding for ListBox listBoxStudents.ItemsSource = stuList; listBoxStudents.DisplayMemberPath = "Name"; //Setting Binding for TextBox Binding binding = new Binding("SelectedItem.Id") { Source = listBoxStudents }; textBoxId.SetBinding(TextBox.TextProperty, binding);
The effect is as follows:
After the DisplayMember property is assigned, the ListBox will create an equal number of listboxitems when it obtains the ItemsSource, and create a Binding with the DisplayMemberPath property value as the Path. The Binding target is the content plug-in of the ListBoxItem (actually a TextBox). The process of creating a Binding is SelectTemplate method of DisplayMemberTemplateSelector class The format of method definition is as follows:
public override DataTemplate SelectTemplate(object item, DependencyObject container) { //... FrameworkElementFactory text = ContentPresenter.CreateTextBlockFactory(); Binding binding = new Binding(); binding.XPath = _displayMemberPath; binding.StringFormat = _stringFormat; text.SetBinding(TextBlock.TextProperty, binding); //... }
Using DataTemplate
Remove listboxstudents from C# code DisplayMemberPath = "Name";, XAML code is as follows:
<StackPanel> <TextBlock Text="Student ID:" FontWeight="Bold" Margin="5"/> <TextBox x:Name="textBoxId" Margin="5"/> <TextBlock Text="Student List:" FontWeight="Bold" Margin="5"/> <ListBox x:Name="listBoxStudents" Height="150" Margin="5"> <ListBox.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal"> <TextBlock Text="{Binding Path=Id}" Width="30"/> <TextBlock Text="{Binding Path=Name}" Width="60"/> <TextBlock Text="{Binding Path=Age}" Width="30"/> </StackPanel> </DataTemplate> </ListBox.ItemTemplate> </ListBox> </StackPanel>
The effect is as follows:
When using the collection type as the ItemsSource of the List control, it is generally considered to use ObservableCollection instead of List, because the ObservableCollection class implements the INotifyCollectionChanged and INotifyPropertyChanged interfaces, which can immediately notify the List control that displays the change of the collection, and the change will appear immediately.
Use ADO Net object as the source of Binding
Yes NET development NET class operates on the database. The common work is to read the data from the database into the DataTable, and then display the DataTable in the UI list control (such as transcript, blog post list, forum post list, etc.). In the popular software architecture, the DataTable is not directly displayed, but the data in the DataTable is first converted into an appropriate user-defined type set through LINQ and other means. However, WPF also supports the direct establishment of Binding between list control and datatable.
Suppose there is an instance of DataTable, and the data content is shown in the table:
Id | Name | Age |
---|---|---|
1 | Tim | 29 |
2 | Tom | 28 |
3 | Tony | 27 |
4 | Kyle | 26 |
5 | Vina | 25 |
6 | Emily | 24 |
The loading method is as follows:
private DataTable Load() { DataTable dt = new DataTable(); dt.Columns.Add("Id"); dt.Columns.Add("Name"); dt.Columns.Add("Age"); dt.Rows.Add("1", "Tim", "29"); dt.Rows.Add("2", "Tom", "28"); dt.Rows.Add("3", "Tony", "27"); dt.Rows.Add("4", "Kyle", "26"); dt.Rows.Add("5", "Vina", "25"); dt.Rows.Add("6", "Emily", "24"); return dt; }
Display DataTable using ListBox
XAML code is as follows:
<StackPanel Background="LightBlue"> <ListBox x:Name="listBoxStudents" Height="130" Margin="5"/> <Button Content="Load" Height="25" Margin="5,0" Click="Button_Click"/> </StackPanel>
The C# code is as follows:
private void Button_Click(object sender, RoutedEventArgs e) { //Get DataTable instance DataTable dt = Load(); listBoxStudents.DisplayMemberPath = "Name"; listBoxStudents.ItemsSource = dt.DefaultView; }
The effect is as follows:
The most important code is listboxstudents ItemsSource = dt. DefaultView;, The defaultview property of DataTable is an object of DataView type (implementing IEnumerable interface), which can be assigned to listbox Itemssource property.
Display DataTable using ListView
In most cases, the ListView control will be selected to display a DataTable. The XAML code is as follows:
<StackPanel Background="LightBlue"> <ListView x:Name="listViewStudents" Height="200" Margin="5"> <ListView.View> <GridView> <GridViewColumn Header="Id" Width="60" DisplayMemberBinding="{Binding Id}"/> <GridViewColumn Header="Name" Width="80" DisplayMemberBinding="{Binding Name}"/> <GridViewColumn Header="Age" Width="60" DisplayMemberBinding="{Binding Age}"/> </GridView> </ListView.View> </ListView> <Button Content="Load" Height="25" Margin="5,0" Click="Button_Click"/> </StackPanel>
The C# code is as follows:
private void Button_Click(object sender, RoutedEventArgs e) { //Get DataTable instance DataTable dt = Load(); listViewStudents.ItemsSource = dt.DefaultView; }
The effect is as follows:
There are several points to note:
- ListView and GridView are not controls of the same level. ListView is a derived class of ListBox and GridView is a derived class of ViewBase. The View property of ListView is an object of ViewBase type. GridView can be used as the View of ListView, but not as an independent control. At present, only one GridView of ListView is available. It is estimated that Microsoft will expand here.
- The content property of GridView is columns (GridViewColumnCollection type object), and < GridView. Com is omitted here Columns>...</ GridView. Columns > this layer of tag (XAML supports the abbreviation of content attributes) defines three GridViewColumn objects directly in the content part of the.
- One of the most important properties of the GridViewColumn object is DisplayMemberBinding (the type is BindingBase). You can specify what kind of Binding this column uses to associate data (unlike ListBox, which uses DisplayMemberPath property).
- If you want to use a more complex structure to represent the Header or data of this column, you can set the HeaderTemplate and CellTemplate properties for GridViewColumn, both of which are DataTemplate.
Datatable cannot be directly used to assign value to ItemsSource. However, you can put the DataTable object in the DataContext property of an object, and set a Binding with neither Path nor Source for ItemsSource. The Binding can automatically find its DefaultView and use it as its own Source:
private void Button_Click(object sender, RoutedEventArgs e) { //Get DataTable instance DataTable dt = Load(); listViewStudents.DataContext = dt; listViewStudents.SetBinding(ListView.ItemsSourceProperty, new Binding()); }
Use XML data as the Binding source
The. NET Framework provides two sets of class libraries for processing XML data:
- Class libraries that conform to DOM (Document Object Model) standards: including XmlDocument, XmlElement, XmlNode, XmlAttribute and other classes. They are characterized by being regular and powerful, but carrying too much tradition and complexity of XML.
- Class library based on LINQ (language integrated query): including XDocument, XElement, XNode, xaattribute and other classes. It is characterized by that LINQ can be used for query and operation, which is convenient and fast, but it will produce many temporary objects.
This section mainly explains the XML class library based on DOM standard. The part based on LINQ will be discussed in the next section.
Most data transmission of modern programming is based on the protocols related to SOAP (Simple Object Access Protocol), and SOAP transmits objects by serializing them into XML text. XML text is tree structured, so XML can be easily used to represent linear sets (such as Array, List, etc.) and tree structured data * *.
When XML data is used as the Source of Binding, the XPath attribute will be used instead of the Path attribute to specify the Source of data. The function of XPath as an XML language has a complete set of syntax. For a detailed explanation, you can refer to:
Displays a linear collection from an XML document
The following XML text is the information of a group of students (assumed to be stored in D:\RawData.xml file), as follows:
<?xml version="1.0" encoding="utf-8" ?> <StudentList> <Student Id="1"> <Name>Tim</Name> </Student> <Student Id="2"> <Name>Tom</Name> </Student> <Student Id="3"> <Name>Vina</Name> </Student> <Student Id="4"> <Name>Emily</Name> </Student> </StudentList>
Display it in a ListView control. The XAML code is as follows:
<StackPanel Background="LightBlue"> <ListView x:Name="listViewStudents" Height="200" Margin="5"> <ListView.View> <GridView> <GridView.Columns> <GridViewColumn Header="Id" Width="60" DisplayMemberBinding="{Binding XPath=@Id}"/> <GridViewColumn Header="Name" Width="80" DisplayMemberBinding="{Binding XPath=Name}"/> </GridView.Columns> </GridView> </ListView.View> </ListView> <Button Content="Load" Height="25" Margin="5,0" Click="Button_Click"/> </StackPanel>
In the Click event handler of Button, you can specify XML through the Document or Source attribute of XmlDataProvider. The code is as follows:
//The first one: manually load the XML Document and assign it to the Document attribute of XmlDataProvider private void Button_Click(object sender, RoutedEventArgs e) { XmlDocument doc = new XmlDocument(); doc.Load(@"D:\RawData.xml"); XmlDataProvider xdp = new XmlDataProvider(); xdp.Document = doc; //Use XPath to select the data to be exposed //Now we need to expose a group of students xdp.XPath = @"/StudentList/Student"; listViewStudents.DataContext = xdp; listViewStudents.SetBinding(ListView.ItemsSourceProperty, new Binding()); } //The second method: directly specify the location of the XML document (whether the XML document is stored on the local hard disk or on the network) to the Source attribute of the XmlDataProvider private void Button_Click(object sender, RoutedEventArgs e) { XmlDataProvider xdp = new XmlDataProvider(); xdp.Source = new Uri(@"D:\RawData.xml"); xdp.XPath = @"/StudentList/Student"; listViewStudents.DataContext = xdp; listViewStudents.SetBinding(ListView.ItemsSourceProperty, new Binding()); }
The effect is as follows:
The key in XAML code are DisplayMemberBinding="{Binding XPath=@Id}" and DisplayMemberBinding="{Binding XPath=Name}". They indicate the XML path of interest for the two columns of GridView respectively. The Attribute of XML element is represented by @ symbol plus string, and the child element is represented by string without @ symbol.
Display tree data structure from XAML code
XAML code is as follows:
<Window.Resources> <XmlDataProvider x:Key="xdp" XPath="FileSystem/Folder"> <x:XData> <FileSystem xmlns=""> <Folder Name="Books"> <Folder Name="Programming"> <Folder Name="Windows"> <Folder Name="WPF"/> <Folder Name="MFC"/> <Folder Name="Delphi"/> </Folder> </Folder> <Folder Name="Tools"> <Folder Name="Development"/> <Folder Name="Designment"/> <Folder Name="Players"/> </Folder> </Folder> </FileSystem> </x:XData> </XmlDataProvider> </Window.Resources> <Grid> <TreeView ItemsSource="{Binding Source={StaticResource xdp}}"> <TreeView.ItemTemplate> <HierarchicalDataTemplate ItemsSource="{Binding XPath=Folder}"> <TextBlock Text="{Binding XPath=@Name}"/> </HierarchicalDataTemplate> </TreeView.ItemTemplate> </TreeView> </Grid>
The effect is as follows:
If you write XmlDataProvider directly in XAML code, its XML data needs to be placed in < x: XDATA ></ x: In the XDATA > tab, StaticResource and hierarchical Datatemplate will be understood after learning Resource and Template.
Use LINQ search results as the source of Binding
. NET Framework 3.0 began to support LINQ (language integrated query). Using LINQ, you can easily operate collection objects, DataTable objects and XML objects without nesting several layers of foreach loops.
The result of LINQ query is an IEnumerable object, and IEnumerable is derived from IEnumerable, so it can be used as ItemsSource of list control.
Created a class named Student:
public class Student { public int Id { get; set; } public string Name { get; set; } public int Age { get; set; } }
XAML code is as follows:
<StackPanel Background="LightBlue"> <ListView x:Name="listViewStudents" Height="145" Margin="5"> <ListView.View> <GridView> <GridViewColumn Header="Id" Width="60" DisplayMemberBinding="{Binding Id}"/> <GridViewColumn Header="Name" Width="100" DisplayMemberBinding="{Binding Name}"/> <GridViewColumn Header="Age" Width="80" DisplayMemberBinding="{Binding Age}"/> </GridView> </ListView.View> </ListView> <Button Content="Load" Height="25" Margin="5,0" Click="Button_Click"/> </StackPanel>
Retrieve all students whose names begin with the letter T from a filled List object. The code is as follows:
private void Button_Click(object sender, RoutedEventArgs e) { List<Student> stuList = new List<Student>() { new Student(){Id=0,Name="Tim",Age=29}, new Student(){Id=1,Name="Tom",Age=28}, new Student(){Id=2,Name="Kyle",Age=27}, new Student(){Id=3,Name="Tony",Age=26}, new Student(){Id=4,Name="Vina",Age=25}, new Student(){Id=5,Name="Mike",Age=24} }; listViewStudents.ItemsSource = from stu in stuList where stu.Name.StartsWith("T") select stu; }
Retrieve all students whose names begin with the letter T from a filled DataTable object. The code is as follows:
private void Button_Click(object sender, RoutedEventArgs e) { DataTable dt = this.GetDataTable(); listViewStudents.ItemsSource = from row in dt.Rows.Cast<DataRow>() where Convert.ToString(row["Name"]).StartsWith("T") select new Student() { Id = int.Parse(row["Id"].ToString()), Name = row["Name"].ToString(), Age = int.Parse(row["Age"].ToString()) }; } private DataTable GetDataTable() { DataTable dt = new DataTable(); dt.Columns.Add("Id"); dt.Columns.Add("Name"); dt.Columns.Add("Age"); dt.Rows.Add("1", "Tim", "29"); dt.Rows.Add("2", "Tom", "28"); dt.Rows.Add("3", "Tony", "27"); dt.Rows.Add("4", "Kyle", "26"); dt.Rows.Add("5", "Vina", "25"); dt.Rows.Add("6", "Emily", "24"); return dt; }
Retrieve all students whose names begin with the letter T from an XML file storing data (D:RawData.xml). The code is as follows:
<?xml version="1.0" encoding="utf-8"?> <StudentList> <Class> <Student Id="0" Name="Tim" Age="29"/> <Student Id="1" Name="Tom" Age="28"/> <Student Id="2" Name="Mess" Age="27"/> </Class> <Class> <Student Id="3" Name="Tony" Age="26"/> <Student Id="4" Name="Vina" Age="25"/> <Student Id="5" Name="Emily" Age="24"/> </Class> </StudentList>
private void Button_Click(object sender, RoutedEventArgs e) { XDocument xdoc = XDocument.Load(@"D:\RawData.xml"); listViewStudents.ItemsSource = from element in xdoc.Descendants("Student") where element.Attribute("Name").Value.StartsWith("T") select new Student() { Id = int.Parse(element.Attribute("Id").Value), Name = element.Attribute("Name").Value, Age = int.Parse(element.Attribute("Age").Value) }; }
Note: xdoc Descendants ("student") this method can span the level of XML.
The effect is as follows:
Use the ObjectDataProvider object as the source of the Binding
Sometimes it is difficult to ensure that all data of a class is exposed using attributes. For example, the required data may be the return value of a method. The risk and cost of redesigning the underlying class will be relatively high, and it is impossible to change the compiled class when black box references the class library. At this time, it is necessary to use ObjectDataProvider to wrap the data object as the Binding source.
ObjectDataProvider provides the object as a data source to Binding. The previous XmlDataProvider also provides XML data as a data source to Binding. The parent classes of both are DataSourceProvider abstract classes.
There is a class called Calculator, which has methods to calculate addition, subtraction, multiplication and division:
class Calculator { //addition public string Add(string arg1, string arg2) { double x = 0; double y = 0; double z = 0; if (double.TryParse(arg1, out x) && double.TryParse(arg2, out y)) { z = x + y; return z.ToString(); } return "Input Error!"; } //Other algorithms }
First, create a WPF project and add a Button. The Click event handler of the Button is as follows:
private void Button_Click(object sender, RoutedEventArgs e) { ObjectDataProvider odp = new ObjectDataProvider(); odp.ObjectInstance = new Calculator(); odp.MethodName = "Add"; odp.MethodParameters.Add("100"); odp.MethodParameters.Add("200"); MessageBox.Show(odp.Data.ToString()); }
The effect is as follows:
It can be seen from the above that the relationship between ObjectDataProvider object and its wrapped object is as follows:
The ObjectDataProvider is used as the Binding Source to realize that the third TextBox can display the sum of the input numbers of the above two textboxes in real time. The code and screenshot are as follows:
<StackPanel Background="LightBlue"> <TextBox x:Name="textBoxArg1" Margin="5"/> <TextBox x:Name="textBoxArg2" Margin="5"/> <TextBox x:Name="textBoxResult" Margin="5"/> </StackPanel>
//Create and configure ObjectDataProvider objects ObjectDataProvider odp = new ObjectDataProvider(); odp.ObjectInstance = new Calculator(); odp.MethodName = "Add"; odp.MethodParameters.Add("0"); odp.MethodParameters.Add("0"); //Create a Binding with the ObjectDataProvider object as the Source. Path is the element in the collection referenced by the MethodParameters property of the ObjectDataProvider object //The Binding object is only responsible for writing the data collected from the UI to its direct Source (that is, the ObjectDataProvider object), rather than the Calculator object wrapped by the ObjectDataProvider object //The UpdataSourceTrigger property is set to return the value to the Source as soon as there is an update Binding bindingToArg1 = new Binding("MethodParameters[0]") { Source = odp, BindsDirectlyToSource = true, UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged }; Binding bindingToArg2 = new Binding("MethodParameters[1]") { Source = odp, BindsDirectlyToSource = true, UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged }; //Create a Binding with the ObjectDataProvider object as the Source. The ObjectDataProvider object itself represents the Data. Path uses Instead of its Data attribute Binding bindingToResult = new Binding(".") { Source = odp }; //Associating a Binding to a UI element textBoxArg1.SetBinding(TextBox.TextProperty, bindingToArg1); textBoxArg2.SetBinding(TextBox.TextProperty, bindingToArg2); textBoxResult.SetBinding(TextBox.TextProperty, bindingToResult);
The ObjectDataProvider class is used to wrap an object that exposes data by methods. Here, an ObjectDataProvider object is created first, and then a Calculator object is used to assign a value to its ObjectInstance property, so a Calculator object is wrapped in the ObjectDataProvider object.
There is another way to create a wrapped object. Tell the ObjectDataProvider the type of the wrapped object and the constructor you want to call. Let the ObjectDataProvider create the wrapped object by itself. This method of specifying the type and constructor is generally used in XAML code. The code is as follows:
//... odp.ObjectType = typeof(YourClass); odp.ConstructorParameters.Add(arg1); odp.ConstructorParameters.Add(arg2); //...
The difference between overloaded methods lies in the parameter list. In the above code, two string objects are added to the MethodParameters property, which is equivalent to telling the ObjectDataProvider object to call the Add method with two string parameters in the Calculator object (the property of MethodParameters is type sensitive).
All three textboxes use ObjectDataProvider objects as data sources, but the first two textboxes restrict the data flow of Binding for the following reasons:
- MethodParameters of ObjectDataProvider are not dependent properties and cannot be the target of Binding.
- The concept of data-driven UI requires that data objects be used as the Source of Binding and UI elements as the Target of Binding as much as possible.
RelativeSource using Binding
Sometimes you only know the relative relationship between the Source object and the object as the Binding target in the UI layout. For example, the control is associated with its own property and the data of its own container. At this time, you should use the RelativeSource property of Binding. The data type of RelativeSource attribute is RelativeSource class. Several static or non static attributes of this class can control the way it searches for relative data sources.
The RelativeSource class has three non static attributes:
- AncestorLevel property: the level offset starting from the Binding target control (for example, in the following example, the offset of d2 is 1, the offset of g2 is 2, and so on).
- AncestorType property: tells Binding which type of object to look for as its own source. Objects of other types will be skipped.
- Mode attribute: Yes RelativeSourceMode enumeration , values include: PreviousData, TemplatedParent, Self and FindAncestor.
There are three static attributes of RelativeSource class: Previous, Self and TemplateParent. Their types are RelativeSource class. These three static attributes are used to directly obtain the RelativeSource instance in XAML code. The essence is to create a RelativeSource instance, set the Mode attribute of the instance to the corresponding value, and then return the instance.
Source code reference of RelativeSource class RelativeSource , some source codes of static attributes are as follows:
public static RelativeSource PreviousData { get { if (s_previousData == null) { s_previousData = new RelativeSource(RelativeSourceMode.PreviousData); } return s_previousData; } }
The following is an example of placing a TextBox in a multi-layer layout control. The XAML code is as follows:
<Grid x:Name="g1" Background="Red" Margin="10"> <DockPanel x:Name="d1" Background="Orange" Margin="10"> <Grid x:Name="g2" Background="Yellow" Margin="10"> <DockPanel x:Name="d2" Background="LawnGreen" Margin="10"> <TextBox x:Name="textBox1" FontSize="24" Margin="10"/> </DockPanel> </Grid> </DockPanel> </Grid>
Look outward from the first layer of Binding. After finding the first Grid type object, take it as your own source. The C# code, equivalent XAML and effect screenshot are as follows:
RelativeSource rs = new RelativeSource(RelativeSourceMode.FindAncestor); rs.AncestorLevel = 1; rs.AncestorType = typeof(Grid); Binding binding = new Binding("Name") { RelativeSource = rs }; textBox1.SetBinding(TextBox.TextProperty, binding);
Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type Grid},AncestorLevel=1},Path=Name}"
Look outward from the first layer of Binding. After finding the second DockPanel type object, take it as your own source. The C# code, equivalent XAML and effect screenshot are as follows:
RelativeSource rs = new RelativeSource(RelativeSourceMode.FindAncestor); rs.AncestorLevel = 2; rs.AncestorType = typeof(DockPanel); Binding binding = new Binding("Name") { RelativeSource = rs }; textBox1.SetBinding(TextBox.TextProperty, binding);
Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type DockPanel},AncestorLevel=2},Path=Name}"
TextBox is associated with its own Name attribute. The C# code, equivalent XAML and effect screenshot are as follows:
RelativeSource rs = new RelativeSource(); rs.Mode = RelativeSourceMode.Self; Binding binding = new Binding("Name") { RelativeSource = rs }; textBox1.SetBinding(TextBox.TextProperty, binding);
<!--Use non static attributes--> Text="{Binding RelativeSource={RelativeSource Mode=Self},Path=Name}" <!--Use static properties--> Text="{Binding RelativeSource={ x:Static RelativeSource.Self} ,Path=Name}"
Data conversion and verification by Binding
Binding can verify the validity of data through the ValidationRules property, and set the Converter for data through the Converter property (when different data types are required at both ends).
Data verification of Binding
The ValidationRules property type of Binding is Collection. You can set multiple data verification conditions for each Binding, and each condition is an object of ValidationRule type.
ValidationRule class is an abstract class. When using it, you need to create its derived class and implement its Validate method. For the return value of Validate method (ValidationResult type object):
- If the verification passes, set the IsValid property of the return value to true.
- If the verification fails, set the return value IsValid property to false and set an appropriate message content (usually a string) for the ErrorContent property.
Draw a TextBox and a Slider on the UI, and prepare a derived class of ValidationRule. The code is as follows:
<StackPanel> <TextBox x:Name="textBox1" Margin="5"/> <Slider x:Name="slider1" Minimum="0" Maximum="100" Margin="5"/> </StackPanel>
public class RangeValidationRule : ValidationRule { //The Validate method needs to be implemented public override ValidationResult Validate(object value, CultureInfo cultureInfo) { double d = 0; if (double.TryParse(value.ToString(), out d)) { if (d >= 00 && d <= 100) { return new ValidationResult(true, null); } } return new ValidationResult(false, "Validation Failed"); } }
In the background C# code, use Binding to associate the Slider and TextBox -- take the Slider as the source and the TextBox as the target.
Verify the data only when the Target is updated by an external method
Binding is used in the background C# code to associate with the Slider as the source and TextBox as the target. The value range of the Slider is 0 to 100. It is necessary to check whether the value entered in the TextBox is in the range of 0 to 100. The C# code is as follows:
//Create Binding Binding binding = new Binding("Value") { Source = slider1 }; binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged; RangeValidationRule rvr = new RangeValidationRule(); binding.ValidationRules.Add(rvr); textBox1.SetBinding(TextBox.TextProperty, binding);
When the value between 0 and 100 is entered, the program will display normally. When the value outside the interval or the value that cannot be parsed, the TextBox will display a red border, and the error value will not be passed to the Source. The effect is as follows:
The data is also verified when the Target is updated by the Source property
By default, Binding verifies data only when the Target is updated by an external method, while the Source property from Binding does not verify when the Target is updated. When the data from the Source may also have problems, you need to set the ValidatesOnTargetUpdated property of the verification condition to true.
Change the value range of slider1 from 0 to 100 to - 10 to 110. When the Slider of the Slider moves out of the valid range, the TextBox will also display the effect of verification failure. The code and effect are as follows:
<Slider x:Name="slider1" Minimum="-10" Maximum="110" Margin="5"/>
//Create Binding Binding binding = new Binding("Value") { Source = slider1 }; binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged; RangeValidationRule rvr = new RangeValidationRule(); rvr.ValidatesOnTargetUpdated = true; binding.ValidationRules.Add(rvr); textBox1.SetBinding(TextBox.TextProperty, binding);
The error message of verification failure is displayed
When the verification is wrong, the ValidationResult object returned by the Validate method carries an error message. The knowledge that will be explained in detail later - Routed Event is needed to display the error message.
When creating a Binding, set the NotifyOnValidationError property of the Binding object to true. In this way, when the data verification fails, the Binding will send a signal like an alarm, which will propagate on the UI element tree starting from the Target of the Binding object. Every time a signal reaches a node, if a listener (event handler) for this signal is set on this node, then the listener will be triggered to process the signal. After the signal is processed, the programmer can also choose whether to let the signal continue to propagate downward or terminate it -- this is the routing event. The transmission process of the signal on the UI element tree is called Route.
The code and effect are as follows:
//Create Binding Binding binding = new Binding("Value") { Source = slider1 }; binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged; RangeValidationRule rvr = new RangeValidationRule(); rvr.ValidatesOnTargetUpdated = true; binding.ValidationRules.Add(rvr); binding.NotifyOnValidationError = true; textBox1.SetBinding(TextBox.TextProperty, binding); textBox1.AddHandler(Validation.ErrorEvent, new RoutedEventHandler(ValidationError)); //Listener private void ValidationError(object sender, RoutedEventArgs e) { if (Validation.GetErrors(textBox1).Count > 0) { textBox1.ToolTip = Validation.GetErrors(textBox1)[0].ErrorContent.ToString(); } }
Data conversion of Binding
Binding has a mechanism called data conversion. When the data associated with the Path on the Source side is inconsistent with the data type of the Target attribute on the Target side (for example, the Value attribute of Slider is double and the Text attribute of TextBox is string), a Data Converter can be added.
The conversion between double type and string type is relatively simple. WPF class library automatically does it for us, but WPF can't do the conversion between some types for us, as shown in the following cases:
- The data in the Source is Y, N and X values (which may be char type, string type or user-defined enumeration type). The corresponding check box control on the UI needs to map these three values to its IsChecked attribute value (bool type)
- When text has been entered in the TextBox, the Button for login will appear. This is the conversion between string type and Visibility enumeration type or bool type (the Mode of Binding will be OneWay).
- The data in the Source may be Male or Female (string or enumeration). The UI corresponds to the Image control used to display the avatar. At this time, the value in the Source needs to be converted into the corresponding avatar Image URI (also OneWay).
In the above cases, you can only write the Converter by yourself. The method is to create a class and let this class implement the IValueConverter interface. The IValueConverter interface is defined as follows:
public interface IValueConverter { object Convert(object value, Type targetType, object parameter, CultureInfo culture); object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture); }
When the data flows from the Binding Source to the Target, the Converter method will be called; Conversely, the ConvertBack method will be called. The two methods have as like as two peas:
- The first parameter is object, which ensures the reusability of Converter to the greatest extent (the actual type can be judged in the method body);
- The second parameter is used to determine the return type of the method, which can be understood as outputType to avoid confusion with the Binding Target;
- The third parameter is used to pass additional information into the method. If you need to pass more than one information, you can put the information into a collection object to pass it into the method.
If the Mode attribute of the Binding object is TwoWay or Default, and the behavior is consistent with TwoWay, both methods may be called; If the Mode is OneWay or the Default behavior is consistent with OneWay, only the Convert method will be called. The same is true for other cases.
The following example is a comprehensive example of Converter. The purpose of the program is to display the status of some military aircraft to players in the list.
First, create several custom data types:
//type public enum Category { Bomber, Fighter } //state public enum State { Available, Locked, Unknown } //aircraft public class Plane { //The Category attribute is mapped to the icon of bomber or fighter in the UI, public Category Category { get; set; } public string Name { get; set; } //The State attribute is mapped as a CheckBox in the UI public State State { get; set; } }
The icon of bomber (Bomber.png) or fighter (Fighter.png) is as follows:
Two converters need to be provided. One is one-way conversion from Category type to string type (XAML compiler can parse string objects into image resources), and the other is between State and bool? Two way conversion between types, the code is as follows:
public class CategoryToSourceConverter : IValueConverter { //Convert Category to Uri public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { Category c = (Category)value; switch (c) { case Category.Bomber: return @"\Icons\Bomber.png"; case Category.Fighter: return @"\Icons\Fighter.png"; default: return null; } } //Will not be called public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } } public class StateToNullableBoolConverter : IValueConverter { //Convert State to bool? public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { State s = (State)value; switch (s) { case State.Available: return true; case State.Locked: return false; case State.Unknown: default: return null; } } //Will bool? Convert to State public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { bool? nb = (bool?)value; switch (nb) { case true: return State.Available; case false: return State.Locked; case null: default: return State.Unknown; } } }
In the XAML code, two instances of Converter are created in the form of resources, and a DataTemplate for displaying data is added to listBoxPlane. The code is as follows:
<Window.Resources> <local:CategoryToSourceConverter x:Key="cts"/> <local:StateToNullableBoolConverter x:Key="stnb"/> </Window.Resources> <StackPanel Background="LightBlue"> <ListBox x:Name="listBoxPlane" Height="160" Margin="5"> <ListBox.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal"> <Image Width="40" Height="40" Source="{Binding Path=Category,Converter={StaticResource cts}}"/> <TextBlock Text="{Binding Path=Name}" Width="60" Margin="80,0"/> <CheckBox IsThreeState="True" IsChecked="{Binding Path=State,Converter={StaticResource stnb}}"/> </StackPanel> </DataTemplate> </ListBox.ItemTemplate> </ListBox> <Button x:Name="buttonLoad" Content="Load" Height="25" Margin="5,0" Click="buttonLoad_Click"/> <Button x:Name="buttonSave" Content="Save" Height="25" Margin="5,0" Click="buttonSave_Click"/> </StackPanel>
The Click event processor of the Load button is responsible for assigning the data of a group of aircraft to the ItemsSource attribute of the ListBox, and the Click event processor of the Save button is responsible for writing the data changed by the user into the file. The code is as follows:
//Load button Click event handler private void buttonLoad_Click(object sender, RoutedEventArgs e) { List<Plane> planeList = new List<Plane>() { new Plane(){Category=Category.Bomber,Name="B-1",State=State.Unknown}, new Plane(){Category=Category.Bomber,Name="B-2",State=State.Unknown}, new Plane(){Category=Category.Fighter,Name="F-22",State=State.Unknown}, new Plane(){Category=Category.Fighter,Name="Su-47",State=State.Unknown}, new Plane(){Category=Category.Bomber,Name="B-52",State=State.Unknown}, new Plane(){Category=Category.Fighter,Name="J-10",State=State.Unknown}, }; listBoxPlane.ItemsSource = planeList; } //Save button Click event handler private void buttonSave_Click(object sender, RoutedEventArgs e) { StringBuilder sb = new StringBuilder(); foreach (Plane p in listBoxPlane.Items) { sb.AppendLine(string.Format("Category={0},Name={1},State={2}", p.Category, p.Name, p.State)); } File.WriteAllText(@"D:\PlaneList.txt", sb.ToString()); }
Run the program and click CheckBox to change the State of the aircraft. The effect is as follows:
Click the Save button to open D: \ planelist Txt, the data are as follows:
Category=Bomber,Name=B-1,State=Locked Category=Bomber,Name=B-2,State=Available Category=Fighter,Name=F-22,State=Available Category=Fighter,Name=Su-47,State=Unknown Category=Bomber,Name=B-52,State=Unknown Category=Fighter,Name=J-10,State=Unknown
MultiBinding
When the information to be displayed in the UI is determined by more than one data source, you need to use MultiBinding, that is, MultiBinding. Like Binding, MultiBinding takes BindingBase as the base class. MultiBinding can be used whenever Binding objects can be used.
MultiBinding has an attribute called Bindings (type: Collection). Through this attribute, MultiBinding aggregates a group of binding objects. Binding objects in this collection can have their own data verification and conversion mechanism. The data collected by them will jointly determine the data transmitted to the MultiBinding target. The schematic diagram is as follows:
There is a UI for new user registration (including four textboxes and a Button), as well as the following restrictions:
- Input the user name in the first and second textboxes, and the content shall be consistent.
- The third and fourth textboxes input the user's E-Mail, and the content shall be consistent.
- When all the contents of the TextBox meet the requirements, the Button is available.
The XAML code of UI is as follows:
<StackPanel Background="LightBlue"> <TextBox x:Name="textBox1" Height="23" Margin="5"/> <TextBox x:Name="textBox2" Height="23" Margin="5,0"/> <TextBox x:Name="textBox3" Height="23" Margin="5"/> <TextBox x:Name="textBox4" Height="23" Margin="5,0"/> <Button x:Name="button1" Content="Sumbit" Width="80" Margin="5"/> </StackPanel>
Set the code of MultiBinding to implement Converter. The code is as follows:
//Prepare basic Binding Binding b1 = new Binding("Text") { Source = textBox1 }; Binding b2 = new Binding("Text") { Source = textBox2 }; Binding b3 = new Binding("Text") { Source = textBox3 }; Binding b4 = new Binding("Text") { Source = textBox4 }; //Preparing MultiBinding MultiBinding mb = new MultiBinding() { Mode = BindingMode.OneWay }; mb.Bindings.Add(b1); //Note: MultiBinding is sensitive to the order of Add subbinding mb.Bindings.Add(b2); mb.Bindings.Add(b3); mb.Bindings.Add(b4); mb.Converter = new LogonMultiBindingConverter(); //Associate MultiBinding object with Button button1.SetBinding(Button.IsEnabledProperty, mb); //Converter public class LogonMultiBindingConverter : IMultiValueConverter { public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) { if (!values.Cast<string>().Any(text => string.IsNullOrEmpty(text)) && values[0].ToString() == values[1].ToString() && values[2].ToString() == values[3].ToString()) { return true; } return false; } //Will not be called public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) { throw new NotImplementedException(); } }
The following points should be noted:
- MultiBinding is sensitive to the order of adding child bindings, which determines the order of data collected into the Converter.
- The Converter of MultiBinding implements the IMultiValueConverter interface.
The program effect is as follows:
reference material
WPF Binding
XPath syntax
XML Path Language (XPath)
RelativeSource
RelativeSourceMode enumeration