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

45 Views Asked by At

Following MVVM, I have developed a TreeView and now I want mouse right click to show ContextMenu on certain TreeViewItem. TreeViewItem in yellow 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.

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. Only one time ContextMenu possible

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>

MainViewModel:

public class MainViewModel
{
    public ObservableCollection<ViewItem> ListBoxSource { get; set; }
    public ObservableCollection<ViewItem> TreeViewSource { get; set; }

    public MainViewModel()
    {
        ListBoxSource = new ObservableCollection<ViewItem>();
        TreeViewSource = new ObservableCollection<ViewItem>();

        for (int i = 1; i < 20; i++) ListBoxSource.Add(new ViewItem($"ListBoxItem {i}"));

        ViewItem defaultView = new ViewItem("Root", 1) ;
        TreeViewSource.Add(defaultView);
        for (int i = 1; i < 10; i++) defaultView.ViewItems.Add(new ViewItem($"TreeViewItem {i}"));
    }
}

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.
0

There are 0 best solutions below