WPF MenuItem 小传 (3) -- ContextMenu 开启前与关闭前的处理

ContextMenu 本身只有 Opened (开启后) 和 Closed (关闭后) 事件,那要如何处理 【开启前】与【关闭前】?

前情提要

写习惯 .NET 的人应该都知道框架对于事件命名的其中一个规则是用 ed 字尾表示「某个动作发生以后」的事件,而用 ing 字尾表示「某个动作发生之前」的事件。但是在 ContextMenu class 身上左翻右找也就只有 Opened 和 Closed,难道没法处理 Opening 和 Closing 吗?

首先要知道的是当一个元素需要 ContextMenu 的时候,其实是设定一个 ContextMenu 的执行个体给 FrameworkElement.ContextMenu 属性,微软把处理开启前和关闭前的事件摆在 FrameworkElement 身上,也就是:

  1. FrameworkElement.ContextMenuOpening 事件
  2. FrameworkElement.ContextMenuClosing 事件

江湖一点诀,说完没半撇,唯一的问题只是知道这玩意在哪而已。

来个範例

不免俗总要有点範例来玩玩,假设的情境是画面上有两个 Border,在不同 Border 开启的时候会有不同的选项被启用或禁用。

在 MainViewModel 中,先处理储存两个 Border 资讯。

 public class MainViewModel : NotifyPropertyBase
 {
     private ICommand _borderLoadedCommand;
     public ICommand BorderLoadedCommand
     {
         get
         {
             if (_borderLoadedCommand == null)
             {
                 _borderLoadedCommand = new RelayCommand((x) =>
                 {
                     var border = x as Border;
                     if (border != null)
                     {
                         if (!BorderDictionary.ContainsKey(border.Name))
                         {
                             BorderDictionary.Add(border.Name, border);
                         }
                     }
                 });
             }
             return _borderLoadedCommand;
         }
     }

     private Dictionary<string, Border> BorderDictionary { get; set; }
     public MainViewModel()
     {
         BorderDictionary = new Dictionary<string, Border>();
     }
 }

XAML 的部分则是让两个 Border 的 Loaded 事件繫结到 BorderLoadedCommand。(需安装 Microsoft.Xaml.Behaviors.Wpf 套件)

 <Grid>
     <Grid.RowDefinitions >
         <RowDefinition Height="*"/>
         <RowDefinition Height="*"/>
     </Grid.RowDefinitions>
     <Border Background="LightSteelBlue" x:Name="lightSteelBlueBorder" >
         <i:Interaction.Triggers>
             <i:EventTrigger EventName="Loaded">
                 <i:InvokeCommandAction Command="{Binding BorderLoadedCommand}" CommandParameter="{Binding ElementName=lightSteelBlueBorder}" />
             </i:EventTrigger>
         </i:Interaction.Triggers>
     </Border>
     <Border Background="SpringGreen" Grid.Row="1" x:Name="springGreenBorder">
         <i:Interaction.Triggers>
             <i:EventTrigger EventName="Loaded">
                 <i:InvokeCommandAction Command="{Binding BorderLoadedCommand}"  CommandParameter="{Binding ElementName=springGreenBorder}" />
             </i:EventTrigger>
         </i:Interaction.Triggers>
     </Border>
 </Grid>

设计 MenuItem 使用的 ViewModel,其中IsEnabled 属性会决定这个选项是否要启用:

 public class MenuItemViewModel : NotifyPropertyBase
 {
     private string _header;

     public string Header
     {
         get => _header;
         set => SetProperty(ref _header, value);
     }

     private bool _isEnabled;
     public bool IsEnabled
     {
         get => _isEnabled;
         set => SetProperty(ref _isEnabled, value);
     }

     private RelayCommand _command;
     public RelayCommand Command
     {
         get => _command;
         set => SetProperty(ref _command, value);
     }

     private ObservableCollection<MenuItemViewModel> _menuItems;
     public ObservableCollection<MenuItemViewModel> MenuItems
     {
         get => _menuItems;
         set => SetProperty(ref _menuItems, value);
     }
 }

