none
DataBinding: TypeDescriptionProvider and INotifyPropertyChanged

    Question

  • *** Edited the question to hopefully explain the situation better ***
    *** All example code written directly it may have a few mistakes ***

    Hi All,

    I've got an interesting data-binding question that I hoping someone here may be able to help explain what is going on.

    We have our engine objects (simple objects that wrap parts of our game engine) and we are data binding the interface to properties on these objects.

    class ExampleEngineObject : INotifyPropertyChanged 
      string _name; 
     
      public string Name 
      { 
         get { return _name; } 
         set  
         { 
            if (String.Compare(_name, value, true) == 0) 
              return
           
            _name = value; 
            NotifyPropertyChanged("Name"); 
         } 
      } 
       
      public event PropertyChangedEventHandler PropertyChanged; 
        
      protected void NotifyPropertyChanged(string propertyName) 
      { 
         if (PropertyChanged != null
            PropertyChanged(thisnew PropertyChangedEventArgs(propertyName)); 
      } 

    In WPF we are data binding to these properties. This is all fine. In fact we don't need to implemented INotifyPropertyChanged as the WPF data binding engine will try to subscribe to the property descriptor changed event through AddValueChanged() method.  (just learned this today)

    I need to somehow listen to these property change events to implement an undo/redo system for the application. The best way that we've come up with to 'intercept' the property change is to create a custom type descriptor and property descriptors for our engine object.

    class ExampleEngineObjectPropertyDescriptor : PropertyDescriptor 
        
       public override object GetValue(object component) 
       {  
         return _pd.GetValue(component); 
       } 
     
       public override object SetValue(object component, object value) 
       {  
         object currentValue = _pd.GetValue(component); 
         // create undo action with currentValue and value 
         _pd.SetValue(component, value); 
       } 

    By using a property descriptor we can get the current value, create the undo action, and set the new value. The undo action knows enough information about the property to later commit/rollback the change via .NET reflection SetValue(). We are adding the provider to the engine object inside our xaml.cs file by calling TypeDescriptor.AddProvider(...).

    public partial ExampleUserInterface 
        public readonly DependencyProperty EngineObjectProperty = DependencyProperty.Register("EngineObject", ...,  new PropertyChangedCallback(OnPropertyChanged)); 
     
        static void OnEngineObjectProperty_Changed(object sender, DependencyPropertyChangedEventArgs e) 
        { 
           if (e.NewValue != null
             TypeDescriptor.AddProvider(...); 
        } 
     


    Now one of the issues is that our custom property descriptor (with the required TypeDescriptorProvider and CustomTypeDescriptor etc) doesn't work when the engine object implements INotifyPropertyChanged. I've tracked this down to the TypeDescriptorProvider.GetProvider() is never called. I believe this has to do the WPF data binding engine and how it treats objects that implement INotifyPropertyChanged.

    If I don't implement INotifyPropertyChanged the property descriptors work fine but it the property doesn't know that it has been updated.



    ***bing***
    while writing this post I think I figured what I need to do: in my custom PropertyDescriptor I need to call the OnValueChanged in the SetValue(). which then if the engine object doesn't implement INotifyPropertyChange the data binding engine will try to subscribe to the change event via the AddValueChanged() method.

    ***hmm***
    someone else has also suggested using a wrapper around the property descriptor that as that would be easier.

    public class PropertyDescriptorWrapper : INotifyPropertyChanged 
      PropertyDescriptor descriptor; 
      object valueObject; 
      object value; 
     
      public PropertyDescriptor(PropertyDescriptor descriptor, object valueObject) 
      { 
        this.descriptor = descriptor; 
        this.valueObject = valueObject; 
     
        // cache all the values into local variables 
        this.value = this.descriptor.GetValue(object); 
        this.descriptor.AddValueChanged((sender, e) => 
        { 
          this.value = e.Value; 
          OnPropertyChanged("Value"); 
        }  
      } 
       
      public object Value 
      { 
        get { return this.value; } 
        set 
        { 
           if (this.value == value) 
             return
            
           this.value = value; 
           this.descriptor.SetValue(valueObject, value); 
           OnPropertyChanged("Value"); 
        } 
      } 


    well i've written all this anyways may as well post it. hopefully it works or gives someone else a better understanding of data binding.

    • Edited by godennis Monday, September 15, 2008 1:38 PM rephased question to make it clearer. added code examples.
    Monday, September 15, 2008 1:47 AM

All replies

  • I've just read this post that partly explains what is happening:
    Marco Zhou said: Actually you are encountering a another hidden aspect of WPF, that's it WPF's data binding engine will data bind to PropertyDescriptor instance which wraps the source property if the source object is a plain CLR object and doesn't implement INotifyPropertyChanged interface. And the data binding engine will try to subscribe to the property changed event through PropertyDescriptor.AddValueChanged() method. And when the target data bound element change the property values, data binding engine will call PropertyDescriptor.SetValue() method to transfer the changed value back to the source property, and it will simultaneously raise ValueChanged event to notify other subscribers (in this instance, the other subscribers will be the TextBlocks within the ListBox.

    And if you are implementing INotifyPropertyChanged, you are fully responsible to implement the change notification in every setter of the properties which needs to be data bound to the UI. Otherwise, the change will be not synchronized as you'd expect.

    http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/9365bb6a-b411-4967-9a03-ae2a810fb215



    • Proposed as answer by Jim Zhou - MSFT Friday, September 19, 2008 2:19 AM
    • Marked as answer by Marco Zhou Friday, September 19, 2008 12:16 PM
    • Unmarked as answer by Marco Zhou Friday, September 19, 2008 12:16 PM
    Monday, September 15, 2008 2:40 AM
  • ** bump ** i edited original post adding a better explanation, code samples, and possible solution. any more ideas welcome. :)
    Monday, September 15, 2008 1:41 PM