之前基於WPF 3.0開發的應用程序有一個DelegateCommand類型,在升級至WPF 4.0后發現CanExecuteChanged事件產生通知后對應的UI並未產生變化。
1 /// <summary> 2 /// 委托命令 3 /// </summary> 4 public class DelegateCommand<T> : ICommand 5 { 6 /// <summary> 7 /// 命令執行前事件 8 /// </summary> 9 public event EventHandler<CancelEventArgs> Executing; 10 11 /// <summary> 12 /// 命令執行后事件 13 /// </summary> 14 public event EventHandler Executed; 15 16 private Boolean _canExecuteCache; 17 18 /// <summary> 19 /// 構造函數 20 /// </summary> 21 protected DelegateCommand() { } 22 23 /// <summary> 24 /// 構造函數 25 /// </summary> 26 /// <param name="executeActionFunc">執行函數</param> 27 public DelegateCommand(Action<T> executeActionFunc) 28 : this(executeActionFunc, null) { } 29 30 /// <summary> 31 /// 構造函數 32 /// </summary> 33 /// <param name="executeActionFunc">執行函數</param> 34 /// <param name="canExecuteFunc">可執行函數</param> 35 public DelegateCommand(Action<T> executeActionFunc, Func<T, Boolean> canExecuteFunc) 36 { 37 this.ExecuteActionFunc = executeActionFunc; 38 this.CanExecuteFunc = canExecuteFunc; 39 } 40 41 /// <summary> 42 /// 執行函數 43 /// </summary> 44 public Action<T> ExecuteActionFunc { get; protected set; } 45 46 /// <summary> 47 /// 可執行函數 48 /// </summary> 49 public Func<T, Boolean> CanExecuteFunc { get; protected set; } 50 51 #region ICommand Members 52 53 /// <summary> 54 /// 允許執行變化事件 55 /// </summary> 56 public event EventHandler CanExecuteChanged; 57 58 /// <summary> 59 /// 命令是否可執行 60 /// </summary> 61 /// <param name="parameter">參數</param> 62 /// <returns>是否可執行</returns> 63 public Boolean CanExecute(Object parameter) 64 { 65 if (this.CanExecuteFunc == null) 66 return true; 67 68 Boolean bResult = this.CanExecuteFunc((T)parameter); 69 70 if (bResult != _canExecuteCache) 71 { 72 _canExecuteCache = bResult; 73 74 EventHandler handler = CanExecuteChanged; 75 76 if (handler != null) 77 handler(parameter, EventArgs.Empty); 78 } 79 80 return bResult; 81 } 82 83 /// <summary> 84 /// 執行命令 85 /// </summary> 86 /// <param name="parameter">參數</param> 87 public void Execute(Object parameter) 88 { 89 CancelEventArgs e = new CancelEventArgs(false); 90 EventHandler<CancelEventArgs> beforeHandler = Executing; 91 92 if (beforeHandler != null) 93 beforeHandler(parameter, e); 94 95 if (!e.Cancel) 96 { 97 this.ExecuteActionFunc((T)parameter); 98 99 EventHandler afterHandler = Executed; 100 101 if (afterHandler != null) 102 afterHandler(parameter, e); 103 } 104 } 105 106 #endregion 107 }
對於ICommand的綁定,WPF內部會訂閱CanExecuteChanged事件,當對應的ICommand實現產生通知時調用CanExecute函數確認是否需要更新控件狀態。調用Delegate的GetInvocationList函數發現WPF 3.0返回的Target是對應的控件(比如Button),WPF 4.0則返回CanExecuteChangedEventManager類型。在CanExecuteChangedEventManager內部它需要使用sender關聯WeakEventTable獲取被派發的ListenerList,而問題代碼就在RaiseCanExecuteChanged時傳遞的sender使用了parameter:
1 /// <summary> 2 /// 引起允許執行變化事件 3 /// </summary> 4 public void RaiseCanExecuteChanged() 5 { 6 EventHandler handler = CanExecuteChanged; 7 8 if (handler != null) 9 handler(parameter, EventArgs.Empty); 10 }
修改為:
1 /// <summary> 2 /// 引起允許執行變化事件 3 /// </summary> 4 public void RaiseCanExecuteChanged() 5 { 6 EventHandler handler = CanExecuteChanged; 7 8 if (handler != null) 9 handler(this, EventArgs.Empty); 10 }