ฉันต้องการเลือก WPF TreeView Node เมื่อคลิกขวาก่อนที่จะแสดง ContextMenu
สำหรับ WinForms ฉันสามารถใช้รหัสเช่นนี้ค้นหาโหนดที่คลิกภายใต้เมนูบริบททางเลือก WPF คืออะไร?
ฉันต้องการเลือก WPF TreeView Node เมื่อคลิกขวาก่อนที่จะแสดง ContextMenu
สำหรับ WinForms ฉันสามารถใช้รหัสเช่นนี้ค้นหาโหนดที่คลิกภายใต้เมนูบริบททางเลือก WPF คืออะไร?
คำตอบ:
ทั้งนี้ขึ้นอยู่กับวิธีการที่ต้นไม้ที่มีประชากร, ผู้ส่งและค่า 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;
}
if (treeViewItem == null) treeView.SelectedIndex = -1
หรือtreeView.SelectedItem = null
. ฉันเชื่อว่าควรใช้งานได้
หากคุณต้องการโซลูชัน 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>
มีสองส่วนที่น่าสนใจ:
TreeViewItem.IsSelected
คุณสมบัติถูกผูกไว้กับIsSelected
ทรัพย์สินในมุมมองแบบ การตั้งค่าIsSelected
คุณสมบัติบน view-model เป็น true จะเลือกโหนดที่เกี่ยวข้องในทรี
เมื่อPreviewMouseRightButtonDown
ยิงบนส่วนภาพของโหนด (ในตัวอย่างนี้ a TextBlock
) IsSelected
คุณสมบัติบน view-model จะถูกตั้งค่าเป็น true กลับไปที่ 1. คุณจะเห็นว่าโหนดที่เกี่ยวข้องซึ่งถูกคลิกในโครงสร้างกลายเป็นโหนดที่เลือก
วิธีการหนึ่งที่จะได้รับการผสมผสานการโต้ตอบในโครงการของคุณคือการใช้แพคเกจ NuGet Unofficial.Blend.Interactivity
i
และei
. แก้ปัญหาการแมป namespace ไปและแม้ว่าพวกเขาซึ่งประกอบสามารถพบได้ในผมถือว่า: xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
และxmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
ซึ่งจะพบใน System.Windows.Interactivity และ Microsoft.Expression.Interactions ประกอบตามลำดับ
ChangePropertyAction
พยายามตั้งค่าIsSelected
คุณสมบัติของวัตถุข้อมูลที่ถูกผูกไว้ซึ่งไม่ได้เป็นส่วนหนึ่งของ UI ดังนั้นจึงไม่มีIsSelected
คุณสมบัติ ฉันทำอะไรผิดหรือเปล่า?
IsSelected
คุณสมบัติตามที่ระบุไว้ในย่อหน้าที่สองของคำตอบของฉัน: สมมติว่าTreeView
เป็นข้อมูลที่ผูกไว้กับคอลเลกชันมุมมองแบบลำดับชั้นที่มีคุณสมบัติบูลีนIsSelected
... (เน้นของฉัน)
ใน 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;
}
}
ใช้แนวคิดดั้งเดิมจาก alex2k8 จัดการภาพที่ไม่ใช่ภาพอย่างถูกต้องจาก Wieser Software Ltd, XAML จาก Stefan, IsSelected จาก 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 ส่งคืนค่า null เมื่อ LogicalTreeHelper ส่งคืนค่าดังนั้นคงที่
เกือบถูกต้องแต่คุณต้องระวังภาพที่ไม่ใช่ภาพในแผนภูมิ (เช่น a 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;
}
ฉันคิดว่าการลงทะเบียนตัวจัดการชั้นเรียนควรทำเคล็ดลับ เพียงลงทะเบียนตัวจัดการเหตุการณ์ที่กำหนดเส้นทางบน 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;
}
}
อีกวิธีหนึ่งในการแก้ปัญหาโดยใช้ MVVM คือคำสั่ง bind สำหรับคลิกขวาที่โมเดลมุมมองของคุณ มีคุณสามารถระบุตรรกะอื่น ๆ source.IsSelected = true
รวมทั้ง ใช้เฉพาะxmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
จาก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;
}
}
ฉันมีปัญหากับการเลือกเด็กด้วยเมธอด HierarchicalDataTemplate ถ้าฉันเลือกลูกของโหนดมันจะเลือกรูทพาเรนต์ของลูกนั้น ฉันพบว่าเหตุการณ์ MouseRightButtonDown จะถูกเรียกใช้สำหรับทุกระดับที่เด็กเป็น ตัวอย่างเช่นถ้าคุณมีต้นไม้อะไรทำนองนี้:
Item 1
- Child 1
- Child 2
- Subitem1
- Subitem2
ถ้าฉันเลือก 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; });
}
รู้สึกอึดอัดเล็กน้อย แต่โดยพื้นฐานแล้วฉันตั้งค่าบูลีนเป็นจริงในการส่งผ่านครั้งแรกและรีเซ็ตบนเธรดอื่นในไม่กี่วินาที (3 ในกรณีนี้) ซึ่งหมายความว่าครั้งต่อไปจะผ่านจุดที่พยายามจะเลื่อนขึ้นต้นไม้จะถูกข้ามไปโดยที่คุณเลือกโหนดที่ถูกต้อง ดูเหมือนว่าจะใช้งานได้ :-)
MouseButtonEventArgs.Handled
true
ตั้งแต่เด็กเป็นคนแรกที่ถูกเรียก การตั้งค่าคุณสมบัตินี้เป็นจริงจะปิดใช้งานการโทรอื่น ๆ ไปยังผู้ปกครอง
คุณสามารถเลือกได้โดยใช้เหตุการณ์เมื่อวางเมาส์ลง ซึ่งจะทริกเกอร์การเลือกก่อนที่เมนูบริบทจะเริ่มขึ้น
หากคุณต้องการอยู่ในรูปแบบ 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;
}
}
ดู
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>