使用MVVM获取选定的TreeViewItem

时间:2012-02-04 18:07:16

标签: wpf mvvm treeview selecteditem

所以有人建议使用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}" />

我问了这个问题:&#34;您的视图模型如何获取此信息?我得到ContentPresenter保留所选项目,但我们如何将其转换为视图模型?&#34;但目前还没有答案。

所以,我的整体问题是:&#34;是否有简单的方式将所选项目提供给我的视图模型?&#34;

5 个答案:

答案 0 :(得分:28)

要执行您想要的操作,您可以修改ItemContainerStyle的{​​{1}}:

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属性将设置为true。另一方面,如果在视图模型上将IsSelected设置为true,则将选择树视图模型中的节点。同样随着扩展。

如果你没有树中每个项目的视图模型,那么你应该得到一个。没有视图模型意味着您将模型对象用作视图模型,但为了使其工作,这些对象需要IsSelected属性。

要在父视图模型(绑定到IsSelected并具有子视图模型集合的那个)上公开SelectedItem属性,您可以像这样实现它:

TreeView

如果您不想跟踪树上每个项目的选择,您仍然可以使用public ChildViewModel SelectedItem { get { return Items.FirstOrDefault(i => i.IsSelected); } } 上的SelectedItem属性。但是,为了能够实现“MVVM样式”,您需要使用混合行为(可用作各种NuGet包 - 搜索“混合交互”)。

这里我添加了一个TreeView,每当所选项在树中发生变化时,它将调用一个命令:

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> 的{​​{1}}的{​​{1}}处添加媒体资源SetSelectedItemCommand。当树视图的选定项更改时,将使用所选项作为参数调用命令上的DataContext方法。创建命令的最简单方法可能是使用TreeView(谷歌来获取实现,因为它不是WPF的一部分)。

允许双向绑定而不使用笨重命令的更好的替代方法是在Stack Overflow上使用Steve Greatrex提供的BindableSelectedItemBehavior

答案 1 :(得分:5)

我可能会使用SelectedItemChanged事件在您的VM上设置相应的属性。

答案 2 :(得分:1)

根据Martin的回答,我做了一个简单的应用程序,展示了如何应用所提出的解决方案。

示例代码使用Cinch V2框架来支持MVVM,但可以轻松更改它以使用您偏好的框架。

对于那些感兴趣的人,在GitHub上here is the code

希望它有所帮助。

答案 3 :(得分:0)

派对迟到了,但对于现在遇到这种情况的人来说,我的解决方案是:

  1. 添加对'System.Windows.Interactivity'的引用
  2. 将以下代码添加到treeview元素中。 <i:Interaction.Triggers> <i:EventTrigger EventName="SelectedItemChanged"> <i:InvokeCommandAction Command="{Binding SomeICommand}" CommandParameter="{Binding ElementName=treeviewName, Path=SelectedItem}" /> </i:EventTrigger> </i:Interaction.Triggers>
  3. 这将允许您使用标准的MVVM ICommand绑定来访问SelectedItem,而无需使用后面的代码或一些冗长的工作。

答案 4 :(得分:0)

晚了派对,但作为MVVMLight用户的替代选择:

  1. 将TreeViewItem绑定到您的ViewModel以获得对IsSelected属性的更改。
  2. 创建MVVMLight消息(例如PropertyChangeMes​​sage),以发送SelectedItem ViewModel或Model项
  3. 注册您的TreeView主机(或其他一些必要的ViemModels)以侦听此消息。

整个实施过程非常快,而且效果很好。

此处为IsSelected属性(SourceItem是所选ViewModel项的Model部分):

       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