C# WPF – 利用「Attached Property」 把 RoutedEvent 接上 ICommand

本文說明怎樣把 DoubleClick 鏈接至 ICommand。方法不少。推薦使用 Attach Property 方式,由於它能把任何 RoutedEvent 接上任何 ICommand。程序員

以前寫過一篇博文關於 MVVM 中雙擊事件觸發 ICommand 的辦法,我說要麼你本身寫 Attached Property,要麼下載別人寫好的,好比支持 Collections 的 CommandBehaviors。我認爲這兩個辦法是比較好的。有網友說我沒有解釋清楚,由於我以爲 Attached Property 有點離題,跟 MVVM 關係不太大。反正有得用就好了。google

下面以 ListView 爲例。spa

 

1. InputBindings3d

先不說 Attached Property,看看有什麼辦法能夠把雙擊綁定到 ICommand。最簡單的辦法是 InputBindings。code

XAML:orm

<ListView.InputBindings><MouseBinding Gesture="LeftDoubleClick" Command=""/></ListView.InputBindings>

支持 KeyBinding (鍵盤),和 MouseBinding (鼠標)。能作到,若是隻須要管鍵盤或鼠標,這是比較簡單。xml

 

2. 隱形 Button (不建議)blog

我見過第二個辦法,隱形 Button, (Visibility=」Collapsed」),ICommand 綁定進去,ListView MouseDoubleClick 在視圖創建句柄,由它再觸發 Button 的 Command.Execute(object)。事件

XAML:ip

<Button Name="button1" Visibility="Collapsed" Command=""/><ListView  MouseDoubleClick="ListView_MouseDoubleClick"/>

Code:

privatevoid ListView_MouseDoubleClick(object sender, MouseButtonEventArgs e) {
    button1.Command.Execute(null);
}

這比較傻,不建議。

 

3. Attached Property

MSDN 有介紹怎樣爲控件添加新的屬性,這裏不詳細說了。關鍵是靜態方法 Set,和靜態 DependencyProperty。(MSDN 說 GET SET 都要,但其實寫 XAML 時只用到 SET,後續啓動後,你須要拿回屬性值才須要 GET)。

先看一下,Attached Property 是怎樣寫的,熱熱身:

CODE:

publicstaticclass MyProperty {
    publicstaticreadonly DependencyProperty ParameterProperty = 
        DependencyProperty.RegisterAttached(
            "Parameter",
            typeof(Object),
            typeof(MyProperty),
            new FrameworkPropertyMetadata(null)
        );
    publicstatic Object GetParameter(UIElement obj) {
        return obj.GetValue(ParameterProperty);
    }
    publicstaticvoid SetParameter(UIElement obj, Object value) {
        obj.SetValue(ParameterProperty, value);
    }
}

get、set 參數 UIElement 類型是爲了確保全部控件能用它。這 Parameter 沒有配置CallBack,這個MyProperty不對值變化作什麼動做,也不設置默認值,因此 RegisterAttached 時候 FrameworkPropertyMetadata是 null。

命名規範必須跟從,MSDN 有說明。當你但願在 XAML 這屬性叫作 Parameter 的時候(RegisterAttached 的第一個參數),它的get、set 方法必須命名爲 GetParameter 和 SetParameter。編譯後 XAML 可用。

XAML:

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:y="clr-namespace:WpfApplication1"
        Title="MainWindow" Height="350" Width="525"><Grid><ListView y:MyProperty.Parameter="ABC"/></Grid></Window>

新手記得加上正確的 XML namespace,xmlns:y="clr-namespace:WpfApplication1" 是由於我把MyProperty類放在這 WpfApplication1 項目的最外層。

知道了怎麼寫 Attached Property 以後,入正題,加入 ICommand。爲靈活性,作法是讓程序員配置要綁的 RoutedEvent ,和對應要觸發的 ICommand 同時做爲 DependencyProperty,讓程序員本身配置哪一個Event 接哪一個 ICommand。(注:handler 那 Dictionary 的作法,和 Detach Attach 是參考某大神的)。爲縮短代碼,只寫 ICommand 和 Event,沒寫 ICommand 的命令參數。

