WPF中改進(jìn)自定義Command一些想法
在WPF中定義的接口為ICommand,叫這個(gè)名字顯而易見(jiàn),為什么不叫IXXXCommand,比如ICurryCommand,不好意思微軟的WPF控件不是我做的,否則我會(huì)考慮這一命名方案。當(dāng)然如果你感覺(jué)微軟定義的有缺陷準(zhǔn)備自己著手打造一套全新控件包括新的ICommand接口,那么您可以就此跳過(guò)了。
- publicinterfaceICommand
 - {
 - eventEventHandlerCanExecuteChanged;
 - boolCanExecute(objectparameter);
 - voidExecute(objectparameter);
 - }
 
對(duì)于Execute方法不難理解,對(duì)于命令模式來(lái)說(shuō)有個(gè)統(tǒng)一的處理函數(shù)是必須的,自然包括可能的傳參;而對(duì)于CanExecute從字面意義上就可以了解到——方法能不能執(zhí)行,實(shí)際意義在于吃不到就不要讓人看到,執(zhí)行了函數(shù)然后告訴你由于啥啥狀況實(shí)際上不能運(yùn)行,還不如一開(kāi)始就告訴別人這個(gè)函數(shù)執(zhí)行不了,至于為什么執(zhí)行不了,那就要自己想辦法通知咯;那為什么要有個(gè)事件呢?打個(gè)比方店里貨賣(mài)完了我不能買(mǎi)了,但進(jìn)貨后可以買(mǎi),可什么時(shí)候能進(jìn)到貨我并不知道,需要店家通知。讓店家通知這個(gè)動(dòng)作在程序來(lái)說(shuō)就是注冊(cè)事件,告訴命令的發(fā)出者什么時(shí)候才能執(zhí)行,這個(gè)也就是CanExecuteChanged的由來(lái);在WPF中對(duì)于控件不能CanExecute的做法通常都是把控件的IsEnable設(shè)成False,當(dāng)注冊(cè)的CanExecuteChanged得到回應(yīng)時(shí)才設(shè)置成True。
- //Summary:
 - //Definesanobjectthatknowshowtoinvokeacommand.
 - publicinterfaceICommandSource
 - {
 - //Summary:
 - //Getsthecommandthatwillbeexecutedwhenthecommandsourceisinvoked.
 - ICommandCommand{get;}
 - //
 - //Summary:
 - //Representsauserdefineddatavaluethatcanbepassedtothecommandwhen
 - //itisexecuted.
 - //
 - //Returns:
 - //Thecommandspecificdata.
 - objectCommandParameter{get;}
 - //
 - //Summary:
 - //Theobjectthatthecommandisbeingexecutedon.
 - IInputElementCommandTarget{get;}
 - }
 
