views:

53

answers:

2

Hello All,

I'm really falling in love with the whole .Net databinding scheme... but apparently there are still a couple of gotchas out there. Let's say that my class has a member variable of type double named Susan. Well there seems to be no immediate way to bind Susan to a text box SusanText because the binding looks something like this

SusanText.DataBindings.Add("Text",datasource,"Property")

And Susan isn't a property. So I can make Susan a public property, but that kinda stinks... what if I want to keep Susan hidden? (I guess I could make Susan a public property of a private instance of some internal class... but that's a lot of work for a little double.) However, I have a bigger problem coming up, so for the sake of argument let's go ahead and do this:

private double Susan_;
public double Susan{ get; set;}
...
SusanText.DataBindings.Add("Text",this,"Susan")

Then everything initially seems to work as expected. If I alter SusanText, Susan is altered correspondingly. However, the problem arrises when I alter Susan directly. I would like for SusanText to be automatically updated. So I suspect that I need to make Susan a subclass of double that implements some sort of IBindable interface, so that if Susan is databound to SusanText that the appropriate Events are registered and Susan will notify others if she is modified.

What is the simplest way to make Susan do what I want her to do?

Thanks!

+2  A: 

DataBinding expects the class it's bound to to raise INotifyPropertyChanged to indicate that a value has been altered and needs to be re-read; unfortunately implementing this still requires some manual coding (or something like PostSharp to IL-weave the necessary code).

So, to support databinding you can't use automatically implemented properties, or be bound directly to the field--neither gives you the opportunity to raise the necessary events (the binding will work, but the value won't be updated when changed).

Another shortcoming of DataBinding is that it doesn't consider threading. If a background thread modifies a databound value then the databinding will try to make a cross-threaded call to update the UI--which is bad. The best workaround I've found is to let bindable classes hold an instance of their UI's Synchronization Context, which will let you ensure updates to the UI are invoked on the UI thread.

using System.ComponentModel;

namespace MyWebGrocer.Uma.UI
{
  public class BoundClass : INotifyPropertyChanged
  {
    private string _Name;

    private int _Age;

    public event PropertyChangedEventHandler PropertyChanged;

    public string Name
    {
      get
      {
        return _Name;
      }
      set
      {
        _Name = value;
        OnPropertyChanged("Name");
      }
    }

    public int Age
    {
      get
      {
        return _Age;
      }
      set
      {
        _Age = value;
        OnPropertyChanged("Age");
      }
    }

    protected void OnPropertyChanged(string propertyName)
    {
      var propertyChanged = PropertyChanged;
      if (propertyChanged != null)
      {
        PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
      }
    }
  }
}

I know it all sounds discouraging--these shortcomings of databinding were learned the hard way. However, it's better to be aware of--and compensate for--these issues early on before they manifest as bigger problems.

STW
"you can't use automatically implemented properties" well, you can, you just have to fire INPC after setting the property. Seems a bit odd, but it is a useful pattern sometimes, such as when you want to update multiple properties as part of a pseudo transaction.
Will
@Will, you can--but having a consuming class (B) call a method on the databound class (A), to make (A) raise an event, reeks of smell. Events in .NET are special delegates in that they're only ever intended to be fired from inside the class--requiring external components to trigger an event is begging for people to forget. IMO it's better to do the extra work and make the databound class capable of raising its own events.
STW
@stw just an option, just an option. I'm all for using DependencyObjects anyhow. Easier the more bound properties you have.
Will
Depends on whether you're using Forms or WPF. His binding statement looks more like Forms. In that case DependencyObjects don't work.
CuppM
Cool, this was kinda what I expected to see - I just didn't know how to implement it. Specifically, I didn't know about the INotifyProperyChanged or how to implement it. Thanks!
John Berryman
And copy, paste... it works. Cool, now I've got a new design pattern for my GUIs. I stick all the GUI's state variables in a class like the one you provided and databind to it.
John Berryman
A: 

Hard to tell you a simplest way without knowing how your code comes together. If you're setting bindings in code, seems like you're still doing things the hard way.

Here's the simplest way:

namespace MyProject
{
    public sealed class ViewModel : DependencyObject //handles all databinding voodoo
    {
        public string Susan
        {
            get { return (string)GetValue(SusanProperty); }
            set { SetValue(SusanProperty, value); }
        }

        public static readonly DependencyProperty 
            SusanProperty = DependencyProperty.Register
                ("Susan", typeof(string), 
                typeof(ViewModel));
    }
}

and in the xaml:

<Window.DataContext>
  <ViewModel xmlns="clr-namespace:MyProject" />
</Window.DataContext>
<Grid>
  <TextBox Text="{Binding Susan}" />
</Grid>
Will
Unfortunately there is just a little too much voodoo here for a newb like me. I don't know anything about xaml yet. It seems really cool and useful, but no one's willing to pay me to learn it right now. Thanks though.
John Berryman
@John honestly its not hard at all to learn, and as you can see binding is MUCH easier than when done in code. Also, with editor enhancements such as design time data, you can use the property window to create your bindings against the actual data context types. The water is awesome, come on in.
Will