背景
我对一个treeview使用了数据模板
<TreeView.ItemTemplate>
<!--子项的绑定-->
<HierarchicalDataTemplate DataType="{x:Type local_md:ToolsNodeItem}" ItemsSource="{Binding Path=Children}">
<StackPanel Orientation="Horizontal" Background="Transparent">
<Image Source="{Binding Icon}" Width="20" Margin="0,0,10,0"/>
<TextBlock Text="{Binding DisplayName}" VerticalAlignment="Center"/>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
数据源的数据结构为:
public class ToolsNodeItem
{
public string Icon { get; set; }
public string DisplayName { get; set; }
//Tree的绑定源就要这样套娃!
public ObservableCollection<ToolsNodeItem> Children { get; set; }
public ToolsNodeItem()
{
Children = new ObservableCollection<ToolsNodeItem>();
}
}
完成后大概就是这个样子
现在我的需求是,右键点击这些节点,弹出一个右键菜单然后对节点进行操作。比如删除这个节点。
弹出菜单不难:(加上这段就行)
<StackPanel.ContextMenu>
<ContextMenu>
<MenuItem Header="删除" Command="{Binding Path=DataContext.MenuItemCmd, Source={x:Reference Name=top_uc}}" CommandParameter="删除"/>
<MenuItem Header="上移" Command="{Binding Path=DataContext.MenuItemCmd, Source={x:Reference Name=top_uc}}" CommandParameter="上移"/>
<MenuItem Header="下移" Command="{Binding Path=DataContext.MenuItemCmd, Source={x:Reference Name=top_uc}}" CommandParameter="下移"/>
</ContextMenu>
</StackPanel.ContextMenu>
现在问题来了,因为是右键菜单,你会发现右键单击后,菜单是弹出来了,但是此时节点并没有被选中,所以此时即使你为treeview设置了PreviewMouseLeftButtonDown这些事件,然后查看sender(treeview)发现,treeview的SelectItem依然是null,这样就无法确认到底是那个item别选中,就没办法对item操作。所以首先要想办法让右键能选中控件。
于是我需要将模板中的StackPanel添加鼠标右键事件:下面这种是MVVM的写法!
<TreeView.ItemTemplate>
<!--子项的绑定-->
<HierarchicalDataTemplate DataType="{x:Type local_md:ToolsNodeItem}" ItemsSource="{Binding Path=Children}">
<StackPanel Orientation="Horizontal" Background="Transparent">
<i:Interaction.Triggers>
<i:EventTrigger EventName="PreviewMouseRightButtonDown">
<i:InvokeCommandAction Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.SelectTreeNodeCommand}" CommandParameter="{Binding}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<StackPanel.ContextMenu>
<ContextMenu>
<MenuItem Header="删除" Command="{Binding Path=DataContext.MenuItemCmd, Source={x:Reference Name=top_uc}}" CommandParameter="删除"/>
<MenuItem Header="上移" Command="{Binding Path=DataContext.MenuItemCmd, Source={x:Reference Name=top_uc}}" CommandParameter="上移"/>
<MenuItem Header="下移" Command="{Binding Path=DataContext.MenuItemCmd, Source={x:Reference Name=top_uc}}" CommandParameter="下移"/>
</ContextMenu>
</StackPanel.ContextMenu>
<Image Source="{Binding Icon}" Width="20" Margin="0,0,10,0"/>
<TextBlock Text="{Binding DisplayName}" VerticalAlignment="Center"/>
<StackPanel.ToolTip>
<TextBlock Text="{Binding TypeName}"/>
</StackPanel.ToolTip>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
这里需要说明一下:以前,我都是这么写的:
<i:EventTrigger EventName="PreviewMouseRightButtonDown">
<prism:InvokeCommandAction Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.SelectTreeNodeCommand}"/>
</i:EventTrigger>
这样写,命令接收的参数就是我们平时使用事件的e的内容,但是由于我们用到了数据模板,导致e中的内容是和你点击的地方不同而不同的:
如果你点击的位置是TreeViewItem的图片位置,那么这里的OriginalSource以及Source就是Image,如果你点到的是文字位置对应的就是TextBlock。如果不使用数据模板的话,我们得到的其实就是TreeViewItem,然后使用TreeViewItem对象,调用Forks就好了。
用了数据版本就没法拿到TreeViewItem吗?
于是我咨询了一下老师:
目前我认为,用了数据版本就基本没办法拿到TreeViewItem了。
于是就有了:
<i:InvokeCommandAction Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.SelectTreeNodeCommand}" CommandParameter="{Binding}" />
有了它,也拿不到TreeViewItem,但是得到了ToolsNodeItem,就是TreeViewItem对应的数据源的子项:(不管你点击TreeViewItem的哪个位置都一样)
ItemContainerStyle
回想到老师的这句话:
我拿不到TreeViewItem但是我可以,绑定TreeViewItem的IsSelected属性啊!但是数据模板不是TreeViewItem啊?
这就要看 ItemContainerStyle 这个样式了,它规定了子项的样式,而该样式就可以设置属性IsSelected!
这个几个容易混淆的概念,大家可以对比一下:
于是,我又在ToolsNodeItem模型中增加了一个属性IsSelected
private bool isSelected =false;
public bool IsSelected
{
get { return isSelected; }
set { SetProperty(ref isSelected, value); }
}
前台代码添加部分:
BasedOn="{StaticResource {x:Type TreeViewItem}}"
这里的BasedOn是为了继续使用handcontrol的样式,覆盖默认的样式。
数据驱动界面
最后将ToolsNodeItem中的IsSelected设置为True,这样就能通过数据IsSelected,驱动界面TreeViewItem的IsSelected为True,从而选中。
所以使用了数据模板之后,就要将 数据驱动界面 进行到底。
番外
其实,用了模板后,有一种方法能拿到TreeViewItem
不过也是,选中之后的事情了所以,不适合当前背景需求。