Как создать ViewModel на основе объекта DataContext объекта DataTemplate? - PullRequest
1 голос
/ 30 июня 2011

Я работаю с MVVM некоторое время, и эта проблема (если это проблема) постоянно ставит меня в тупик.

У меня есть ItemControl, привязанный к коллекции в моем MainViewModel

ViewModel

public class MainViewModel : ViewModelBase
{
    public ObservableCollection<string> Names { get; set; }
}

XAML

<ItemsControl ItemsSource="{Binding Names}">
  <ItemsControl.ItemTemplate>
    <DataTemplate>
      <view:NameView />
    </DataTemplate>
  </ItemsControl.ItemTemplate>
</ItemsControl>

Свойство DataContext каждого имеет тип string (связано непосредственно с моделью), но что, если я хочу, чтобы DataContext былпривязан к ViewModel, которая основана на свойстве.Как бы мне было создать экземпляр ViewModel и передать ему модель (строку).

Надеюсь, это имеет смысл.

Ответы [ 2 ]

1 голос
/ 30 июня 2011

Я думаю, вы спрашиваете о соответствии шаблонов данных.

Если вы хотите, чтобы привязка выбрала шаблон данных, который он использует для представления объекта во время выполнения, самый простой способ - поместить шаблоны в словарь ресурсов и установить их свойство DataType, например ::

.
<ItemsControl ItemsSource="{Binding Things}">
  <ItemsControl.Resources>
     <DataTemplate DataType="{x:Type Thing1}">
        ...
     </DataTemplate>
     <DataTemplate DataType="{x:Type Thing2}">
        ...
     </DataTemplate>
  </ItemsControl.Resources>
</ItemsControl>

Теперь, если ваша модель представления вместо свойства Names имеет свойство Things:

public ObservableCollection<object> Things { get; set; }

вы можете заполнить коллекцию объектами Thing1 и Thing2, и ItemsControl предоставит каждому соответствующий шаблон.

Если вы хотите выбрать другой шаблон, основанный на значении свойства, также есть несколько способов сделать это. Одним из них является написание DataTemplateSelector , которое дает вам очень детальный контроль над тем, какой шаблон выбирается, но требует от вас фактически кодировать (и тестировать, и документировать) что-то.

Другой способ - использовать стили для отображения и скрытия контента на основе триггера. На самом деле это не выбирает разные шаблоны как таковые, но выполняет более или менее одно и то же. Поместите это в свой шаблон элемента, и он будет отображать один набор контента, когда Name - «Thing1», а другой, когда Name - «Thing2».

A очень хорошая особенность этого подхода в том, что в отличие от выбора шаблона, он динамический: если значение свойства Name изменяется во время выполнения (и ваша модель представления реализует уведомление об изменении свойства), так будет и то, что появляется на виде.

<StackPanel>
   <ContentControl>
      <ContentControl.Style>
         <Style TargetType="ContentControl">
            <Setter Property="Visibility" Value="Collapsed"/>
            <Style.Triggers>
               <DataTrigger Binding="{Binding Name}" Value="Thing1">
                  <Setter Property="Visibility" Value="Visible"/>
               </DataTrigger>
            </Style.Triggers>
         </Style>
      </ContentControl.Style>
      <!-- content to display when Name = Thing1 goes here -->
   </ContentControl>
   <ContentControl>
      <ContentControl.Style>
         <Style TargetType="ContentControl">
            <Setter Property="Visibility" Value="Collapsed"/>
            <Style.Triggers>
               <DataTrigger Binding="{Binding Name}" Value="Thing2">
                  <Setter Property="Visibility" Value="Visible"/>
               </DataTrigger>
            </Style.Triggers>
         </Style>
      </ContentControl.Style>
      <!-- content to display when Name = Thing2 goes here -->
   </ContentControl>
</StackPanel>

Наконец, , если вы на самом деле пытаетесь создать разные модели представлений в своей коллекции на основе значений строки в коллекции Names, вы делаете это в модели представлений. Вы можете сделать это:

public IEnumerable<object> ViewModels
{
   get
   {
      foreach (string name in Names)
      {
         switch (name)
         {
            case "Thing1":  yield return new Thing1ViewModel(name);
            case "Thing2":  yield return new Thing2ViewModel(name);
            default:  throw new InvalidOperationException();
         }
      }
   }
}

Если значения в Names изменяются во время выполнения, у вас возникает существенно более сложная проблема: вам нужно реализовать ViewModels как свойство ObservableCollection<object>, и вам, возможно, придется пойти так далеко, чтобы обработать сбор - изменять события в коллекции Names и обновлять коллекцию ViewModels всякий раз, когда элемент в Names добавляется, удаляется или изменяется.

1 голос
/ 30 июня 2011

Почему бы не сделать следующее?

public ObservableCollection<NameViewModel> Names { get; set; }

Это кажется немного странным, но AFAIK нет конкретного запрета на то, чтобы одна виртуальная машина знала о других.

В случае, когда вы используете DI для разрешения виртуальной машины, очевидно, что ваш дизайн должен быть скорректирован. Например, вы можете создать NamesView, который является UserControl с общедоступным DependencyProperty типа IEnumerable<string>. Затем ViewModel NamesView привязывается к этому DP ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...