RecentComments

Comment RSS

Control Templates, Style Templates and Custom Elements in WPF

by Ioannis 29. January 2009 09:00

 

In the WinForms ages, when you needed a TextBox control with some special built-in additional functionality, the only option you had was to create a new control by inheriting from the TextBox control. Moreover, in cases where you needed to change the visual appearance of a control (for example, enlarge the scrollbar of a ListBox) sometimes you had to resort to the plain Win32 API to do your job.

WPF has a different way of dealing with such requirements and this way is related to the following three WPF constructs:

  • Style Templates
  • Control Templates
  • Custom Elements

In this post we will get a glimpse of the three.

First of all, we need to establish the difference between the logical and visual tree of a window. The XAML syntax enforces a hierarchy in the controls. If for example you put a TextBox and a Button Control in a StackPanel in a Window, then apparently you create the hierarchy below, which is also referred as the logical tree.

 

The parent-child relation in the tree should be interpreted as a loose aggregation in code. For example, in the Figure above, the stack panel "has a" list of Controls such as Button and TextBox.

The logical tree is a behavioral abstraction of the actual hierarchical tree formed for the Controls in a Window. And the term "behavioral" means that a programmer needs the logical tree in order to know which events he/she needs to catch in the code behind file. This implies that in WPF the Controls themselves as we know them carry only a specific behavior definition and not their actual appearance characteristics. For example, a button is a clickable content of some sort not a clickable gray box with a string text. Actually, for each control, there is a whole bunch of classes also belonging to the hierarchy that have to do with the appearance of the control in the window. If those additional "visualization" classes are included in the logical tree we will end up with the visual tree of the windows and its controls.

So, for this small introduction, let us forget about the default appearance of a button and try to create our own. The WPF mechanisms allow us to do such thing by specifying a Control Template for our Button Control. 

Go ahead and create a test WPF Project and in the default Window place a button. Go above the Grid markup and place the declaration of a new control template for the button:

<Window.Resources>

<ControlTemplate x:Key="ButtonDemoTemplate" TargetType="{x:Type Button}">

</ControlTemplate>

</Window.Resources>


Then go to the button's XAML declaration and define the template you want your button to use:

<Button Height="23" Name="button1" Width="75" Template="{StaticResource ButtonDemoTemplate}">Button</Button>


You will notice that the button has disappeared. That is because you have redefined the visual appearance of a button to nothing! Now place the following code inside the template:

<ControlTemplate x:Key="ButtonDemoTemplate" TargetType="{x:Type Button}">

<Canvas>

<Ellipse Canvas.Top="10" Canvas.Left="10" Width="50" Height="50" Fill="Blue" Stroke="Black" StrokeThickness="4" />

<Label Canvas.Top="23" Canvas.Left="10" FontSize="10" Foreground="Yellow" Content="Click Me!"/>

</Canvas>

</ControlTemplate>


Now the button has been dressed with a new visual style (the click me message is important otherwise nobody will realize that this is a button ;)):

This button now works as expected supporting the Click event and all the others. We have actually altered the visual appearance of the button and as a result the visual tree hierarchy is now as follows:

But note that the Content property of the button that is set to the string "Button" is not displayed. In order to display the value of the Content property, we need to define in the Template, a "Placeholder" for it. This placeholder is actually the definition of the ContentPresenter class in our template (we substitute here the label control with a Placeholder class):

<Window.Resources>

<ControlTemplate x:Key="ButtonDemoTemplate" TargetType="{x:Type Button}">

<Canvas>

<Ellipse Canvas.Top="10" Canvas.Left="10" Width="50" Height="50" Fill="Blue" Stroke="Black" StrokeThickness="4" />

<ContentPresenter Canvas.Top="27" Canvas.Left="15"/>

</Canvas>

</ControlTemplate>

</Window.Resources>


Now the Content property of the button can be set and displayed:

<Button Height="23" Name="button1" Width="75" Template="{StaticResource ButtonDemoTemplate}" Content="Click Me!" Foreground="Yellow" FontSize="9" />


