How to bind Command in ContextMenuItem

BabyHai 61 Reputation points
2023-09-25T10:08:05.2533333+00:00

Thanks to @Hui Liu-MSFT and @Peter Fleischer (former MVP) ,now I am able to operate my TreeView and do Drag&Drop on it. Also I can ONLY display ContextMenu by right click on the first level of TreeViewItem, which is required. You can see the current situation in the gif.

Only the first level shows ContextMenu

Now I am facing an issue that I want to bind command to each of the ContextMenuItem. I tried lots of solution online but they all don't work in my case. Since I am not using RelayCommand but command class, I fail to find a proper solution.

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">
                <TextBlock Text="{Binding Path=ItemName}" Margin="0,0,10,0" />
            </StackPanel>
        </HierarchicalDataTemplate>
        <ContextMenu x:Key="FirstLevelContextMenu" Focusable="False">
            <MenuItem Header="1.5A0" Focusable="False"></MenuItem>
            <MenuItem Header="A0" Focusable="False"></MenuItem>
            <MenuItem Header="A1" Focusable="False"></MenuItem>
            <MenuItem Header="A2" Focusable="False"></MenuItem>
            <MenuItem Header="A3" Focusable="False"></MenuItem>
            <MenuItem Header="A4" Focusable="False"></MenuItem>
        </ContextMenu>
    </TreeView.Resources>
</TreeView>

MainViewModel:

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

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

        ShowContextMenuCommand = new ShowContextMenuCommand(this);
    }
}

CommandBase:

 public abstract class CommandBase : ICommand
 {
     public event EventHandler CanExecuteChanged;

     public virtual bool CanExecute(object parameter) => true;

     public abstract void Execute(object parameter);

     protected void OnCanExecutedChanged()
     {
         CanExecuteChanged?.Invoke(this, new EventArgs());
     }
 }

ShowContexxtMenuCommand:

public class ShowContextMenuCommand : CommandBase
{
    private readonly MainViewModel _viewModel;

    public ShowContextMenuCommand(MainViewModel viewModel)
    {
        _viewModel = viewModel;
    }

    public override void Execute(object parameter)
    {
    }
}

TreeVieewDragDropBehavior:

public class TreeViewDragDropBehavior : Behavior<UIElement>
{
    // for saving TreeViewItem to drag
    private TreeViewItem draggedTVI = null;
    private TreeViewItem lastClickedItem = 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)
    {
        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;
            }
        }
    }

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

ViewItem:

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

Could someone helps me to bind the command? As long as the command is fired once the options in ContextMenu clicked is.

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
0 comments No comments
{count} votes

Accepted answer
  1. Hui Liu-MSFT 41,146 Reputation points Microsoft Vendor
    2023-09-26T02:48:14.3066667+00:00

    Hi,@BabyHai. Welcome Microsoft Q&A. For the problem of bind the command to TreeViewItem, you could try to refer to the following code.

     <TreeView Name="templateTreeview"       Grid.Column="1"      
                      ItemsSource="{Binding TreeViewSource}" Width="200" 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">
                           
                            <TextBlock x:Name="MyControl" Text="{Binding Path=ItemName}" Margin="0,0,10,0" />
                        </StackPanel>
                        
                    </HierarchicalDataTemplate>
                    <ContextMenu x:Key="FirstLevelContextMenu" Focusable="False">
                         <MenuItem Header="1.5A0" Focusable="False" Command="{Binding DataContext.A01Command, RelativeSource={RelativeSource AncestorType={x:Type TreeView}}}" CommandParameter="{Binding }" />
                        <MenuItem Header="Ctx1" Focusable="False" Command="{Binding DataContext.Ctx1Command, RelativeSource={RelativeSource AncestorType={x:Type TreeView}}}" CommandParameter="{Binding}" />
                        <MenuItem Header="Ctx2" Focusable="False" Command="{Binding DataContext.ContextMenuItemCommand, RelativeSource={RelativeSource AncestorType={x:Type TreeView}}}" CommandParameter="{Binding}" />
                        <MenuItem Header="Ctx3" Focusable="False" Command="{Binding DataContext.ContextMenuItemCommand, RelativeSource={RelativeSource AncestorType={x:Type TreeView}}}" CommandParameter="{Binding}" />
                       
                    </ContextMenu>
                </TreeView.Resources>
            </TreeView>
    

    Codebedhind:

    
     public class MainViewModel
        {
            public ObservableCollection<ViewItem> ListBoxSource { get; set; }
            public ObservableCollection<ViewItem> TreeViewSource { get; set; }
    
            public RelayCommand A01Command { get; private set; }
            public RelayCommand Ctx1Command { get; private set; }
            public MainViewModel()
            {
               ...
                A01Command = new RelayCommand(ExecuteA01Command);
                Ctx1Command = new RelayCommand(ExecuteCtx1Command);
             
            }
    
            private void ExecuteA01Command(object parameter)
            {
                if (parameter is ViewItem viewItem)
                {
                    
                    MessageBox.Show($"Clicked on '{viewItem.ItemName}'");
                }
    
            }
            private void ExecuteCtx1Command(object parameter)
            {
                other action
            }
          
    
        }
    
    

    RelayCommand:

        {
            private readonly Action<object> _execute;
            private readonly Predicate<object> _canExecute;
    
            public RelayCommand(Action<object> execute)
                : this(execute, null)
            {
            }
    
            public RelayCommand(Action<object> execute, Predicate<object> canExecute)
            {
                if (execute == null)
                    throw new ArgumentNullException("execute");
                _execute = execute;
                _canExecute = canExecute;
            }
    
            public bool CanExecute(object parameter)
            {
                return _canExecute == null ? true : _canExecute(parameter);
            }
    
            public event EventHandler CanExecuteChanged
            {
                add { CommandManager.RequerySuggested += value; }
                remove { CommandManager.RequerySuggested -= value; }
            }
    
            public void Execute(object parameter)
            {
                _execute(parameter);
            }
        }
    
    
    

    The result:

    3


    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.


0 additional answers

Sort by: Most helpful