Получить выбранный 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, и многие из них запутаны и сложны. Есть ли простой способ получить выбранный элемент в моей модели представления?
Примечание: есть SelectedItem
свойство на TreeView`, но оно доступно только для чтения. Что меня расстраивает, так это то, что только для чтения это нормально; Я не хочу менять это с помощью кода. Но я не могу использовать его, потому что компилятор жалуется, что он только для чтения.
Было также на первый взгляд элегантное предложение сделать что-то вроде этого:
<ContentPresenter Content="{Binding ElementName=treeView1, Path=SelectedItem}" />
И я задал этот вопрос: "Как ваша модель представления может получить эту информацию? ContentPresenter
содержит выбранный элемент, но как мы можем передать это модели представления?"Но ответа пока нет.
Итак, мой общий вопрос: "Есть ли простой способ получить выбранный элемент в моей модели представления?"
4 ответа
Чтобы сделать то, что вы хотите, вы можете изменить 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}"/>
Ваша модель представления тогда должна выставить логическое значение IsExpanded
имущество.
Обратите внимание, что эти свойства работают в обоих направлениях, поэтому, если пользователь выбирает узел в дереве, IsSelected
свойство view-модели будет установлено в true. С другой стороны, если вы установите IsSelected
в true для модели представления будет выбран узел в дереве для этой модели представления. И аналогично с расширенным.
Если у вас нет модели представления для каждого элемента в дереве, тогда вы должны получить ее. Отсутствие модели представления означает, что вы используете объекты модели в качестве модели представления, но для этого для работы этих объектов требуется 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, предоставленный Стивом Грейтрексом здесь, при переполнении стека.
Я бы, наверное, использовал SelectedItemChanged
событие, чтобы установить соответствующее свойство на вашей виртуальной машине.
Основываясь на ответе Мартина, я сделал простое приложение, показывающее, как применить предложенное решение.
В примере кода используется инфраструктура Cinch V2 для поддержки MVVM, но его можно легко изменить, чтобы использовать платформу, которую вы предпочитаете.
Для тех, кто заинтересован, вот код на GitHub
Надеюсь, поможет.
Несколько поздно на вечеринку, но для тех, кто сталкивается с этим сейчас, мое решение было:
- Добавить ссылку на 'System.Windows.Interactivity'
- Добавьте следующий код в свой элемент дерева.
<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 без необходимости использования кода или какой-либо длительной работы.
Также опоздал на вечеринку, но в качестве альтернативы для пользователей MVVMLight:
- Свяжите TreeViewItem с ViewModel, чтобы получить изменения свойства IsSelected.
- Создайте сообщение MVVMLight (например, PropertyChangeMessage), отправив элемент SelectedItem ViewModel или Model
- Зарегистрируйте хост 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