Выберите узел TreeView при щелчке правой кнопкой мыши перед отображением ContextMenu
Я хотел бы выбрать узел WPF TreeView по щелчку правой кнопкой мыши, прямо перед отображением ContextMenu.
для WinForms я мог бы использовать такой код найти узел щелкнул в контекстном меню, каковы альтернативы WPF?
10 ответов:
в зависимости от способа заполнения дерева,отправитель и E. исходные значения могут отличаться.
одним из возможных решений является использование e.OriginalSource и поиск TreeViewItem с помощью VisualTreeHelper:
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.
предположим
TreeView
данные привязаны к иерархической коллекции view-моделей, имеющих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>
есть две интересные детали:
The
TreeViewItem.IsSelected
свойство привязано кIsSelected
свойство модели представления. УстановкаIsSelected
свойство view-model to true выделит соответствующий узел в дереве., когда
PreviewMouseRightButtonDown
срабатывает на визуальной части узла (в этом примере aTextBlock
) theIsSelected
свойство на view-model имеет значение true. Возвращаясь к 1. вы можете видеть, что соответствующий узел, который был нажат в дереве, становится выбранным узлом.один из способов получить интерактивность Blend в вашем проекте-использовать пакет NuGet неофициальный.Смешение.Интерактивность.
использование "элемента.Focus ();" не работает на 100%, используя "элемент.IsSelected = true; " делает.
в XAML добавьте обработчик PreviewMouseRightButtonDown в 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>
затем обработайте событие следующим образом:
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, XAML от Stefan, IsSelected от Erlend и мой вклад в то, чтобы действительно сделать статический метод универсальным:
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; }
Edit: предыдущий код всегда работал нормально для этого сценария, но в другом сценарии VisualTreeHelper.GetParent возвращает null, когда LogicalTreeHelper возвращает значение, поэтому исправлено что.
Почти Правильно, но вы должны следить за не визуальные эффекты в дереве, (как
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; }
Я думаю, что регистрация обработчика класса должна сделать трюк. Просто зарегистрируйте обработчик перенаправленных событий на PreviewMouseRightButtonDownEvent TreeViewItem в своем приложении.код XAML.cs код файла, как это:
/// <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"
С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; } }
вы можете выбрать его с помощью события on mouse down. Это вызовет выбор перед запуском контекстного меню.
У меня была проблема с выбором детей с помощью метода HierarchicalDataTemplate. Если бы я выбрал дочерний узел, он каким-то образом выбрал бы корневого родителя этого ребенка. Я узнал, что событие MouseRightButtonDown будет вызвано для каждого уровня, на котором был ребенок. Например, если у вас есть дерево, что-то вроде этого:
пункт 1
Ребенку 1
Ребенку 2
- Subitem1
- Subitem2Если я выбрал Подпункт 2 событие будет срабатывать три раза, и будет выбран пункт 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 в этом случае). Это означает, что следующий проходит через то, где он будет пытаться двигаться вверх по дереву, будет пропущен, оставив вас с правильным выбранным узлом. Это, кажется, работает до сих пор: -)