(如下代碼網上其實不少,也有不少版本,大同小異)

CODE:

 

using System.Collections.Generic;
using System.Windows;
using System.Windows.Input;

namespace WpfApplication1 {

    publicstaticclass CommandBehavior {

        // UI,Handler Listprivatestatic Dictionary<UIElement, RoutedEventHandler> handlers =new Dictionary<UIElement, RoutedEventHandler>();

        #region Command Propertypublicstaticreadonly DependencyProperty CommandProperty = 
            DependencyProperty.RegisterAttached(
                "Command",
                typeof(ICommand),
                typeof(CommandBehavior),
                new FrameworkPropertyMetadata() {
                    DefaultValue =null,
                    PropertyChangedCallback =new PropertyChangedCallback(OnCommandPropertyChanged)
                }
            );
        publicstatic ICommand GetCommand(UIElement obj) {
            return (ICommand)obj.GetValue(CommandProperty);
        }
        publicstaticvoid SetCommand(UIElement obj, ICommand value) {
            obj.SetValue(CommandProperty, value);
        }

        #endregion#region Event Propertypublicstaticreadonly DependencyProperty EventProperty = 
            DependencyProperty.RegisterAttached(
                "Event",
                typeof(RoutedEvent),
                typeof(CommandBehavior),
                new FrameworkPropertyMetadata() {
                    DefaultValue =null,
                    PropertyChangedCallback =new PropertyChangedCallback(OnEventPropertyChanged)
                }
            );
        publicstatic RoutedEvent GetEvent(DependencyObject obj) {
            return (RoutedEvent)obj.GetValue(EventProperty);
        }
        publicstaticvoid SetEvent(DependencyObject obj, RoutedEvent value) {
            obj.SetValue(EventProperty, value);
        }

        #endregion#region CallBacksprivatestaticvoid OnCommandPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) {
            UIElement element = obj as UIElement;
            ICommand oldCommand = args.OldValue as ICommand;
            ICommand newCommand = args.NewValue as ICommand;
            RoutedEvent routedEvent = element.GetValue(EventProperty) as RoutedEvent;

            Detach(element, routedEvent, oldCommand);
            Attach(element, routedEvent, newCommand);
        }

        privatestaticvoid OnEventPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) {
            UIElement element = obj as UIElement;
            RoutedEvent oldEvent = args.OldValue as RoutedEvent;
            RoutedEvent newEvent = args.NewValue as RoutedEvent;
            ICommand command = element.GetValue(CommandProperty) as ICommand;

            Detach(element, oldEvent, command);
            Attach(element, newEvent, command);
        }

        #endregionprivatestaticvoid Attach(UIElement element, RoutedEvent Event, ICommand command) {
            if (Event !=null&& element !=null&& command !=null) {
                RoutedEventHandler InvokeCommandHandler =new RoutedEventHandler(delegate {
                    command.Execute(null);
                });
                handlers.Add(element, InvokeCommandHandler);
                element.AddHandler(Event, InvokeCommandHandler);
            }
        }

        privatestaticvoid Detach(UIElement element, RoutedEvent Event, ICommand command) {
            if (Event !=null&& element !=null&& command !=null) {
                RoutedEventHandler handler = handlers[element];
                if (handler !=null) {
                    element.RemoveHandler(Event, handler);
                    handlers.Remove(element);
                }
            }
        }
    }
}

 

 

跟以前那個 Parameter 例子很像,只是同一個靜態類,作了兩個屬性,一個叫作 Event,一個叫作 Command。另外,多了一個 Dictionary,還有,此次 Event 和 Command 的變化,都註冊了 PropertyChangedCallback 的句柄。最下面的 Attach Detach 的 private 幫助方法,只是重構時從PropertyChangedCallBack 的句柄抽出來而已。

