Ioannis Panagopoulos blog

Tutorials on HTML5, Javascript, WinRT and .NET

Data Binding with WPF

by Ioannis Panagopoulos

Data Binding in WPF has been through a substantial change compared to Data Binding in Windows Forms. It kinda became simpler mainly due to the fact that a BindingSource is no longer needed for the job. In this post we will cover the basic elements of databinding in WPF.

The first thing someone may want to do in an application which is related to databinding is have a property of a control change according to a property of another control. WPF provides a very easy way of doing this, one that actually does not require any cs code at all.

For example you may want to change the FontSize property of a TextBlock control according to the Value Property of a Slider control (see figure below): 

The Slider control is defined like that:

<Slider x:Name="SliderFontSize" Minimum="8" Maximum="56" .../>

And we need the FontSize property of a TextBlock to change according to the Value property of the Slider (SliderFontSize) . In XAML we write:

<TextBlock ... FontSize="{Binding ElementName=SliderFontSize,Path=Value}" ...>
   
This is a typical script whose font size is changing according to the value of 
    the slider control above. It is implemented by simple data binding between
    the TextBlock's FontSize property and the Slider's Value property.
</TextBlock>

In general for Control Property to Control Property data binding, we give the source Control a name (eg SourceControlName) and take note of the property that provides the data eg SourceProperty. Then we go to the target control (eg TargetControl), select the property that we want to equalize with the SourceProperty's value (eg TargetProperty)  and write the following:  

 <TargetControl ... TargetProperty="{Binding ElementName=SourceControlName,Path=SourceProperty} " ... >

Another example is the one below where the Text Property of a TextBox is displayed also in the Content Property of the Label control:

The implementation is similar to the previous one:

<TextBox x:Name="TextBoxTestText" Width="130" Text="This is replicated"/>
<Label ... Content="{Binding ElementName=TextBoxTestText,Path=Text}" Background="Beige"/>

The second Data Bininding scenario is  when we perform data binding with a BO (Business Object or in general an instance of a class). Suppose we have the following BO:

public class TestShape
    {
        private string _name;
        public string Name
        {
            get { return _name; }
            set { _name = value;}
        }

        private double _scaleX;
        public double ScaleX
        {
            get { return _scaleX; }
            set { _scaleX = value; }
        }

        private double _scaleY;
        public double ScaleY
        {
            get { return _scaleY; }
            set { _scaleY = value;}
        }

        private string _shapeColor;
        public string ShapeColor
        {
            get { return _shapeColor; }
            set { _shapeColor = value; }
        }
     }

This business object describes a shape with a name, two scaling factors and a color. We want to provide to the user a form where he/she can set those values and a place to display the resulting shape. This is captured in the following figure: 

 

First of all we create the TextBox Controls and the ComboBox Control for the data as shown below:

<StackPanel DockPanel.Dock="Top" x:Name="StackPanelShapeData">
     <StackPanel Orientation="Horizontal" Margin="5">
           <TextBlock Text="Shape Name: "/>
           <TextBox Width="130"/>
      </StackPanel>
      <StackPanel Orientation="Horizontal" Margin="5">
           <TextBlock Text="Shape Width: "/>
           <TextBox Width="130" />
       </StackPanel>
       <StackPanel Orientation="Horizontal" Margin="5">
           <TextBlock Text="Shape Height: "/>
           <TextBox Width="130"/>
       </StackPanel>
       <StackPanel Orientation="Horizontal" Margin="5">
           <TextBlock Text="Shape Color: "/>
           <ComboBox Width="200"/>
       </StackPanel>
</StackPanel>

In the TextBox Controls we usually bind to the Text Property. Therefore for each TextBox control with bind the Text property with a specific property from our BO. For example, for the Name property of the BO:

 <TextBox Width="130" Text="{Binding Path=Name}">

We just provide the name of the BO Property as the value of the Path of the Binding. We do the same for all the other TextBox Controls, leaving the ComboBox Control untouched for now. The only thing left to be done is specify that all those data bound property names from our BO should reflect the values  of a specific BO. We do that by setting the DataContext property of the data bound control or of a parent Control of all the data bound controls (actually for data binding the compiler searches for the first control in the hierarchy that has a DataContext associated with it and uses that). In our case in the code-behind file we create such an object and set the DataContext of the StackPanel named StackPanelShapeData which is a parent to all other controls that are bound to the BO:

public WindowBindingDemo()
        {
            InitializeComponent();

            _demoShape.Name = "Test";
            _demoShape.ScaleX = 2;
            _demoShape.ScaleY = 1;
            _demoShape.ShapeColor = "Black";

            this.PolylineShapeDrawn.DataContext = _demoShape;
            this.StackPanelShapeData.DataContext = _demoShape;
         }

The PolylineShapeDrawn control is the canvas that displays the shape and which is also data bound to the BO (again note the Path properties being set to BO property names and note that the DataContext of the Canvas Controls which is the parent of the data bound controls is set to _demoShape in the code above):

<Canvas DockPanel.Dock="Top" x:Name="PolylineShapeDrawn" MinHeight="100" Margin="5" Background="Beige" VerticalAlignment="Stretch">
        <Label Content="{Binding Path=Name,Mode=OneWay}"/>
        <Polyline Points="25,25 0,50 25,75 50,50 25,25 25,0" Stroke="Blue" StrokeThickness="4" Fill="{Binding Path=ShapeColor,Mode=OneWay}" Canvas.Left="155" Canvas.Top="10">
           <Polyline.RenderTransform>
              <ScaleTransform ScaleX="{Binding Path=ScaleX,Mode=OneWay}" ScaleY="{Binding Path=ScaleY,Mode=OneWay}"/>
           </Polyline.RenderTransform>
        </Polyline>
</Canvas>

Note that the Mode=OneWay specified above means that the properties are only retrieving Data from the BO and they are not setting any properties of the BO. It seems that we are finished, but there is a small detail that needs to be tackled. Suppose that we want to provide a button that sets the scaling factors to 1 when pressed. We then add the following XAML code:

<Button Content="Set to 1" Margin="5 0 0 0" Click="Button_Click"/>

And in the code behind file we provide the following handler:

 private void Button_Click(object sender, RoutedEventArgs e)
 {
            this._demoShape.ScaleX = 1;
            this._demoShape.ScaleY = 1;
 }

You will notice that when you run the application and you press the button, the values of the BO change but those changes are not reflected to the controls of the UI. This happens because nobody informs the GUI that the values have changed. For this reason, we need to implement the System.ComponentModel.INotifyPropertyChanged interface in our BO. This interface will be used by the Binding mechanism for being notified when there is a change in a property. In each property of the BO we must implement something like the following: 

public double ScaleX
{
  get { return _scaleX; }
  set { _scaleX = value
    if (PropertyChanged != null
     PropertyChanged(thisnew 
     System.ComponentModel.PropertyChangedEventArgs("ScaleX")); }
}

This code is interpeted as: Set the new value to the property and if someone needs to be informed (in our case if the property is bound, the control needs to be informed) inform it that the property has changed.

Implementing this interface solves the problem and you can see in the application that values in the GUI immediately reflect the underlying property values.

The only thing left to be done is bind the ShapeColor property of the BO to the ComboBox. Note that the property we need to set is a string and not a SolidColorBrush as we would do in code. That is because the data bound value will have to pass the Type Converter in XAML (more about Type Converters can be easily found on msdn) which operates with string as input. To fill the ComboBox we create a list of color names in the code and set the ItemsSource property to the list:

private List<string> _colors = new List<string>();

        public WindowBindingDemo()
        {
            InitializeComponent();

            
            _colors.Add("Red");
            _colors.Add("Green");
            _colors.Add("Blue");
            _colors.Add("Black");
             this.ComboBoxColor.ItemsSource = _colors;
             ...

This will display the colors in the ComboBox. Then as we did with the TextBoxes, we bind the ShapeColor value to the appropriate property:

<ComboBox x:Name="ComboBoxColor" Width="200" SelectedValue="{Binding Path=ShapeColor}" />

Run the application and you will see that the binding works. Suppose now that you want to add a button that adds to the list of the ComboBox the color "Beige". You would do something like this:

<Button Content="Add Beige" Margin=" 5 0 0 0" Click="Button_Click_1"/>

And the handler:

 private void Button_Click_1(object sender, RoutedEventArgs e)
 {
      this._colors.Add("Beige");
 }

The problem is that this code alhough it adds a new color to the list the change is not reflected in the ComboBox. This is due to the fact that the list does not generate events of the INotifyCollectionChanged interface. If you want the lists in your GUI to reflect changes in theri data bound lists concerning adding or removing elements your lists should either implement the INotifyCollectionChanged interface or be of ObservableCollection type.

Just rename the List<string> definition to:

private 
   System.Collections.ObjectModel.ObservableCollection<string> _colors = 
       new System.Collections.ObjectModel.ObservableCollection<string>();

That is for now. The next post will be about WPF Validation so stay "tuned". You can dowload the demo project of this tutorial from here: BindingDemo.zip (50,74 kb)

kick it on DotNetKicks.com
blog comments powered by Disqus
hire me