這個題目想了半天,不太好用一句話描述。這樣,舉個簡單的應用情境:在用Windows Forms製作嚮導程式的時候,通常會有“上一步”、“下一步”這樣的按鈕。假設現在需要做一個通用的“嚮導製作架構”,那麼我們就需要在這個“嚮導製作架構”中,對“上一步”、“下一步”這些按鈕是否可用(是否Enabled)進行控制。而控制條件是由開發人員在實際使用“嚮導製作架構”進行開發時確定的。比如:只有在當前嚮導頁面的某個文字框裡被輸入了字串以後,“下一步”才可用;或者只有在某個按鈕被按下的時候,“下一步”才可用。於是,我們的“嚮導製作架構”要能夠允許開發人員來確定,當觸發什麼事情的時候,“下一步”才可用。
我們先從特例入手,假設嚮導頁面上只有一個按鈕叫btn,只有點擊btn以後,“下一步”才能夠被點擊。編程上很容易實現這個效果:直接在表單上訂閱btn的Click事件,在Click事件裡,寫下“btnNext.Enabled = true;"這樣一句話。
現在問題來了:我們需要為開發人員提供一個“嚮導製作架構”,也就是說,這個架構根本無法預測開發人員需要訂閱哪些控制項的哪些事件,只能留出一個介面,讓開發人員自己調用這個介面實現事件的動態註冊。Windows Forms提供的控制項類型多種多樣,而且不同的事件有著不同的函數簽名(也就是委託,比如Click事件和MouseDown事件就是用的兩個不同的委託),如何讓我們的架構能夠支援任意的控制項,並在任意控制項的任意事件發生時,調用“btnNext.Enabled = true;”這條語句,使得“下一步”按鈕可用呢?
要實現這樣的功能,我們需要用到反射。首先,定義一個泛型方法,在這個方法裡,我們直接對btnNext進行設定,如下:
protected void DoTrigger<T>(object sender, T eventArgs) where T : System.EventArgs{ this.btnNext.Enabled = true;}
然後,根據使用者給定的控制項執行個體和事件名稱,獲得EventInfo對象。這個EventInfo裡有個重要的屬性,就是EventHandlerType,它就是定義event所使用的委託類型。為了使開發人員指定的事件能夠綁定到上面的DoTrigger函數上,我們需要知道那個EventArgs的具體類型,下面的代碼可以將某個委託類型的所有參數類型全部讀取出來:
private Type[] GetDelegateParameterTypes(Type d){ if (d.BaseType != typeof(MulticastDelegate)) { throw new InvalidOperationException("Not a delegate."); } MethodInfo invoke = d.GetMethod("Invoke"); if (invoke == null) { throw new InvalidOperationException("Not a delegate."); } ParameterInfo[] parameters = invoke.GetParameters(); Type[] typeParameters = new Type[parameters.Length]; for (int i = 0; i < parameters.Length; i++) { typeParameters[i] = parameters[i].ParameterType; } return typeParameters;}
注意:如果是標準的事件委託,一般情況下都是第一個參數為object類型,第二個參數為EventArgs綁定類型,無傳回值的簽名格式。換句話說,一般情況下,上面的這段代碼返回的數組包含兩個對象:object和一個繼承於EventArgs(或者是EventArgs本身)的類型。在這裡,我們取數組裡的第二個成員。
好了,現在通過反射,獲得DoTrigger方法的MethodInfo,並通過MethodInfo.MakeGenericMethod方法,將上一步獲得的EventArgs類型綁定到DoTrigger方法上,並使用Delegate.CreateDelegate產生Event Handler:
private Delegate GetEventHandler(Control control, EventInfo eventInfo){ try { if (eventInfo == null) throw new Exception(string.Format("Unable to find an event named '{0}' on the control '{1}'.", eventInfo.Name, control)); Type[] delegateParameters = this.GetDelegateParameterTypes(eventInfo.EventHandlerType); if (delegateParameters == null || delegateParameters.Length != 2) throw new InvalidOperationException(string.Format("Event '{0}' is not valid.", eventInfo.Name)); Type eventArgsType = delegateParameters[1]; MethodInfo doEventMethod = this.GetType().GetMethod("DoTrigger", BindingFlags.NonPublic | BindingFlags.Instance); if (doEventMethod == null) throw new Exception("DoTrigger method doesn't exist."); if (!doEventMethod.IsGenericMethod) throw new Exception("DoTrigger method is not a generic method."); MethodInfo concreteDoEventMethod = doEventMethod.MakeGenericMethod(eventArgsType); Delegate d = Delegate.CreateDelegate(eventInfo.EventHandlerType, this, concreteDoEventMethod); return d; } catch { throw; }}
通過向上面的方法傳入一個任意控制項和該控制項中任意事件的Method Info,即可獲得處理該事件的Event Handler,也就是由DoTrigger泛型方法來處理該指定的事件:
public void RegisterTrigger(Control control, string eventName){ try { EventInfo eventInfo = control.GetType().GetEvent(eventName, BindingFlags.Public | BindingFlags.Instance); Delegate d = this.GetEventHandler(control, eventInfo); eventInfo.AddEventHandler(control, d); } catch { throw; }}
最後,在使用的時候,代碼就簡單啦:
private void Form_Load (object sender, System.EventArgs e){ this.RegisterTrigger (btn, "Click"); this.RegisterTrigger (textBox1, "TextChanged"); this.RegisterTrigger (textBox2, "MouseDown");}
現在,不管是btn被單擊,還是textBox1裡的文字被更改,還是在textBox2滑鼠按鍵被按下,都會直接觸發DoTrigger函數,進而使得“下一步”按鈕變得可用(Enabled為true)。