WPF绑定到ViewModel集合无法按预期显示

时间:2016-11-18 09:09:24

标签: wpf data-binding itemssource

我的同事和我一直在拼命想要理解为什么我们无法按预期呈现ViewModel的集合。我们创建了一个非常简单的例子来说明问题。

基本上,我们有一个StupidPerson类,它有一个名字和一个朋友列表(也是StupidPerson的)。在MainViewModel中,我们创建了根StupidPerson并将其他四个StupidPerson添加给他的朋友。 MainWindow只使用StupidPersonViewModel显示源StupidPerson。

StupidPersonViewModel具有所有的功能和口哨,StupidPersonView背后的代码甚至实现了DependencyProperty。 StupidPersonView将ItemsControl的ItemsSource绑定到StupidPersonViewModel的StupidFriends属性。

为了尝试所有不同的可能性,我们肯定会过于复杂化。我期望从下面的XAML看到的是“姓名:Fred”,其次是“Friends:”,然后是四个“Name:XXXX”和空的“Friends:”列表。但是,我得到的是4个空的StupidPerson。

发生的事情是,不是使用我在MainViewModel中创建的StupidPersonViewModel,它们绑定到ItemsSource,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));
    }
}

1 个答案:

答案 0 :(得分:2)

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。决不。任何博客或在线教程都告诉你这是完全错误的。