Я хотел бы выбрать узел 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
. Я считаю, что оба варианта должны работать.
- person ; 29.02.2016
Если вам нужно решение только для 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
запускается в визуальной части узла (в этом примере 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 соответственно .
- person prlc; 23.02.2015
ChangePropertyAction
пытается установить свойство IsSelected
привязанного объекта данных, который не является частью пользовательского интерфейса, поэтому у него нет свойства IsSelected
. Я делаю что-то неправильно?
- person Antonín Procházka; 24.03.2017
IsSelected
, как указано во втором абзаце моего ответа: Предположим, что TreeView
- это данные, привязанные к иерархической коллекции моделей представления, имеющих Логическое свойство IsSelected
... (выделено мной).
- person Martin Liversage; 26.03.2017
Использование «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 возвращал значение, поэтому это исправлено.
Почти правильно, но вам нужно чтобы следить за невизуальными элементами в дереве (например, 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, другие вызовы родительского элемента будут отключены.
- person Basit Anwer; 25.03.2019
Вы можете выбрать его, нажав кнопку мыши. Это вызовет выбор до того, как появится контекстное меню.
Если вы хотите оставаться в рамках шаблона 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>