Получить выбранный TreeViewItem с помощью MVVM - PullRequest
14 голосов
/ 04 февраля 2012

Итак, кто-то предложил использовать WPF TreeView, и я подумал: «Да, это похоже на правильный подход». Теперь, спустя часы, я просто не могу поверить, насколько сложно было использовать этот элемент управления. Благодаря многим исследованиям я смог заставить работать элемент управления TreeView, но я просто не могу найти «правильный» способ передачи выбранного элемента в модель представления. Мне не нужно устанавливать выбранный элемент из кода; Мне просто нужна моя модель вида, чтобы знать, какой элемент выбран пользователем.

Пока у меня есть XAML, который сам по себе не очень интуитивен. Это все в теге UserControl.Resources:

<CollectionViewSource x:Key="cvs" Source="{Binding ApplicationServers}">
    <CollectionViewSource.GroupDescriptions>
        <PropertyGroupDescription PropertyName="DeploymentEnvironment"/>
    </CollectionViewSource.GroupDescriptions>
</CollectionViewSource>

<!-- Our leaf nodes (server names) -->
<DataTemplate x:Key="serverTemplate">
    <TextBlock Text="{Binding Path=Name}"/>
</DataTemplate>

<!-- Note: The Items path refers to the items in the CollectionViewSource group (our servers).
           The Name path refers to the group name. -->
<HierarchicalDataTemplate x:Key="categoryTemplate"
                          ItemsSource="{Binding Path=Items}"
                          ItemTemplate="{StaticResource serverTemplate}">
    <TextBlock Text="{Binding Path=Name}" FontWeight="Bold"/>
</HierarchicalDataTemplate>

А вот дерево:

<TreeView DockPanel.Dock="Bottom" ItemsSource="{Binding Source={StaticResource cvs}, Path=Groups}"
              ItemTemplate="{StaticResource categoryTemplate}">
            <Style TargetType="TreeViewItem">
                <Setter Property="IsSelected" Value="{Binding Path=IsSelected}"/>
            </Style>
        </TreeView>

Это правильно показывает серверы по среде (dev, QA, prod). Тем не менее, я нашел различные способы получить выбранный элемент на SO, и многие из них запутаны и сложны. Есть ли простой способ получить выбранный элемент в моей модели вида?

Примечание. В TreeView есть свойство SelectedItem, но оно доступно только для чтения. Что меня расстраивает, так это то, что только для чтения это нормально; Я не хочу менять это с помощью кода. Но я не могу использовать его, потому что компилятор жалуется, что он только для чтения.

Было также на первый взгляд элегантное предложение сделать что-то вроде этого:

<ContentPresenter Content="{Binding ElementName=treeView1, Path=SelectedItem}" />

И я задал этот вопрос: «Как ваша модель представления может получить эту информацию? Я понимаю, что ContentPresenter содержит выбранный элемент, но как мы можем передать это модели представления?» Но ответа пока нет.

Итак, мой общий вопрос: «Есть ли простой способ передать выбранный элемент в мою модель вида?»

Ответы [ 5 ]

29 голосов
/ 04 февраля 2012

Чтобы сделать то, что вы хотите, вы можете изменить ItemContainerStyle из TreeView:

<TreeView>
  <TreeView.ItemContainerStyle>
    <Style TargetType="TreeViewItem">
      <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
    </Style>
  </TreeView.ItemContainerStyle>
</TreeView>

Затем ваша модель представления (модель представления для каждого элемента в дереве) должна быть выставленалогическое IsSelected свойство.

Если вы хотите иметь возможность контролировать расширение определенного TreeViewItem, вы также можете использовать установщик для этого свойства:

<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>

Ваше мнениеЗатем -model должен предоставить логическое свойство IsExpanded.

Обратите внимание, что эти свойства работают в обоих направлениях, поэтому, если пользователь выбирает узел в дереве, свойство IsSelected модели представления будет установлено направда.С другой стороны, если вы установите IsSelected в true для модели представления, будет выбран узел в дереве для этой модели представления.И также с расширенным.

Если у вас нет модели представления для каждого элемента в дереве, тогда вы должны получить его.Отсутствие модели представления означает, что вы используете объекты модели в качестве моделей представления, но для этого для работы этих объектов требуется свойство IsSelected.

