Wybierz węzeł TreeView prawym przyciskiem myszy przed wyświetleniem ContextMenu


Odpowiedzi:


130

W zależności od sposobu wypełnienia drzewa wartości nadawcy i e.Source mogą się różnić .

Jednym z możliwych rozwiązań jest użycie e.OriginalSource i znalezienie TreeViewItem przy użyciu 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;
}

czy to zdarzenie dotyczy TreeView czy TreeViewItem?
Louis Rhys,

1
Czy masz pomysł, jak odznaczyć wszystko, jeśli prawym przyciskiem myszy znajduje się puste miejsce?
Louis Rhys,

Jedyna odpowiedź, która pomogła z 5 innych ... Naprawdę robię coś złego z populacją drzewa, dzięki.

3
W odpowiedzi na pytanie Louisa Rhysa: if (treeViewItem == null) treeView.SelectedIndex = -1lub treeView.SelectedItem = null. Uważam, że jedno i drugie powinno działać.
James M

24

Jeśli potrzebujesz rozwiązania obsługującego tylko XAML, możesz użyć funkcji Blend Interactivity.

Załóżmy, że TreeViewdane is są powiązane z hierarchiczną kolekcją modeli widoków posiadających Booleanwłaściwość IsSelectedi Stringwłaściwość, Namea także kolekcję elementów podrzędnych o nazwie 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>

Istnieją dwie interesujące części:

  1. TreeViewItem.IsSelectedNieruchomość jest związana z IsSelectednieruchomości na widoku model. Ustawienie IsSelectedwłaściwości w modelu widoku na true spowoduje wybranie odpowiedniego węzła w drzewie.

  2. Po PreviewMouseRightButtonDownuruchomieniu na wizualnej części węzła (w tym przykładzie a TextBlock) IsSelectedwłaściwość modelu widoku jest ustawiona na wartość true. Wracając do 1. widać, że odpowiedni węzeł, który został kliknięty w drzewie, staje się węzłem wybranym.

Jednym ze sposobów uzyskania interaktywności mieszania w projekcie jest użycie pakietu NuGet Unofficial.Blend.Interactivity .


2
Świetna odpowiedź, dziękuję! Przydałoby się pokazanie, do czego są rozwiązywane mapowania ii eiprzestrzeni nazw i w jakich zestawach można je znaleźć. Zakładam: xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"i xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions", które znajdują się odpowiednio w zestawach System.Windows.Interactivity i Microsoft.Expression.Interactions.
prlc

To nie pomogło, ponieważ ChangePropertyActionpróbuje ustawić IsSelectedwłaściwość powiązanego obiektu danych, który nie jest częścią interfejsu użytkownika, więc nie ma IsSelectedwłaściwości. czy robię coś źle?
Antonín Procházka

@ AntonínProcházka: Moja odpowiedź wymaga, aby twój „obiekt danych” (lub model widoku) miał IsSelectedwłaściwość określoną w drugim akapicie mojej odpowiedzi: Załóżmy, że TreeViewdane są powiązane z hierarchicznym zbiorem modeli widoku posiadających właściwość logicznąIsSelected ... (moje podkreślenie).
Martin Liversage

16

Korzystanie z "item.Focus ();" wydaje się, że nie działa w 100%, używając "item.IsSelected = true;" robi.


Dzięki za tę wskazówkę. Pomogło mi.
i8abug

Dobra wskazówka. Najpierw wywołuję Focus (), a następnie ustawiam IsSelected = true.
Jim Gomes

12

W języku XAML dodaj program obsługi PreviewMouseRightButtonDown w języku XAML: In XAML, add a PreviewMouseRightButtonDown handler in 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>

Następnie obsłuż zdarzenie w następujący sposób:

    private void TreeViewItem_PreviewMouseRightButtonDown( object sender, MouseEventArgs e )
    {
        TreeViewItem item = sender as TreeViewItem;
        if ( item != null )
        {
            item.Focus( );
            e.Handled = true;
        }
    }

2
Nie działa zgodnie z oczekiwaniami, zawsze otrzymuję element główny jako nadawca. Znalazłem podobne rozwiązanie, jeden social.msdn.microsoft.com/Forums/en-US/wpf/thread/… Dodane w ten sposób programy obsługi zdarzeń działają zgodnie z oczekiwaniami. Jakieś zmiany w kodzie, aby go zaakceptować? :-)
alex2k8

Najwyraźniej zależy to od tego, jak zapełnisz widok drzewa. Kod, który opublikowałem, działa, ponieważ jest to dokładny kod, którego używam w jednym z moich narzędzi.
Stefan

Uwaga, jeśli ustawisz tutaj punkt debugowania, możesz zobaczyć, jakiego typu jest twój nadawca, który oczywiście będzie się różnić w zależności od tego, jak skonfigurujesz drzewo

Wydaje się, że to najprostsze rozwiązanie, gdy działa. U mnie to zadziałało. W rzeczywistości powinieneś po prostu rzutować sender jako TreeViewItem, ponieważ jeśli nie jest, jest to błąd.
craftworkgames

12

Korzystając z oryginalnego pomysłu z alex2k8, poprawnie obsługując elementy niewizualne z Wieser Software Ltd, XAML od Stefana, IsSelected z Erlend i mój wkład w stworzenie statycznej metody 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>

Kod C # za:

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

Edycja: poprzedni kod zawsze działał dobrze w tym scenariuszu, ale w innym scenariuszu VisualTreeHelper.GetParent zwrócił wartość null, gdy LogicalTreeHelper zwrócił wartość, więc naprawiliśmy to.


1
Aby to zrobić, ta odpowiedź implementuje to w rozszerzeniu DependencyProperty: stackoverflow.com/a/18032332/84522
Terrence

7

Prawie dobrze , ale musisz uważać na elementy niewidoczne w drzewie (jak Runna przykład a).

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

ta ogólna metoda wydaje się trochę dziwna, jak mogę jej użyć, kiedy piszę TreeViewItem treeViewItem = VisualUpwardSearch <TreeViewItem> (e.OriginalSource as DependencyObject); daje mi błąd konwersji
Rati_Ge

TreeViewItem treeViewItem = VisualUpwardSearch <TreeViewItem> (e.OriginalSource as DependencyObject) as TreeViewItem;
Anthony Wieser,

6

Myślę, że zarejestrowanie programu obsługi klas powinno załatwić sprawę. Po prostu zarejestruj procedurę obsługi zdarzeń kierowanych w obiekcie PreviewMouseRightButtonDownEvent TreeViewItem w pliku kodu app.xaml.cs w następujący sposób:

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

Pracował dla mnie! I też proste.
dvallejo

2
Cześć Nathan. Wygląda na to, że kod jest globalny i wpłynie na każdy TreeView. Czy nie byłoby lepiej mieć rozwiązanie, które jest tylko lokalne? Może to powodować skutki uboczne?
Eric Ouellet

Ten kod jest rzeczywiście globalny dla całej aplikacji WPF. W moim przypadku było to wymagane zachowanie, więc było spójne dla wszystkich widoków drzewa używanych w aplikacji. Możesz jednak zarejestrować to zdarzenie w samej instancji treeview, więc ma ono zastosowanie tylko do tego treeview.
Nathan Swannet

2

Innym sposobem rozwiązania tego problemu za pomocą MVVM jest polecenie wiązania prawym przyciskiem myszy z modelem widoku. Możesz tam również określić inną logikę source.IsSelected = true. To używa tylko xmlns:i="http://schemas.microsoft.com/expression/2010/intera‌​ctivity"z System.Windows.Interactivity.

XAML do wyświetlenia:

<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>

Zobacz model:

    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

Miałem problem z wyborem dzieci za pomocą metody HierarchicalDataTemplate. Gdybym wybrał dziecko węzła, w jakiś sposób wybrałoby głównego rodzica tego dziecka. Dowiedziałem się, że zdarzenie MouseRightButtonDown będzie wywoływane na każdym poziomie, na jakim było dziecko. Na przykład, jeśli masz takie drzewo:

Pozycja 1
   - Dziecko 1
   - Dziecko 2
      - Podelement1
      - Podelement2

Gdybym wybrał Subitem2, zdarzenie uruchomiłoby się trzy razy i zostałby wybrany element 1. Rozwiązałem to za pomocą wywołania logicznego i asynchronicznego.

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

Wydaje się trochę lepki, ale zasadniczo ustawiam wartość logiczną na true przy pierwszym przejściu i resetuję ją w innym wątku w ciągu kilku sekund (w tym przypadku 3). Oznacza to, że następne przejście przez miejsce, w którym spróbuje przejść w górę, zostanie pominięte, pozostawiając wybrany właściwy węzeł. Jak dotąd wydaje się, że działa :-)


Odpowiedź jest zestaw MouseButtonEventArgs.Handleddo true. Ponieważ dziecko jest pierwszym wezwaniem. Ustawienie tej właściwości na true spowoduje wyłączenie innych wywołań do rodzica.
Basit Anwer

0

Możesz go wybrać za pomocą zdarzenia po naciśnięciu myszy. Spowoduje to wybranie przed uruchomieniem menu kontekstowego.


0

Jeśli chcesz pozostać w ramach wzorca MVVM, możesz wykonać następujące czynności:

Widok:

<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>

Kod za:

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

Teraz możesz albo zareagować na zmianę właściwości ClickedTreeElement, albo możesz użyć polecenia, które wewnętrznie działa z ClickedTreeElement.

Rozszerzony widok:

<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>
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.