我尝试在WPF中的默认构造函数中设置DataContext属性的顺序。
<StackPanel>
<ListBox ItemsSource="{Binding MyItems, PresentationTraceSources.TraceLevel=High}"></ListBox>
<TextBlock Text="{Binding SomeText}"></TextBlock>
<TextBlock Text="{Binding SomeNum}"></TextBlock>
<TextBlock Text="{Binding Path=Person.Name}"></TextBlock>
<ListBox ItemsSource="{Binding Path=PersonList}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"></TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
1)在 InitializeComponent
方法之前设置DataContext
public partial class MainWindow : Window, INotifyPropertyChanged
{
private string someText = "Default text";
public List<string> MyItems { get; set; }
public List<Person> PersonList { get; set; }
public Person Person { get; set; }
public int SomeNum { get; set; }
public string SomeText
{
get
{
return someText;
}
set
{
someText = value;
OnPropertyChanged("SomeText");
}
}
public MainWindow()
{
this.DataContext = this;
MyItems = new List<string>();
PersonList = new List<Person>();
Person = new Person();
InitializeComponent();
/*These changes are not reflected in the UI*/
SomeNum = 7;
Person.Name = "Andy";
/*Changes reflected with a help of INotifyPropertyChanged*/
SomeText = "Modified Text";
/* Changes to the Lists are reflected in the UI */
MyItems.Add("Red");
MyItems.Add("Blue");
MyItems.Add("Green");
MyItems[0] = "Golden";
PersonList.Add(new Person() { Name = "Xavier" });
PersonList.Add(new Person() { Name = "Scott" });
PersonList[0].Name = "Jean";
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
public class Person
{
public string Name { get; set; } = "Default Name";
}
调用InitializeComponent
方法后,除了使用INotifyPropertyChanged
的属性之外,属性值不会反映在UI中。到目前为止,一切都很清楚。
但是我注意到列表项的更改也反映在UI中。怎么会?
我一直认为,为了反映从集合中添加/删除,我需要ObservableCollection并在列表对象上实现INotifyPropertyChanged
以检测这些对象的修改。这是什么意思?
2)在 InitializeComponent
方法之后设置DataContext
为什么在InitializeComponent
之后设置一个DataContext属性是MVVM的一个坏习惯?你能更彻底地描述它还是给出一个简单的代码示例?
答案 0 :(得分:1)
我一直认为,为了反映从集合中添加/删除我需要
ObservableCollection<T>
并在列表对象上实现INotifyPropertyChanged
以检测这些对象的修改。
如果您希望在视图模型更改期间可靠更新UI,则可以这样做。
这是什么意思?
“含义”是指在您的特定情况下,您正在做出无效的假设。 WPF组件经历了各种初始化步骤,其中只有一些是InitializeComponent()
方法的一部分。
例如,如果您要将值更新的代码移动到Loaded
事件的处理程序中,您会发现部分的更新反映在UI中,但不是全部。
如果您将相同的代码移动到使用Dispatcher.InvokeAsync()
优先级DispatcherPriority.SystemIdle
调用的方法中,您会发现将会观察到 none 的更新,除非对于由INotifyPropertyChanged
支持的人。在这种情况下,您将明确等待初始化的每个方面完成,并且初始化代码不再有机会观察更新的值。
一切都与时间有关。在UI结束观察之前设置值的任何代码都可以在没有INotifyPropertyChanged
或等效的情况下成功完成。但在这种情况下,你完全受制于当前框架实施的摆布。初始化的不同部分在不同的时间发生,并且这些部分并未全部记录,因此您依赖于未记录的行为。它可能不会改变,但你无法确定。
为什么在
InitializeComponent
之后设置一个DataContext属性对MVVM来说是不好的做法?
不是。不要相信你在互联网上阅读的所有内容,甚至(特别是!)。
如果您想放弃INotifyPropertyChanged
的实施,那么在分配DataContext
之前初始化所有视图模型数据非常重要。但是,即使您在调用DataContext
后分配InitializeComponent
,也会观察到该分配(因为DataContext
是依赖属性,因此向框架提供了属性更改通知)和UI将从您的视图模型数据中检索所有绑定数据。
重要的是在分配DataContext
之前初始化视图模型数据。相对于InitializeComponent()
发生的情况并不重要。
答案 1 :(得分:0)
当视图模型属性不触发PropertyChanged事件时,必须在将视图模型实例分配给视图的DataContext之前设置其值。
但是,如果在调用InitializeComponent之前或之后分配DataContext,那么无关紧要:
给出像
这样的绑定<TextBlock Text="{Binding SomeText}"/>
这两个序列都会导致在视图中显示属性值:
DataContext = new { SomeText = "Hello, World." };
InitializeComponent();
和
InitializeComponent();
DataContext = new { SomeText = "Hello, World." };