轉帖|其它|編輯:郝浩|2011-08-11 14:57:00.000|閱讀 2977 次
概述:本文主要介紹C# 如何移除所有和事件關聯的委派函數,希望對大家有幫助。
# 界面/圖表報表/文檔/IDE等千款熱門軟控件火熱銷售中 >>
有個關于事件的問題一直困擾著我,比方說代碼這樣寫
this.dgv_User.CellValueChanged -=
new System.Windows.Forms.DataGridViewCellEventHandler
(this.dgv_User_CellValueChanged);
//------------
//這里面又調用了另一個方法,這個方法里面也有這么兩句
this.dgv_User.CellValueChanged -=
new System.Windows.Forms.DataGridViewCellEventHandler
(this.dgv_User_CellValueChanged);
this.dgv_User.CellValueChanged +=
new System.Windows.Forms.DataGridViewCellEventHandler
(this.dgv_User_CellValueChanged);
//------------
this.dgv_User.CellValueChanged +=
new System.Windows.Forms.DataGridViewCellEventHandler
(this.dgv_User_CellValueChanged);
這樣的結果就是最后dgv的CellValueChanged被重復綁定了this.dgv_User_CellValueChanged事件,當 dgv的單元格值發生變化的時候就會觸發兩次,不管你綁了多少次這樣程序的效率就降低了.有個錯誤我要糾正,以前我以為C#中事件的訂閱不像VB那 樣,VB中直接RemoveHandler就能把所有綁定的方法移除,然后經過測試發現VB跟C#是一樣的,也會重復綁定,RemoveHandler也 只能一次一次的取消綁定怎么避免這個問題呢?
方法一:人為的控制dgv的CellValueChanged事件被訂閱的次數,移除一個,綁定一 個;這種方式只能做到盡可能降低事件被重復訂閱的幾率,因為當一個程序比較復雜的時候,難免會發生在一個函數(包含事件的訂閱和取消訂閱注銷)里面嵌套另 一個函數(包含事件的訂閱和取消訂閱注銷),這樣仍然會導致性能下降,久而久之變成頑疾。
方法二:讓我們來一起研究我寫了這樣一個事件
public class TestEvent
{
public delegate void DataGridViewCellEventNewHandler
(object sender, DataGridViewCellEventArgs e);
public event DataGridViewCellEventNewHandler CellValueChangedNew;
public int GetEventCount()
{
if (CellValueChangedNew == null)
return 0;
else
return CellValueChangedNew.GetInvocationList().Count();
}
}
注意,這里的DataGridViewCellEventNewHandler不是DataGridView的DataGridViewCellEventHandler;然后在程序中使用這個類的事件
TestEvent testEvent = new TestEvent();
testEvent.CellValueChangedNew -= new TestEvent.DataGridViewCellEventNewHandler(this.dgv_User_CellValueChanged);
if (testEvent.GetEventCount() == 0)
testEvent.CellValueChangedNew += new TestEvent.DataGridViewCellEventNewHandler(this.dgv_User_CellValueChanged);
事件本質上是一個MulticastDelegate對象,所以使用MulticastDelegate的GetInvocationList 方法可以得到事件被訂閱的列表,也就是調用這個事件的函數列表,然后用count返回所有的被訂閱次數。想到這里,我們是不是可以重寫 DataGridview控件,加上一個監控CellValueChanged被訂閱次數的方法,現在我們來試下。
public class CustomDataGridView : DataGridView
{
public int GetEventCount()
{
if (this.CellValueChanged == null)
return 0;
else
return CellValueChanged.GetInvocationList().Count();
}
}
編譯一下,發現報錯。原因是錯誤 事件“System.Windows.Forms.DataGridView.CellValueChanged”只能出現在 += 或 -= 的左邊 。原來雖然CellValueChanged事件雖然是MulticastDelegate對象,但是如果要使用 MulticastDelegate的GetInvocationList方法卻只能在最初定義CellValueChanged事件的類的內部才能使 用,就像我寫的TestEvent里面,因為事件是內部定義的,所以能夠使用GetInvocationList方法。既然不能用他寫的事件,那么我們自 己寫一個CellValueChanged不行嗎?試一下
public class CustomDataGridView : DataGridView
{
public delegate void DataGridViewCellEventNewHandler
(object sender, DataGridViewCellEventArgs e);
public event DataGridViewCellEventNewHandler CellValueChangedNew;
public int GetEventCount()
{
if (CellValueChangedNew == null)
return 0;
else
return CellValueChangedNew.GetInvocationList().Count();
}
}
重寫了DataGridview,自己定義了CellValueChanged事件,控件寫好了,看看能不能用
this.dgv_User.CellValueChangedNew+=
new CustomDataGridView.DataGridViewCellEventNewHandler
(dgv_User_CellValueChangedNew);
程序跑起來,卻發現無論怎么改變單元格的值都不能觸發dgv_User_CellValueChangedNew事件,看來這么做還是不行啊,至于為什么沒 有觸發這個事件我也很納悶,有經驗的朋友可以教教我。后來在csdn上發帖,也沒有得到很大的幫助,就在這個時候,向技術總監王總發出求助,他給了一個貼 子,剛好可以解決我的問題,用的是反射。這是那個帖子的源碼
EventHandlerList buttonEvents =
(EventHandlerList)this.button1.GetType().InvokeMember("Events", System.Reflection.BindingFlags.GetProperty
| System.Reflection.BindingFlags.Instance
| System.Reflection.BindingFlags.NonPublic, null, this.button1, null);
buttonEvents.GetType().GetMethod("button1_Click");
PropertyInfo propertyInfo =
(typeof(System.Windows.Forms.Button)).GetProperty("Events", BindingFlags.Instance | BindingFlags.NonPublic);
EventHandlerList eventHandlerList =
(EventHandlerList)propertyInfo.GetValue(button1, null);
FieldInfo fieldInfo =
(typeof(Control)).GetField("EventClick", BindingFlags.Static | BindingFlags.NonPublic);
Delegate d = eventHandlerList[fieldInfo.GetValue(null)];
if (d != null)
{
foreach (Delegate temp in d.GetInvocationList())
{
Console.WriteLine(temp.Method.Name);
//這個地方可以清除所有的委托,也可以使用條件清除指定委托,沒有辦法直接清除所有的
}
}
C#的Control封裝了EventHandlerList, 但它是protected的, 所以我們不能簡單的看到它的存在, 不過, 如果你走Debug Mode的話, 還是可以看得很清楚的, 但如果真要把它拿出來用, 就只能用Reflect了
上面的代碼是button的,怎么用到DataGridview上來,只要稍加改動
private void ClearEvent()
{
PropertyInfo propertyInfo =
(typeof(System.Windows.Forms.CustomDataGridView)).
GetProperty("Events", BindingFlags.Instance |
BindingFlags.NonPublic);
EventHandlerList eventHandlerList =
(EventHandlerList)propertyInfo.GetValue(dgv_User, null);
FieldInfo fieldInfo = (typeof(DataGridView)).GetField("EVENT_" + "DATAGRIDVIEW" + "CELLVALUECHANGED", BindingFlags.Static |
BindingFlags.NonPublic);
Delegate d = eventHandlerList[fieldInfo.GetValue(this.dgv_User)];
if (d != null)
{
foreach (Delegate temp in d.GetInvocationList())
{
//這個地方可以清除所有的委托,也可以使用條件清除指定委托,沒有辦法直接清除所有的
}
}
}
這里要注意的是GetField方法里面的string name參數,如果是DataGridView的事件都要是這種格式"EVENT_" + "DATAGRIDVIEW" + "CELLVALUECHANGED"(event+dgv+大寫的事件名),不然返回的都是null。
大功告成,現在可以用temp的MulticastDelegate對象的方法得到你想要的東西,單個取消訂閱、取消所有訂閱、得到所有被訂閱的次數、訂閱該事件的所有函數等等等等信息。
本站文章除注明轉載外,均為本站原創或翻譯。歡迎任何形式的轉載,但請務必注明出處、不得修改原文相關鏈接,如果存在內容上的異議請郵件反饋至chenjj@fc6vip.cn
文章轉載自:博客園