Saya ingin memilih WPF TreeView Node dengan klik kanan, tepat sebelum ContextMenu ditampilkan.
Untuk WinForms saya bisa menggunakan kode seperti ini Temukan node yang diklik di bawah menu konteks , apa saja alternatif WPF?
Saya ingin memilih WPF TreeView Node dengan klik kanan, tepat sebelum ContextMenu ditampilkan.
Untuk WinForms saya bisa menggunakan kode seperti ini Temukan node yang diklik di bawah menu konteks , apa saja alternatif WPF?
Jawaban:
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;
}
if (treeViewItem == null) treeView.SelectedIndex = -1
atau treeView.SelectedItem = null
. Saya percaya keduanya harus bekerja.
Jika Anda menginginkan solusi khusus XAML, Anda dapat menggunakan Blend Interactivity.
Asumsikan TreeView
data is terikat ke kumpulan hierarki model tampilan yang memiliki Boolean
properti IsSelected
dan String
properti Name
serta 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:
The TreeViewItem.IsSelected
properti terikat ke IsSelected
properti pada tampilan model. Menyetel IsSelected
properti pada model tampilan ke true akan memilih simpul yang sesuai di pohon.
Saat PreviewMouseRightButtonDown
diaktifkan pada bagian visual dari node (dalam contoh ini a TextBlock
) IsSelected
properti 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 .
i
dan ei
namespace 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.
ChangePropertyAction
mencoba menyetel IsSelected
properti dari objek data terikat, yang bukan bagian dari UI, jadi tidak memiliki IsSelected
properti. Apakah saya melakukan sesuatu yang salah?
IsSelected
properti seperti yang dinyatakan dalam paragraf kedua jawaban saya: Asumsikan TreeView
data terikat ke kumpulan hierarki model tampilan yang memiliki properti BooleanIsSelected
... (penekanan saya).
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;
}
}
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.
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;
}
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;
}
}
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/interactivity"
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;
}
}
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 :-)
MouseButtonEventArgs.Handled
ke true
. Karena anak itu yang pertama dipanggil. Pengaturan properti ini ke true akan menonaktifkan panggilan lain ke induk.
Anda dapat memilihnya dengan acara mouse down. Itu akan memicu pemilihan sebelum menu konteks dimulai.
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>