Tôi muốn chọn một nút WPF TreeView khi nhấp chuột phải, ngay trước khi ContextMenu hiển thị.
Đối với WinForms, tôi có thể sử dụng mã như thế này Tìm nút được nhấp dưới menu ngữ cảnh , các lựa chọn thay thế WPF là gì?
Tôi muốn chọn một nút WPF TreeView khi nhấp chuột phải, ngay trước khi ContextMenu hiển thị.
Đối với WinForms, tôi có thể sử dụng mã như thế này Tìm nút được nhấp dưới menu ngữ cảnh , các lựa chọn thay thế WPF là gì?
Câu trả lời:
Tùy thuộc vào cách cây được điền, giá trị người gửi và e.Source có thể khác nhau .
Một trong những giải pháp khả thi là sử dụng e.OriginalSource và tìm TreeViewItem bằng 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
hoặc treeView.SelectedItem = null
. Tôi tin rằng một trong hai sẽ hoạt động.
Nếu bạn muốn một giải pháp chỉ dành cho XAML, bạn có thể sử dụng Blend Interactivity.
Giả sử TreeView
dữ liệu là liên kết với một tập hợp phân cấp các mô hình chế độ xem có thuộc Boolean
tính IsSelected
và thuộc String
tính Name
cũng như tập hợp các mục con được đặt tên 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>
Có hai phần thú vị:
Các TreeViewItem.IsSelected
tài sản được ràng buộc với IsSelected
tài sản trên quan điểm mô hình. Đặt thuộc IsSelected
tính trên view-model thành true sẽ chọn nút tương ứng trong cây.
Khi PreviewMouseRightButtonDown
kích hoạt trên phần trực quan của nút (trong ví dụ này là a TextBlock
) thuộc IsSelected
tính trên mô hình chế độ xem được đặt thành true. Quay trở lại 1. bạn có thể thấy rằng nút tương ứng đã được nhấp vào trong cây sẽ trở thành nút được chọn.
Một cách để có được Blend Interactivity trong dự án của bạn là sử dụng gói NuGet Unofficial.Blend.Interactivity .
i
và ei
không gian tên giải quyết cái gì và chúng có thể được tìm thấy trong những tổ hợp nào. Tôi giả sử: xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
và xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
, được tìm thấy trong các tập hợp System.Windows.Interactivity và Microsoft.Expression.Interaction tương ứng.
ChangePropertyAction
nó đang cố gắng thiết lập một IsSelected
thuộc tính của đối tượng dữ liệu liên kết, đối tượng này không phải là một phần của giao diện người dùng, vì vậy nó không có thuộc IsSelected
tính. Tôi có làm điều gì sai?
IsSelected
tính như đã nêu trong đoạn thứ hai của câu trả lời của tôi: Giả sử TreeView
dữ liệu được liên kết với tập hợp phân cấp các mô hình chế độ xem có thuộc tính BooleanIsSelected
... (nhấn mạnh của tôi).
Trong XAML, thêm trình xử lý PreviewMouseRightButtonDown trong 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>
Sau đó, xử lý sự kiện như sau:
private void TreeViewItem_PreviewMouseRightButtonDown( object sender, MouseEventArgs e )
{
TreeViewItem item = sender as TreeViewItem;
if ( item != null )
{
item.Focus( );
e.Handled = true;
}
}
Sử dụng ý tưởng ban đầu từ alex2k8, xử lý chính xác không có hình ảnh từ Wieser Software Ltd, XAML từ Stefan, IsSelected từ Erlend và đóng góp của tôi trong việc thực sự tạo ra phương pháp tĩnh Chung:
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>
Mã C # đằng sau:
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;
}
Chỉnh sửa: Mã trước đó luôn hoạt động tốt cho trường hợp này, nhưng trong trường hợp khác, VisualTreeHelper.GetParent trả về null khi LogicalTreeHelper trả về một giá trị, vì vậy hãy khắc phục điều đó.
Gần như đúng , nhưng bạn cần phải chú ý đến những hình ảnh không có trong cây, (chẳng hạn như 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;
}
Tôi nghĩ rằng đăng ký một trình xử lý lớp nên thực hiện thủ thuật. Chỉ cần đăng ký trình xử lý sự kiện được định tuyến trên PreviewMouseRightButtonDownEvent của TreeViewItem trong tệp mã app.xaml.cs của bạn như sau:
/// <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;
}
}
Một cách khác để giải quyết nó bằng cách sử dụng MVVM là lệnh bind để nhấp chuột phải vào mô hình chế độ xem của bạn. Ở đó bạn cũng có thể chỉ định logic khác source.IsSelected = true
. Điều này chỉ sử dụng xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
từSystem.Windows.Interactivity
.
XAML để xem:
<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>
Xem mô hình:
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;
}
}
Tôi gặp sự cố khi chọn trẻ em bằng phương pháp HierarchicalDataTemplate. Nếu tôi chọn nút con của một nút, bằng cách nào đó, nó sẽ chọn nút cha gốc của nút con đó. Tôi phát hiện ra rằng sự kiện MouseRightButtonDown sẽ được gọi cho mọi cấp độ của đứa trẻ. Ví dụ, nếu bạn có một cái cây như thế này:
Mục 1
- Con 1
- Con 2
- Con1
- Con2
Nếu tôi chọn Subitem2, sự kiện sẽ kích hoạt ba lần và mục 1 sẽ được chọn. Tôi đã giải quyết điều này bằng một boolean và một cuộc gọi không đồng bộ.
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; });
}
Nó cảm thấy một chút rắc rối nhưng về cơ bản tôi đã đặt boolean thành true trong lần chuyển đầu tiên và đặt lại nó trên một luồng khác trong vài giây (3 trong trường hợp này). Điều này có nghĩa là nút tiếp theo đi qua nơi nó sẽ cố gắng di chuyển lên cây sẽ bị bỏ qua để lại cho bạn đúng nút được chọn. Nó có vẻ hoạt động cho đến nay :-)
MouseButtonEventArgs.Handled
thành true
. Vì đứa trẻ là người đầu tiên được gọi. Cài đặt thuộc tính này thành true sẽ vô hiệu hóa các lệnh gọi khác đến cấp độ gốc.
Bạn có thể chọn nó bằng sự kiện khi di chuột xuống. Điều đó sẽ kích hoạt lựa chọn trước khi menu ngữ cảnh khởi động.
Nếu bạn muốn ở trong mẫu MVVM, bạn có thể làm như sau:
Lượt xem:
<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>
Mã ẩn:
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);
}
Bây giờ bạn có thể phản ứng với sự thay đổi thuộc tính ClickedTreeElement hoặc bạn có thể sử dụng một lệnh hoạt động nội bộ với ClickedTreeElement.
Chế độ xem mở rộng:
<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>