如何在SL4中的索引子项上实现INotifyPropertyChanged?

时间:2011-09-20 16:53:01

标签: silverlight data-binding silverlight-4.0

我的ViewModel类有一个类型为'Messages'的子属性,它具有索引属性,如:

public class ViewModel
{
    // ...
    public Messages Messages
    {
        get
        {
            if (_messages == null)
            {
                LoadMessagesAsync();
                _messages = new Messages();
            }
            return _messages;
        }
        set
        {
            _messages = values;
            PropertyChanged(new PropertyChangedArgs("Messages");
        }
    }
    // ...
    private void LoadMessagesAsync()
    {
        // Do the service call
        Messages = theResult;
    }
}

public class Messages
{
    // ...
    public String this[String name]
    {
        get { return _innerDictionary[name]; }
    }
    // ...
}

我认为我不需要填补剩下的空白,因为它很简单。

我遇到的问题是,当我将Messages属性设置为新对象时,绑定不会更新。以下是我在XAML中引用属性的方法(使用ViewModel作为DataContext):

<TextBlock Text="{Binding Messages[HelloWorld]}" />

我的假设是,当为“Messages”属性引发PropertyChanged事件时,绑定会更新。

我在别处读过我的Messages类应该为属性名称引用一个带有空字符串(“”),“Item []”或“Item [”+ name +“]”的PropertyChanged事件。但是,由于我完全替换了Messages对象,因此我无法真正更改内容,因此无法正常工作。

我如何使这项工作?

更新

所以我已经完成了对行为和BCL源代码的深入研究,以了解如何使我的代码工作的方法。我所学到的是双重的:

首先,Silverlight数据绑定实际上是从Messages属性中查看返回对象作为绑定的源。因此,绑定不会处理从ViewModel(发件人是ViewModel)提升PropertyChanged。我实际上必须从Messages类中引发事件。

这与使用以下内容没有什么不同:Text = {Binding Messages.HelloWorld}“

Myles代码工作的原因是'Data'返回'this',因此绑定被欺骗为将父类视为绑定源。

那就是说,即使我这么做,所以我的孩子对象引发了这个事件,它仍然无法正常工作。这是因为绑定使用System.Windows.IndexerListener作为绑定目标。在SourcePropertyChanged方法中,侦听器检查属性名称是否为“Item []”但不执行任何操作。下一个语句委托给PropertyListener,它检查属性名称,只有当它等于“Item [HelloWorld]”时才处理该事件。

因此,除非我明确地为我的集合中的每个可能值引发事件,否则UI将永远不会更新。这是令人失望的,因为其他文章和帖子表明“Item []”应该有效但是看来源证明不是。

尽管如此,我仍然希望有一种方法可以实现我的目标。

2 个答案:

答案 0 :(得分:1)

确定这里的根本问题是Binding没有指定Path,因此,绑定框架在处理PropertyChanged事件时不知道要注意哪个属性名称。所以我为绑定制作了一个路径,以便更改通知工作。

我编写了以下代码,证明当实际的基础字典发生变化时,索引器绑定会被刷新:

视图模型

public class BindingTestViewModel : AppViewModelBase, IBindingTestViewModel

    {
        private Dictionary<string, object> _data = new Dictionary<string, object>();

        public BindingTestViewModel()
        {
            _data.Add("test","1");
            _data.Add("test2", "21");
        }

        public object this[string index]
        {
            get
            {
                return _data[index];
            }
            set
            {
                _data[index] = value;
                NotifyOfPropertyChange(() => Data);

            }
        }

        public object Data
        {
            get
            {
                return this;
            }
        }

        public void Refresh()
        {
            _data = new Dictionary<string, object>
                {
                    {"test", "2"}, {"test2", "22"}
                };
            NotifyOfPropertyChange(() => Data);
        }

    }

我的XAML:

<navigation:Page.Resources>
    <Converters:IndexConverter x:Name="IndexConverter"></Converters:IndexConverter>
</navigation:Page.Resources>

<Grid x:Name="LayoutRoot">
    <TextBox Height="23"
             HorizontalAlignment="Left"
             Margin="288,206,0,0"
             Name="textBox1"
             Text="{Binding Path=Data,Converter={StaticResource IndexConverter},ConverterParameter=test}"
             VerticalAlignment="Top" Width="120" />
    <Button x:Name="ReloadDict" Click="ReloadDict_Click" Width="50" Height="30" Content="Refresh" VerticalAlignment="Top"></Button>
</Grid>

转换器:

public class IndexConverter : IValueConverter
{
    public object Convert(object value, Type targetType,
           object parameter, CultureInfo culture)
    {
        var vm = value as BindingTestViewModel;
        var index = parameter as string;
        return vm[index];
    } 


    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

答案 1 :(得分:0)

我不一定喜欢回答我自己的问题,但我找到了解决问题的方法。我已经能够保留原始流程并解决SL4数据绑定的特性问题。代码看起来也更清晰了。

归结为我不再替换子对象。这似乎是关键。相反,我创建了一个实例,让该实例根据需要管理更改内部项目列表。子对象在更改后会通知父对象,因此父对象可以引发PropertyChanged事件。以下是我如何使用它的简短示例:

public class ViewModel
{
    // ...
    public Messages Messages
    {
        get
        {
            if (_messages == null)
            {
                lock (_messagesLock)
                {
                    if (_messages == null)
                    {
                        _messages = new Messages();
                        _messages.ListChanged += (s, e) =>
                        {
                            NotifyPropertyChanged("Messages");
                        };
                    }
                }
            }

            return _messages;
        }
    }
}

public class Messages
{
    // ...

    public String this[String name]
    {
        get
        {
            if (_innerDictionary == null)
            {
                _innerDictionary = new Dictionary<String, String>();
                LoadMessagesAsync();
            }
            return _innerDictionary[name];
        }
    }

    // ...

    private void LoadMessagesAsync()
    {
        // Do the service call
        _innerDictionary = theResult;
        NotifyListChanged();
    }

    // ...

    public event EventHandler ListChanged;
}

为简洁起见,我遗漏了明显的部分。