我想在显示 ContextMenu 之前右键单击选择一个 WPF TreeView 节点。
对于 WinForms,我可以使用类似Find node clicked under context menu的代码,有哪些 WPF 替代方案?
我想在显示 ContextMenu 之前右键单击选择一个 WPF TreeView 节点。
对于 WinForms,我可以使用类似Find node clicked under context menu的代码,有哪些 WPF 替代方案?
根据填充树的方式,发送者和 e.Source 值可能会有所不同。
一种可能的解决方案是使用 e.OriginalSource 并使用 VisualTreeHelper 查找 TreeViewItem:
private void OnPreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
TreeViewItem treeViewItem = VisualUpwardSearch(e.OriginalSource as DependencyObject);
if (treeViewItem != null)
{
treeViewItem.Focus();
e.Handled = true;
}
}
static TreeViewItem VisualUpwardSearch(DependencyObject source)
{
while (source != null && !(source is TreeViewItem))
source = VisualTreeHelper.GetParent(source);
return source as TreeViewItem;
}
如果您想要一个仅 XAML 的解决方案,您可以使用 Blend Interactivity。
假设TreeView
数据绑定到视图模型的分层集合,该集合具有一个Boolean
属性IsSelected
和一个String
属性Name
以及一个名为的子项集合Children
。
<TreeView ItemsSource="{Binding Items}">
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Name}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="PreviewMouseRightButtonDown">
<ei:ChangePropertyAction PropertyName="IsSelected" Value="true" TargetObject="{Binding}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBlock>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
有两个有趣的部分:
该TreeViewItem.IsSelected
属性绑定到IsSelected
视图模型上的属性。将视图模型上的属性设置IsSelected
为 true 将选择树中的相应节点。
当PreviewMouseRightButtonDown
在节点的可视部分(在此示例中为 a TextBlock
)触发时IsSelected
,视图模型上的属性设置为 true。回到 1. 你可以看到在树中被点击的对应节点变成了被选中的节点。
在项目中获得 Blend Interactivity 的一种方法是使用 NuGet 包Unofficial.Blend.Interactivity。
使用“item.Focus();” 使用“item.IsSelected = true;”似乎不能 100% 工作 做。
在 XAML 中,在 XAML 中添加 PreviewMouseRightButtonDown 处理程序:
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<!-- We have to select the item which is right-clicked on -->
<EventSetter Event="TreeViewItem.PreviewMouseRightButtonDown" Handler="TreeViewItem_PreviewMouseRightButtonDown"/>
</Style>
</TreeView.ItemContainerStyle>
然后像这样处理事件:
private void TreeViewItem_PreviewMouseRightButtonDown( object sender, MouseEventArgs e )
{
TreeViewItem item = sender as TreeViewItem;
if ( item != null )
{
item.Focus( );
e.Handled = true;
}
}
使用来自 alex2k8 的原始想法,正确处理来自 Wieser Software Ltd 的非视觉对象、来自 Stefan 的 XAML、来自 Erlend 的 IsSelected,以及我对真正使静态方法通用的贡献:
XAML:
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<!-- We have to select the item which is right-clicked on -->
<EventSetter Event="TreeViewItem.PreviewMouseRightButtonDown"
Handler="TreeViewItem_PreviewMouseRightButtonDown"/>
</Style>
</TreeView.ItemContainerStyle>
后面的 C# 代码:
void TreeViewItem_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
TreeViewItem treeViewItem =
VisualUpwardSearch<TreeViewItem>(e.OriginalSource as DependencyObject);
if(treeViewItem != null)
{
treeViewItem.IsSelected = true;
e.Handled = true;
}
}
static T VisualUpwardSearch<T>(DependencyObject source) where T : DependencyObject
{
DependencyObject returnVal = source;
while(returnVal != null && !(returnVal is T))
{
DependencyObject tempReturnVal = null;
if(returnVal is Visual || returnVal is Visual3D)
{
tempReturnVal = VisualTreeHelper.GetParent(returnVal);
}
if(tempReturnVal == null)
{
returnVal = LogicalTreeHelper.GetParent(returnVal);
}
else returnVal = tempReturnVal;
}
return returnVal as T;
}
编辑:以前的代码在这种情况下总是可以正常工作,但在另一种情况下,当 LogicalTreeHelper 返回值时 VisualTreeHelper.GetParent 返回 null,因此修复了该问题。
几乎正确,但您需要注意树中的非视觉对象(例如 a Run
)。
static DependencyObject VisualUpwardSearch<T>(DependencyObject source)
{
while (source != null && source.GetType() != typeof(T))
{
if (source is Visual || source is Visual3D)
{
source = VisualTreeHelper.GetParent(source);
}
else
{
source = LogicalTreeHelper.GetParent(source);
}
}
return source;
}
我认为注册一个类处理程序应该可以解决问题。只需在 app.xaml.cs 代码文件中的 TreeViewItem 的 PreviewMouseRightButtonDownEvent 上注册一个路由事件处理程序,如下所示:
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
EventManager.RegisterClassHandler(typeof(TreeViewItem), TreeViewItem.PreviewMouseRightButtonDownEvent, new RoutedEventHandler(TreeViewItem_PreviewMouseRightButtonDownEvent));
base.OnStartup(e);
}
private void TreeViewItem_PreviewMouseRightButtonDownEvent(object sender, RoutedEventArgs e)
{
(sender as TreeViewItem).IsSelected = true;
}
}
使用 MVVM 解决它的另一种方法是右键单击视图模型的绑定命令。在那里您可以指定其他逻辑以及source.IsSelected = true
. 这仅使用xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
from System.Windows.Interactivity
。
用于查看的 XAML:
<TreeView ItemsSource="{Binding Items}">
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Name}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="PreviewMouseRightButtonDown">
<i:InvokeCommandAction Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.TreeViewItemRigthClickCommand}" CommandParameter="{Binding}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBlock>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
查看型号:
public ICommand TreeViewItemRigthClickCommand
{
get
{
if (_treeViewItemRigthClickCommand == null)
{
_treeViewItemRigthClickCommand = new RelayCommand<object>(TreeViewItemRigthClick);
}
return _treeViewItemRigthClickCommand;
}
}
private RelayCommand<object> _treeViewItemRigthClickCommand;
private void TreeViewItemRigthClick(object sourceItem)
{
if (sourceItem is Item)
{
(sourceItem as Item).IsSelected = true;
}
}
我在使用 HierarchicalDataTemplate 方法选择子项时遇到问题。如果我选择了一个节点的子节点,它会以某种方式选择该子节点的根父节点。我发现 MouseRightButtonDown 事件会在孩子所在的每个级别都被调用。例如,如果你有一棵这样的树:
项目 1
- 子项 1 - 子项 2
-子项 1
-子项
2
如果我选择了 Subitem2,该事件将触发 3 次,并且将选择项目 1。我用布尔值和异步调用解决了这个问题。
private bool isFirstTime = false;
protected void TaskTreeView_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
var item = sender as TreeViewItem;
if (item != null && isFirstTime == false)
{
item.Focus();
isFirstTime = true;
ResetRightClickAsync();
}
}
private async void ResetRightClickAsync()
{
isFirstTime = await SetFirstTimeToFalse();
}
private async Task<bool> SetFirstTimeToFalse()
{
return await Task.Factory.StartNew(() => { Thread.Sleep(3000); return false; });
}
感觉有点笨拙,但基本上我在第一次通过时将布尔值设置为 true,并在几秒钟内将其重置在另一个线程上(在本例中为 3)。这意味着它将跳过它试图向上移动的下一个路径,从而使您选择正确的节点。到目前为止它似乎有效:-)
您可以使用鼠标按下事件来选择它。这将在上下文菜单启动之前触发选择。
如果您想留在 MVVM 模式中,您可以执行以下操作:
看法:
<TreeView x:Name="trvName" ItemsSource="{Binding RootElementListView}" Tag="{Binding ClickedTreeElement, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type models:YourTreeElementClass}" ItemsSource="{Binding Path=Subreports}">
<TextBlock Text="{Binding YourTreeElementDisplayProperty}" PreviewMouseRightButtonDown="TreeView_PreviewMouseRightButtonDown"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
代码背后:
private void TreeView_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
if (sender is TextBlock tb && tb.DataContext is YourTreeElementClass te)
{
trvName.Tag = te;
}
}
视图模型:
private YourTreeElementClass _clickedTreeElement;
public YourTreeElementClass ClickedTreeElement
{
get => _clickedTreeElement;
set => SetProperty(ref _clickedTreeElement, value);
}
现在,您可以对 ClickedTreeElement 属性更改做出反应,也可以使用在内部与 ClickedTreeElement 配合使用的命令。
扩展视图:
<UserControl ...
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity">
<TreeView x:Name="trvName" ItemsSource="{Binding RootElementListView}" Tag="{Binding ClickedTreeElement, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseRightButtonUp">
<i:InvokeCommandAction Command="{Binding HandleRightClickCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type models:YourTreeElementClass}" ItemsSource="{Binding Path=Subreports}">
<TextBlock Text="{Binding YourTreeElementDisplayProperty}" PreviewMouseRightButtonDown="TreeView_PreviewMouseRightButtonDown"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</UserControl>