Note also that if you need to set a property of your template, to a value of a property of the control tha uses it, you can establish a TemplateBinding. For example to change the Fill color of the Ellipse based on the value of the Background property of the Button Control you do the following:

<Ellipse Canvas.Top="10" Canvas.Left="10" Width="50" Height="50" Fill="{TemplateBinding Background}" Stroke="Black" StrokeThickness="4" />

And now you can set the Background property to change the color of the control:

<Button Height="23" Name="button1" Width="75" Template="{StaticResource ButtonDemoTemplate}" Background="Black" Content="Click Me!" Foreground="Yellow" FontSize="9" />

This type of binding is especially useful in more "important" properties such as the Padding property of a control, which in normal circumstances will be ignored if not bound to some properties of the Control Template. Even in our example, you could establish a TemplateBinding between the Content property of the control and the Text property of the Label and have similar functionality to the one using the ContentPresnter.

As you would have noticed by now, although the button you have just created is clickable, there is no visual indication of that to the user (eg change of color when the mouse is over the button etc). For this kind of behavior we need triggers. Triggers will not be covered here. I will give you just an example of a trigger that will change the fill color of the Ellipse when the mouse is over the button:


<ControlTemplate x:Key="ButtonDemoTemplate" TargetType="{x:Type Button}">

<Canvas>

<Ellipse x:Name="DemoEllipse" Canvas.Top="10" Canvas.Left="10" Width="50" Height="50" Fill="{TemplateBinding Background}" Stroke="Black" StrokeThickness="4" />

<ContentPresenter Canvas.Top="27" Canvas.Left="15"/>

</Canvas>

<ControlTemplate.Triggers>

<Trigger Property="IsMouseOver" Value="True">

<Setter TargetName="DemoEllipse" Property="Fill" Value="Yellow"/>

</Trigger>

</ControlTemplate.Triggers>

</ControlTemplate>


And this is the basic stuff to know for Control Templates in WPF. In few words, we use Control Templates to alter completely the visual appearance of a control (the visual tree of the control) and allow the programmer to set some properties of the visual appearance through TemplateBindings.

The difference with styles is that styles are only capable of setting automatically the properties that are allowed to be set programmatically by the Control and the Control Template.

To extend the previous example, suppose that we want a Style for our button that has the font size of the content to 12, has a red background and when the mouse is over the content changes to "Oh No!". Then, in the Window.Resources section of the window, we define the following style:


<Style x:Key="ButtonStyle">

<Style.Setters>

<Setter Property="Button.Background" Value="Red"/>

<Setter Property="Button.FontSize" Value="12"/>

</Style.Setters>

<Style.Triggers>

<Trigger Property="Button.IsMouseOver" Value="true">

<Setter Property="Button.Content" Value="Oh No!"/>

</Trigger>

</Style.Triggers>

</Style>


Note that there are two areas of interest nested in the style tags. The style Setters that set values of properties and the style triggers that change the properties based on conditions. Note that the properties to be set in this example are declared by including along with their name, the control they apply to, which means that you can use the same style and apply it to various controls (something like applying themes). You use the style as follows:

<Button Style="{StaticResource ButtonStyle}" Height="23" Name="button1" Width="75" Template="{StaticResource ButtonDemoTemplate}" Foreground="Black" />

If you define the same property in a style and also in the XAML declaration of the control the second overrides the first.

You can also use styles to set the same event handler for a number of controls. So for one more time, you use control Templates to modify the visual elements that create a control and styles to alter their properties on demand. If the control was an HTML page, the Control Template is the HTML Template and the Style Template is the css stylesheet that accompanies it.

So, when do we create custom elements? Well with the Style Template you can alter visual appearance to its detail, with Control Templates you can alter visual appearance to its core, and with a custom element you get the bonus of being able to alter functionality (behavior).

In our example:

  • We needed a button with and circular appearance and we created a new Control Template
  • We needed to define a family of such buttons that will have a Red background (and the other stuff described) and we have created a Style Template.
  • Now we need the button to display a message whenever it is clicked (and this functionality appears in a whole bunch of buttons in our application to justify the reason we implement a custom element for it).

