views:

29

answers:

2

Hello,

I need some help about WPF and Databinding.

Let's say I have a ClassA with a member of ClassB. ClassB has again a member, maybe an int:

ClassB
{
  public int MemberOfB { get; set; }
}

ClassA
{
    private ClassB _theB;
    public ClassB MemberOfA
    {
        get {return _theB;}
        set
        {
            _theB = value;

            // Need to do something here...
        }
    }
}

When I have a Databinding in XAML like this:

<TextBox Text="{Binding Path=MemberOfA.MemberOfB}"/>

Where the Datacontext of the Textbox is an object of type ClassA.

As you can see, i need to do some computations in the setter of MemberOfA in ClassA. But with the databinding above, this setter is of course never called, because it binds to the member of ClassB.

So, how can i get to be informed if the MemberOfA changes (when I Type something into the Textbox)? Are there any best practices? (Didn't check the code in Visual Studio, so there may be some syntax errors).

Thanks, Walter

+2  A: 

The best way to handle this would probably be to make B implement INotifyPropertyChanged. When A gets a new instance of B, have it hook up to the PropertyChanged event (and unhook from the old B's event if necessary).

public B MemberOfA {
    get { return _b; }
    set {

        if (_b != null) { _b.PropertyChanged -= B_PropertyChanged; }

        _b = value;

        if (_b != null) { _b.PropertyChanged += B_PropertyChanged; }

        DoWhatever(_b);

    }
}

private void B_PropertyChanged(object sender, PropertyChangedEventArgs e) {
    DoWhatever((B)sender);
}
Josh Einstein
But what if you havent't control over the code of ClassB so you can't implement INotifyPropertyChanged there? Do you have to wrap all the member of ClassB into ClassA? From time to time i think the whole WPF databinding thing is absolute unpracticable...
Walter
As someone who used to despise data binding, I can assure you WPF data binding is not impractical. I would highly suggest looking into the MVVM pattern which specifically addresses this concern. The idea is that you create a ViewModel which acts as a facade over an inflexible model that may not be data binding friendly. In this case you would have a ViewModel that exposes members of B directly as members of the ViewModel and your view would bind to that instead.
Josh Einstein
Also, can you give more info about what ClassB actually is? If it's a simple type and you want to create new instances of ClassB whenever the text box changes, you could use an IValueConverter to convert the value from the UI into a new instance of ClassB that would then be assigned to A's property.
Josh Einstein
If I don't have control of a class because it's imported from some external library, I will almost always create a local proxy class that I do have control over. I suggest you create a class called ClassBProxy that looks just like B. You can implement INotifyPropertyChanged in ClassBProxy and forward the property changes to an internal member variable of type ClassB. If you write your own code so that it depends on ClassBProxy instead of directly on ClassB, you'll have a lot more flexibility.
Chris McKenzie
A: 

Fundamental problem

In my opinion, all 3rd party vendors ought to implement at least one of NET Framework's standard notification mechanisms, which are:

  • The original XyzChanged event pattern
  • INotifyPropertyChanged
  • INotifyCollectionChanged
  • OnDependencyPropertyChanged

All of these are fully supported by WPF, so if your vendor implements any of these you should be able to just drop their objects into your WPF application and go.

Unfortunately you will find many 3rd party libraries that don't implement any of these mechanisms: Instead they implement their own custom change notification, or even none at all!

In the rest of this answer I will explain several possible soutions to this problem.

1. Wrap 3rd party objects in wrapper models

You may create a "wrapper model" object to parallel each 3rd party model object that doesn't implement standard notifications. Expose all the 3rd party's properties and methods as your own (yes, this is a lot of code). Register with the 3rd party object's nonstandard notifications mechanism and send standard notifications using INotifyPropertyChanged. This solution is labor-intensive and can be a maintenance headache, but it works well and is many times a good way to make the best of a bad situation.

Many people call these wrapper models "view models" but this terminology leads to confusion: A "view model" is fundamentally a model that models the current state of the user interface such as what the user has open right now, what objects are selected, the current search filter, etc. WPF data binding typically binds to the view model for these types of properties but directly to the model objects themselves for the actual data.

When model objects don't support standard notification, it is common to overload the use of the view model objects to expose both "real" view model properties and also properties from the underlying models. Thus the view model acts both as a real view model and also a wrapper model: The additional properties have nothing to do with UI state but are merely a convenient way to get proper change notification without using a separate wrapper model around the broken 3rd party object.

There is nothing inherently wrong with merging your wrapper model and your view model up into a single object, but it confuses the terminology. For many people I think "view model" has come to be synonymous with "wrapper model" and I think that's a shame. If the wrapper model is kept separate from the view model you get a cleaner interface in your XAML and can more easily adapt once the vendor fixes their change notification.

You may not have time to generate wrapper models for every 3rd party model object, or you may realize that doing so would be impractical due to maintenance issues, updates, or other considerations. It can be a pain to continually be adding and updating your wrapper model every time the 3rd party object changes. In this case I would consider one of the alternative solutions below.

2. Replace DataContext on notification

You may simply tap into the 3rd party's change notification mechanism, and every time an update is signalled simply clear out the relevant DataContext (set it to null), and set it again. This will cause all WPF data bindings to be re-evaluated.

This solution is a bit like killing a fly with a sledgehammer, but it actually works and gets the job done. If it is only simple objects it works well, but as things get more complex it can be messy. If you have ItemsControls or ContentPresenters that create visual trees for your items, setting DataContext to null and back will cause those visual trees to be regenerated. This means you will lose scroll positions, Expander settings, and other UI-specific state within them. You can solve this by binding those properties to a view model to preserve their state.

This solution may be the only solution if the 3rd party's change notification mechanism is broken or inconsistent. It also tends to be the simplest "quick and dirty" solution if you need to get something usable out the door TODAY.

3. Wrap using TransparentProxy / RealProxy

If your 3rd party library includes a sane and consistent but nonstandard change notification mechanism, you have another alternative: You can implement a generalized mapping between the 3rd party mechanism and the standard INotifyPropertyChanged mechanism using the TransparentProxy / RealProxy mechanism of NET Framework.

To do this:

  1. Expose the INotifyPropertyChanged interface on the TransparentProxy, and on your RealProxy handle add_PropertyChanged and remove_PropertyChanged by registering with the 3rd party's event notification system
  2. When the RealProxy receives a property get on a property, do any 3rd party registration required for change notification on that particular property (if not already done) before returning the property
  3. Automatically convert each object returned by the 3rd party property getters into a TransparentProxy of the same object.

Now you can bind your DataContext to an intially-constructed TransparentProxy and from then on pretend the 3rd party library uses a standard change notification mechanism and is completely compatible with WPF.

The TransparentProxy / RealProxy solution is a lot of work to set up and is specific to the 3rd party library's notification mechanism, but once it is working you don't need to worry about out-of-sync UI or losing UI state during refreshes.

4. Use a timer

If the 3rd party library provides no change notifications at all, you can simply set a timer that periodically checks for changes.

Your timer will probably scan the 3rd party object for relevant changes from the values during the last scan. If any are found, it uses one of the previous techniques to provide a standard notification of the changes. In other words, it will signal the wrapper model or RealProxy of the change or it will clear/set a DataContext.

The simplest possible timer solution just periodically sets DataContext to null and then sets it back again without checking for changes.

5. Encourage your 3rd party to implement one of the standard mechanisms

NET Framework defines four different change notification mechanisms, all of which are fully supported by WPF, so it seems inexcusable for anyone to generate objects that support none of these.

Hopefully the next version of your third party library will properly implement one of these standard change notification mechanisms. Contact your vendor and ask them to do so, or at least to create bridge code.

The sad part is, at the moment Microsoft is the worst culprit of all: Neither LINQ to SQL nor LINQ to Entities implements standard change notification on their objects! I think that's too bad because people tend to follow Microsoft's example.

Ray Burns