Выберите узел TreeView, щелкнув правой кнопкой мыши перед отображением ContextMenu.

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

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


person alex2k8    schedule 26.02.2009    source источник


Ответы (11)


В зависимости от способа заполнения дерева отправителя и 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;
}
person alex2k8    schedule 27.02.2009
comment
это событие для TreeView или TreeViewItem? - person Louis Rhys; 20.11.2012
comment
Есть идеи, как отменить выбор всего, если щелчок правой кнопкой мыши находится в пустом месте? - person Louis Rhys; 21.11.2012
comment
Единственный ответ, который помог из 5 других ... Я действительно делаю что-то не так с древовидным населением, спасибо. - person ; 10.12.2015
comment
В ответ на вопрос Луи Риса: 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>

Есть две интересные части:

  1. Свойство TreeViewItem.IsSelected привязано к свойству IsSelected в модели представления. Установка для свойства IsSelected в модели представления значения true выберет соответствующий узел в дереве.

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

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

person Martin Liversage    schedule 15.01.2013
comment
Отличный ответ, спасибо! Было бы полезно показать, что разрешают сопоставления пространств имен 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
comment
Это не помогло, поскольку ChangePropertyAction пытается установить свойство IsSelected привязанного объекта данных, который не является частью пользовательского интерфейса, поэтому у него нет свойства IsSelected. Я делаю что-то неправильно? - person Antonín Procházka; 24.03.2017
comment
@ AntonínProcházka: Мой ответ требует, чтобы ваш объект данных (или модель представления) имел свойство IsSelected, как указано во втором абзаце моего ответа: Предположим, что TreeView - это данные, привязанные к иерархической коллекции моделей представления, имеющих Логическое свойство IsSelected ... (выделено мной). - person Martin Liversage; 26.03.2017

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

person Erlend    schedule 13.10.2009
comment
Спасибо за этот совет. Помог мне. - person i8abug; 02.11.2010
comment
Хороший совет. Сначала я вызываю Focus (), а затем устанавливаю IsSelected = true. - person Jim Gomes; 09.06.2015

В 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;
        }
    }
person Stefan    schedule 26.02.2009
comment
Это не работает должным образом, я всегда получаю корневой элемент как отправитель. Я нашел аналогичное решение social.msdn.microsoft.com/Forums/en-US/wpf/thread/ Добавленные таким образом обработчики событий работают должным образом. Какие-либо изменения в вашем коде, чтобы принять его? :-) - person alex2k8; 27.02.2009
comment
Очевидно, это зависит от того, как вы заполняете древовидное представление. Код, который я опубликовал, работает, потому что это точный код, который я использую в одном из своих инструментов. - person Stefan; 27.02.2009
comment
Обратите внимание: если вы установите здесь точку отладки, вы можете увидеть, какой тип вашего отправителя, который, конечно, будет отличаться в зависимости от того, как вы настроили дерево. - person ; 11.04.2013
comment
Это кажется самым простым решением, когда оно работает. У меня это сработало. Фактически, вы должны просто указать отправителя как TreeViewItem, потому что в противном случае это ошибка. - person craftworkgames; 08.11.2014

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

person Sean Hall    schedule 12.09.2012
comment
Для дальнейшего развития этот ответ реализует это в расширении DependencyProperty: stackoverflow.com/a/18032332/84522 - person Terrence; 31.10.2013

Почти правильно, но вам нужно чтобы следить за невизуальными элементами в дереве (например, 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; 
}
person Anthony Wieser    schedule 08.04.2011
comment
этот общий метод кажется немного странным, как я могу его использовать, когда я пишу TreeViewItem treeViewItem = VisualUpwardSearch ‹TreeViewItem› (e.OriginalSource as DependencyObject); это дает мне ошибку преобразования - person Rati_Ge; 17.07.2012
comment
TreeViewItem treeViewItem = VisualUpwardSearch ‹TreeViewItem› (e.OriginalSource как DependencyObject) как TreeViewItem; - person Anthony Wieser; 18.07.2012

Я думаю, что регистрация обработчика класса должна помочь. Просто зарегистрируйте перенаправленный обработчик событий в 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;
    }
}
person Nathan Swannet    schedule 07.10.2011
comment
Сработало у меня! И тоже просто. - person dvallejo; 22.05.2012
comment
Привет, Натан. Похоже, что код глобальный и повлияет на каждый TreeView. Разве не лучше было бы иметь только локальное решение? Это могло вызвать побочные эффекты? - person Eric Ouellet; 02.08.2013
comment
Этот код действительно глобален для всего приложения WPF. В моем случае это было обязательным поведением, поэтому оно было согласованным для всех древовидных представлений, используемых в приложении. Однако вы можете зарегистрировать это событие в самом экземпляре treeview, поэтому оно применимо только для этого treeview. - person Nathan Swannet; 23.05.2016

Другой способ решить эту проблему с помощью MVVM - это привязать команду для щелчка правой кнопкой мыши к вашей модели представления. Там вы можете указать другую логику, а также 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;
        }
    }
person benderto    schedule 22.03.2017

У меня возникла проблема с выбором детей с помощью метода 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). Это означает, что следующие проходы, через которые будет предпринята попытка продвижения вверх по дереву, будут пропущены, и вы останетесь с правильным выбранным узлом. Вроде пока работает :-)

person Zoey    schedule 27.09.2013
comment
Ответ - установить MouseButtonEventArgs.Handled в true. Так как ребенок будет вызван первым. Если для этого свойства установлено значение true, другие вызовы родительского элемента будут отключены. - person Basit Anwer; 25.03.2019

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

person Scott Thurlow    schedule 27.04.2012

Если вы хотите оставаться в рамках шаблона 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>
person RonnyR    schedule 07.11.2019