We create a new class that inherits the Button and override the Click event as follows (we also add a boolean to allow the developper stop this behavior if he/she likes):

     public class MyButton:Button
    {
        private bool _showMessage=false;
        public bool ShowMessage
        {
            get { return _showMessage; }
            set { _showMessage = value; }
        }
       
        protected override void OnClick()
        {
            base.OnClick();
            if (ShowMessage) MessageBox.Show("I am always shown!");
        }
     }

Now alter the XAML for the button as follows:

<local:MyButton ShowMessage="True" Style="{StaticResource ButtonStyle}" ...

Of course you need to declare the "local" namespace in the Window tag as follows:

xmlns:local="clr-namespace:YourProjectNamespace"...

Implementing the custom element offered you this added functionality "out of the box" by controlling the ShowMessage boolean. Since the behavior is after the base.OnClick() method, it will be executed after any user-defined logic residing in a user-defined event handler of the Click event.

This concludes a very brief comparison of the three ways of altering a control in WPF.  For once more thank you for taking the time to read this.

kick it on DotNetKicks.com

Tags:

WPF

Data Validation in WPF

by Ioannis 20. January 2009 08:51

In the previous post, I have presented a basic intro to DataBinding in WPF. This post, extends the previous one by adding a brief introduction on data validation using WPF.

When it comes to data validations, one is faced with the following concerns:

  1. Where the validation logic will reside? Will it be part of the GUI logic or the Business object?
  2. How is the user going to be informed on validation errors?


 

In this post, I will try to tackle those concerns. For this reason, we will create a small window that allows the user to edit the FullName and Rating properties of a Client object. The Client class is implemented in the code-behind file and supports the INotifyPropertyChanged interface in order for the GUI to reflect immediately property value changes that occur in the code. The initial class code is given below:

public class Client : System.ComponentModel.INotifyPropertyChanged
    {
        private string _fullName;
        public string FullName
        {
            get { return _fullName; }
            set { _fullName = value
                  if (PropertyChanged != null
                      PropertyChanged(thisnew System.ComponentModel.PropertyChangedEventArgs("FullName")); }
        }

        private int _rating;
        public int Rating
        {
            get { return _rating; }
            set { _rating = value
                  if (PropertyChanged != null
                      PropertyChanged(thisnew System.ComponentModel.PropertyChangedEventArgs("Rating")); }
        }

        #region INotifyPropertyChanged Members
            public event 
                   System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
        #endregion
    }
 


 

The implemented window that supports the aforementioned functionality is given below:

The XAML code that creates this window and is ready to support Data Binding with a Client business object is as follows:

<Window x:Class="ValidationDemo.WindowValidationDemo"

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

Title="Validation Demo" Height="150" Width="432">

<Grid>

<Grid.ColumnDefinitions>

<ColumnDefinition Width="80"/>

<ColumnDefinition/>

</Grid.ColumnDefinitions>

<Grid.RowDefinitions>

<RowDefinition Height="30"/>

<RowDefinition Height="30"/>

<RowDefinition/>

</Grid.RowDefinitions>

<TextBlock Grid.Column="0" Grid.Row="0" Text="FullName:" VerticalAlignment="Center" HorizontalAlignment="Right" Margin="0 0 5 0"/>

<TextBox Grid.Column="1" Grid.Row="0" Text="{Binding Path=FullName}" VerticalAlignment="Center"/>

<TextBlock Grid.Column="0" Grid.Row="1" Text="Rating:" VerticalAlignment="Center" HorizontalAlignment="Right" Margin="0 0 5 0"/>

<StackPanel Grid.Column="1" Grid.Row="1" Orientation="Horizontal">

<TextBox Text="{Binding Path=Rating}" VerticalAlignment="Center" Width="30" HorizontalAlignment="Left" TextAlignment="Center"/>

<Button Content="+" VerticalAlignment="Center" Margin="10 0 0 0" Click="Button_Click"/>

</StackPanel>

<UniformGrid Columns="2" Rows="1" Grid.Column="1" Grid.Row="2" Height="30" VerticalAlignment="Top" Margin="5">

<Button Content="Save" Margin="3"/>

<Button Content="Cancel" Margin="3"/>

</UniformGrid>

</Grid>

</Window>


 

We create a new Client object to bind to the Window. The creation and binding happens on the window's contructor, just below the InitializeComponent() method:

private Client _testClient;
public WindowValidationDemo()
{
      InitializeComponent();
       _testClient = new Client { FullName="Ioannis Panagopoulos", Rating=0 };
       this.DataContext = _testClient;
}
 


 

There are two things worth mentioning here. The one is related to the way the object is initialized. Note that there is no constructor in the Client class.In C#3.0 you can initialize the properties of an object during construction in blocks of code enclosed by {}. Neat isn't it? Also note that due to the hierarchical nature of the DataContext property, we can set the Window's DataContext property to the Client Business Object and be sure that the bindings will find their paths ;)

Finally, we implement the handler for the Click event of the button as follows:

private void Button_Click(object sender, RoutedEventArgs e)
{
      _testClient.Rating++;
}
 


 

It's time now to start dealing with the issues concerning Data Validation. First of all, we need to decide where the validation logic's implementation resedes. Should it be completely decoupled from the business object or not? Initially, we distinguish Data Validation into three main categories:

  1. Data need to be validated for type conversion errors (eg writing an alphanumeric string in a textbox which is databound to an integer).
  2. Data need to be validated for values that do not make sense in their own context (eg. A rating property can only take positive values up to 10).
  3. Data need to be validated for values that do not make sense in the application's context (eg. the departure date of a trip should be smaller than the arrival date unless you are implementing a time machine!)


 

Type conversion errors (case 1) must throw exceptions and the exceptions must be caught by the GUI. Type conversion errors are serious and they are justified to be raising exceptions. In WPF, a type conversion error exception of this kind is raised by default in a data bound TextBox Control and by default it is ignored. In order to catch the exception you need to set the property ValidatesOnExceptions to True (here it is done for the Rating textbox):

 <TextBox Text="{Binding Path=Rating,ValidatesOnExceptions=True}">

This will display a red border around the TextBox when the entered value cannot be converted to the type of the Rating property (integer)- that is - an exception has been thrown. So far so good. But we may need to inform the user by providing a more descriptive message. For this reason, we hook up to the Validation.Error event and set the tooltip's value to the reported error in the event handler. We also need to explicitely state that the Binding object should be raising such events by setting the NotifyOnVaidationError property to true.

So we set the NotifyOnValidationError property to true and set the event handler for the Validation.Error event:

<TextBox Text="{Binding Path=Rating,ValidatesOnExceptions=True,NotifyOnValidationError=true}" Validation.Error="TextBox_Error"/>

And the implementation of the event handler is as follows:

private void TextBox_Error(object sender, ValidationErrorEventArgs e)
{
    if (e.Action == ValidationErrorEventAction.Added)
    {
        ((Control)sender).ToolTip = e.Error.ErrorContent.ToString();
    }
    else
    {
        ((Control)sender).ToolTip = "";
    }
}
 


 

This will display the tooltip with a description of the validation error:

Now how about cases 2,3 which are related to data that do not make sense? Well those validations are usually related to the "meaning" of our application and our business objects and therefore they should be decoupled from the GUI. Such validations can be implememented in the following ways:

You can raise exceptions at the Setters of the data bound business object. For example, the following code in the FullName setter raises and exception when the FullName contains the name "John":

public string FullName
{
    get { return _fullName; }
    setx
    {
        if (_fullName != value)
        {
            _fullName = value;
            if (_fullName!=null && _fullName.Contains("John")) throw new ApplicationException("FullName cannot be John!");
            if (PropertyChanged != null)
                PropertyChanged(thisnew System.ComponentModel.PropertyChangedEventArgs("FullName"));
           
        }
    }
}
 


 

The XAML code for the data bound TextBox will be like this:

<TextBox Grid.Column="1" Grid.Row="0" Text="{Binding Path=FullName, ValidatesOnExceptions=true, ValidatesOnDataErrors=true, NotifyOnValidationError=true}" VerticalAlignment="Center" Validation.Error="TextBox_Error"/>


 

Again we are notified for the exception and the handler is called with the same code as before. But there is a problem. The error message displayed in the tooltip says "Exception has been thrown by a target of an Invocation" although we have supplied a different message in the exception. To show the correct message you need to display the message in e.Error.Exception.InnerException.

Raising exceptions in the Setters is not a very good approach since those properties are also set by code and sometimes it is ok to temporarily leave them with error values.

So another approach is to implement the IDataErrorInfo Interface. This interface is used by the Data Binding mechanism to validate error. For example in the following, we restrict the Rating value to be below 10 (Of course we state that our class implements the Systems.ComponentModel.IDataErrorInfo interface):

#region IDataErrorInfo Members

// This is not used in XAML (was used in WinForms)
public string Error
{
    get { throw new NotImplementedException(); }
}

public string this[string propertyName]
{
    get
    {
        if (propertyName == "Rating")
        {
            if (_rating > 10) return "Value must be smaller than 10!";
        }
        return "";
    }
}

#endregion



The XAML code should be altered to support those validation errors as follows:

<TextBox Text="{Binding Path=Rating, ValidatesOnExceptions=true, ValidatesOnDataErrors=true, NotifyOnValidationError=true}" Validation.Error="TextBox_Error"/>


 

This approach is a better alternative to the previous one. It is useful in cases where we want the Business Object to carry the validation logic if it is going to be used in different forms and all of those have the same validation needs for he object.

The third and the last approach is one that is totally decoupled both from the GUI and the Business Object. It is based on implementing custom Validators. For example this is a validator that provides an error when the value is smaller than 0 (note that you need to use the System.Windows.Controls assembly apart from the default ones):

    public class DemoValidator : ValidationRule
    {
        public override ValidationResult Validate(object value,
            System.Globalization.CultureInfo cultureInfo)
        {
            int IntValue=0;
            ValidationResult result = null;
            try
            {
               IntValue=Convert.ToInt16(value);
            }
            catch (Exception)
            {
                return new ValidationResult(false"Please provide a number");
            }

            if (IntValue < 0) return new ValidationResult(false"Please provide a number greater than 0");
            
            return new ValidationResult(truenull);
        }
    }
 


 

In the XAML file, in order to use this validator we first declare the namespace it resides:

<Window x:Class="ValidationDemo.WindowValidationDemo"

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

xmlns:local="clr-namespace:ValidationDemo"

Title="Validation Demo" Height="150" Width="432">

And then apply it to the Rating TextBox as follows:


 

 

A few things here. First, we have changed the XAML syntax (nesting the Binding element) to request the use of our custom validator. Second,since we are using all of the possible validators in this scenario, a question is raised about the sequence they are called. In other words, do I need to check whether the value is a number in the custom validator? For answers to those questions check out in here at the end of the article, the sequence of validation and also here for the property ValidationStep that controls when the custom validators will be executed. Finally for information on how to change the red box around the textbox and provide your own, read a nice article by Paul Stovell here.

That summarizes a brief introduction to Data Validation in WPF. As it seems in large scale applications the most flexible and useful approach is to use custom validators in conjunction with the native exception system of WPF.

The demo application can be downloaded here: ValidationDemo.zip (87,28 kb)

kick it on DotNetKicks.com

<TextBox VerticalAlignment="Center" Width="30" HorizontalAlignment="Left" TextAlignment="Center" Validation.Error="TextBox_Error">

<Binding Path="Rating" ValidatesOnExceptions="true" ValidatesOnDataErrors="True" NotifyOnValidationError="True">

<Binding.ValidationRules>

<local:DemoValidator/>

</Binding.ValidationRules>

</Binding>

</TextBox>

Tags:

WPF

Data Binding with WPF

by Ioannis 19. January 2009 12:04

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

Tags:

WPF

Powered by BlogEngine.NET 1.5.0.7

Programming Blogs - BlogCatalog Blog Directory Add to Technorati Favorites

MVP Award

Ioannis Panagopoulos





This blog is using BlogEngine.Net and is hosted in the hoster below. I have not experienced any problems installing BlogEngine.Net in the host and I am satisfied with the host's response times. Therefore I recommend it.


DiscountASP Add