【wpf】 当用了数据模板之后如何获取控件的Item?

背景

 我对一个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!

这个几个容易混淆的概念,大家可以对比一下:

这些都是,wpf给我提供的接口,“入侵” 控件内部的“密码”,只有搞清楚这些,我们才能更好的实现数据驱动界面!将bingding用的更加的灵活。

于是,我又在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,从而选中。

所以使用了数据模板之后,就要将 数据驱动界面 进行到底。

结果展示

 实现了数据绑定之后,上下移动也是很简单的,移动数据源中的数据即可:

 private void WorkflowModify(string para)
 {
     var toolsNodeItem = workflowTreeViewObj.SelectedItem as ToolsNodeItem;
     if (toolsNodeItem != null)
     {
         var cur_index = WorkflowViewToolsTree.IndexOf(toolsNodeItem);
         if (para == "删除")
         {
             WorkflowViewToolsTree.Remove(toolsNodeItem);
         }
         else if (para == "上移")
         {
             cur_index = WorkflowViewToolsTree.IndexOf(toolsNodeItem);
             if (cur_index != 0)
             {
                 WorkflowViewToolsTree.Move(cur_index, cur_index-1);
             }
             else
             {
                 MessageBox.Show("到顶了");
             }
             
         }
         else if (para == "下移")
         {
             if (cur_index != WorkflowViewToolsTree.Count-1)
             {
                 WorkflowViewToolsTree.Move(cur_index, cur_index + 1);
             }
             else
             {
                 MessageBox.Show("到地板了");
             }
         }
     }
 }

番外

其实,用了模板后,有一种方法能拿到TreeViewItem

 不过也是,选中之后的事情了所以,不适合当前背景需求。

posted @ 2022-12-01 21:36  宋桓公  阅读(196)  评论(0编辑  收藏  举报