Thursday, May 16, 2013

How to make a consistent bind in WPF.

Hello everyone, to make your first bind can be a very traumatizing experience and even for someone with a little more experience a simple mistake can drive you crazy. So in this post we are showing some tips and good practice to make a consistent binding. First of all we recommend the Josh Smith post, that in fact is one of our references for this little post.

Different from Josh we will make our example in a very simple program, that’s why our purpose is to show some tips and tricks, but without entering very deep in the subject. So, let’s consider a very simple program where you have a simple label and two buttons, one button make your label red and the other button makes your label blue, like the pictures above:





Thinking a little bit in our problem we obviously will need a property that indicates the color of the label. This property will be, somehow, bound with the color property of our label. There are some things you need to pay attention when making your bind, that we will call gold rules:

1 – You need to indicate to your view who is your ViewModel. The ViewModel class is a class that has all the properties that your view will bind with.
2 – Your property need to be public, so the view can “see” it;
3 – You need to notify your view when your property changes their values;

So, in our example we will have the classes (files):
  • A view .xaml (basically it is just the design of your program);
  • A view class .cs (this is the other part of your view);
  • A ViewModel class .cs (this class will have all the view properties).

Note: The WPF is specific designed for a View Model ViewModel (MVVM)  paradigm, so if you do not know what I am talking about I recommend you take a look at the Josh post first or look for some other MVVM post (soon we are going go available one here).
Continuing with our “complex” program, the .xaml will contain our view program, decelerated as a .xml document. In the background the .NET take this .xaml code and make its magic to transform everything in C#.

<Window x:Class="BasicBind.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525" DataContext="{Binding RelativeSource={RelativeSource Self}, Path=Model}">
   
  <Grid>
        <StackPanel>
            <Label Foreground="{Binding Path=LabelColor}" Content="Change My Color" VerticalAlignment="Center" HorizontalAlignment="Center" FontSize="40"/>
            <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
                <Button Content="Change it to Red" Margin="5" Click="ChangeToRedButtonClick"></Button>
                <Button Content="Change it to Blue" Margin="5" Click="ChangeToBlueButtonClick"></Button>
            </StackPanel>                                    
        </StackPanel>
    </Grid>
</Window>

The View class .cs, in an elegant program, can´t process anything that is not related with the view, this way you can separate your view code to your ViewModel code, making your code easier to maintain and test. Theoretically your interface is total independent from your code, if you do not like your interface anymore you can simply change it and remake the binds for the ViewModel properties that everything will work perfectly (at least theoretically).
So, our “extremely complex” View .cs class is described above:
using System.Windows;

namespace BasicBind
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow

{
    private ViewModel Model;
   

    public MainWindow()
    {
        InitializeComponent();

        Model = new ViewModel();

        DataContext = Model;
    }

    private void ChangeToRedButtonClick(object sender, RoutedEventArgs e)
    {
        Model.SetLabelToRed();
    }

    private void ChangeToBlueButtonClick(object sender, RoutedEventArgs e)
    {
        Model.SetLabelToBlue();
    }

}
}
Here came our first gold ruler: The View Model Indicate. It is in your View .cs that you say to it who is its ViewModel:
DataContext = Model;

This line just says: The object that has all the properties you will make your binds are in the Model object.
This is essential, without this line of code your view will be lost and your binds will NEVER work.
The third part of our extraordinary program is our ViewModel. The view model, how I said before, will have the properties that will be bound with our view, in our case the label color:
namespace BasicBind
{
    public class ViewModel : INotifyPropertyChanged
    {
        private Brush _labelColor;
        public Brush LabelColor
        {
            get
            {
                return _labelColor;
            }
            set
            {
                _labelColor = value;
                NotifyChangedOnProperty("LabelColor");
            }
        }

        public void SetLabelToRed()
        {
            LabelColor = Brushes.Red;
        }

        public void SetLabelToBlue()
        {
            LabelColor = Brushes.Blue;
        }

        public ViewModel()
        {
            LabelColor = Brushes.Black;
        }

                public event PropertyChangedEventHandler PropertyChanged;

        protected void NotifyChangedOnProperty(string propertyName)
        {
            VerifyPropertyName(propertyName);
            PropertyChangedEventHandler handler = this.PropertyChanged;
            if (handler != null)
            {
                var e = new PropertyChangedEventArgs(propertyName);
                handler(this, e);
            }
        }

        [Conditional("DEBUG")]
        [DebuggerStepThrough]
        private void VerifyPropertyName(string propertyName)
        {
            if (TypeDescriptor.GetProperties(this)[propertyName] == null)
            {
                string msg = "Invalid property name: " + propertyName;
                throw new Exception(msg);
            }
        }
    }
}
And here came the second gold rule: All the bound properties need to be public. If your property is not public how your view (that is another class) will see it?
 And the third gold rule: Notify the changes on the view model properties. To make it possible the first thing you need is to extends the INotifyPropertyChanged interface on the view model. How to implement correctly the property change method is one of the biggest mysterious, but you can copy one of the many examples in the internet:
        protected void NotifyChangedOnProperty(string propertyName)
        {
            PropertyChangedEventHandler handler = this.PropertyChanged;
            if (handler != null)
            {
                var e = new PropertyChangedEventArgs(propertyName);
                handler(this, e);
            }
        }
But, as you can see, the parameter for the method is a string containing the name of your property. So, if you change the name of your property during your coding process you will have a very hard to find bug in your program, because the Rename tool of the Visula Studio will not change this string. To avoid this situation I recommend use the method above and call it at the beginner of the NotifyPropertyChanged method. Basically this method will verify if your property name really exists, and it will be executed just in debug mode, this way your client release version will never run this peace of code:
 [Conditional("DEBUG")]
        [DebuggerStepThrough]
        private void VerifyPropertyName(string propertyName)
        {
            if (TypeDescriptor.GetProperties(this)[propertyName] == null)
            {
                string msg = "Invalid property name: " + propertyName;
                throw new Exception(msg);
            }
        }
And your NotifyPropertyChanged method became:
 protected void NotifyChangedOnProperty(string propertyName)
        {
            VerifyPropertyName(propertyName);
            PropertyChangedEventHandler handler = this.PropertyChanged;
            if (handler != null)
            {
                var e = new PropertyChangedEventArgs(propertyName);
                handler(this, e);
            }
        }

Following this basic rules you will be able to make a consistent bind and be able to debug it with something wrong happens.
Some other gold tips are:
  • Try to notify the changes on your properties allays in the set method;
  • Be careful with ready only properties (properties that do not have set method), the notify changed in this situations is very tricky, and you need to pay attention to know exactly where its values is changed to notify it correctly;
  • You can make a ViewModelBase class that implements the INotifyPropertyChanged and make yours ViewModel classes inherit from it, so you will need to implement it just once.

If you know or use some other trick let us know! ;-)
You can download this sample code on the link above:



We hope you liked it. Doubts? Leave a comment!!
That´s all folks!!
Até a próxima!!
TapiocaCom Team.


No comments:

Post a Comment