目录

WPF-CommandBinding 与 RoutedEvent 的关系.

一、先建立全局认知

┌─────────────────────────────────────────────────────────┐
│                    WPF 命令系统                          │
│                                                         │
│   ICommand          RoutedCommand         CommandBinding │
│   (接口)             (路由命令)              (命令绑定)    │
│                                                         │
│          底层全部依赖 RoutedEvent 实现传播                 │
└─────────────────────────────────────────────────────────┘

核心结论: RoutedCommand 的执行和判断能否执行,本质上是通过触发 RoutedEvent 在可视化树上传播,由 CommandBinidng 拦截并处理。

二、关键角色

2.1 ICommand 接口

public interface ICommand
{
    bool CanExecute(object parameter);
    void Execute(object parameter);
    event EventHandler CanExecuteChanged;
}

2.2 RoutedCommand(实现了 ICommand)

public class RoutedCommand : ICommand
{
    // RoutedCommand 自身没有业务逻辑,它的 Execute/CanExecute 内部做的是 -> 触发 RoutedEvent
    
    // 这两个就是 RoutedCommand 内部定义的路由事件
    public static readonly RoutedEvent ExecutedEvent;
    public static readonly RoutedEvent CanExecuteEvent;
    // (实际定义在 CommandManager 中)
}

2.3 CommandBinding

public class CommandBinding
{
    public ICommand Command { get; set; }
    
    // 这两个就是对 RoutedEvent 的处理器
    public event ExecutedRoutedEventHandler Executed;
    public event CanExecuteRoutedEventHandler CanExecute;
}

2.4 CommandManager (静态管理器)

public static class CommandManger
{
    // 这两个是真正的 RoutedEvent
    public static readonly RoutedEvent ExecutedEvent = 
        EventManager.RegisterRoutedEvent(
    		"Executed",
    		RoutingStrategy.Bubble,		// 冒泡
    		typeof(ExecutedRoutedEventHandler),
    		typeof(CommandManager));
    
    public static readonly RoutedEvent CanExecuteEvent = 
        EventManager.RegisterRoutedEvent(
    		"CanExecute",
    		RoutingStratrgy.Bubble,		// 冒泡
    		typeof(CanExecuteRoutedEventHandler),
    		typeof(CommandManager));
    
    // 还有对应的 Tunnel(Preview) 版本
    public static readonly RoutedEvent PreviewExecutedEvent;
    public static readonly RoutedEvent PreviewCanExecuteEvent;
}

三、完整执行流程

场景

<Window>
    <Window.CommandBindings>
        <CommandBinding Command="ApplicationCommands.Save"
                        Executed="Save_Executed"
                        CanExecute="Save_CanExecute"/>
    </Window.CommandBindings>
    
    <Grid>
        <StackPanel>
            <Button Command="ApplicationCommands.Save" Content="保存"/>
        </StackPanel>
    </Grid>
</Window>

点击按钮后发生了什么?

用户点击 Button
┌─────────────────────────────────────────────────────┐
│ Step 1: Button.OnClick()                            │
│         Button 发现自己绑定了 Command                 │
│         调用 RoutedCommand.Execute(parameter, Button)│
└──────────────────────┬──────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ Step 2: RoutedCommand.Execute() 内部                 │
│                                                     │
│   // 关键!不是直接执行业务逻辑                        │
│   // 而是触发 RoutedEvent!                          │
│                                                     │
│   ExecutedRoutedEventArgs args = new ...;            │
│   args.RoutedEvent = CommandManager.ExecutedEvent;   │
│   args.Command = this; // Save 命令                  │
│                                                     │
│   // 先触发 Tunnel                                   │
│   target.RaiseEvent(previewArgs);                   │
│   // 再触发 Bubble                                   │
│   target.RaiseEvent(args);                          │
└──────────────────────┬──────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ Step 3: RoutedEvent 开始在可视树中冒泡传播            │
│                                                     │
│   Button ──→ StackPanel ──→ Grid ──→ Window         │
│                                                     │
│   每经过一个节点,检查该节点的 CommandBindings         │
└──────────────────────┬──────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ Step 4: 到达 Window 时                               │
│                                                     │
│   Window 有 CommandBinding:                          │
│     Command = ApplicationCommands.Save               │
│                                                     │
│   匹配成功!                                         │
│     → 调用 Save_Executed 处理器                      │
│     → e.Handled = true                              │
│     → 冒泡停止                                       │
└─────────────────────────────────────────────────────┘

四、源码级解析

4.1 RoutedCommand.Execute 源码简化

public class RoutedCommand : ICommand
{
    void ICommand.Execute(object parameter)
    {
        Execute(parameter, FilterInputElement(Keyboard.FocusedElement));
    }
    
    public void Execute(object parameter, IInputElement target)
    {
        // 不是执行业务逻辑,而是触发路由事件!
        ExecuteImpl(parameter, target);
    }
    