作為命令的發(fā)出者,也就是調(diào)用者,微軟也給出了一個(gè)接口,自定義Command意義不必說(shuō)了,它通常都是在控件的Click中執(zhí)行,最常見(jiàn)的Button,CheckBox,RadioButton(注意這些控件實(shí)際都繼承于ButtonBase,所以你需要制作有Click動(dòng)作的控件不是有特別需求建議從他繼承)MenuItem,CommandParameter就是Command中Execute方法的參數(shù)。最后一個(gè)屬性CommandTarget是為了解決類(lèi)似這種情況:右鍵菜單上有個(gè)粘貼命令,執(zhí)行命令后是把剪貼板的內(nèi)容復(fù)制到相對(duì)應(yīng)的文字框中,而不是把剪貼板的內(nèi)容拷貝到右鍵菜單上,這里的CommandTarget便是那個(gè)文字框,CommandTarget默認(rèn)為當(dāng)Command是RoutedCommand才能使用;當(dāng)CommandTarget為空時(shí),MSDN的說(shuō)法是找到當(dāng)前焦點(diǎn)所對(duì)應(yīng)的控件(KeyboardFoucs),如點(diǎn)擊Button,命令執(zhí)行后得到焦點(diǎn)的應(yīng)該是你點(diǎn)擊的那個(gè)Button,可我Reflector的結(jié)果貌似CommandTarget為空時(shí),直接用了Command發(fā)出的者,雖然都是同一個(gè)Button,但總感覺(jué)有點(diǎn)怪。
說(shuō)了這些你是不是覺(jué)得這三個(gè)屬性的值應(yīng)該都是外部給的,可微軟居然定義為get只讀,我也百思不得其解,這里還值得一提的是ICommandSource只是一種規(guī)范,和命令必須繼承ICommand不同(要不然至少微軟的控件不認(rèn)),不是必須的,可為了規(guī)范期間建議繼承該接口,方便他人閱讀理解也好為一些操作統(tǒng)一做法。
內(nèi)置Command
前面說(shuō)了ICommand只是一個(gè)接口,好處是你可以隨意實(shí)現(xiàn),壞處便是每次使用都需要建立一個(gè)實(shí)現(xiàn)它的具體類(lèi),那么微軟有沒(méi)有給個(gè)默認(rèn)的實(shí)現(xiàn)類(lèi),答案是肯定的,它叫做RoutedCommand,不用不知道,一用嚇一跳,默認(rèn)的這個(gè)RoutedCommand類(lèi)居然不能傳委托,為什么說(shuō)不能穿委托很詫異,上面說(shuō)了Command的主要功能是有個(gè)函數(shù)讓人執(zhí)行,可函數(shù)不傳給他,你讓別人執(zhí)行啥?(派生于他的類(lèi)幾乎啥也做不了——他沒(méi)有任何虛方法),微軟這里又用了一招——CommandBinding,他彌補(bǔ)了RoutedCommand在功能上的缺陷,可以為ICommand指定CanExecute委托和Execute委托,RoutedCommand是ICommand的具體實(shí)現(xiàn),自然可以舒舒服服的享用,不過(guò)CommandBinding的出現(xiàn)真的只為了RoutedCommand的亡羊補(bǔ)牢?
試想有這樣一種要求,在xaml中有個(gè)Grid,Grid中有個(gè)Button,點(diǎn)擊Button需要Grid背景變色??吹竭@個(gè)要求很多人可能笑了,很簡(jiǎn)單嘛,注冊(cè)Button的Click事件,為Grid取個(gè)名字,在Click的事件委托中為Grid的Background賦值,沒(méi)錯(cuò)。
假使把這個(gè)Button封裝到一個(gè)UserControl中,Grid中包含的只是UserControl,這個(gè)時(shí)候依舊需要點(diǎn)擊Button來(lái)修改Grid的顏色,有些人已經(jīng)破口而出了,在UserControl中定義一個(gè)事件,在Button的Click事件委托中調(diào)用這個(gè)事件,一切看起來(lái)都很輕松;
那么現(xiàn)在假設(shè)Button被裝到一個(gè)Style中,我繼承的不是UserControl而是Control,你可能會(huì)聳聳肩,說(shuō)道那只好注冊(cè)事件路由就可以了比如this.AddHandle(Button.ClickEvent,XXDelegate);可如果我現(xiàn)在里面放的按鈕不是一個(gè)而是一百個(gè)呢?我只需要其中的一個(gè)有改變Grid的功能。為Button取個(gè)名字然后判斷也是個(gè)辦法,用Button上的文字顯然會(huì)受到多語(yǔ)言的困擾。
最后這個(gè)為Grid改變背景的功能還被放到另外50個(gè)按鈕上以及一些MenuItem上,甚至需要Ctrl+K這樣的快捷鍵來(lái)實(shí)現(xiàn),您是否還有熱情為他們一一取名判斷?
那用CommandBinding怎么解決呢?綜觀這些按鈕,菜單,快捷鍵的作用只有一個(gè),就是為Grid改變背景,那么換句話說(shuō)他們執(zhí)行的是同一個(gè)命令,只要讓Grid知道有人執(zhí)行了這個(gè)命令,然后得到這個(gè)消息后自己改變背景就可以了,也可以理解為命令沿可視樹(shù)向上通知直到有人接收。
命令的向上傳遞,容易讓我們想到事件路由,事實(shí)也是如此,我們知道事件路由首先得定義一個(gè)RoutedEvent,事件發(fā)出者通過(guò)方法RaiseEvent傳遞RoutedEventArgs參數(shù)通知,當(dāng)RoutedEventArgs中的Handled屬性為T(mén)rue時(shí),會(huì)阻止之后的事件執(zhí)行,除非事件在開(kāi)始的時(shí)候是通過(guò)AddHandle方法注冊(cè),且把第三個(gè)參數(shù)handledEventsToo設(shè)為了True,那么這個(gè)RoutedEvent在哪里?這個(gè)我們又要說(shuō)到CommandManager這個(gè)類(lèi),他在其中定義了PreviewExecutedEvent,ExecutedEvent,PreviewCanExecuteEvent等事件,通過(guò)Reflector可以看到UIElement的RegisterEvents方法中有這樣的定義(其中的type指的是typeof(UIElement)):
也就是說(shuō)凡是派生于UIElement的子類(lèi)都可以受到這個(gè)路由傳遞。同理沒(méi)有繼承與UIElement的類(lèi)只要注冊(cè)以上事件便可接受Command的響應(yīng)。大家具體實(shí)做后會(huì)發(fā)現(xiàn),CommandManager.ExecutedEvent的參數(shù)ExecutedRoutedEventArgs類(lèi)它的構(gòu)造函數(shù)是internal,意思就是說(shuō)我們不能通過(guò)普通的new來(lái)創(chuàng)建,通常在我們習(xí)慣性的問(wèn)候了一些女性后,便開(kāi)始接受這樣無(wú)奈的事實(shí)——使用RoutedCommand是官方唯一指定的具備引發(fā)CommandManager.ExecutedEvent條件的途徑(可以實(shí)例化ExecutedRoutedEventArgs,內(nèi)部關(guān)系到處存在,唉…)。
說(shuō)來(lái)這些或許有人開(kāi)始點(diǎn)頭,之后又開(kāi)始疑惑這和CommandBinding有啥關(guān)系,完全是CommandManager和RoutedCommand的那點(diǎn)事,他怎么進(jìn)行第三者插足來(lái)運(yùn)行那些委托方法?以UIElement.OnExecutedThunk來(lái)做說(shuō)明,它其實(shí)調(diào)用的是CommandManager.OnExecuted(objectsender,ExecutedRoutedEventArgse)sender就是當(dāng)前的UIElement,這個(gè)方法會(huì)瞧瞧UIElement上的CommandBindingCollection看其中的CommandBinding包含的Command有沒(méi)有和e中的Command相同的,因?yàn)槭鞘录酚?,他可根?jù)可視樹(shù)往上找,一個(gè)不成再看下一個(gè),如果有則執(zhí)行CommandBinding的OnExecuted,也就是運(yùn)行委托傳入的方法,之后把e.Handled設(shè)為T(mén)rue,這使得我們同一個(gè)Command的委托方法只能用CommandBinding一次,連續(xù)定義幾個(gè)相同委托的CommandBinding沒(méi)有任何意義,同理CommandManager.AddExecutedHandler加入的委托也不能引發(fā),除非顯示的用AddHanlde把第三個(gè)參數(shù)設(shè)為T(mén)rue——
uiElementControl.AddHanlde(CommandManager.ExecutedEvent,xxxDelegate,true),設(shè)成True的后果是這個(gè)委托每次必執(zhí)行。
擴(kuò)展自定義Command
對(duì)于程序來(lái)說(shuō),我們希望把業(yè)務(wù)邏輯和呈現(xiàn)盡量分離,以期實(shí)現(xiàn)不同UI的相同調(diào)用,一個(gè)程序B/S架構(gòu)能用,C/S架構(gòu)也能用,或許有人說(shuō)了:這不就是要把業(yè)務(wù)封裝成個(gè)DLL或是WebService嘛,我們?cè)谟肳CF完全沒(méi)問(wèn)題。是的,這樣可以更方便的測(cè)試并增加代碼的重用性降低出錯(cuò)幾率。隨著人口的增長(zhǎng),剩余勞動(dòng)力的增加,各種分工愈趨細(xì)化…等等,先不要仍雞蛋,開(kāi)個(gè)玩笑也不行?拿Web前端打比方,需要的技術(shù)可能有javascript、vbScript、css、html、圖片處理(如PS),在有些狀況下這事我們?nèi)噶耍趦?nèi)心深處或許有一個(gè)聲音:我需要美工;潛臺(tái)詞是沒(méi)有美術(shù)細(xì)胞。
我們希望美工干什么?界面美化?廢話?界面美化包括頁(yè)面布局、色調(diào)搭配、圖片修改等,那么之上的這些技術(shù)中留下的可能只剩javascript和vbscript了,那javascript能干什么?在ajax沒(méi)有誕生的歲月,有段時(shí)間他已經(jīng)淪落到做些簡(jiǎn)單的動(dòng)畫(huà)效果和動(dòng)態(tài)增加表單元素之類(lèi)的地步,頁(yè)面回調(diào)刷新,太復(fù)雜的也沒(méi)有必要,甚至于在那段時(shí)間我都有聽(tīng)到一些少用javascript的言論,現(xiàn)在反觀自然是毛骨悚然,如同回望50年前的生活,也是不可想象的,時(shí)代在進(jìn)步,思想也在變化。
Ajax中數(shù)據(jù)一般是傳遞json,由于http的局限我們通過(guò)字符串來(lái)模擬對(duì)象,一個(gè)對(duì)象通常對(duì)應(yīng)固定的UI,當(dāng)對(duì)象數(shù)據(jù)發(fā)生變化時(shí)UI也能夠發(fā)現(xiàn)變化,我們希望有份模板可以留給美工修改,假設(shè)對(duì)象為Employee上面有個(gè)屬性為Name,那么UI上會(huì)有個(gè)div它的innerHTML為其對(duì)應(yīng)呈現(xiàn),Name為王五,innerHTML也為王五,Name為張三時(shí),innerHTML自動(dòng)的也更改為張三,這種在Web上近乎的天方夜譚,但在WPF中卻成為了可能,甚至于Employee上有個(gè)行為Walk(),在UI上操作按鈕執(zhí)行的可以是Employee這個(gè)行為。不過(guò)調(diào)用這個(gè)行為的方式我們成為Command。
既然是數(shù)據(jù)對(duì)象那么它可以完全不理會(huì)UI的呈現(xiàn)方式,在WPF你要把Name放到一個(gè)TextBlock上還是一個(gè)Label上,這個(gè)Label的顏色是紅是白可以由界面設(shè)計(jì)者說(shuō)了算,這稱為MVVM模式??蓪?duì)于行為WPF還不能完全綁定到對(duì)象上的方法,要把方法轉(zhuǎn)換到Command中去,也就是說(shuō)要把方法轉(zhuǎn)換成ICommand的Execute的形式——void,且只能傳一個(gè)參數(shù)。而且這樣的話RoutedCommand也就失去了功效,他不能傳委托,對(duì)象又不知道具體的前端控件不能使用CommandBinding,這時(shí)我們需要自定一個(gè)Command
- ///Acommandwhosesolepurposeisto
 - ///relayitsfunctionalitytoother
 - ///objectsbyinvokingdelegates.The
 - ///defaultreturnvaluefortheCanExecute
 - ///methodis'true'.
 - ///</summary>
 - publicclassDelegateCommand:ICommand
 - {
 - #regionFields
 - readonlyAction<object>_execute;
 - readonlyPredicate<object>_canExecute;
 - #endregion//Fields
 - #regionConstructors
 - ///<summary>
 - ///Createsanewcommandthatcanalwaysexecute.
 - ///</summary>
 - ///<paramname="execute">Theexecutionlogic.</param>
 - publicDelegateCommand(Action<object>execute)
 - :this(execute,null)
 - {
 - }
 - ///<summary>
 - ///Createsanewcommand.
 - ///</summary>
 - ///<paramname="execute">Theexecutionlogic.</param>
 - ///<paramname="canExecute">Theexecutionstatuslogic.</param>
 - publicDelegateCommand(Action<object>execute,Predicate<object>canExecute)
 - {
 - if(execute==null)
 - thrownewArgumentNullException("execute");
 - _execute=execute;
 - _canExecute=canExecute;
 - }
 - #endregion//Constructors
 - #regionICommandMembers
 - [DebuggerStepThrough]
 - publicboolCanExecute(objectparameter)
 - {
 - return_canExecute==null?true:_canExecute(parameter);
 - }
 - publiceventEventHandlerCanExecuteChanged
 - {
 - add{CommandManager.RequerySuggested+=value;}
 - remove{CommandManager.RequerySuggested-=value;}
 - }
 - publicvoidExecute(objectparameter)
 - {
 - _execute(parameter);
 - }
 - #endregion//ICommandMembers對(duì)于其中的
 - publiceventEventHandlerCanExecuteChanged
 - {
 - add{CommandManager.RequerySuggested+=value;}
 - remove{CommandManager.RequerySuggested-=value;}
 - }
 
您可能有點(diǎn)疑惑,我們知道CanExecuteChanged是給命令執(zhí)行體通知是否可執(zhí)行命令用的(譬如控件的IsEnable屬性是否更改),也就是上面比方中店里貨到了,店家通知我可以買(mǎi)貨了,可通知必須要對(duì)應(yīng)的Command去發(fā)出,且一個(gè)個(gè)發(fā)出這便有些麻煩,這個(gè)時(shí)候我們需要把事件注冊(cè)到全局統(tǒng)一發(fā)出,CommandManager.RequerySuggested就給我們提供了這樣方便,注冊(cè)后可用CommandManager.InvalidateRequerySuggested()來(lái)統(tǒng)一引發(fā),當(dāng)在主線程外使用該方法注意需要這樣來(lái)調(diào)用
- Application.Current.Dispatcher.BeginInvoke((Action)delegate()
 - {
 - CommandManager.InvalidateRequerySuggested();
 - }
 
System.Windows.Threading.DispatcherPriority.Normal);繼承于UIElement的類(lèi),當(dāng)鼠標(biāo)點(diǎn)擊、鍵盤(pán)按下或鼠標(biāo)滾輪也會(huì)觸發(fā)該方法。
你可能有個(gè)疑問(wèn),事件可是強(qiáng)引用,一旦加入這個(gè)全局的事件,是否會(huì)發(fā)生內(nèi)存泄露,這點(diǎn)你可以放心,全局事件只是看上去,實(shí)際上它是用WeakReference來(lái)存放加入的委托,執(zhí)行委托的時(shí)候判斷WeakReference的Target是否為空,為空則清除,你可以用工具看下CommandManager的源碼就完全清楚了,RoutedCommand也是用這個(gè)全局方式來(lái)處理。同理如果你認(rèn)為統(tǒng)一引發(fā)效能太差或沒(méi)有必要也可以自己手動(dòng)引發(fā),如Prism中的DelegateCommand就需要自己調(diào)用他的RaiseCanExecuteChanged函數(shù)來(lái)引發(fā),值得注意的是Prism中的事件沒(méi)有采用弱引用機(jī)制,你的Command和UI多次切換會(huì)有內(nèi)存泄漏,建議使用微軟在MVVMDEMO中的DelegateCommand,它在構(gòu)造函數(shù)中還有參數(shù)來(lái)開(kāi)關(guān)是否要加入CommandManager.RequerySuggested,此Command已在在附錄中。
到這里大家似乎已經(jīng)很滿意了,差不多自己也就是這么做的,可有沒(méi)有想過(guò),這樣的話CommandBinding是用不了的,畢竟有時(shí)候需要用它做些UI層的攔截,如命令執(zhí)行完之后可以把當(dāng)前對(duì)話框關(guān)閉這也屬于UI層面的,那CommandBinding為什么用不了?我們沒(méi)有引發(fā)CommandManager上的事件像CommandManager.ExecutedEvent。沒(méi)有引發(fā)也就沒(méi)有路由事件,沒(méi)有糧食怎么吃肉?通過(guò)CommandManager.AddExecutedHandler加入的委托也是用不的了,都是用的CommandManager.ExecutedEvent事件。
不能引發(fā)路由,就讓能引發(fā)的來(lái)做。已經(jīng)有人迫不及待了:不就new個(gè)RoutedCommand,然后把我們自定義的Command中的方法剝離出來(lái)賦給CommandBinding。這里需要用到附加屬性,前端需要這樣定義,而不能直接為Command賦值:
- <Buttonlocal:CommandAttachBehavior.Command="{BindingSave}">Save</Button>CommandAttachBehavior類(lèi)如下:
 - publicstaticclassVisualExtension
 - {
 - publicstaticTFindAncestor<T>(thisVisualvisual,Predicate<T>predicate)whereT:Visual
 - {
 - while(visual!=null&&!predicate(visualasT))
 - {
 - visual=(Visual)VisualTreeHelper.GetParent(visual);
 - }
 - return(T)visual;
 - }
 - }
 - ///<summary>
 - ///AttachedpropertythatcanbeusedtocreateabindingforaCommandModel.Setthe
 - ///CommandAttachBehavior.CommandpropertytoaCommandModel.
 - ///</summary>
 - publicstaticclassCommandAttachBehavior
 - {
 - publicstaticreadonlyDependencyPropertyCommandProperty
 - =DependencyProperty.RegisterAttached("Command",typeof(ICommand),typeof(CommandAttachBehavior),
 - newPropertyMetadata(newPropertyChangedCallback(OnCommandInvalidated)));
 - publicstaticICommandGetCommand(DependencyObjectsender)
 - {
 - return(ICommand)sender.GetValue(CommandProperty);
 - }
 - publicstaticvoidSetCommand(DependencyObjectsender,ICommandcommand)
 - {
 - sender.SetValue(CommandProperty,command);
 - }
 - ///<summary>
 - ///CallbackwhentheCommandpropertyissetorchanged.
 - ///</summary>
 - privatestaticvoidOnCommandInvalidated(DependencyObjectsender,DependencyPropertyChangedEventArgse)
 - {
 - varcommand=e.NewValueasICommand;
 - if(command==null)
 - return;
 - varel=senderasUIElement;
 - if(el==null)
 - thrownewArgumentNullException();
 - if(elisICommandSource)
 - {
 - varroutedCommand=newRoutedCommand();
 - vartype=el.GetType();
 - varpropInfo=type.GetProperty("Command");
 - propInfo.SetValue(el,command,null);
 - el.Dispatcher.BeginInvoke((Action)delegate
 - {
 - varelParent=el.FindAncestor<UIElement>(u=>!(uisICommandSource));
 - if(elParent==null)
 - return;
 - elParent.CommandBindings.Add(newCommandBinding(routedCommand,
 - (target,arg)=>
 - {
 - command.Execute(arg.Parameter);
 - },
 - (target,arg)=>
 - {
 - arg.CanExecute=command.CanExecute(arg.Parameter);
 - }));
 - },DispatcherPriority.Render);
 - }
 - }
 - }
 
大家可能問(wèn)了用CommandBinding用就用了,那為什么還需要把他綁定到非命令父類(lèi),問(wèn)題是綁定到他自己本身話CommandManager.AddExecutedHandler還是不能用,會(huì)被CommandBinding給攔截掉,這里要注意下CommandManager.AddExecutedHandler的用法,由于它注冊(cè)的是CommandManager.ExecutedEvent事件,如果你把它注冊(cè)給容器,而這個(gè)容器包含很多Button,各個(gè)Button命令不同,路由事件的特性會(huì)使得任一命令發(fā)出時(shí)都會(huì)響應(yīng)注冊(cè)的委托,原因是這些命令都引發(fā)了CommandManager.ExecutedEvent事件,所以僅對(duì)當(dāng)前控件的命令攔截的話最好只注冊(cè)到命令發(fā)出者本身(Button)。
這種方法雖然可以攔截了,但CommandBinding已經(jīng)被用了,外部無(wú)法再使用,況且循環(huán)找父類(lèi)效率也差,為什么要在Render之后才找呢?如果你用了類(lèi)似Prism框架中Region的延遲加載一開(kāi)始會(huì)找不到父類(lèi)。我們自定義的Command淪為了中間的代理對(duì)象,想手動(dòng)控制CanExecuteChanged也變的望塵莫及。
思來(lái)想去無(wú)奈為了實(shí)例化ExecutedRoutedEventArgs我只好用了反射的方法:
varargsConstructo=typeof(ExecutedRoutedEventArgs).GetConstructors(BindingFlags.NonPublic|BindingFlags.Instance);
ExecutedRoutedEventArgsargs=(ExecutedRoutedEventArgs)argsConstructo[0].Invoke(newobject[]{this,parameter});
args.RoutedEvent=CommandManager.PreviewExecutedEvent;由于引發(fā)這個(gè)事件需要實(shí)際UIElement、UIElement3D或ContentElement對(duì)象,只有這些類(lèi)才擁有RaiseEvent方法,所以我為DelegateCommand又定義了一個(gè)IElement接口來(lái)承接對(duì)象,為了讓CommandTarget也能使用,Render之后我才對(duì)IElement賦值,因?yàn)槲也恢繡ommandTarget屬性是否會(huì)定義在Command之后。CommandAttachBehavior類(lèi)上的OnCommandInvalidated改寫(xiě)為如下:(我改進(jìn)的DelegateCommand也在附件)
- privatestaticvoidOnCommandInvalidated(DependencyObjectsender,DependencyPropertyChangedEventArgse)
 - {
 - varcommand=e.NewValueasICommand;
 - if(command==null)
 - return;
 - sender.Dispatcher.BeginInvoke((Action)delegate
 - {
 - ICommandSourcecommandSource=senderasICommandSource;
 - if(commandSource!=null)
 - {
 - vardelegateCommand=commandasIElement;
 - if(delegateCommand!=null)
 - delegateCommand.Target=commandSource.CommandTarget??(IInputElement)sender;
 - vartype=sender.GetType();
 - varpropInfo=type.GetProperty("Command");
 - propInfo.SetValue(sender,command,null);
 - }
 - },DispatcherPriority.Render);
 - }
 