Чтобы предоставить свойство SelectedItem в родительском представлении-модель (тот, который вы привязываете к TreeView и который имеет коллекцию дочерних моделей представления), вы можете реализовать его так:

public ChildViewModel SelectedItem {
  get { return Items.FirstOrDefault(i => i.IsSelected); }
}

Если вы не хотите отслеживатьдля выбора каждого отдельного элемента в дереве вы все равно можете использовать свойство SelectedItem в TreeView.Тем не менее, чтобы сделать это в стиле «MVVM», вам нужно использовать поведение Blend (доступно в виде различных пакетов NuGet - поиск «blend интерактивность»).

Здесь я добавил EventTrigger, которыйВызывайте команду каждый раз, когда выбранный элемент изменяется в дереве:

<TreeView x:Name="treeView">
  <i:Interaction.Triggers>
    <i:EventTrigger EventName="SelectedItemChanged">
      <i:InvokeCommandAction
        Command="{Binding SetSelectedItemCommand}"
        CommandParameter="{Binding SelectedItem, ElementName=treeView}"/>
    </i:EventTrigger>
  </i:Interaction.Triggers>
</TreeView>

Вам нужно будет добавить свойство SetSelectedItemCommand в DataContext из TreeView, возвращающего ICommand.Когда выбранный элемент древовидного представления изменяется, вызывается метод Execute для команды с выбранным элементом в качестве параметра.Самый простой способ создания команды - это, вероятно, использовать DelegateCommand (Google, чтобы получить реализацию, поскольку она не является частью WPF).

Возможно, лучшая альтернатива, которая позволяет двустороннее связывание без неуклюжихкоманда должна использовать BindableSelectedItemBehavior , предоставленный Стивом Грейтрексом здесь при переполнении стека.

5 голосов
/ 04 февраля 2012

Я бы, вероятно, использовал событие SelectedItemChanged для установки соответствующего свойства на вашей виртуальной машине.

1 голос
/ 15 июня 2013

Основываясь на ответе Мартина, я сделал простое приложение, показывающее, как применить предложенное решение.

В примере кода используется инфраструктура Cinch V2 для поддержки MVVM, но его можно легко изменить, используя предпочитаемую вами среду.

Для тех, кто заинтересован, вот код на GitHub

Надеюсь, это поможет.

0 голосов
/ 03 апреля 2019

Также опоздал на вечеринку, но в качестве альтернативы для пользователей MVVMLight:

  1. Свяжите TreeViewItem с ViewModel, чтобы получить изменения свойства IsSelected.
  2. Создание сообщения MVVMLight (например, PropertyChangeMessage) с отправкой элемента SelectedItem ViewModel или Model
  3. Зарегистрируйте хост TreeView (или, при необходимости, другие ViemModels), чтобы прослушать это сообщение.

Вся реализация очень быстрая и отлично работает.

Здесь свойство IsSelected (SourceItem - часть модели выбранного элемента ViewModel):

       Public Property IsSelected As Boolean
        Get
            Return _isSelected
        End Get
        Set(value As Boolean)
            If Me.HasImages Then
                _isSelected = value
                OnPropertyChanged("IsSelected")
                Messenger.Default.Send(Of SelectedImageFolderChangedMessage)(New SelectedImageFolderChangedMessage(Me, SourceItem, "SelectedImageFolder"))
            Else
                Me.IsExpanded = Not Me.IsExpanded
            End If
        End Set
    End Property

и вот код хоста ВМ:

    Messenger.Default.Register(Of SelectedImageFolderChangedMessage)(Me, AddressOf NewSelectedImageFolder)

    Private Sub NewSelectedImageFolder(msg As SelectedImageFolderChangedMessage)
        If msg.PropertyName = "SelectedImageFolder" Then
            Me.SelectedFolderItem = msg.NewValue
        End If
    End Sub
0 голосов
/ 02 мая 2017

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

  1. Добавить ссылку на 'System.Windows.Interactivity'
  2. Добавьте следующий код в элемент дерева. <i:Interaction.Triggers> <i:EventTrigger EventName="SelectedItemChanged"> <i:InvokeCommandAction Command="{Binding SomeICommand}" CommandParameter="{Binding ElementName=treeviewName, Path=SelectedItem}" /> </i:EventTrigger> </i:Interaction.Triggers>

Это позволит вам использовать стандартную привязку MVVM ICommand для доступа к SelectedItem без необходимости использования кода или какой-либо длительной работы.

...