    private void ExecuteImpl(object parameter, IInputElement target)
    {
        // 构造事件参数
        var args = new ExecutedRoutedEventArgs(this, parameter);
        
        // ========== 关键:触发 RoutedEvent ==========
        
        // 1. Tunnel 阶段
        args.RoutedEvent = CommandManager.PreviewExecutedEvent;
        target.RaiseEvent(args);
        
        // 2. Bubble 阶段
        if (!args.Handled)
        {
            args.RoutedEvent = CommandManager.ExecutedEvent;
            target.RaiseEvent(args);
        }
        
        // 如果没人处理,什么都不会发生
    }
}

4.2 RoutedCommand.CanExecute 源码简化

public bool CanExecute(object parameter, IInputElement target)
{
    // 同样是触发路由事件!
    var args = new CanExecuteRoutedEventArgs(this, parameter);
    
    // Tunnel
    args.RoutedEvent = CommandManager.PreviewCanExecuteEvent;
    target.RaiseEvent(args);
    
    // Bubble
    if (!args.Handled)
    {
        args.RoutedEvent = CommandManager.CanExecuteEvent;
        target.RaiseEvent(args);
    }
    
    return args.CanExecute;  // CommandBinding 会设置这个值
}

4.3 CommandBinding 如何拦截?

UIElement 的静态构造函数注册了 类级别处理器

static UIElement()
{
    // 注册类处理器,拦截命令相关的路由事件
    EventManager.RegisterClassHandler(
        typeof(UIElement),
        CommandManager.ExecutedEvent,
        new ExecutedRoutedEventHandler(OnExecutedThunk));
    
    EventManager.RegisterClassHandler(
        typeof(UIElement),
        CommandManager.CanExecuteEvent,
        new CanExecuteRoutedEventHandler(OnCanExecuteThunk));
        
    // Tunnel 版本也注册
    EventManager.RegisterClassHandler(
        typeof(UIElement),
        CommandManager.PreviewExecutedEvent,
        new ExecutedRoutedEventHandler(OnPreviewExecutedThunk));
    
    EventManager.RegisterClassHandler(
        typeof(UIElement),
        CommandManager.PreviewCanExecuteEvent,
        new CanExecuteRoutedEventHandler(OnPreviewCanExecuteThunk));
}

类处理器内部逻辑:

private static void OnExecutedThunk(object sender, ExecutedRoutedEventArgs e)
{
    UIElement uie = sender as UIElement;
    
    // 遍历该元素的 CommandBindings
    if (uie.CommandBindings != null)
    {
        foreach (CommandBinding binding in uie.CommandBindings)
        {
            // 命令匹配?
            if (binding.Command == e.Command)
            {
                // 调用 CanExecute 确认能否执行
                if (binding.CheckCanExecute(sender, e))
                {
                    // 调用 CommandBinding 的 Executed 处理器 
                    binding.OnExecuted(sender, e);
                    e.Handled = true;  // 阻止继续冒泡
                }
            }
        }
    }
}

五、完整流程图

                    RoutedCommand.Execute()
              ┌─── RaiseEvent ───┐
              │  PreviewExecuted  │  (Tunnel: Window → Button)
              │  ExecutedEvent    │  (Bubble: Button → Window)
              └────────┬─────────┘
         路由到每个 UIElement 节点
         ┌─────────────────────────────┐
         │  UIElement.OnExecutedThunk  │  (类级别处理器)
         │         ↓                   │
         │  遍历 CommandBindings       │
         │         ↓                   │
         │  匹配 Command?             │
         │    ├── No  → 继续冒泡       │
         │    └── Yes → 检查 CanExecute│
         │              ├── false → 跳过│
         │              └── true       │
         │                  ↓          │
         │         调用 Executed 处理器  │
         │         e.Handled = true    │
         │         冒泡停止             │
         └─────────────────────────────┘

六、CanExecute 的定时刷线机制

6.1 CommandManager.RequerySuggested

public static class CommandManager
{
    // 当 WPF 认为命令状态可能变化时触发
    public static event EventHandler RequerySuggested;
    
    // 手动触发刷线
    public static void InvalidateRequerySuggested()
    {
        // 触发所有命令重新查询 CanExecute
    }
}

6.2 自动触发时机

以下情况 WPF 自动调用 InvalidateRequerySuggested:

┌─────────────────────────────┐
│ • 焦点变化 (Focus Changed)   │
│ • 键盘输入                   │
│ • 鼠标点击                   │
│ • 属性变化                   │
│ • Dispatcher 空闲时          │
└─────────────────────────────┘
  对所有绑定了 RoutedCommand 的控件
  重新触发 CanExecuteEvent 路由事件
  CommandBinding.CanExecute 处理器被调用
  Button.IsEnabled 自动更新

6.3 CanExecute 路由事件流程

