使用MVVM获取选定的TreeViewItem

Bob*_*orn 16 wpf treeview selecteditem 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>
Run Code Online (Sandbox Code Playgroud)

这是树视图:

<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>
Run Code Online (Sandbox Code Playgroud)

这通过环境(dev,QA,prod)正确显示服务器.但是,我已经在SO上找到了各种方法来获取所选项目,而且很多都很复杂和困难.有没有一种简单的方法可以将所选项目添加到我的视图模型中?

注意:SelectedItemTreeView`上有一个属性,但它是只读的.让我感到沮丧的是,只读是好的; 我不想通过代码更改它.但我无法使用它,因为编译器抱怨它是只读的.

做这样的事情也有一个看似优雅的建议:

<ContentPresenter Content="{Binding ElementName=treeView1, Path=SelectedItem}" />
Run Code Online (Sandbox Code Playgroud)

我问了这个问题:"你的视图模型如何获取这些信息?我得到的是ContentPresenter所选项目,但我们如何将其转化为视图模型?" 但是还没有答案.

所以,我的整体问题是:"是否有一种简单的方法可以将所选项目添加到我的视图模型中?"

Mar*_*age 29

做你想做的,你可以修改什么ItemContainerStyleTreeView:

<TreeView>
  <TreeView.ItemContainerStyle>
    <Style TargetType="TreeViewItem">
      <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
    </Style>
  </TreeView.ItemContainerStyle>
</TreeView>
Run Code Online (Sandbox Code Playgroud)

然后,您的视图模型(树中每个项目的视图模型)必须公开布尔IsSelected属性.

如果您希望能够控制某个特定TreeViewItem的扩展,那么您也可以使用该属性的setter:

<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
Run Code Online (Sandbox Code Playgroud)

然后,您的视图模型必须公开布尔IsExpanded属性.

请注意,这些属性可以双向工作,因此,如果用户在树中选择一个节点IsSelected,则视图模型的属性将设置为true.另一方面,如果IsSelected在视图模型上设置为true,则将选择树视图模型中的节点.同样扩大了.

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

SelectedItem在父视图模型上显示一个属性(绑定到该视图模型TreeView并具有子视图模型集合的属性),您可以像这样实现它:

public ChildViewModel SelectedItem {
  get { return Items.FirstOrDefault(i => i.IsSelected); }
}
Run Code Online (Sandbox Code Playgroud)

如果您不想跟踪树上每个项目的选择,您仍然可以使用该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>
Run Code Online (Sandbox Code Playgroud)

你必须在返回的a SetSelectedItemCommand上添加一个属性.当树视图的选定项更改时,将使用所选项作为参数调用命令上的方法.创建命令的最简单方法可能是使用a (谷歌获取实现,因为它不是WPF的一部分).DataContextTreeViewICommandExecuteDelegateCommand

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

  • @BobHorn:在MVVM中,您可以在视图模型对象中包装模型对象,也可以使模型对象如此丰富,以至于它们可以用作视图模型.如果将`Foo`对象包装在`FooViewModel`中并向该视图模型添加`IsSelected`属性,您将发现选择很容易处理.`TreeView`控件通过`TreeViewItem`对象公开选择而不是`TreeView`控件iself,你需要在视图模型中镜像它. (3认同)

H.B*_*.B. 5

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

  • @BobHorn:不一定,但人们对代码背后太过痴迷,这不是什么大问题...... (2认同)