Мой коллега и я отчаянно пытались понять, почему мы не можем получить коллекцию ViewModels для рендеринга, как ожидалось. Мы создали очень простой пример, демонстрирующий проблему.WPF привязка к коллекции ViewModels не отображается, как ожидалось
В принципе, у нас есть класс StupidPerson, который имеет имя и список друзей (также StupidPerson's). В MainViewModel мы создаем корень StupidPerson и добавляем к своим друзьям четыре других StupidPerson's. MainWindow просто отображает источник StupidPerson с помощью StupidPersonViewModel.
У StupidPersonViewModel есть все колокола и свистки, а код за StupidPersonView даже реализует DependencyProperty. StupidPersonView связывает ItemsSource элемента Items с свойством StupidFriends StupidPersonViewModel.
У нас есть, конечно, сложные вещи, чтобы попробовать всевозможные возможности. То, что я ожидаю увидеть от XAML ниже, - «Name: Fred», а затем «Friends:», а затем четыре «Name: XXXX» и пустые списки «Друзья:». Однако я получаю 4 пустых StupidPerson's.
Что происходит, так это то, что вместо использования StupidPersonViewModel, созданного в MainViewModel, привязанного к ItemsSource, магия XAML обновляет четыре пустых StupidPersonViewModel и использует их для рендеринга элементов. Очевидно, он связан с созданным мной списком, потому что он отображает только 4 пустых ViewModels.
Полностью сбит с толку.
<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, поскольку у вас есть логика в установщике Person в StupidPersonViewModel для создания ViewModels из списка StupidFriends? –