Pilih TreeView Node pada klik kanan sebelum menampilkan ContextMenu

Jawaban:

130

Bergantung pada cara pohon itu diisi, pengirim dan nilai e. Sumber dapat bervariasi .

Salah satu solusi yang memungkinkan adalah dengan menggunakan e.OriginalSource dan menemukan TreeViewItem menggunakan 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;
}
alex2k8.dll
sumber
apakah acara ini untuk TreeView atau TreeViewItem?
Louis Rhys
1
an Ada ide bagaimana cara membatalkan semua pilihan jika klik kanan ada di lokasi kosong?
Louis Rhys
Satu-satunya jawaban yang membantu dari 5 lainnya ... Saya benar-benar melakukan sesuatu yang salah dengan populasi treeview, terima kasih.
3
Sebagai jawaban atas pertanyaan Louis Rhys: if (treeViewItem == null) treeView.SelectedIndex = -1atau treeView.SelectedItem = null. Saya percaya keduanya harus bekerja.
James M
24

Jika Anda menginginkan solusi khusus XAML, Anda dapat menggunakan Blend Interactivity.

Asumsikan TreeViewdata is terikat ke kumpulan hierarki model tampilan yang memiliki Booleanproperti IsSelecteddan Stringproperti Nameserta kumpulan item anak bernama 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>

Ada dua bagian yang menarik:

  1. The TreeViewItem.IsSelectedproperti terikat ke IsSelectedproperti pada tampilan model. Menyetel IsSelectedproperti pada model tampilan ke true akan memilih simpul yang sesuai di pohon.

  2. Saat PreviewMouseRightButtonDowndiaktifkan pada bagian visual dari node (dalam contoh ini a TextBlock) IsSelectedproperti pada model tampilan disetel ke true. Kembali ke 1. Anda dapat melihat bahwa simpul terkait yang diklik di pohon menjadi simpul yang dipilih.

Salah satu cara untuk mendapatkan Blend Interactivity dalam proyek Anda adalah dengan menggunakan paket NuGet Unofficial.Blend.Interactivity .

Martin Liversage
sumber
2
Jawaban yang bagus, terima kasih! Akan sangat membantu untuk menunjukkan apa yang dipecahkan oleh pemetaan idan einamespace dan rakitan mana mereka dapat ditemukan. Saya berasumsi: xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"dan xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions", yang ditemukan di rakitan System.Windows.Interactivity dan Microsoft.Expression.Interactions masing-masing.
prlc
Ini tidak membantu karena ChangePropertyActionmencoba menyetel IsSelectedproperti dari objek data terikat, yang bukan bagian dari UI, jadi tidak memiliki IsSelectedproperti. Apakah saya melakukan sesuatu yang salah?
Antonín Procházka
@ AntonínProcházka: Jawaban saya mengharuskan "objek data" (atau model tampilan) Anda memiliki IsSelectedproperti seperti yang dinyatakan dalam paragraf kedua jawaban saya: Asumsikan TreeViewdata terikat ke kumpulan hierarki model tampilan yang memiliki properti BooleanIsSelected ... (penekanan saya).
Martin Liversage
16

Menggunakan "item.Focus ();" sepertinya tidak berfungsi 100%, menggunakan "item.IsSelected = true;" tidak.

Erlend
sumber
Terima kasih atas tip ini. Membantuku.
i8abug
Tip bagus. Saya memanggil Focus () terlebih dahulu, lalu mengatur IsSelected = true.
Jim Gomes
12

Di XAML, tambahkan penangan PreviewMouseRightButtonDown di 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>

Kemudian tangani acara seperti ini:

    private void TreeViewItem_PreviewMouseRightButtonDown( object sender, MouseEventArgs e )
    {
        TreeViewItem item = sender as TreeViewItem;
        if ( item != null )
        {
            item.Focus( );
            e.Handled = true;
        }
    }
Stefan
sumber
2
Ini tidak berfungsi seperti yang diharapkan, saya selalu mendapatkan elemen root sebagai pengirim. Saya telah menemukan solusi serupa satu social.msdn.microsoft.com/Forums/en-US/wpf/thread/… Penangan acara menambahkan cara ini bekerja seperti yang diharapkan. Adakah perubahan pada kode Anda untuk menerimanya? :-)
alex2k8
Ini tampaknya tergantung pada bagaimana Anda mengisi tampilan pohon. Kode yang saya posting berfungsi, karena itulah kode persis yang saya gunakan di salah satu alat saya.
Stefan
Catatan jika Anda menetapkan titik men-debug di sini Anda dapat melihat apa jenis pengirim Anda yang tentu saja akan berbeda berdasarkan pada bagaimana Anda setup pohon
Ini sepertinya solusi paling sederhana saat berhasil. Itu berhasil untuk saya. Faktanya, Anda sebaiknya mengubah pengirim sebagai TreeViewItem karena jika tidak, itu adalah bug.
craftworkgames
12

