Виберіть TreeView Node клацанням правою кнопкою миші перед відображенням ContextMenu


100

Я хотів би вибрати WPF TreeView Node клацанням правою кнопкою миші, безпосередньо перед відображенням ContextMenu.

Для WinForms я можу використовувати такий код, як цей вузол Find, натиснутий у контекстному меню , які альтернативи WPF?

Відповіді:


130

Залежно від способу заселення дерева значення відправника та 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;
}

це подія для TreeView чи TreeViewItem?
Луї Ріс

1
будь-яка ідея, як зняти все, якщо клацнути правою кнопкою миші порожнє місце?
Луї Ріс

Єдина відповідь, яка допомогла з 5 інших ... Я справді щось не так роблю з населенням дерева, дякую.

3
Відповідаючи на запитання Луї Ріса: if (treeViewItem == null) treeView.SelectedIndex = -1або treeView.SelectedItem = null. Я вважаю, що або треба працювати.
Джеймс М

24

Якщо ви хочете вирішити лише 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. The TreeViewItem.IsSelectedВластивість прив'язаний до IsSelectedвласності на вигляд-моделі. Якщо встановити IsSelectedвластивість на вигляд-моделі на істинне, вибере відповідний вузол у дереві.

  2. При PreviewMouseRightButtonDownпожежі на візуальній частині вузла (у цьому зразку а TextBlock) IsSelectedвластивість у вигляд-моделі встановлюється на істину. Повернувшись до 1., можна побачити, що відповідний вузол, на який було натиснуто дерево, стає вибраним вузлом.

Один із способів отримати суміш інтерактивності у своєму проекті - використовувати пакет NuGet Unofficial.Blend.Interactivity .


2
Чудова відповідь, дякую! Було б корисно показати, на що вирішуються відображення 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 відповідно.
prlc

Це не допомогло, оскільки ChangePropertyActionце намагається встановити IsSelectedвластивість зв'язаного об'єкта даних, який не є частиною інтерфейсу користувача, тому він не має IsSelectedвластивості. Я щось роблю не так?
Антонін Прочазка

@ AntonínProcházka: Моя відповідь вимагає, щоб ваш "об'єкт даних" (або модель перегляду) мав IsSelectedвластивість, як зазначено у другому абзаці моєї відповіді: Припустимо, TreeViewце дані, пов'язані з ієрархічною колекцією моделей перегляду, що мають булеві властивостіIsSelected ... (мій акцент).
Мартін Ліверсанс

16

Використання "item.Focus ();" не працює на 100%, використовуючи "item.IsSelected = true;" робить.


Дякую за цю пораду. Допомогли мені.
i8abug

Гарна порада. Спочатку я закликаю Focus (), а потім встановлюю IsSelected = true.
Джим Гомес

12

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

2
Це не працює, як очікувалося, я завжди отримую кореневий елемент як відправник. Я знайшов подібне рішення, одне з обробників подій додало, що так працює, як і очікувалося, один social.msdn.microsoft.com/Forums/en-US/wpf/thread/… Будь-які зміни у вашому коді, щоб прийняти його? :-)
alex2k8

Мабуть, це залежить від того, як ви заповнюєте вигляд дерева. Код, який я розмістив, працює, тому що це саме той код, який я використовую в одному зі своїх інструментів.
Стефан

Зверніть увагу, якщо ви встановите точку налагодження тут, ви можете побачити тип вашого відправника, який, звичайно, відрізнятиметься від того, як ви налаштуєте дерево

Це здається найпростішим рішенням, коли воно працює. Це працювало для мене. Насправді вам слід просто подати відправника як TreeViewItem, тому що якщо це не так, це помилка.
craftworkgames

12

Використовуючи оригінальну ідею від alex2k8, правильну обробку невізуальних засобів від Wieser Software Ltd, XAML від Stefan, IsSelected from 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>

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 повернув нульове значення, коли LogicalTreeHelper повернув значення, так виправив це.


1
Щоб відповісти цьому, ця відповідь реалізує це в розширенні DependencyProperty: stackoverflow.com/a/18032332/84522
Terrence

7

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

цей загальний метод видається дещо дивним, як я можу ним користуватися, коли пишу TreeViewItem treeViewItem = VisualUpwardSearch <TreeViewItem> (напр. OrigSource як DependencyObject); це дає мені помилку конверсії
Rati_Ge

TreeViewItem treeViewItem = VisualUpwardSearch <TreeViewItem> (e.OriginalSource як DependencyObject) як TreeViewItem;
Ентоні Візер

6

Я думаю, що реєстрація обробника класу повинна зробити свою справу. Просто зареєструйте маршрутизований обробник подій у 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;
    }
}

Працювали для мене! І просто теж.
dvallejo

2
Привіт Натане. Звучить, що код глобальний і вплине на кожен TreeView. Не було б краще мати рішення, яке є лише локальним? Це може створити побічні ефекти?
Ерік Оуеллет

Цей код справді глобальний для всієї програми WPF. У моєму випадку це вимагало поведінки, тому воно відповідало всім переглядам дерев, які використовуються в програмі. Однак ви можете зареєструвати цю подію в самому екземплярі treeview, щоб він застосовний лише для цього перегляду дерева.
Натан Суоннет

2

Ще один спосіб вирішити це за допомогою 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;
        }
    }

1

У мене виникли проблеми з вибором дітей методом 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. Оскільки дитина є першою, яку викликають. Якщо встановити цю властивість на істинне, вимкніть інші виклики батьків.
Базіт Анвер

0

Ви можете вибрати його за допомогою події вниз. Це призведе до вибору до початку контекстного меню.


0

Якщо ви хочете залишитися у межах 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>
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.