控件、事件、命令,三者是一塊兒的組合,某 UIElement 的某 RoutedEvent 觸發到某 ICommand 的 Execute。但RoutedEvent 觸發的是 RoutedEventHandler 句柄,不是 ICommand。因此這個靜態類所作最重要的事,見 private static void Attach(),就是建立新的 RoutedEventHandler,讓它執行委託運行 command 的 Execute,而後把準備好 RoutedEventHandler 以後粘上 UIElement,即 AddHandler(RoutedEvent,RoutedEventHandler)。把這搭配,UIElement 和已作好ICommand委託的 RoutedEventHandler,放在 Dictionary,是爲了 Detach 時候找回。

要作 Detach 是由於,DependencyProperty 的值是能變化的(上例中是 Event和Command這兩個,都能在運行時變),不必定是寫死在 XAML,好比 {Binding Path=XXX} 這狀況。萬一 Command 變了,或者 RoutedEvent 變了,上述作好了的搭配就失效,是須要 RemoveHandler 而後從新組合。因此,PropertyChangedCallBack 所作的,都是先 Detach 舊值(args.OldValue),而後再 Attach 粘上新值(args.NewValue)。無論 Event 變仍是 Command 變,都須要如此。

這靜態類的解釋到此爲止。不復雜。用法以下:

XAML:

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:y="clr-namespace:WpfApplication1"
        Title="MainWindow" Height="350" Width="525"><Grid><ListView 
            y:CommandBehavior.Command="{Binding Path=TestCommand}"
            y:CommandBehavior.Event="ListView.MouseDoubleClick"></ListView></Grid></Window>

由於一開始設置了Command 和 Event 的默認值爲 null (RegisterAttached 時候的 FrameworkPropertyMetadata 內,DefaultValue),因此 XAML 運行寫入值時,值變化觸發 CallBack,完成了咱們須要的鏈接。

最後,改一下 CommandBehavior,讓它能接受參數,傳過去 ICommand。由於 ICommand 的命令參數類型是 object,因此寫的 CommandParameter 類型也是 object。

完整版本 CODE:

using System.Collections.Generic;
using System.Windows;
using System.Windows.Input;

namespace WpfApplication1 {

    publicstaticclass CommandBehavior {

        // UI,Handler Listprivatestatic Dictionary<UIElement, RoutedEventHandler> handlers =new Dictionary<UIElement, RoutedEventHandler>();

        #region Command Propertypublicstaticreadonly DependencyProperty CommandProperty = 
            DependencyProperty.RegisterAttached(
                "Command",
                typeof(ICommand),
                typeof(CommandBehavior),
                new FrameworkPropertyMetadata() {
                    DefaultValue =null,
                    PropertyChangedCallback =new PropertyChangedCallback(OnCommandPropertyChanged)
                }
            );
        publicstatic ICommand GetCommand(UIElement obj) {
            return (ICommand)obj.GetValue(CommandProperty);
        }
        publicstaticvoid SetCommand(UIElement obj, ICommand value) {
            obj.SetValue(CommandProperty, value);
        }

        #endregion#region Event Propertypublicstaticreadonly DependencyProperty EventProperty = 
            DependencyProperty.RegisterAttached(
                "Event",
                typeof(RoutedEvent),
                typeof(CommandBehavior),
                new FrameworkPropertyMetadata() {
                    DefaultValue =null,
                    PropertyChangedCallback =new PropertyChangedCallback(OnEventPropertyChanged)
                }
            );
        publicstatic RoutedEvent GetEvent(DependencyObject obj) {
            return (RoutedEvent)obj.GetValue(EventProperty);
        }
        publicstaticvoid SetEvent(DependencyObject obj, RoutedEvent value) {
            obj.SetValue(EventProperty, value);
        }

        #endregion#region CommandParameter Propertypublicstaticreadonly DependencyProperty CommandParameterProperty = 
            DependencyProperty.RegisterAttached(
                "CommandParameter",
                typeof(object),
                typeof(CommandBehavior),
                new FrameworkPropertyMetadata(null)
            );
        publicstaticobject GetCommandParameter(UIElement obj) {
            return obj.GetValue(CommandParameterProperty);
        }
        publicstaticvoid SetCommandParameter(UIElement obj, object value) {
            obj.SetValue(CommandParameterProperty, value);
        }

        #endregion#region CallBacksprivatestaticvoid OnCommandPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) {
            UIElement element = obj as UIElement;
            ICommand oldCommand = args.OldValue as ICommand;
            ICommand newCommand = args.NewValue as ICommand;
            RoutedEvent routedEvent = element.GetValue(EventProperty) as RoutedEvent;
            object commandParameter = element.GetValue(CommandParameterProperty);

            Detach(element, routedEvent, oldCommand);
            Attach(element, routedEvent, newCommand, commandParameter);
        }