完成 MainViewModel 中整个 Menu 内容的资料:

 public class MainViewModel : NotifyPropertyBase
 {
     private ObservableCollection<MenuItemViewModel> _menuItems;
     public ObservableCollection<MenuItemViewModel> MenuItems
     {
         get => _menuItems;
         set => SetProperty(ref _menuItems, value);
     }      

     private ICommand _borderLoadedCommand;

     public ICommand BorderLoadedCommand
        {
            get
            {
                if (_borderLoadedCommand == null)
                {
                    _borderLoadedCommand = new RelayCommand((x) =>
                    {
                        var border = x as Border;
                        if (border != null)
                        {
                            if (!BorderDictionary.ContainsKey(border.Name))
                            {
                                BorderDictionary.Add(border.Name, border);
                            }
                        }
                    });
                }
                return _borderLoadedCommand;
            }
        }

     private Dictionary<string, Border> BorderDictionary { get; set; }

     private void InitialMenuItems()
        {
            MenuItems = new ObservableCollection<MenuItemViewModel>
            {
                new MenuItemViewModel
                {
                    Header = "File",
                    IsEnabled = false,
                    MenuItems = new ObservableCollection<MenuItemViewModel>
                    {
                        new MenuItemViewModel
                        {
                            Header = "New",
                            Command = new RelayCommand((x) => { Console.WriteLine("New"); }),
                            IsEnabled = true
                        },
                        new MenuItemViewModel
                        {
                            Header = "Open",
                            Command = new RelayCommand((x) => { Console.WriteLine("Open"); }),
                            IsEnabled = true
                        },
                        new MenuItemViewModel
                        {
                            Header = "Save",
                            Command = new RelayCommand((x) => { Console.WriteLine("Save"); }),
                            IsEnabled = true
                        },
                        new MenuItemViewModel
                        {
                            Header = "Exit",
                            Command = new RelayCommand((x) => { Console.WriteLine("Exit"); }),
                            IsEnabled = true
                        }
                    }
                },
                new MenuItemViewModel
                {
                    Header = "Edit",
                    IsEnabled = false,
                    MenuItems = new ObservableCollection<MenuItemViewModel>
                    {
                        new MenuItemViewModel
                        {
                            Header = "Copy",
                            Command = new RelayCommand((x) => { Console.WriteLine("Copy"); }),
                            IsEnabled = true
                        },
                        new MenuItemViewModel
                        {
                            Header = "Paste",
                            Command = new RelayCommand((x) => { Console.WriteLine("Paste"); }),
                            IsEnabled = true
                        }
                    }
                }
            };
        }

     public MainViewModel()
     {
         BorderDictionary = new Dictionary<string, Border>();
         InitialMenuItems();
     }
 }

MainViewModel 加入 Opening 与 Closing 的相对应命令:

 private ICommand _menuOpeningCommand;
 public ICommand MenuOpeningCommand
 {
     get
     {
         if (_menuOpeningCommand == null)
         {
             _menuOpeningCommand = new RelayCommand((x) =>
             {
                 foreach (var item in MenuItems)
                 {
                     if (BorderDictionary["lightSteelBlueBorder"].IsMouseOver)
                     {
                         if (item.Header == "File")
                         {
                             item.IsEnabled = true; 
                         }
                         else
                         {
                             item.IsEnabled = false;
                         }
                     }
                     else if (BorderDictionary["springGreenBorder"].IsMouseOver)
                     {
                         if (item.Header == "Edit")
                         {
                             item.IsEnabled = true;
                         }
                         else
                         {
                             item.IsEnabled = false;
                         }
                     }
                 }
             });
         }
         return _menuOpeningCommand;
     }
 }

 private ICommand _menuClosingCommand;
 public ICommand MenuClosingCommand
 {
     get
     {
         if (_menuClosingCommand == null)
         {
             _menuClosingCommand = new RelayCommand((x) =>
             {
                 Debug.WriteLine("MenuClosingCommand");
             });
         }
         return _menuClosingCommand;
     }
 }

因为结构简单,所以没考虑要把程式码写得太高大上,意思到就好。这样就完成整个 ViewModel 。

XAML 中主要就是把 MenuOpeningCommand 繫结到 Window.ContextMenuOpening 事件;MenuClosingCommand 则繫结到 Window.ContextMenuClosing 事件。相关部分如以下:

<Window x:Class="WpfMenuItemStorySample003.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
        xmlns:local="clr-namespace:WpfMenuItemStorySample003"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="ContextMenuOpening">
            <i:InvokeCommandAction Command="{Binding MenuOpeningCommand}"/>
        </i:EventTrigger>
        <i:EventTrigger EventName="ContextMenuClosing">
            <i:InvokeCommandAction Command="{Binding MenuClosingCommand}"/>
        </i:EventTrigger>
    </i:Interaction.Triggers>
    <Window.DataContext>
        <local:MainViewModel />
    </Window.DataContext>    
    <Window.ContextMenu>
        <ContextMenu ItemsSource="{Binding MenuItems}" x:Name="contextMenu">
            <ContextMenu.Resources >
                <HierarchicalDataTemplate ItemsSource="{Binding MenuItems}" DataType="{x:Type local:MenuItemViewModel}">
                    <TextBlock Text="{Binding Header}"/>
                </HierarchicalDataTemplate>
            </ContextMenu.Resources>
            <ContextMenu.ItemContainerStyle>
                <Style TargetType="MenuItem">
                    <Setter Property="Command" Value="{Binding Command}"/>
                    <Setter Property="IsEnabled" Value="{Binding IsEnabled}"/>
                </Style>
            </ContextMenu.ItemContainerStyle>
        </ContextMenu>
    </Window.ContextMenu>   

如此就完成了。Github 上的相关範例在此连结。

 

关于作者: 网站小编

码农网专注IT技术教程资源分享平台,学习资源下载网站,58码农网包含计算机技术、网站程序源码下载、编程技术论坛、互联网资源下载等产品服务,提供原创、优质、完整内容的专业码农交流分享平台。

热门文章

5 点赞(415) 阅读(67)