自定義Command的其他一些改進(jìn)做法
通常來(lái)說(shuō)對(duì)自定義Command改進(jìn)的還有增加泛型,泛型有什么用呢?這個(gè)其實(shí)是給Execute里的參數(shù)用的,他的參數(shù)按照ICommand規(guī)定默認(rèn)是object,可有時(shí)候我們的參數(shù)是個(gè)Employee類(lèi),那么在執(zhí)行的時(shí)候我們需要做Employeeemployee=argasEmployee的操作,假如穿進(jìn)來(lái)的參數(shù)直接是Employee自然不需要這么做了,而轉(zhuǎn)成Employee對(duì)象的操作在Command中已經(jīng)被做掉——CanExecute((T)parameter)。
自定義Command雖好,可一個(gè)控件限定一個(gè)Command有時(shí)候就會(huì)顯的不夠用,或者那個(gè)控件壓根沒(méi)有Command那不完了,MVVM沒(méi)法混了?沒(méi)有命令事件總該有吧,什么,沒(méi)有事件?單純顯示用的?那他憑什么有行為?有事件的話,我們可以注冊(cè)事件在委托中執(zhí)行Command,具體做法請(qǐng)參考Prism中的ButtonBaseClickCommandBehavior、CommandBehaviorBase、Click這三個(gè)類(lèi)。
Prism中還有個(gè)關(guān)于Command的類(lèi)叫做CompositeCommand,他主要為了解決幾個(gè)自定義Command一起能執(zhí)行的問(wèn)題:一次增加了多了訂單,只要每個(gè)訂單都被允許保存,則不需要一個(gè)個(gè)點(diǎn)訂單的Save按鈕,來(lái)個(gè)SaveAll一起保存,要是里面有個(gè)訂單不能保存,那么SaveAll是不能用的。實(shí)現(xiàn)原理也比較直觀,就是把幾個(gè)自定義Command放到一個(gè)列表并注冊(cè)他們的CanExecuteChanged,看是不是都能被執(zhí)行,如果不能執(zhí)行則CompositeCommand的CanExecute為false,能執(zhí)行則用CanExecuteChanged通知前端控件,執(zhí)行時(shí)只要循環(huán)執(zhí)行列表中Command的Execute方法即可。
一般定義的Command不能控制ExecutedRoutedEventArgs中的Handled屬性,我把他提了出來(lái)用ref來(lái)控制,這種做法似乎有點(diǎn)讓ViewModel知曉UI的味道,可有時(shí)候還是必要的,如我的SaveCommand結(jié)束后本該會(huì)有個(gè)關(guān)閉窗口的CommandBinding相隨,可執(zhí)行SaveCommand時(shí)發(fā)生了錯(cuò)誤,這時(shí)就要把Handled設(shè)為T(mén)rue不能讓之后的CommandBinding進(jìn)行。
PS:我自己改進(jìn)的這個(gè)DelegateCommand也有些缺點(diǎn)比如需要用附加屬性,這樣用起來(lái)就比較不統(tǒng)一,還有就是反射用的較多效率不說(shuō),也破壞了原有的對(duì)象封裝,并需要在Command中放入了UI元素(IElement),希望本文是拋磚引玉,當(dāng)然被拍磚引來(lái)的玉,我也同樣歡迎。
【編輯推薦】















 
 
 




 
 
 
 