如何在 SL4 中的索引子级上实现 INotifyPropertyChanged?
我的 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 类应该引发一个 PropertyChanged 事件,其中属性名称为空字符串 ("")、"Item[]" 或 "Item["+name+"]"。但是,由于我完全替换了 Messages 对象,因此这将不起作用,因为我从未真正更改过内容。
我该如何进行这项工作?
更新
因此,我对行为和 BCL 源代码进行了一些深入研究,以了解预期的方式来找出如何使我的代码工作。我了解到的内容有两个方面:
首先,Silverlight 数据绑定实际上将 Messages 属性的返回对象视为绑定的源。因此,从 ViewModel(发送者是 ViewModel)引发 PropertyChanged 不会由绑定处理。我实际上必须从 Messages 类中引发该事件。
与使用以下内容没有什么不同: Text={Binding Messages.HelloWorld}"
Myles 的代码工作的原因是“Data”返回“this”,因此绑定被欺骗将父类视为绑定源。
这 说,即使我这样做,我的子对象引发事件,它仍然无法工作,这是因为绑定使用 System.Windows.IndexerListener 作为绑定目标,在 SourcePropertyChanged 方法中,侦听器检查属性。 name 是“Item[]”,但不执行任何操作。下一个语句委托给 PropertyListener,它检查属性名称,并且仅在它等于“Item[HelloWorld]”时才处理该事件
,除非我显式引发该事件 。我的集合中的每个可能的值,用户界面永远不会更新,这是令人失望的,因为其他文章和帖子表明“Item[]”应该有效,但查看源代码证明并非如此,
尽管如此,我仍然希望有办法。实现我的目标。
My ViewModel class has a child property of type 'Messages' that has an indexed property, like:
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]; }
}
// ...
}
I don't think I need to fill in the rest of the gaps as it is all straight-forward.
The problem I am having is that the binding is not updating when I set the Messages property to a new object. Here is how I am referencing the property in XAML (with ViewModel as the DataContext):
<TextBlock Text="{Binding Messages[HelloWorld]}" />
It was my assumption that the binding would update when the PropertyChanged event was raised for the "Messages" property.
I've read elsewhere that my Messages class should raise a PropertyChanged event with either an empty string (""), "Item[]" or "Item["+name+"]" for the property name. However, since I am completely replacing the Messages object, this won't work as I never actually change the contents.
How do I make this work?
UPDATE
So I've done some digging into the behavior and into the BCL source code to see what's expected as a way to figure out how to make my code work. What I've learned is two-fold:
First, Silverlight data-binding is actually looking at the return object from the Messages property as the source of the binding. So raising PropertyChanged from ViewModel (sender is ViewModel) is not handled by the binding. I actually have to raise the event from the Messages class.
This is no different than using the following: Text={Binding Messages.HelloWorld}"
The reason that Myles' code work is that 'Data' returns 'this' so the binding is fooled into treating the parent class as the binding source.
That said, even if I make it so my child object raises the event, it still won't work. This is because the binding uses the System.Windows.IndexerListener as the binding target. In the SourcePropertyChanged method, the listener checks if the property name is "Item[]" but takes no action. The next statement delegates to the PropertyListener which checks the property name and only handles the event if it is equal to "Item[HelloWorld]".
So, unless I explicitly raise the event for each possible value within my collection, the UI will never update. This is disappointing because other articles and posts indicate that "Item[]" should work but looking at the source proves otherwise.
Nevertheless, I still hold out hope that there is a way to accomplish my goals.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
好的,这里的根本问题是 Binding 没有指定 Path,因此,绑定框架不知道在处理 PropertyChanged 事件时要查找哪个属性名称。因此,我为绑定创建了一个路径,以便更改通知发挥作用。
我编写了以下代码,证明当实际底层字典更改时索引器绑定会刷新:
ViewModel
My XAML:
转换器:
OK the underlying problem here is that the Binding does not have a Path specified, therefore, the binding framework does not know which property name to look out for when handling PropertyChanged events. So I have fabricated a Path for the binding in order for change notification to work.
I have wrote the following code that proves the indexer binding is refreshed when the actual underlying dictionary changes:
ViewModel
My XAML:
The converter:
我不一定喜欢回答自己的问题,但我已经找到了解决问题的方法。我已经能够保持原来的流程并解决 SL4 数据绑定的特殊问题。而且代码看起来也更干净一些。
归根结底是我不再替换子对象。这似乎是关键。相反,我创建一个实例,并让该实例根据需要管理更改内部项目列表。子对象在发生更改时通知父对象,以便父对象可以引发 PropertyChanged 事件。以下是我如何让它工作的一个简短示例:
为了简洁起见,我省略了明显的部分。
I don't necessarily like answering my own questions but I have found a solution to my problem. I've been able to keep my original flow and address the idiosyncrocies of SL4 data-binding. And the code seems a bit cleaner, too.
What it boils down to is that I don't replace the child object anymore. That seems to be the key. Instead, I create a single instance and let that instance manage changing the internal list of items as needed. The child object notifies the parent when it has changed so the parent can raise the PropertyChanged event. The following is a brief example how I've gotten it to work:
For brevity, I've left out the obvious parts.