How to display ContextMenu on certain TreeViewItem and highlight the selected TreeViewItem in WPF following MVVM

BabyHai 61 Reputation points
2023-09-20T10:36:51.4633333+00:00

Following MVVM, I have developed a TreeView and now I want mouse right click to show ContextMenu on certain TreeViewItem. As you can see in the screeshot, I only want the ContextMenu to be displayed when mouse right clicks on the first level TreeViewItem(those in yellow). Clicks on any other places will not show the ContextMenu.

TreeViewItem

Another issue I am facing is, after I right click on a TreeViewItem to show the ContextMenu, I cannot perform any other operations on the whole window. Not to show ContextMenu on other TreeViewItem or not to collapse a node. Somehow the whole window lost focus. Only when I move mouse out of the window and move back into the window, I can do operation again, but only one operation and the same issue appears again. Below shows you an example.

GifMaker_20230920120115040

The codes are as follows:

XAML:

<TreeView Name="templateTreeview" ItemsSource="{Binding TreeViewSource}" FontSize="14" AllowDrop="True" Margin="5,5,5,10">
    <i:Interaction.Behaviors>
        <local:TreeViewDragDropBehavior />
    </i:Interaction.Behaviors>
    <TreeView.ItemContainerStyle>
        <Style TargetType="{x:Type TreeViewItem}">
            <Setter Property="IsExpanded" Value="True" />
        </Style>
    </TreeView.ItemContainerStyle>
    <TreeView.Resources>
        <HierarchicalDataTemplate DataType="{x:Type local:ViewItem}" ItemsSource="{Binding Path=ViewItems}">
            <StackPanel Orientation="Horizontal">
                <StackPanel.ContextMenu>
                    <ContextMenu Focusable="False">
                        <MenuItem Header="Ctx1" Focusable="False"></MenuItem>
                        <MenuItem Header="Ctx2" Focusable="False"></MenuItem>
                        <MenuItem Header="Ctx3" Focusable="False"></MenuItem>
                    </ContextMenu>
                </StackPanel.ContextMenu>
                <TextBlock Text="{Binding Path=ItemName}" Margin="0,0,10,0" />
            </StackPanel>
        </HierarchicalDataTemplate>
    </TreeView.Resources>
</TreeView>

TreeViewDragDropBehavior:

using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interactivity;
using System.Windows.Media;

namespace TreeviewExample
{
    public class TreeViewDragDropBehavior : Behavior<UIElement>
    {
        // for saving TreeViewItem to drag
        private TreeViewItem draggedTVI = null;

        protected override void OnAttached()
        {
            base.OnAttached();

            AssociatedObject.PreviewMouseLeftButtonDown += PreviewMouseLeftButtonDown;
            AssociatedObject.MouseMove += tv_MouseMove;
            AssociatedObject.DragOver += tv_DragOver;
            AssociatedObject.Drop += Tv_Drop;
            AssociatedObject.DragLeave += tv_DragLeave;
            AssociatedObject.PreviewMouseRightButtonDown += AssociatedObject_PreviewMouseRightButtonDown;
        }

        private void AssociatedObject_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
        {
            draggedTVI = FindAnchestor<TreeViewItem>((DependencyObject)e.OriginalSource);
            draggedTVI.Background = Brushes.MediumPurple;
        }

        // save TreeViewItem to drag
        private void PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            draggedTVI = FindAnchestor<TreeViewItem>((DependencyObject)e.OriginalSource);
        }

        // start Drag&Drop when mouse is moved and there's a saved TreeViewItem
        private void tv_MouseMove(object sender, MouseEventArgs e)
        {
            if (draggedTVI != null)
            {
                // Find the data behind the TreeViewItem
                ViewItem dragData = draggedTVI.DataContext as ViewItem;

                // Initialize the drag & drop operation
                DragDrop.DoDragDrop(draggedTVI, dragData, DragDropEffects.Move);
                // reset saved TreeViewItem
                draggedTVI = null;
            }
        }

        // highlight target
        private void tv_DragOver(object sender, DragEventArgs e)
        {
            TreeViewItem tvi = FindAnchestor<TreeViewItem>((DependencyObject)e.OriginalSource);
            if (tvi != null) tvi.Background = Brushes.MediumPurple;
        }

        private void Tv_Drop(object sender, DragEventArgs e)
        {
            if (sender is TreeView)
            {
                MainViewModel vm = (sender as TreeView).DataContext as MainViewModel;

                // check for data
                if (!e.Data.GetDataPresent(typeof(ViewItem))) return;
                // store the drop target
                ViewItem targetItem = (e.OriginalSource as TextBlock)?.DataContext as ViewItem;
                if (targetItem == null) return;
                ViewItem data = (ViewItem)e.Data.GetData(typeof(ViewItem));

                //if drag from ListBox to TreeView
                if (draggedTVI == null)
                {
                    
                        ViewItem viewItemToInsert = new ViewItem(data.ItemName);
                        targetItem.ViewItems.Add(viewItemToInsert);
                    
                }
                else if (!targetItem.Equals(draggedTVI.DataContext as ViewItem))
                {
                    DeleteViewNodeFromSource(vm.TreeViewSource, data);
                    targetItem.ViewItems.Add(data);
                }
            }

            // reset background on target TreeViewItem
            TreeViewItem tvi = FindAnchestor<TreeViewItem>((DependencyObject)e.OriginalSource);
            if (tvi != null) tvi.Background = Brushes.White;
        }

