绑定到 ViewModel 集合的 WPF 无法按预期显示
WPF binding to a collection of ViewModels fails to display as expected
我和我的同事一直在拼命地试图理解为什么我们不能让一组 ViewModels 按预期呈现。我们创建了一个非常简单的示例来演示该问题。
基本上,我们有一个 StupidPerson class,它有一个名字和一个朋友列表(也是 StupidPerson 的)。在 MainViewModel 中,我们创建根 StupidPerson 并将其他四个 StupidPerson 的朋友添加到他的朋友中。 MainWindow 仅使用 StupidPersonViewModel 显示源 StupidPerson。
StupidPersonViewModel 具有所有花里胡哨的功能,StupidPersonView 背后的代码甚至实现了 DependencyProperty。 StupidPersonView 将 ItemsControl 的 ItemsSource 绑定到 StupidPersonViewModel 的 StupidFriends 属性。
为了尝试所有不同的可能性,我们确实把事情复杂化了。我希望从下面的 XAML 中看到的是 "Name: Fred",然后是 "Friends:",然后是四个 "Name: XXXX" 和空的 "Friends:" 列表。然而,我得到的是 4 个空的 StupidPerson 的。
发生的事情是,我没有使用我在 MainViewModel 中创建的绑定到 ItemsSource 的 StupidPersonViewModel,XAML 魔法正在更新四个空的 StupidPersonViewModel,并将它们用于要呈现的项目。它显然绑定到我创建的列表,因为它只呈现 4 个空的 ViewModel。
一头雾水
<UserControl x:Class="StupidXaml.StupidPersonView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:StupidXaml"
mc:Ignorable="d"
d:DesignHeight="300"
Background="White" Width="509.016">
<UserControl.DataContext>
<local:StupidPersonViewModel />
</UserControl.DataContext>
<StackPanel>
<Label Content="{Binding Name}" />
<Label Content="Friends:" />
<ItemsControl Margin="10,0,0,0" ItemsSource="{Binding StupidFriends}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:StupidPersonView />
</DataTemplate>
<!--<DataTemplate DataType="local:StupidPersonViewModel">
<StackPanel Orientation="Horizontal">
--><!-- Proves that binding is a StupidPersonViewModel --><!--
<Label Content="{Binding}"></Label>
--><!-- Both of these work! --><!--
<Label Content="{Binding Name}"></Label>
<Label Content="{Binding Person.Name}"></Label>
--><!-- But none of these work. How is this possible!? -->
<!-- DataContext binding -->
<!--<local:StupidPersonView DataContext="{Binding}" />
<local:StupidPersonView DataContext="{Binding DataContext, ElementName=item}" />-->
<!-- Dependency Property binding -->
<!--<local:StupidPersonView Person="{Binding Person}" />
<local:StupidPersonView Person="{Binding DataContext.Person, ElementName=item}" />
<local:StupidPersonView Person="{Binding DataContext.Person, ElementName=item, BindsDirectlyToSource=True}" x:Name="self" />--><!--
</StackPanel>
</DataTemplate>-->
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
显示这个:
simplest attempt
和这个XAML
<UserControl x:Class="StupidXaml.StupidPersonView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:StupidXaml"
mc:Ignorable="d"
d:DesignHeight="300"
Background="White" Width="509.016">
<UserControl.DataContext>
<local:StupidPersonViewModel />
</UserControl.DataContext>
<StackPanel>
<Label Content="{Binding Name}" />
<Label Content="Friends:" />
<ItemsControl Margin="10,0,0,0" ItemsSource="{Binding StupidFriends}">
<ItemsControl.ItemTemplate>
<!--<DataTemplate>
<local:StupidPersonView />
</DataTemplate>-->
<DataTemplate DataType="local:StupidPersonViewModel">
<StackPanel Orientation="Horizontal">
<!--Proves that binding is a StupidPersonViewModel-->
<Label Content="{Binding}"></Label>
<!--Both of these work!-->
<Label Content="{Binding Name}"></Label>
<Label Content="{Binding Person.Name}"></Label>
<!--But none of these work. How is this possible!?-->
<!--DataContext binding-->
<local:StupidPersonView DataContext="{Binding}" />
<local:StupidPersonView DataContext="{Binding DataContext, ElementName=item}" />
<!--Dependency Property binding-->
<local:StupidPersonView Person="{Binding Person}" />
<local:StupidPersonView Person="{Binding DataContext.Person, ElementName=item}" />
<local:StupidPersonView Person="{Binding DataContext.Person, ElementName=item, BindsDirectlyToSource=True}" x:Name="self" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
显示如下:
all other attempts
public class MainViewModel
{
public StupidPersonViewModel Source { get; set; }
public MainViewModel()
{
Source = new StupidPersonViewModel { Person = new StupidPerson { Name = "Fred" } };
Source.Person.StupidFriends.Add(new StupidPerson { Name = "Bob" });
Source.Person.StupidFriends.Add(new StupidPerson { Name = "Greg" });
Source.Person.StupidFriends.Add(new StupidPerson { Name = "Frank" });
Source.Person.StupidFriends.Add(new StupidPerson { Name = "Tommy" });
}
}
public class StupidPersonViewModel : INotifyPropertyChanged
{
[CanBeNull]
public string Name => $"Name: {this.Person?.Name}";
private StupidPerson person;
[CanBeNull]
public StupidPerson Person
{
get { return this.person; }
set
{
this.person = value;
this.RaisePropertyChanged(nameof(this.Person));
this.StupidFriends = new ObservableCollection<StupidPersonViewModel>();
foreach (var friend in value.StupidFriends)
{
this.StupidFriends.Add(new StupidPersonViewModel { Person = friend });
}
this.RaisePropertyChanged(nameof(this.Name));
this.RaisePropertyChanged(nameof(this.StupidFriends));
}
}
private void RaisePropertyChanged(string property)
{
this.OnPropertyChanged(property);
}
private ObservableCollection<StupidPersonViewModel> stupidFriends;
public ObservableCollection<StupidPersonViewModel> StupidFriends
{
get { return this.stupidFriends; }
set
{
this.stupidFriends = value;
this.RaisePropertyChanged(nameof(this.StupidFriends));
}
}
//public StupidPersonViewModel()
//{
//}
//public StupidPersonViewModel(StupidPerson person)
//{
// this.Person = person;
//}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
实现 UserControl 时常犯的一个错误是将其 DataContext
属性 显式设置为预期视图模型的实例,就像您通过
所做的那样
<UserControl.DataContext>
<local:StupidPersonViewModel />
</UserControl.DataContext>
这样做可以有效地防止 UserControl 从其父控件继承 DataContext,正如
中预期的那样
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:StupidPersonView />
</DataTemplate>
</ItemsControl.ItemTemplate>
其中继承的 DataContext 是 ItemsSource
集合的一个元素。
这里的继承是指Dependency Property Value Inheritance.
所以不要显式设置 UserControl 的 DataContext。绝不。任何告诉您的博客或在线教程都是完全错误的。
我和我的同事一直在拼命地试图理解为什么我们不能让一组 ViewModels 按预期呈现。我们创建了一个非常简单的示例来演示该问题。
基本上,我们有一个 StupidPerson class,它有一个名字和一个朋友列表(也是 StupidPerson 的)。在 MainViewModel 中,我们创建根 StupidPerson 并将其他四个 StupidPerson 的朋友添加到他的朋友中。 MainWindow 仅使用 StupidPersonViewModel 显示源 StupidPerson。
StupidPersonViewModel 具有所有花里胡哨的功能,StupidPersonView 背后的代码甚至实现了 DependencyProperty。 StupidPersonView 将 ItemsControl 的 ItemsSource 绑定到 StupidPersonViewModel 的 StupidFriends 属性。
为了尝试所有不同的可能性,我们确实把事情复杂化了。我希望从下面的 XAML 中看到的是 "Name: Fred",然后是 "Friends:",然后是四个 "Name: XXXX" 和空的 "Friends:" 列表。然而,我得到的是 4 个空的 StupidPerson 的。
发生的事情是,我没有使用我在 MainViewModel 中创建的绑定到 ItemsSource 的 StupidPersonViewModel,XAML 魔法正在更新四个空的 StupidPersonViewModel,并将它们用于要呈现的项目。它显然绑定到我创建的列表,因为它只呈现 4 个空的 ViewModel。
一头雾水
<UserControl x:Class="StupidXaml.StupidPersonView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:StupidXaml"
mc:Ignorable="d"
d:DesignHeight="300"
Background="White" Width="509.016">
<UserControl.DataContext>
<local:StupidPersonViewModel />
</UserControl.DataContext>
<StackPanel>
<Label Content="{Binding Name}" />
<Label Content="Friends:" />
<ItemsControl Margin="10,0,0,0" ItemsSource="{Binding StupidFriends}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:StupidPersonView />
</DataTemplate>
<!--<DataTemplate DataType="local:StupidPersonViewModel">
<StackPanel Orientation="Horizontal">
--><!-- Proves that binding is a StupidPersonViewModel --><!--
<Label Content="{Binding}"></Label>
--><!-- Both of these work! --><!--
<Label Content="{Binding Name}"></Label>
<Label Content="{Binding Person.Name}"></Label>
--><!-- But none of these work. How is this possible!? -->
<!-- DataContext binding -->
<!--<local:StupidPersonView DataContext="{Binding}" />
<local:StupidPersonView DataContext="{Binding DataContext, ElementName=item}" />-->
<!-- Dependency Property binding -->
<!--<local:StupidPersonView Person="{Binding Person}" />
<local:StupidPersonView Person="{Binding DataContext.Person, ElementName=item}" />
<local:StupidPersonView Person="{Binding DataContext.Person, ElementName=item, BindsDirectlyToSource=True}" x:Name="self" />--><!--
</StackPanel>
</DataTemplate>-->
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
显示这个: simplest attempt
和这个XAML
<UserControl x:Class="StupidXaml.StupidPersonView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:StupidXaml"
mc:Ignorable="d"
d:DesignHeight="300"
Background="White" Width="509.016">
<UserControl.DataContext>
<local:StupidPersonViewModel />
</UserControl.DataContext>
<StackPanel>
<Label Content="{Binding Name}" />
<Label Content="Friends:" />
<ItemsControl Margin="10,0,0,0" ItemsSource="{Binding StupidFriends}">
<ItemsControl.ItemTemplate>
<!--<DataTemplate>
<local:StupidPersonView />
</DataTemplate>-->
<DataTemplate DataType="local:StupidPersonViewModel">
<StackPanel Orientation="Horizontal">
<!--Proves that binding is a StupidPersonViewModel-->
<Label Content="{Binding}"></Label>
<!--Both of these work!-->
<Label Content="{Binding Name}"></Label>
<Label Content="{Binding Person.Name}"></Label>
<!--But none of these work. How is this possible!?-->
<!--DataContext binding-->
<local:StupidPersonView DataContext="{Binding}" />
<local:StupidPersonView DataContext="{Binding DataContext, ElementName=item}" />
<!--Dependency Property binding-->
<local:StupidPersonView Person="{Binding Person}" />
<local:StupidPersonView Person="{Binding DataContext.Person, ElementName=item}" />
<local:StupidPersonView Person="{Binding DataContext.Person, ElementName=item, BindsDirectlyToSource=True}" x:Name="self" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
显示如下: all other attempts
public class MainViewModel
{
public StupidPersonViewModel Source { get; set; }
public MainViewModel()
{
Source = new StupidPersonViewModel { Person = new StupidPerson { Name = "Fred" } };
Source.Person.StupidFriends.Add(new StupidPerson { Name = "Bob" });
Source.Person.StupidFriends.Add(new StupidPerson { Name = "Greg" });
Source.Person.StupidFriends.Add(new StupidPerson { Name = "Frank" });
Source.Person.StupidFriends.Add(new StupidPerson { Name = "Tommy" });
}
}
public class StupidPersonViewModel : INotifyPropertyChanged
{
[CanBeNull]
public string Name => $"Name: {this.Person?.Name}";
private StupidPerson person;
[CanBeNull]
public StupidPerson Person
{
get { return this.person; }
set
{
this.person = value;
this.RaisePropertyChanged(nameof(this.Person));
this.StupidFriends = new ObservableCollection<StupidPersonViewModel>();
foreach (var friend in value.StupidFriends)
{
this.StupidFriends.Add(new StupidPersonViewModel { Person = friend });
}
this.RaisePropertyChanged(nameof(this.Name));
this.RaisePropertyChanged(nameof(this.StupidFriends));
}
}
private void RaisePropertyChanged(string property)
{
this.OnPropertyChanged(property);
}
private ObservableCollection<StupidPersonViewModel> stupidFriends;
public ObservableCollection<StupidPersonViewModel> StupidFriends
{
get { return this.stupidFriends; }
set
{
this.stupidFriends = value;
this.RaisePropertyChanged(nameof(this.StupidFriends));
}
}
//public StupidPersonViewModel()
//{
//}
//public StupidPersonViewModel(StupidPerson person)
//{
// this.Person = person;
//}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
实现 UserControl 时常犯的一个错误是将其 DataContext
属性 显式设置为预期视图模型的实例,就像您通过
<UserControl.DataContext>
<local:StupidPersonViewModel />
</UserControl.DataContext>
这样做可以有效地防止 UserControl 从其父控件继承 DataContext,正如
中预期的那样<ItemsControl.ItemTemplate>
<DataTemplate>
<local:StupidPersonView />
</DataTemplate>
</ItemsControl.ItemTemplate>
其中继承的 DataContext 是 ItemsSource
集合的一个元素。
这里的继承是指Dependency Property Value Inheritance.
所以不要显式设置 UserControl 的 DataContext。绝不。任何告诉您的博客或在线教程都是完全错误的。