Я хотел бы выбрать узел WPF TreeView правой кнопкой мыши, прямо перед отображением ContextMenu.
Для WinForms я мог бы использовать такой код, как этот узел поиска, который был нажат в контекстном меню , каковы альтернативы WPF?
Я хотел бы выбрать узел WPF TreeView правой кнопкой мыши, прямо перед отображением ContextMenu.
Для WinForms я мог бы использовать такой код, как этот узел поиска, который был нажат в контекстном меню , каковы альтернативы WPF?
Ответы:
В зависимости от того, как было заполнено дерево, значения отправителя и e.Source могут различаться .
Одно из возможных решений - использовать 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;
}
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
собственности на вид-модели. Установка для IsSelected
свойства модели представления значения true выберет соответствующий узел в дереве.
Когда PreviewMouseRightButtonDown
срабатывает визуальная часть узла (в этом примере a TextBlock
), IsSelected
свойство модели представления устанавливается в значение true. Вернувшись к 1., вы увидите, что соответствующий узел, на котором щелкнули в дереве, становится выбранным узлом.
Один из способов добиться интерактивности Blend в вашем проекте - использовать пакет 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
свойство связанного объекта данных, который не является частью пользовательского интерфейса, поэтому у него нет IsSelected
свойства. Я делаю что-то неправильно?
IsSelected
свойство, как указано во втором абзаце моего ответа: Предположим, что TreeView
это данные привязаны к иерархической коллекции моделей представления, имеющих логическое свойствоIsSelected
... (курсив мой).
Использование «item.Focus ();» похоже, не работает на 100%, используя "item.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 от Стефана, IsSelected от Эрленда и мой вклад в создание статического метода Generic:
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;
}
Изменить: предыдущий код всегда работал нормально для этого сценария, но в другом сценарии VisualTreeHelper.GetParent возвращал значение null, когда LogicalTreeHelper возвращал значение, поэтому это исправлено.
Почти верно , но вам нужно остерегаться невизуальных элементов в дереве (например, 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;
}
Я думаю, что регистрация обработчика класса должна помочь. Просто зарегистрируйте перенаправленный обработчик событий в PreviewMouseRightButtonDownEvent TreeViewItem в файле кода app.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;
}
}
У меня возникла проблема с выбором детей с помощью метода HierarchicalDataTemplate. Если бы я выбрал дочерний элемент узла, он каким-то образом выбрал бы корневого родителя этого дочернего элемента. Я обнаружил, что событие MouseRightButtonDown будет вызываться для каждого уровня, на котором находится ребенок. Например, если у вас есть такое дерево:
Элемент 1
- Ребенок 1
- Ребенок 2
- Подпункт1
- Подпункт2
Если бы я выбрал Subitem2, событие сработало бы три раза, и был бы выбран элемент 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;
}
}
ViewModel:
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>