Andrej Tozon's blog

In the Attic

NAVIGATION - SEARCH

WPF vs. Windows Forms (data) binding

When talking to people about (data) binding in Windows Presentation Foundation, I find that a lot of them are amazed with how easy it is to bind some control's property to another, and the first one being able to react to other's changes. I'm surprised how many people still use data binding in windows forms for just that - binding to data sources, mostly datasets. But, the data can be much more that just some information, contain in data carriers like datasets and other objects.

Let's say you have to write some code to control a control's size with a slider (trackbar). In WPF and XAML, this is trivial:

<Canvas>
    <Slider Height="21" Name="slider1" 
        Width="100" Canvas.Left="178" Canvas.Top="10" 
        Minimum="50" Maximum="200" />
    <Rectangle Canvas.Left="50" Canvas.Top="50" 
        Name="rectangle1" Fill="Red" 
        Height="{Binding ElementName=slider1, Path=Value}" 
        Width="{Binding ElementName=slider1, Path=Value}" />
</Canvas>

Rectangle's Width and Height properties are bound to the value of the slider so when the slider's position changes, the rectangle resizes accordingly. How would you do something like that in Windows Forms?

Event handlers?

private void trackBar1_Scroll(object sender, EventArgs e)
{
    panel1.Width = trackBar1.Value;
    panel1.Height = trackBar1.Value;
}

Sure, why not... [panel1 with Red background is used instead of a Rectangle, and TrackBar is a slider]

But, there's another way, using - you guessed it - binding. Instead of responding to every TrackBar's value change for yourself, you can let the framework do the job:

public Form1()
{
    InitializeComponent();
    panel1.DataBindings.Add("Width", trackBar1, "Value");
    panel1.DataBindings.Add("Height", trackBar1, "Value");
}

Another example: main Form's title should mirror whatever user enters in the TextBox. XAML code:

<Window x:Class="BindingSample.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="{Binding ElementName=textBox1, Path=Text}" Height="300" 
Width="300" Loaded
="Window_Loaded"> ...
</Window>

And instead of doing this with Windows Forms:

private void textBox1_TextChanged(object sender, EventArgs e)
{
    Text = textBox1.Text;
}

You could simply do this:

public Form1()
{
    InitializeComponent();
    DataBindings.Add("Text", textBox1, "Text");
}

Now, how about binding to a custom object? For example, we want the size of our rectangle change randomly through time, and come up with a class like:

public class Randomizer
{
    private int number;
    private Timer timer;

    public Randomizer()
    {
        PickNumber();
        timer = new Timer();
        timer.Interval = 500;
        timer.Tick += new EventHandler(timer_Tick);
        timer.Enabled = true;
    }

    public int Value
    {
        get { return number; }
    }

    private void timer_Tick(object sender, EventArgs e)
    {
        PickNumber();
    }

    private void PickNumber()
    {
        number = new Random().Next(50, 201);
// Fixed for simplicity
    }
}

The Value property of this class will change twice per second, each time holding a number between 50 and 200.
Let's bind panel's Width and Height properties to the Value this class produces...

private Randomizer r = new Randomizer();
public Form1()
{
    InitializeComponent();
    panel1.DataBindings.Add("Width", r, "Value");
    panel1.DataBindings.Add("Height", r, "Value");
}

... and run the application... but... nothing happens, the panel doesn't change its size. That's because it doesn't know that Value property ever changes. To let panel's bindings be aware of Value changes, our Randomizer class has to send some notifications to any clients, bound to Value property. We'll do this by implementing INotifyPropertyChanged interface. Here's the whole class again:

public class Randomizer: INotifyPropertyChanged
{
    private int number;
    private Timer timer;

    public Randomizer()
    {
        PickNumber();
        timer = new Timer();
        timer.Interval = 500;
        timer.Tick += new EventHandler(timer_Tick);
        timer.Enabled = true;
    }

    public int Value 
    {
        get { return number; }
    }

    private void timer_Tick(object sender, EventArgs e)
    {
        PickNumber();
    }

    private void PickNumber()
    {
        number = new Random().Next(50, 201); // Fixed for simplicity
        OnPropertyChanged("Value");
    }

    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged(string name)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(name));
        }
    }
}

With INotifyPropertyChanged interface implemented, our rectangle finally starts animating. To conclude, here's how we would use the Randomizer class in WPF:

1. Declare the class as a resource:

<Window.Resources>
    <local:Randomizer x:Key="r" />
</Window.Resources>

2. Bind the rectangle to the Randomizer class, declared as a resource:

<Rectangle Canvas.Left="50" Canvas.Top="50" 
    Name="rectangle1" Fill="Red" 
    Height="{Binding Source={StaticResource r}, Path=Value}" 
    Width="{Binding Source={StaticResource r}, Path=Value}" />

One thing, worth observing here is that rectangle animates even at design time in Visual Studio's IDE, not only at runtime. Enjoy.