Chọn TreeView Node khi nhấp chuột phải trước khi hiển thị ContextMenu


Câu trả lời:


130

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

sự kiện này dành cho TreeView hay TreeViewItem?
Louis Rhys

1
a Bất kỳ ý tưởng nào về cách bỏ chọn mọi thứ nếu nhấp chuột phải vào một vị trí trống?
Louis Rhys

Câu trả lời duy nhất giúp ích cho 5 người khác ... Tôi thực sự đang làm sai với dân số treeview, cảm ơn.

3
Để trả lời câu hỏi của Louis Rhys: if (treeViewItem == null) treeView.SelectedIndex = -1hoặc treeView.SelectedItem = null. Tôi tin rằng một trong hai sẽ hoạt động.
James M

24

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ử TreeViewdữ 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 Booleantính IsSelectedvà thuộc Stringtính Namecũ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ị:

  1. Các TreeViewItem.IsSelectedtài sản được ràng buộc với IsSelectedtài sản trên quan điểm mô hình. Đặt thuộc IsSelectedtính trên view-model thành true sẽ chọn nút tương ứng trong cây.

  2. Khi PreviewMouseRightButtonDownkích hoạt trên phần trực quan của nút (trong ví dụ này là a TextBlock) thuộc IsSelectedtí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 .


2
Câu trả lời tuyệt vời, cảm ơn bạn! Sẽ rất hữu ích nếu chỉ ra ánh xạ ieikhô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"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.
prlc

Điều này không hữu ích vì ChangePropertyActionnó đang cố gắng thiết lập một IsSelectedthuộ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 IsSelectedtính. Tôi có làm điều gì sai?
Antonín Procházka

@ AntonínProcházka: Câu trả lời của tôi yêu cầu "đối tượng dữ liệu" (hoặc mô hình chế độ xem) của bạn phải có thuộc IsSelectedtính như đã nêu trong đoạn thứ hai của câu trả lời của tôi: Giả sử TreeViewdữ 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).
Martin Liversage

16

Sử dụng "item.Focus ();" dường như không hoạt động 100%, bằng cách sử dụng "item.IsSelected = true;" làm.


Cảm ơn cho mẹo này. Đã giúp tôi.
i8abug

Mẹo tốt. Tôi gọi Focus () đầu tiên, sau đó đặt IsSelected = true.
Jim Gomes

12

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

2
Nó không hoạt động như mong đợi, tôi luôn nhận được phần tử gốc với tư cách là người gửi. Tôi đã tìm thấy một giải pháp tương tự một giải pháp social.msdn.microsoft.com/Forums/en-US/wpf/thread/… Các trình xử lý sự kiện được thêm vào theo cách này hoạt động như mong đợi. Bất kỳ thay đổi nào đối với mã của bạn để chấp nhận nó? :-)
alex2k8

Nó rõ ràng phụ thuộc vào cách bạn bố trí chế độ xem dạng cây. Mã tôi đã đăng hoạt động, vì đó là mã chính xác tôi sử dụng trong một trong các công cụ của mình.
Stefan

Lưu ý nếu bạn thiết lập một điểm debug đây bạn có thể xem những gì gõ gửi của bạn là mà sẽ đương nhiên khác nhau dựa vào cách bạn thiết lập cây

Đây có vẻ như là giải pháp đơn giản nhất khi nó hoạt động. Nó đã làm việc cho tôi. Trên thực tế, bạn chỉ nên truyền người gửi dưới dạng TreeViewItem vì nếu không, đó là một lỗi.
craftworkgames

12

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 đó.


1
Để tiếp tục điều này, câu trả lời này thực hiện điều này trong một phần mở rộng DependencyProperty: stackoverflow.com/a/18032332/84522
Terrence

7

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

phương thức chung này có vẻ hơi lạ làm sao tôi có thể sử dụng nó khi tôi viết TreeViewItem treeViewItem = VisualUpwardSearch <TreeViewItem> (e.OriginalSource as DependencyObject); nó mang lại cho tôi lỗi chuyển đổi
Rati_Ge

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

6

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

Đã làm cho tôi! Và đơn giản nữa.
dvallejo

2
Xin chào Nathan. Có vẻ như mã là toàn cầu và sẽ ảnh hưởng đến mọi TreeView. Sẽ không tốt hơn nếu có một giải pháp chỉ dành cho địa phương? Nó có thể tạo ra tác dụng phụ?
Eric Ouellet

Mã này thực sự là toàn cầu cho toàn bộ ứng dụng WPF. Trong trường hợp của tôi, đây là hành vi bắt buộc nên nó nhất quán cho tất cả các chế độ xem dạng cây được sử dụng trong ứng dụng. Tuy nhiên, bạn có thể đăng ký sự kiện này trên chính một cá thể treeview để nó chỉ áp dụng cho treeview đó.
Nathan Swannet

2

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/intera‌​ctivity"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;
        }
    }

1

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


Câu trả lời là đặt MouseButtonEventArgs.Handledthà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.
Basit Anwer

0

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.


0

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>
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.