CommandManager.InvalidateRequerySuggested()
         
         
  Button 绑定的 RoutedCommand.CanExecute()
         
         
  RaiseEvent(CanExecuteEvent)      路由事件!
         
         
  冒泡传播: Button  StackPanel  Grid  Window
         
         
  Window  CommandBinding 匹配
         
         
  调用 Save_CanExecute 处理器
    e.CanExecute = true/false;
         
         
  Button.IsEnabled = e.CanExecute

七、对比 RoutedCommand vs 普通 ICommand (如 RelayCommand)

┌───────────────────────────────────────────────────────────┐
│                    RoutedCommand                          │
│                                                          │
│  Execute() ──→ RaiseEvent(ExecutedEvent)                 │
│                     │                                    │
│              RoutedEvent 在可视树冒泡                      │
│                     │                                    │
│              CommandBinding 拦截并执行                     │
│                                                          │
│  特点: 命令逻辑在 View 层 (CodeBehind)                    │
│  适用: 应用级命令 (Copy/Paste/Save...)                    │
└───────────────────────────────────────────────────────────┘

┌───────────────────────────────────────────────────────────┐
│                 RelayCommand / DelegateCommand            │
│                                                          │
│  Execute() ──→ 直接调用 Action<T> 委托                    │
│                                                          │
│  没有路由事件!没有可视树传播!                              │
│                                                          │
│  特点: 命令逻辑在 ViewModel 层                             │
│  适用: MVVM 模式                                          │
└───────────────────────────────────────────────────────────┘

详细对比:

特性 RoutedCommand RelayCommand
实现接口 ICommand ICommand
内部机制 RoutedEvent 路由传播 直接委托调用
业务逻辑位置 CommandBinding (View 层) ViewModel 中的方法
可视树传播 支持 不支持
CanExecute 刷新 CommandManager 自动触发 手动调用 RaiseCanExecuteChanged
MVVM 友好 不友好 友好
适用场景 系统命令、控件库 业务命令

八、自定义 RoutedCommand 完整示例

// 1. 定义命令
public static class MyCommands
{
    public static readonly RoutedCommand ExportCommand =
        new RoutedCommand("Export", typeof(MyCommands),
            new InputGestureCollection
            {
                new KeyGesture(Key.E, ModifierKeys.Control) // Ctrl+E
            });
}
<!-- 2. XAML 中使用 -->
<Window>
    <Window.CommandBindings>
        <CommandBinding Command="local:MyCommands.ExportCommand"
                        Executed="Export_Executed"
                        CanExecute="Export_CanExecute"/>
    </Window.CommandBindings>
    
    <Window.InputBindings>
        <KeyBinding Command="local:MyCommands.ExportCommand"
                    Gesture="Ctrl+E"/>
    </Window.InputBindings>
    
    <StackPanel>
        <Button Command="local:MyCommands.ExportCommand" Content="导出"/>
        <!-- Button 的 Click 最终触发:
             RoutedCommand.Execute()
               → RaiseEvent(ExecutedEvent)
                 → 冒泡到 Window
                   → CommandBinding 匹配
                     → Export_Executed 被调用 -->
    </StackPanel>
</Window>
// 3. CodeBehind 处理
private void Export_Executed(object sender, ExecutedRoutedEventArgs e)
{
    // sender = Window (CommandBinding 所在的元素)
    // e.Source = Button (触发命令的元素)
    // e.Command = ExportCommand
    // e.Parameter = CommandParameter
    
    MessageBox.Show("导出成功!");
}

private void Export_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
    e.CanExecute = true;  // 这个值会影响 Button.IsEnabled
}

九、总结关系图

┌─────────────────────────────────────────────────────────────┐
│                                                             │
│   RoutedCommand ─── "我不知道怎么执行,                      │
│        │              我只负责触发路由事件"                    │
│        │                                                    │
│        ▼                                                    │
│   CommandManager.ExecutedEvent  ←── 这是一个 RoutedEvent     │
│        │                                                    │
│        ▼                                                    │
│   可视树冒泡传播                                              │
│   Button → StackPanel → Grid → Window                       │
│        │                                                    │
│        ▼                                                    │
│   UIElement 类处理器拦截                                      │
│   遍历每个节点的 CommandBindings                              │
│        │                                                    │
│        ▼                                                    │
│   CommandBinding ─── "这个命令我认识!                        │
│        │               我来提供执行逻辑"                      │
│        │                                                    │
│        ▼                                                    │
│   Executed / CanExecute 处理器被调用                          │
│                                                             │
└─────────────────────────────────────────────────────────────┘

一句话总结:
  RoutedCommand 是"发号施令者"    → 通过 RoutedEvent 传播命令
  CommandBinding 是"执行者"       → 拦截路由事件并执行业务逻辑
  RoutedEvent 是"传令兵"          → 在可视树中传递命令消息