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?
- Show ContextMenu on certain TreeViewItem
- Enable selected TreeViewItem highlighted and affects no other controller.