【发布时间】:2009-02-26 20:49:13
【问题描述】:
我想在右键单击时选择一个 WPF TreeView 节点,就在显示的 ContextMenu 之前。
对于 WinForms,我可以使用 Find node clicked under context menu 这样的代码,WPF 的替代方案是什么?
【问题讨论】:
标签: wpf treeview contextmenu
我想在右键单击时选择一个 WPF TreeView 节点,就在显示的 ContextMenu 之前。
对于 WinForms,我可以使用 Find node clicked under context menu 这样的代码,WPF 的替代方案是什么?
【问题讨论】:
标签: wpf treeview contextmenu
取决于树的填充方式,the sender and the e.Source values may vary。
一种可能的解决方案是使用 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;
}
【讨论】:
if (treeViewItem == null) treeView.SelectedIndex = -1 或 treeView.SelectedItem = null。我相信两者都应该有效。
如果您想要一个仅限 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 属性。将 view-model 上的 IsSelected 属性设置为 true 将选择树中的相应节点。
当PreviewMouseRightButtonDown 在节点的可视部分(在此示例中为TextBlock)触发时,视图模型上的IsSelected 属性设置为true。回到1.可以看到树中被点击的对应节点成为选中节点。
在您的项目中获得 Blend Interactivity 的一种方法是使用 NuGet 包Unofficial.Blend.Interactivity。
【讨论】:
i 和 ei 命名空间映射解析为什么以及可以在哪些程序集中找到它们会很有帮助。我假设:xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" 和 xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions",它们位于 System.Windows.Interactivity和 Microsoft.Expression.Interactions 程序集。
ChangePropertyAction 试图设置绑定数据对象的 IsSelected 属性,这不是 UI 的一部分,因此它没有 IsSelected 属性.我做错了吗?
IsSelected 属性,如我的回答第二段所述:假设TreeView 是数据绑定到具有 布尔属性 IsSelected... 的视图模型的分层集合(我的重点)。
使用“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,因此修复了该问题。
【讨论】:
Almost Right,但您需要注意树中的非视觉对象(例如 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。
这仅使用来自System.Windows.Interactivity 的xmlns:i="http://schemas.microsoft.com/expression/2010/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)。这意味着下一次通过它试图向上移动的树将被跳过,让您选择正确的节点。到目前为止它似乎有效:-)
【讨论】:
MouseButtonEventArgs.Handled设置为true。因为孩子是第一个被召唤的。将此属性设置为 true 将禁用对父级的其他调用。
您可以使用鼠标按下事件来选择它。这将在上下文菜单启动之前触发选择。
【讨论】:
如果您想保持 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>
【讨论】: