wpf 使用 MVVM 获取选定的 TreeViewItem
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/9143107/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Get Selected TreeViewItem Using MVVM
提问by Bob Horn
So someone suggested using a WPF TreeView
, and I thought: "Yeah, that seems like the right approach." Now, hours and hours later, I simply can't believe how difficult it has been to use this control. Through a bunch of research, I was able to get the TreeView` control working, but I simply cannot find the "proper" way to get the selected item to the view model. I do not need to set the selected item from code; I just need my view model to know which item the user selected.
所以有人建议使用 WPF TreeView
,我想:“是的,这似乎是正确的方法。” 现在,几个小时后,我简直不敢相信使用这个控件有多么困难。通过大量研究,我能够使 TreeView` 控件正常工作,但我根本找不到将所选项目添加到视图模型的“正确”方法。我不需要从代码中设置所选项目;我只需要我的视图模型来知道用户选择了哪个项目。
So far, I have this XAML, which isn't very intuitive on its own. This is all within the UserControl.Resources tag:
到目前为止,我有这个 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>
And here's the treeview:
这是树视图:
<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>
This correctly shows servers by environment (dev, QA, prod). However, I've found various ways on SO to get the selected item, and many are convoluted and difficult. Is there a simpleway to get the selected item to my view model?
这可以按环境(开发、QA、生产)正确显示服务器。但是,我在 SO 上找到了各种方法来获取所选项目,其中许多方法既复杂又困难。有没有一种简单的方法可以将所选项目添加到我的视图模型中?
Note: There is a SelectedItem
property on the TreeView`, but it's read-only. What's frustrating to me is that read-only is just fine; I don't want to change it via code. But I can't use it because the compiler complains that it's read-only.
注意:SelectedItem
TreeView` 上有一个属性,但它是只读的。令我沮丧的是,只读就可以了;我不想通过代码改变它。但是我不能使用它,因为编译器抱怨它是只读的。
There was also a seemingly elegant suggestion to do something like this:
还有一个看似优雅的建议来做这样的事情:
<ContentPresenter Content="{Binding ElementName=treeView1, Path=SelectedItem}" />
And I asked this question: "How can your a view model get this information? I get that ContentPresenter
holds the selected item, but how do we get that over to the view model?" But there is no answer yet.
我问了这个问题:“你的视图模型如何获得这些信息?我知道它ContentPresenter
包含所选项目,但我们如何将其传递给视图模型?” 但目前还没有答案。
So, my overall question is: "Is there a simpleway to get the selected item to my view model?"
所以,我的总体问题是:“有没有一种简单的方法可以将所选项目添加到我的视图模型中?”
回答by Martin Liversage
To do what you want you can modify the ItemContainerStyle
of the TreeView
:
要执行您想要的操作,您可以修改以下ItemContainerStyle
内容TreeView
:
<TreeView>
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
Your view-model (the view-model for each item in the tree) then has to expose a boolean IsSelected
property.
您的视图模型(树中每个项目的视图模型)必须公开一个布尔IsSelected
属性。
If you want to be able to control if a particular TreeViewItem
is expanded you can use a setter for that property too:
如果您希望能够控制某个特定对象TreeViewItem
是否被展开,您也可以为该属性使用 setter:
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
Your view-model then has to expose a boolean IsExpanded
property.
然后您的视图模型必须公开一个布尔IsExpanded
属性。
Note that these properties work both ways so if the user selects a node in the tree the IsSelected
property of the view-model will be set to true. On the other hand if you set IsSelected
to true on a view-model the node in the tree for that view-model will be selected. And likewise with expanded.
请注意,这些属性是双向的,因此如果用户在树中选择一个节点IsSelected
,则视图模型的属性将设置为 true。另一方面,如果您IsSelected
在视图模型上设置为 true,则将选择该视图模型树中的节点。并且同样与扩展。
If you don't have a view-model for each item in the tree, well, then you should get one. Not having a view-model means that you are using your model objects as view-models, but for this to work these objects require an IsSelected
property.
如果树中的每个项目都没有视图模型,那么您应该获得一个。没有视图模型意味着您将模型对象用作视图模型,但要使其工作,这些对象需要一个IsSelected
属性。
To expose an SelectedItem
property on your parent view-model (the one you bind to the TreeView
and that has a collection of child view-models) you can implement it like this:
要SelectedItem
在您的父视图模型(您绑定到TreeView
并且具有子视图模型集合的那个)上公开一个属性,您可以像这样实现它:
public ChildViewModel SelectedItem {
get { return Items.FirstOrDefault(i => i.IsSelected); }
}
If you don't want to track selection on each individual item on the tree you can still use the SelectedItem
property on the TreeView
. However, to be able to do it "MVVM style" you need to use a Blend behavior (available as various NuGet packages - search for "blend interactivity").
如果你不希望跟踪在树中的每个单独的项目选择,你仍然可以使用SelectedItem
的财产TreeView
。但是,为了能够做到“MVVM 风格”,您需要使用混合行为(可用作各种 NuGet 包 - 搜索“混合交互性”)。
Here I have added an EventTrigger
that will invoke a command each time the selected item changes in the tree:
在这里,我添加了一个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>
You will have to add a property SetSelectedItemCommand
on the DataContext
of the TreeView
returning an ICommand
. When the selected item of the tree view changes the Execute
method on the command is called with the selected item as the parameter. The easiest way to create a command is probably to use a DelegateCommand
(google it to get an implementation as it is not part of WPF).
你将不得不增加一个属性SetSelectedItemCommand
上DataContext
的TreeView
返回一个ICommand
。当树视图的选定项更改时,Execute
将使用选定项作为参数调用命令上的方法。创建命令的最简单方法可能是使用DelegateCommand
(google it 以获取实现,因为它不是 WPF 的一部分)。
A perhaps better alternative that allows two-way binding without the clunky command is to use BindableSelectedItemBehaviorprovided by Steve Greatrex here on Stack Overflow.
允许在没有笨拙命令的情况下进行双向绑定的一个更好的替代方法是使用BindableSelectedItemBehavior由 Steve Greatrex 在 Stack Overflow 上提供。
回答by H.B.
I would probably use the SelectedItemChanged
event to set a respective property on your VM.
我可能会使用该SelectedItemChanged
事件在您的 VM 上设置相应的属性。
回答by daspn
Based on Martin's answer I made a simple application showing how to apply the proposed solution.
基于 Martin 的回答,我做了一个简单的应用程序,展示了如何应用所提出的解决方案。
The sample code uses the Cinch V2 framework to support MVVM but it can be easily changed to use the framework of you preference.
示例代码使用 Cinch V2 框架来支持 MVVM,但可以轻松更改以使用您喜欢的框架。
For those interested, here is the codeon GitHub
Hope it helps.
希望能帮助到你。
回答by melodiouscode
Somewhat late to the party but for those who are coming across this now, my solution was:
聚会有点晚了,但对于那些现在遇到这个问题的人,我的解决方案是:
- Add a reference to 'System.Windows.Interactivity'
- Add the following code into your treeview element.
<i:Interaction.Triggers> <i:EventTrigger EventName="SelectedItemChanged"> <i:InvokeCommandAction Command="{Binding SomeICommand}" CommandParameter="{Binding ElementName=treeviewName, Path=SelectedItem}" /> </i:EventTrigger> </i:Interaction.Triggers>
- 添加对“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>
This will allow you to use a standard MVVM ICommand binding to access the SelectedItem without having to use code behind or some long winded work around.
这将允许您使用标准的 MVVM ICommand 绑定来访问 SelectedItem,而无需使用隐藏代码或一些冗长的工作。
回答by Mr.CQ
Also late to the party but as an alternative for MVVMLight users:
也迟到了,但作为 MVVMLight 用户的替代方案:
- Bind the TreeViewItem to your ViewModel to get changes of the IsSelected property.
- Create a MVVMLight Message (like PropertyChangeMessage) sending the SelectedItem ViewModel or Model item
- Register your TreeView host (or some other ViemModels if necessary) to listen for this message.
- 将 TreeViewItem 绑定到您的 ViewModel 以获取 IsSelected 属性的更改。
- 创建发送 SelectedItem ViewModel 或 Model 项目的 MVVMLight 消息(如 PropertyChangeMessage)
- 注册您的 TreeView 主机(或其他一些 ViemModel,如有必要)以侦听此消息。
The whole implementation is very fast and works fine.
整个实现非常快并且工作正常。
Here the IsSelected Property (SourceItem is the Model part of the selected ViewModel item):
这里是 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
and here the VM host code:
这里是 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