WPF DataBinding: DataBinding with objects

Posted on 2/13/2007 @ 4:18 PM in #Silverlight and WPF by | Feedback | 15308 views

Earlier I had blogged about WPF DataBinding, the basic scenarios. Interestingly, I had talked about databinding only one element with another.

Usually, that is not what you would do :-). Usually, you would have a business object (or DataSet even shudder!) which you want to either display through a UI, or maybe even extend the flexibility to the end user, so he can edit it. So essentially, there are two main scenarios

a) Display Only.

b) Edit and Display.

It makes sense to consider that display only is very easily satisfied with one time data binding. It's a bit like fire and forget. So say for instance, you had the following text variable to databind,

public static string myString = www.winsmarts.com;

.. you could very easily acheive the above databinding using the following code

Binding b = new Binding();
b.Source = myString;
MyLabel.SetBinding(ContentProperty, b);

 

The more complex, and frankly more useful scenario, is where changing a textbox on the UI affects the actual data behind the scenes, or heck, changing data affects the UI.

For instance, consider the following class that taps into a performance counter:

public class Processor
{
    private PerformanceCounter pc = null;

    public Processor()
    {
        pc = new PerformanceCounter("Processor", "% Processor Time");
        pc.InstanceName = "_Total";
    }

    public float ProcessorTime
    {
        get {return pc.NextValue();}
    }
}

You can databind the above using this code here:

Processor p = new Processor();
Binding b = new Binding();
b.Source = p;
b.Path = new PropertyPath("ProcessorTime");
MyLabel.SetBinding(ContentProperty, b);

Or in XAML:

<Window ..somJunkITookOut..
    xmlns:src="clr-namespace:WPFApp2"
    >
  <Window.Resources>
    <src:Processor x:Key="P"/>
  </Window.Resources>
    <Grid>
      <Label x:Name="MyLabel" Content="{Binding Source={StaticResource P}, Path=ProcessorTime}"/>
    </Grid>
</Window>

Go ahead and run it. 999/1000 times, your window will look like this -

Now, even if you go do something heavy with your machine, the "0" doesn't change. :-/.

Oh wait, I need a Timer, so I can keep refreshing the value. Great, let us modify the class so it includes a timer.

public class Processor
{
    private PerformanceCounter pc = null;
    private DispatcherTimer tmr = new DispatcherTimer();

    private float procTime;

    public Processor()
    {
        pc = new PerformanceCounter("Processor", "% Processor Time");
        pc.InstanceName = "_Total";

        tmr.Interval = TimeSpan.FromSeconds(1);
        tmr.Tick += new EventHandler(tmr_Tick);
        tmr.IsEnabled = true;
    }

    void tmr_Tick(object sender, EventArgs e)
    {
        procTime = pc.NextValue();
        System.Diagnostics.Debug.WriteLine("............." + procTime);
    }

    public float ProcessorTime
    {
        get { return procTime; }
    }
}

Go ahead and run your Application now. What do you see now?

GODDARNIT!! Is my Timer even working? Look in the "output" window, hmm .. it is indeed working --

Hmm .. what could be going wrong? Why is my data not getting painted in the window, afterall, I DID databind it !!?

Oh hey, my class has no way to Notify the framework that something has changed. THATS IT !! So, how can I build in this notification?

a) Notification using DependencyProperty

I have blogged about DependencyProperties before. So without going into the basics, go ahead and modify your class, so it looks like this:

public class Processor : DependencyObject
{
    private PerformanceCounter pc = null;
    private DispatcherTimer tmr = new DispatcherTimer();

    public static DependencyProperty ProcessorTimeProperty = 
        DependencyProperty.Register("ProcessorTime", typeof(float), typeof(Processor));

    public float ProcessorTime
    {
        get { return (float)GetValue(ProcessorTimeProperty); }
        set { SetValue(ProcessorTimeProperty, value); }
    }

    public Processor()
    {
//.. this code didn't change } void tmr_Tick(object sender, EventArgs e) { ProcessorTime = pc.NextValue(); } }

Go ahead, compile and run. :-). What do ya see? (See ya at the bottom of the blogpost).

b) Notification using INotifyPropertyChanged

There can be certain instannces where you would not want to do this via Dependency properties. You could still acheive the above using the INotifyPropertyChanged interface. Thus, instead of using a DependencyProperty, go ahead and modify the class to as below:

public class Processor : INotifyPropertyChanged
{
    private PerformanceCounter pc = null;
    private DispatcherTimer tmr = new DispatcherTimer();
    private float procTime;

    #region INotifyPropertyChanged Members
    public event PropertyChangedEventHandler PropertyChanged;
    #endregion

    public float ProcessorTime
    {
        get { return procTime; }
    }

    public Processor()
    {
        // .. this code didn't change
    }

    void tmr_Tick(object sender, EventArgs e)
    {
        procTime = pc.NextValue();
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs("ProcessorTime"));
    }
}

This approach has two oustanding advantages:

- If you had multiple values to reflect (say databinding using DataContext), this approach is much more convenient.
- Secondly, your business objects, don't inherit from DependencyObject, which makes them, so darned much easier to use.

Now, in both A) and B), go ahead and compile your app, and run it. What do you see? -->

(The above image is animated, unless imageshack screwed it up).

WOOHOO!!!. Now you can modify the above code to use a TextBox and a different business object instead. You will note that changing a value in the textbox will cause the business object to get changed. In fact, using a property on Binding, you can even set details such as "Change Immediately", or "Change when the user hits Tab", or "Change on a custom trigger". etc.

Okay, I gotta run, but I'll leave you with a funny video.

Sound off but keep it civil:

Older comments..


On 11/16/2007 8:40:29 AM Dominiek said ..
Thx a lot man! Works great!


On 5/9/2009 1:29:14 AM Yuriy said ..
Hi,


thank you very much! Nice article: short, clear, useful.


On 5/9/2010 3:16:18 PM Angshuman said ..
Hello, great article, but its not clear


1. where (i.e. in what scope) and how you are instantiating the processor object


2. what if multiple processor objects are instantiated?


On 11/25/2011 4:40:37 AM Hans de Koster said ..
Hi,


Good clear sample. But what if I instantiate a Processor p2 in code?


How do I acceess that one from xaml?


Thanks in advance.