views:

587

answers:

2

In silverlight, I can not get INotifyPropertyChanged to work like I want it to when binding to a dictionary. In the example below, the page binds to the dictionary okay but when I change the content of one of the textboxes the CustomProperties property setter is not called. The CustomProperties property setter is only called when CustomProperties is set and not when the values within it are set. I am trying to do some validation on the dictionary values and so am looking to run some code when each value within the dictionary is changed. Is there anything I can do here?

C#

public partial class MainPage : UserControl
{

    public MainPage()
    {
        InitializeComponent();

        MyEntity ent = new MyEntity();
        ent.CustomProperties.Add("Title", "Mr");
        ent.CustomProperties.Add("FirstName", "John");
        ent.CustomProperties.Add("Name", "Smith");

        this.DataContext = ent;
    }

}

public class MyEntity : INotifyPropertyChanged
{

    public event PropertyChangedEventHandler System.ComponentModel.INotifyPropertyChanged.PropertyChanged;
    public delegate void PropertyChangedEventHandler(object sender, System.ComponentModel.PropertyChangedEventArgs e);

    private Dictionary<string, object> _customProps;
    public Dictionary<string, object> CustomProperties {
        get {
            if (_customProps == null) {
                _customProps = new Dictionary<string, object>();
            }
            return _customProps;
        }
        set {
            _customProps = value;
            if (PropertyChanged != null) {
                PropertyChanged(this, new PropertyChangedEventArgs("CustomProperties"));
            }
        }
    }

}

VB

Partial Public Class MainPage
    Inherits UserControl

    Public Sub New()
        InitializeComponent()

        Dim ent As New MyEntity
        ent.CustomProperties.Add("Title", "Mr")
        ent.CustomProperties.Add("FirstName", "John")
        ent.CustomProperties.Add("Name", "Smith")

        Me.DataContext = ent
    End Sub

End Class

Public Class MyEntity
    Implements INotifyPropertyChanged

    Public Event PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged

    Private _customProps As Dictionary(Of String, Object)
    Public Property CustomProperties As Dictionary(Of String, Object)
        Get
            If _customProps Is Nothing Then
                _customProps = New Dictionary(Of String, Object)
            End If
            Return _customProps
        End Get
        Set(ByVal value As Dictionary(Of String, Object))
            _customProps = value
            RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("CustomProperties"))
        End Set
    End Property

End Class

Xaml

<TextBox Height="23" Name="TextBox1" Text="{Binding Path=CustomProperties[Title], Mode=TwoWay}" />
<TextBox Height="23" Name="TextBox2" Text="{Binding Path=CustomProperties[FirstName], Mode=TwoWay}" />
<TextBox Height="23" Name="TextBox3" Text="{Binding Path=CustomProperties[Name], Mode=TwoWay}" />
+2  A: 

A collection needs to implement the INotifyCollectionChanged interface in addition to the INotifyPropertyChanged interface to support data binding. The ObservableCollection class implements them for a List-like collection, but I believe there is no dictionary-like collection in the .NET Framework that does this. You likely have to implement it yourself.

dtb
Thanks, I tweeked dr wpf's implementation of the ObservableDictionary: http://drwpf.com/blog/2007/09/16/can-i-bind-my-itemscontrol-to-a-dictionary/
rip
+1  A: 

Here is a one (somewhat oversimplified) solution.

public class MyEntity : INotifyPropertyChanged
{

    public event PropertyChangedEventHandler PropertyChanged;

    private readonly Dictionary<string, object> _customProps = new Dictionary<string, object>();

    public void AddCustomProperty(string key, object value)
    {
        _customProps.Add(key, value);
    }

    public object this[string key]
    {
        get { return _customProps[key]; }
        set
        {
            // The validation code of which you speak here.
            _customProps[key] = value;
            NotifyPropertyChanged("Item[" + key "]");
        }
    }

    private void NotifyPropertyChanged(string name)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(name));
    }
}

MainPage:-

    public MainPage()
    {
        InitializeComponent();

        MyEntity ent = new MyEntity();
        ent.AddCustomProperty("Title", "Mr");
        ent.AddCustomProperty("FirstName", "John");
        ent.AddCustomProperty("Name", "Smith");

        this.DataContext = ent;

    }

MainPage Xaml:-

        <TextBox Height="23" Name="TextBox1" Text="{Binding [Title], Mode=TwoWay}" />
        <TextBox Height="23" Name="TextBox2" Text="{Binding [FirstName], Mode=TwoWay}" />
        <TextBox Height="23" Name="TextBox3" Text="{Binding [Name], Mode=TwoWay}" />

The important thing for your issue is that your code is involved in the assigning the property value into the dictionary. Originaly your code exposed the Dictionary, all the assignment work went through framework component code. In this version MyEntity has a string indexer where the custom properties are assigned directly and the Dictionary is made private.

Note the implementation of INotifyPropertyChanged is not strictly necessary to answer your specific question but I suspect you will need it. If for example you re-assign a new value to a custom property you would want the UI to be notified.

AnthonyWJones