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