首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> 關於.NET

WPF 命令機制深度解析與 Prism 自定義 Command 實戰

     編輯:關於.NET

前言:WPF 內建命令的優勢與隱憂

Command 模式幾乎無所不在——複製、貼上、復原等操作皆是典型應用。它將「請求者」與「執行邏輯」解耦,並天然支援排隊、復原等進階需求。WPF 進一步封裝了 RoutedCommand 與 RoutedUICommand,並內建數十種常用命令、先進的路由機制,讓命令使用變得簡單高效。

但 WPF 內建命令真的能應付所有場景嗎?並非如此。這正是 Prism 另闢蹊徑打造自有 Command 體系的原因。本文將逐層剖析 WPF 命令的四大侷限,並示範如何打造支援多重處理器的自定義命令。

一、理解 RoutedCommand 中的「Routed」(路由)

WPF 提供 RoutedCommand 與 RoutedUICommand(繼承自前者)。MSDN 解釋:它實作 ICommand,並透過元素樹路由——Execute 與 CanExecute 不包含應用邏輯,而是引發事件沿元素樹尋找擁有 CommandBinding 的物件,由其處理程式執行實際邏輯。

路由尋找路徑實例

按鈕 XAML 皆為:<Button Command="Copy" .../>

  • 圖一:TextBox 文字被選中時,工具列「複製」按鈕啟用,但 TextBox 右側按鈕未啟用。
  • 圖二:FlowDocument 文字被選中時,FlowDocument 內按鈕與工具列按鈕皆啟用,TextBox 旁按鈕始終不可用。

為何行為不同?

觀察元素樹:針對 ApplicationCommands.Copy 設有 CommandBinding 的元素分別是「TextBox」與「FlowDocument(Viewer)」。

  • 「TextBox 右側按鈕」向根方向尋找,找不到 Binding → 永遠禁用。
  • 「FlowDocument 內按鈕」向根尋找,找到 FlowDocumentPageViewer 的 Binding → 正常運作。
  • 「工具列按鈕」採用特殊路由:從根(Window)向元素方向尋找。當元素位於 ToolBar、MenuBar 或 FocusManager.IsFocusScope="True" 時啟用此模式。

若給「TextBox 右側按鈕」加上 FocusManager.IsFocusScope="True",它也能被啟用(見圖三)。

侷限一:複雜 UI 下路由邏輯令人暈眩,易出錯。 折中做法是用 CommandTarget 明確指定目標,但在 Composite Application 中,View 需暴露目標或提供 API,且難以預知其他模組會用哪些元素作為 Target。

二、CommandBinding 與 View 層強耦合

CommandBinding 扮演紅娘角色,串聯 Command、CanExecute、Execute。但 CommandBindings 屬性僅存在於 UIElement、ContentElement、UIElement3D——全是可視元素。因為 WPF 內建支援的是 RoutedCommand,非視覺元素無路由可言。即便用 CommandManager.RegisterClassCommandBinding,傳入的仍須是上述三類視覺元素。

侷限二:CommandBinding 強綁定 View 層,無法直接掛接非 UI 類別的邏輯。 採用 MVC、MVP 模式時,只能在頂層視窗或 View 根節點為每個 Command 加 CommandBinding,再轉呼叫其他層方法,顯得笨重。

三、不支援命令組合

組合命令常見於「全部復原」按鈕——它不是簡單的迴圈呼叫多次 Undo,而是組合模式的實現(點擊「全部復原」後再點「重做」觀察行為即知)。可惜 WPF 內建 RoutedCommand 不支援命令組合。

侷限三:無組合模式支援。 例如介面有兩個文字框各自對應 Save 按鈕,若選單列想加「SaveAll」按鈕,處理起來相當棘手。

四、無法為單一 Command 掛接多組 CanExecute/Execute

RoutedCommand 一次只呼叫一個處理器(如兩個文字框中擁有焦點者)。無法一次命令將文字同時貼上到兩個文字框。

侷限四:不支援多重處理邏輯掛接。 這在複雜 UI 中極為實用,也是許多廠商自行實作命令模式的主因。

打造支援多重處理器的自定義 Command

自定義 Command 核心在兩點:

  1. 找地方儲存外部掛入的 CanExecute 與 Execute 委派。
  2. 正確觸發 CanExecuteChanged 事件——這最關鍵也最易出錯,若觸發不當會導致 UI 元素啟用/禁用狀態異常。

完整實作程式碼

public class GreetingsCommand : ICommand
{
    private Func<bool> canExecuteHandler = () => false;
    private Action<string> executeHandler = obj => { };

    public event EventHandler CanExecuteChanged = (sender, e) => { };

    public event Func<bool> CanExecuteHandler
    {
        add { canExecuteHandler += value; CanExecuteChanged(this, EventArgs.Empty); }
        remove { canExecuteHandler -= value; CanExecuteChanged(this, EventArgs.Empty); }
    }

    public event Action<string> ExecuteHandler
    {
        add { executeHandler += value; }
        remove { executeHandler -= value; }
    }

    public void Execute(object parameter)
    {
        Delegate[] delegates = executeHandler.GetInvocationList();
        foreach (Action<string> act in delegates)
        {
            act.Invoke(parameter != null ? parameter.ToString() : string.Empty);
        }
    }

    public bool CanExecute(object parameter)
    {
        Delegate[] delegates = canExecuteHandler.GetInvocationList();
        foreach (Func<bool> fun in delegates)
        {
            if (fun.Invoke()) return true;
        }
        return false;
    }

    public void RaiseCanExecuteChanged()
    {
        CanExecuteChanged(this, EventArgs.Empty);
    }
}

手動觸發 CanExecuteChanged 範例

當 Presenter 發現 Model 資料變更時,主動呼叫 RaiseCanExecuteChanged() 通知 UI 更新狀態:

void Model_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
    if (string.Equals(e.PropertyName, "Greetings"))
    {
        Greetings.RaiseCanExecuteChanged();
    }
}

結語

WPF 內建命令雖強,但在複雜合成應用、非 UI 邏輯解耦、命令組合與多重處理等場景下明顯力有未逮。自行實作 ICommand 並非難事,關鍵在於妥善管理委派集合與正確觸發 CanExecuteChanged。Prism 的 DelegateCommand/CompositeCommand 正是此思路的成熟封裝,實務上建議直接採用或參考其設計。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved