【发布时间】:2016-11-18 09:09:23
【问题描述】:
我和我的同事一直在拼命地试图理解为什么我们不能让 ViewModel 集合按预期进行渲染。我们创建了一个非常简单的示例来演示该问题。
基本上,我们有一个 StupidPerson 类,它有一个名称和一个朋友列表(也是 StupidPerson 的)。在 MainViewModel 中,我们创建根 StupidPerson 并向他的朋友添加其他四个 StupidPerson。 MainWindow 仅使用 StupidPersonViewModel 显示源 StupidPerson。
StupidPersonViewModel 拥有所有的花里胡哨,StupidPersonView 背后的代码甚至实现了一个 DependencyProperty。 StupidPersonView 将 ItemsControl 的 ItemsSource 绑定到 StupidPersonViewModel 的 StupidFriends 属性。
为了尝试所有不同的可能性,我们确实把事情复杂化了。我希望从下面的 XAML 中看到的是“姓名:Fred”,然后是“朋友:”,然后是四个“姓名:XXXX”和空的“朋友:”列表。但是,我得到的是 4 个空的 StupidPerson。
发生的事情是,XAML 魔术不是使用我在 MainViewModel 中创建的绑定到 ItemsSource 的 StupidPersonViewModel,而是新建四个空的 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>
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));
}
}
【问题讨论】:
-
您是否应该首先创建 StupidPerson Fred 和 Fred 的朋友,然后创建视图模型并分配 Person 属性,因为您在 StupidPersonViewModel 的 Person 设置器中有逻辑从 StupidFriends 列表中创建 ViewModels?
标签: wpf data-binding itemssource