【问题标题】:WPF binding to a collection of ViewModels fails to display as expectedWPF 绑定到 ViewModel 集合无法按预期显示
【发布时间】: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>

显示: 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));
    }
}

【问题讨论】:

  • 您是否应该首先创建 StupidPerson Fred 和 Fred 的朋友,然后创建视图模型并分配 Person 属性,因为您在 StupidPersonViewModel 的 Person 设置器中有逻辑从 StupidFriends 列表中创建 ViewModels?

标签: wpf data-binding itemssource


【解决方案1】:

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

【讨论】:

  • 除此之外,您应该考虑使用 TreeView 来显示分层结构的数据。
  • 谢谢。事实上,我们确实对此进行了测试(即没有明确的 UserControl DataContext),但我想在我们的头发拉动练习中,再也没有回到那样做。绝对令人难以置信的是,您发现有多少在线位置明确建议设置 DataContext 不正确。我敢说这是大多数!除了真正让一切工作之外,VS 设计师更加更快乐!此外,如果这不是一个愚蠢的测试,我们肯定会考虑使用 TreeView。
  • 我想重申,当然必须至少设置一次 DataContext。在我们的真实应用程序中,我们有一个多选项卡选项卡控件。每个选项卡都包含一个布局管理器,该管理器又包含一个用户控件。我们必须将每个选项卡的 DataContext 显式设置为影响每个 UserControl 的 ViewModel。否则,我们将不得不将整个 ViewModel 放在 MainWindowViewModel 中。
  • TabControl 是 ItemsControl,因此会自动将第 n 个项目容器的 DataContext 设置为其 ItemsSource 集合的第 n 个元素。
  • 我想通过写出我们确实遵循严格的 MVVM 模式并实现了一个视图模型来结束这一点,该模型提供了绑定到 TabControl 的 ItemsSource 属性的视图集合。它工作得很好。唯一的缺点是无法在设计器中编辑(甚至在选项卡之间切换)。下面是我们的 VM 代码 sn-p。
猜你喜欢
  • 1970-01-01
  • 2012-07-07
  • 1970-01-01
  • 1970-01-01
  • 2016-10-11
  • 1970-01-01
  • 1970-01-01
  • 2014-11-19
  • 2012-12-10
相关资源
最近更新 更多