        // reset background on leaved possible target TreeViewItem
        private void tv_DragLeave(object sender, DragEventArgs e)
        {
            TreeViewItem tvi = FindAnchestor<TreeViewItem>((DependencyObject)e.OriginalSource);
            if (tvi != null) tvi.Background = Brushes.White;
        }

        // Helper to search up the VisualTree
        private static T FindAnchestor<T>(DependencyObject current) where T : DependencyObject
        {
            do
            {
                if (current is T) return (T)current;
                current = VisualTreeHelper.GetParent(current);
            } while (current != null);
            return null;
        }

        private void DeleteViewNodeFromSource(ObservableCollection<ViewItem> viewItems, ViewItem viewItem)
        {
            foreach (ViewItem vi in viewItems)
            {
                if (vi.Equals(viewItem))
                {
                    viewItems.Remove(vi);
                    break;
                }
                else
                    DeleteViewNodeFromSource(vi.ViewItems, viewItem);
            }
        }
    }
}

ViewItem class:

    public class ViewItem
    {
        public string ItemName { get; set; }
        public ObservableCollection<ViewItem> ViewItems { get; } = new ObservableCollection<ViewItem>();
        public int IsVisible { get; set; }
    
        public ViewItem()
        { }
    
        public ViewItem(string name, int IsVisible = 0)
        {
            this.ItemName = name;
            this.IsVisible = IsVisible;
        }
    }
    

I really have no idea about the first issue. For the second issue I think the problem lies in the PreviewMouseRightButtonDown event. In the event function I set the background of the selected TreeViewItem. If I comment the event, such issue will disappear. But I have no idea how to fix it. In the end I still need to make the selected TreeViewItem highlighted till the ContextMenu is closed.

Could someone help me with these two problems?

  1. Show ContextMenu on certain TreeViewItem
  2. Enable selected TreeViewItem highlighted and affects no other controller.
Windows Presentation Foundation
Windows Presentation Foundation
A part of the .NET Framework that provides a unified programming model for building line-of-business desktop applications on Windows.
2,686 questions
C#
C#
An object-oriented and type-safe programming language that has its roots in the C family of languages and includes support for component-oriented programming.
10,364 questions
XAML
XAML
A language based on Extensible Markup Language (XML) that enables developers to specify a hierarchy of objects with a set of properties and logic.
769 questions
{count} votes

Accepted answer
  1. Hui Liu-MSFT 41,146 Reputation points Microsoft Vendor
    2023-09-22T07:28:33.58+00:00

    Hi,@BabyHai . You could check if the effect below is what you want. If so, you can refer to the following code.

    Question 1:

    
       <TreeView.Resources>
                    <HierarchicalDataTemplate DataType="{x:Type local:ViewItem}" ItemsSource="{Binding Path=ViewItems}">
                        <StackPanel Orientation="Horizontal">
                           
                            <TextBlock x:Name="MyControl" Text="{Binding Path=ItemName}" Margin="0,0,10,0" />
                        </StackPanel>
                       
                    </HierarchicalDataTemplate>
                    <ContextMenu x:Key="FirstLevelContextMenu" Focusable="False">
                        <MenuItem Header="Ctx1" Focusable="False"></MenuItem>
                        <MenuItem Header="Ctx2" Focusable="False"></MenuItem>
                        <MenuItem Header="Ctx3" Focusable="False"></MenuItem>
                    </ContextMenu>
                </TreeView.Resources>
    
    

    Codebehind:

      private TreeViewItem lastClickedItem = null;
            private void AssociatedObject_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
            {
    
    
                if (e.RightButton == MouseButtonState.Pressed)
                {
                    TreeViewItem tvi = FindAnchestor<TreeViewItem>((DependencyObject)e.OriginalSource);
                    if (tvi != null)
                    {
                        if (lastClickedItem != null && !lastClickedItem.Equals(tvi))
                        {
    
                            lastClickedItem.Background = Brushes.White;
    
                        }
                        if (FindTreeLevel(tvi) == 0)
                        {
    
                           
                                // Display the context menu for first-level items
                                ContextMenu contextMenu = tvi.FindResource("FirstLevelContextMenu") as ContextMenu;
                                if (contextMenu != null)
                                {
                                    contextMenu.PlacementTarget = tvi;
                                    contextMenu.IsOpen = true;
                                }
                            
                        
                        }
                      
                              tvi.Background = Brushes.MediumPurple;
                            lastClickedItem = tvi;
                    }
                }
                
            }
    
    
            private int FindTreeLevel(DependencyObject control)
            {
                var level = -1;
                if (control != null)
                {
                    var parent = VisualTreeHelper.GetParent(control);
                    while (!(parent is TreeView) && (parent != null))
                    {
                        if (parent is TreeViewItem)
                            level++;
                        parent = VisualTreeHelper.GetParent(parent);
                    }
                }
                return level;
            }
    
    

    The result:

    6

    Question 2:

    Why do you need to right-click or Collapse TreeviewItems if you want to use the right mouse button to click on other controls? TreeviewItems can collapse when clicking the left mouse button.


    If the response is helpful, please click "Accept Answer" and upvote it.

    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.

    1 person found this answer helpful.

0 additional answers

Sort by: Most helpful