我的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 []”应该有效但是看来源证明不是。
尽管如此,我仍然希望有一种方法可以实现我的目标。
答案 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;
}
为简洁起见,我遗漏了明显的部分。