Подтвердить что ты не робот

Получить выбранный TreeViewItem с помощью MVVM

Итак, кто-то предложил использовать 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 содержит выбранный элемент, но как мы можем получить это для модели представления?" Но ответа пока нет.

Итак, мой общий вопрос: "Есть ли простой способ получить выбранный элемент в моей модели представления?"

4b9b3361

Ответ 1

Чтобы сделать то, что вы хотите, вы можете изменить 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}"/>

Затем ваша модель представления должна открыть свойство boolean IsExpanded.

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

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

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

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

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

Здесь я добавил 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, предоставленную Стивом Великим здесь, в Stack Overflow.

Ответ 2

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

Ответ 3

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

Пример кода использует фреймворк Cinch V2 для поддержки MVVM, но его можно легко изменить, чтобы использовать структуру ваших предпочтений.

Для желающих вот код на GitHub

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

Ответ 4

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

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

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

Ответ 5

Также опоздал на вечеринку, но в качестве альтернативы для пользователей 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

и вот код хоста VM:

    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