        privatestaticvoid OnEventPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) {
            UIElement element = obj as UIElement;
            RoutedEvent oldEvent = args.OldValue as RoutedEvent;
            RoutedEvent newEvent = args.NewValue as RoutedEvent;
            ICommand command = element.GetValue(CommandProperty) as ICommand;
            object commandParameter = element.GetValue(CommandParameterProperty);

            Detach(element, oldEvent, command);
            Attach(element, newEvent, command, commandParameter);
        }

        #endregionprivatestaticvoid Attach(UIElement element, RoutedEvent Event, ICommand command, object commandParameter) {
            if (Event !=null&& element !=null&& command !=null) {
                RoutedEventHandler InvokeCommandHandler =new RoutedEventHandler(delegate {
                    command.Execute(commandParameter);
                });
                handlers.Add(element, InvokeCommandHandler);
                element.AddHandler(Event, InvokeCommandHandler);
            }
        }

        privatestaticvoid Detach(UIElement element, RoutedEvent Event, ICommand command) {
            if (Event !=null&& element !=null&& command !=null) {
                RoutedEventHandler handler = handlers[element];
                if (handler !=null) {
                    element.RemoveHandler(Event, handler);
                    handlers.Remove(element);
                }
            }
        }
    }
}

 

完整版本的 CommandBehavior 在 XAML 用法:

XAML:

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:y="clr-namespace:WpfApplication1"
        Title="MainWindow" Height="350" Width="525"><Grid><ListView 
            y:CommandBehavior.Command="{Binding Path=TestCommand}"
            y:CommandBehavior.Event="ListView.MouseDoubleClick"
            y:CommandBehavior.CommandParameter="TestParameter"/></Grid></Window>

Attach Property 方法介紹到此爲止。點擊這裏下載最終版本的代碼

這類簡單,用來解釋工做原理比較合適。但我以前博文沒用這個類,由於以上代碼,有一個明顯缺陷。源於 Dictionary<UIElement, RoutedEventHandler> 這樣的簡單搭配,UIElement 做爲 Key。並且 CommandBehavior 這靜態類,沒有集合暴露給 XAML。這意味着,一個控件,只能設置一次。好比,當一個控件你有兩個 RoutedEvent 但願綁定到兩個ICommand,這代碼不支持。

爲了解決這問題,網上已經有不少人寫好了一個叫作 CommandBehaviorCollection 的類(懶到搜索都不想搜的,點擊這裏),不少不一樣的版本,功能其實都同樣,讓你在 XAML 內一個控件能同時配置多個 Event 和 Command 的組合。這個類就是我在以前博文上用到的那個。我不打算解釋裏面內容,其工做基本原理,與上述代碼一摸同樣,只是它暴露了集合讓你在 XAML 內填多個組合。

我在這羣裏,歡迎加入交流:
開發板玩家羣 578649319開發板玩家羣 578649319
硬件創客 (10105555)硬件創客 (10105555)

相關文章
相關標籤/搜索