Menggunakan ide asli dari alex2k8, menangani non-visual dengan benar dari Wieser Software Ltd, XAML dari Stefan, IsSelected dari Erlend, dan kontribusi saya untuk benar-benar membuat metode statis Generik:

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 # kode di belakang:

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

Sunting: Kode sebelumnya selalu bekerja dengan baik untuk skenario ini, tetapi dalam skenario lain VisualTreeHelper.GetParent mengembalikan null ketika LogicalTreeHelper mengembalikan nilai, jadi perbaiki itu.

Sean Hall
sumber
1
Untuk lebih jauh, jawaban ini mengimplementasikan ini dalam ekstensi DependencyProperty: stackoverflow.com/a/18032332/84522
Terrence
7

Hampir Benar , tetapi Anda perlu berhati-hati terhadap non visual di pohon, (seperti Run, misalnya).

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; 
}
Anthony Wieser
sumber
metode umum ini tampaknya agak aneh bagaimana saya dapat menggunakannya ketika saya menulis TreeViewItem treeViewItem = VisualUpwardSearch <TreeViewItem> (e.OriginalSource as DependencyObject); itu memberi saya kesalahan konversi
Rati_Ge
TreeViewItem treeViewItem = VisualUpwardSearch <TreeViewItem> (e.OriginalSource sebagai DependencyObject) sebagai TreeViewItem;
Anthony Wieser
6

Saya pikir mendaftarkan penangan kelas harus melakukan triknya. Cukup daftarkan pengendali kejadian yang dirutekan pada PreviewMouseRightButtonDownEvent TreeViewItem di file kode app.xaml.cs Anda seperti ini:

/// <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;
    }
}
Nathan Swannet
sumber
Bekerja untuk saya! Dan juga sederhana.
dvallejo
2
Halo Nathan. Sepertinya kode ini bersifat global dan akan memengaruhi setiap TreeView. Bukankah lebih baik memiliki solusi yang hanya bersifat lokal? Apakah bisa menimbulkan efek samping?
Eric Ouellet
Kode ini memang global untuk seluruh aplikasi WPF. Dalam kasus saya, ini adalah perilaku yang diwajibkan sehingga konsisten untuk semua tampilan pohon yang digunakan dalam aplikasi. Namun Anda bisa mendaftarkan acara ini pada instance treeview itu sendiri sehingga hanya berlaku untuk treeview tersebut.
Nathan Swannet
2

Cara lain untuk mengatasinya menggunakan MVVM adalah perintah bind untuk klik kanan ke model tampilan Anda. Di sana Anda juga dapat menentukan logika lain source.IsSelected = true. Ini hanya digunakan xmlns:i="http://schemas.microsoft.com/expression/2010/intera‌​ctivity"dari System.Windows.Interactivity.

XAML untuk dilihat:

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

Lihat 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;
        }
    }
benderto
sumber
1

Saya mengalami masalah saat memilih anak dengan metode HierarchicalDataTemplate. Jika saya memilih anak dari sebuah simpul, entah bagaimana itu akan memilih orang tua akar dari anak itu. Saya menemukan bahwa event MouseRightButtonDown akan dipanggil untuk setiap level anak itu. Misalnya jika Anda memiliki pohon seperti ini:

Item 1
   - Anak 1
   - Anak 2
      - Subitem1
      - Subitem2

Jika saya memilih Subitem2, acara akan diaktifkan tiga kali dan item 1 akan dipilih. Saya menyelesaikan ini dengan boolean dan panggilan asynchronous.

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

Rasanya agak kaku tetapi pada dasarnya saya mengatur boolean ke true pada lintasan pertama dan mengatur ulang di utas lain dalam beberapa detik (3 dalam kasus ini). Ini berarti bahwa lintasan berikutnya di mana ia akan mencoba untuk naik ke pohon akan dilewati sehingga Anda memilih simpul yang benar. Tampaknya berfungsi sejauh ini :-)

Zoey
sumber
Jawabannya adalah untuk set MouseButtonEventArgs.Handledke true. Karena anak itu yang pertama dipanggil. Pengaturan properti ini ke true akan menonaktifkan panggilan lain ke induk.
Basit Anwer
0

Anda dapat memilihnya dengan acara mouse down. Itu akan memicu pemilihan sebelum menu konteks dimulai.

Scott Thurlow
sumber
0

Jika Anda ingin tetap berada dalam pola MVVM, Anda dapat melakukan hal berikut:

Melihat:

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

Kode Belakang:

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

Sekarang Anda dapat bereaksi terhadap perubahan properti ClickedTreeElement atau Anda dapat menggunakan perintah yang secara internal bekerja dengan ClickedTreeElement.

Tampilan Diperpanjang:

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