无法轻松禁用在.NET框架中公开的WinForm控件的事件触发。但是,winform控件遵循事件的标准设计模式,因为所有事件签名都基于
EventHandler Delegate
注册的事件处理程序存储在
EventHandlerList
定义在
Control Class
. 此列表存储在名为“events”的字段(变量)中,仅通过只读属性公开
Events
.
下面的类使用反射将空值临时分配给
events
字段有效地删除为控件注册的所有事件处理程序。
虽然这可能是对模式的滥用,但类实现了
IDisposable Interface
恢复
事件
处理类实例的字段。这样做的原因是为了方便
using
块来包装类用法。
public class ControlEventSuspender : IDisposable
{
private const string eventsFieldName = "events";
private const string headFieldName = "head";
private static System.Reflection.FieldInfo eventsFieldInfo;
private static System.Reflection.FieldInfo headFieldInfo;
private System.Windows.Forms.Control target;
private object eventHandlerList;
private bool disposedValue;
static ControlEventSuspender()
{
Type compType = typeof(System.ComponentModel.Component);
eventsFieldInfo = compType.GetField(eventsFieldName, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
headFieldInfo = typeof(System.ComponentModel.EventHandlerList).GetField(headFieldName, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
}
private static bool FieldInfosAquired()
{
if (eventsFieldInfo == null)
{
throw new Exception($"{typeof(ControlEventSuspender).Name} could not find the field '{ControlEventSuspender.eventsFieldName}' on type Component.");
}
if (headFieldInfo == null)
{
throw new Exception($"{typeof(ControlEventSuspender).Name} could not find the field '{ControlEventSuspender.headFieldName}' on type System.ComponentModel.EventHandlerList.");
}
return true;
}
private ControlEventSuspender(System.Windows.Forms.Control target) // Force using the the Suspend method to create an instance
{
this.target = target;
this.eventHandlerList = eventsFieldInfo.GetValue(target); // backup event hander list
eventsFieldInfo.SetValue(target, null); // clear event handler list
}
public static ControlEventSuspender Suspend(System.Windows.Forms.Control target)
{
ControlEventSuspender ret = null;
if (FieldInfosAquired() && target != null)
{
ret = new ControlEventSuspender(target);
}
return ret;
}
protected virtual void Dispose(bool disposing)
{
if (!this.disposedValue)
{
if (disposing)
{
if (this.target != null)
{
RestoreEventList();
}
}
}
this.disposedValue = true;
}
public void Dispose()
{
Dispose(true);
}
private void RestoreEventList()
{
object o = eventsFieldInfo.GetValue(target);
if (o != null && headFieldInfo.GetValue(o) != null)
{
throw new Exception($"Events on {target.GetType().Name} (local name: {target.Name}) added while event handling suspended.");
}
else
{
eventsFieldInfo.SetValue(target, eventHandlerList);
eventHandlerList = null;
target = null;
}
}
}
中的示例用法
button1_Click
方法
:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
using (ControlEventSuspender.Suspend(comboBox1))
{
comboBox1.SelectedIndex = 3; // SelectedIndexChanged does not fire
}
}
private void button2_Click(object sender, EventArgs e)
{
comboBox1.SelectedIndex = -1; // clear selection, SelectedIndexChanged fires
}
private void button3_Click(object sender, EventArgs e)
{
comboBox1.SelectedIndex = 3; // SelectedIndexChanged fires
}
private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
Console.WriteLine("index changed fired");
System.Media.SystemSounds.Beep.Play();
}
}
肥皂盒谩骂
许多人会说,使用反射来访问非公共类成员是肮脏的,或者是其他贬义词,它引入了
脆性
对代码进行更改,因为有人可能更改基础代码定义,从而使依赖成员名称(魔术字符串)的代码不再有效。这是一个有效的问题,但我认为它与访问外部数据库的代码没有什么不同。
反射可以被认为是针对特定字段(成员:字段、属性、事件)从组件(数据库)查询类型(DATABATE)。它并不比sql语句更脆弱,比如
Select SomeField From SomeTable Where AnotherField=5
. 这种类型的sql代码在世界上是不允许的,没有人会三思而后行,但是一些外力可以很容易地重新定义您所编写的数据库,从而使所有的magic string sql语句都无效。
使用硬编码名称总是有被更改为无效的风险。你必须权衡前进的风险与害怕继续前进的选择,因为有人想听起来权威(通常是
parroting
并批评你实施了解决当前问题的解决方案。