Выберите TreeView Node правой кнопкой мыши перед отображением ContextMenu

Я хотел бы выбрать WPF TreeView Node правой кнопкой мыши, прямо перед отображением ContextMenu.

Для WinForms я мог бы использовать такой код Найти Node в контекстном меню, каковы альтернативы WPF?

Ответ 1

В зависимости от того, как было заполнено дерево, отправитель и значения 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;
}

Ответ 2

Использование "item.Focus();" не работает на 100%, используя "item.IsSelected = true;" делает.

Ответ 3

Если вы хотите использовать только XAML-решение, вы можете использовать Интерактивность Blend.

Предположим, что 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 будет выбирать соответствующий node в дереве.

  • Когда PreviewMouseRightButtonDown срабатывает визуальная часть node (в этом примере a TextBlock), свойство IsSelected в представлении модели равно true. Возвращаясь к 1. вы можете видеть, что соответствующий node, который был нажат в дереве, становится выбранным node.

Одним из способов получения интерактивности Blend в вашем проекте является использование пакета NuGet Unofficial.Blend.Interactivity.

Ответ 4

В 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;
        }
    }

Ответ 5

Используя оригинальную идею от alex2k8, правильно обрабатывая невизуальные изображения от Wieser Software Ltd, XAML от Stefan, IsSelected от Erlend и мой вклад в истинное создание статического метода 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>

Код С# позади:

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 возвращал значение, поэтому исправил это.

Ответ 6

Почти правильно, но вам нужно следить за не визуальными эффектами в дереве (например, 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; 
}

Ответ 7

Я думаю, что регистрация обработчика класса должна делать трюк. Просто зарегистрируйте обработчик маршрутизируемого события в TreeViewItem PreviewMouseRightButtonDownEvent в файле кода 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;
    }
}

Ответ 8

Вы можете выбрать его с помощью события мыши. Это вызовет выбор до того, как начнется контекстное меню.

Ответ 9

У меня возникла проблема с выбором дочерних элементов методом HierarchicalDataTemplate. Если я выбрал дочерний элемент node, он каким-то образом выберет родительский корневой каталог этого дочернего элемента. Я узнал, что событие MouseRightButtonDown будет вызвано для каждого уровня, на котором был ребенок. Например, если у вас есть дерево примерно так:

Пункт 1
    - Ребенок 1
    - Ребенок 2
      - Subitem1
      - Subitem2

Если я выбрал 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; });
    }

Он чувствует себя немного cludgy, но в основном я устанавливаю логическое значение true на первом проходе и reset на другой поток за несколько секунд (в этом случае 3). Это означает, что следующий проход, через который он попытается продвинуться вверх по дереву, будет пропущен, оставив вас с выбранным node. Кажется, что работает до сих пор: -)

Ответ 10

Другим способом решения этой проблемы с помощью MVVM является команда bind для правого щелчка на вашей модели представления. Там вы можете указать другую логику, а также source.IsSelected = true. Это использует только xmlns:i="http://schemas.microsoft.com/expression/2010/intera‌​ctivity" от 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;
        }
    }