您的位置:首页 > 编程语言 > C#

转 C#中动态订阅控件中任意事件的方法

2010-03-30 14:34 225 查看
这个题目想了半天,不太好用一句话描述。这样,举个简单的应用场景:在用Windows Forms制作向导程序的时候,通常会有“上一步”、“下一步”这样的按钮。假设现在需要做一个通用的“向导制作框架”,那么我们就需要在这个“向导制作 框架”中,对“上一步”、“下一步”这些按钮是否可用(是否Enabled)进行控制。而控制条件是由开发人员在实际使用“向导制作框架”进行开发时确定 的。比如:只有在当前向导页面的某个文本框里被输入了字符串以后,“下一步”才可用;或者只有在某个按钮被按下的时候,“下一步”才可用。于是,我们的 “向导制作框架”要能够允许开发人员来确定,当触发什么事情的时候,“下一步”才可用。

我们先从特例入手,假设向导页面上只有一个按钮叫btn,只有点击btn以后,“下一步”才能够被点击。编程上很容易实现这个效果:直接在窗体上订 阅btn的Click事件,在Click事件里,写下“btnNext.Enabled = true;"这样一句话。

现在问题来了:我们需要为开发人员提供一个“向导制作框架”,也就是说,这个框架根本无法预测开发人员需要订阅哪些控件的哪些事件,只能留出一个接 口,让开发人员自己调用这个接口实现事件的动态注册。Windows Forms提供的控件类型多种多样,而且不同的事件有着不同的函数签名(也就是委托,比如Click事件和MouseDown事件就是用的两个不同的委 托),如何让我们的框架能够支持任意的控件,并在任意控件的任意事件发生时,调用“btnNext.Enabled = true;”这条语句,使得“下一步”按钮可用呢?

要实现这样的功能,我们需要用到反射。首先,定义一个泛型方法,在这个方法里,我们直接对btnNext进行设置,如下:

view sourceprint?
1
protected
void
DoTrigger<T>(
object
sender,T  eventArgs)
2
where T :  System.EventArgs
3
{
4
this
.btnNext.Enabled =
true
;
5
}
然后,根据用户给定的控件实例和事件名称,获得EventInfo对象。这个EventInfo里有个重要的属性,就是 EventHandlerType,它就是定义event所使用的委托类型。为了使开发人员指定的事件能够绑定到上面的DoTrigger函数上,我们需 要知道那个EventArgs的具体类型,下面的代码可以将某个委托类型的所有参数类型全部读取出来:

view sourceprint?
01
private
Type[]  GetDelegateParameterTypes(Type d)
02
{
03
if
(d.BaseType !=
typeof
(MulticastDelegate))
04
{
05
throw
new
InvalidOperationException(
"Not a delegate."
);
06
}
07
08
MethodInfo invoke =  d.GetMethod(
"Invoke"
);
09
if
(invoke ==
null
)
10
{
11
throw
new
InvalidOperationException(
"Not a delegate."
);
12
}
13
14
ParameterInfo[]  parameters = invoke.GetParameters();
15
Type[] typeParameters =
new
Type[parameters.Length];
16
for
(
int
i = 0; i  < parameters.Length; i++)
17
{
18
typeParameters[i]  = parameters[i].ParameterType;
19
}
20
21
return
typeParameters;
22
}
注意:如果是标准的事件委托,一般情况下都是第一个参数为object类型,第二个参数为EventArgs绑定类型,无返回值的签名格式。换句话 说,一般情况下,上面的这段代码返回的数组包含两个对象:object和一个继承于EventArgs(或者是EventArgs本身)的类型。在这里, 我们取数组里的第二个成员。

好了,现在通过反射,获得DoTrigger方法的MethodInfo,并通过MethodInfo.MakeGenericMethod方法, 将上一步获得的EventArgs类型绑定到DoTrigger方法上,并使用Delegate.CreateDelegate生成Event Handler:

view sourceprint?
01
private
Delegate  GetEventHandler(Control control,EventInfo eventInfo)
02
{
03
try
04
{
05
if
(eventInfo ==
null
)
06
throw
new
Exception(
string
.Format(
"Unable to find an event named '{0}' on the  control '{1}'."
,
07
eventInfo.Name,control));
08
Type[] delegateParameters =
this
.GetDelegateParameterTypes(eventInfo.EventHandlerType);
09
if
(delegateParameters ==
null
||
10
delegateParameters.Length != 2)
11
throw
new
InvalidOperationException(
string
.Format(
"Event '{0}' is not valid."
,eventInfo.Name));
12
Type eventArgsType = delegateParameters[1];
13
14
MethodInfo  doEventMethod =
this
.GetType().GetMethod(
"DoTrigger"
,
15
BindingFlags.NonPublic | BindingFlags.Instance);
16
17
if
(doEventMethod ==
null
)
18
throw
new
Exception(
"DoTrigger  method doesn't exist."
);
19
if
(!doEventMethod.IsGenericMethod)
20
throw
new
Exception(
"DoTrigger  method is not a generic method."
);
21
MethodInfo concreteDoEventMethod =  doEventMethod.MakeGenericMethod(eventArgsType);
22
Delegate d =Delegate.CreateDelegate(eventInfo.EventHandlerType, 
this
,concreteDoEventMethod);
23
return
d;
24
}
25
catch
26
{
27
throw
;
28
}
29
}
通过向上面的方法传入一个任意控件和该控件中任意事件的Method Info,即可获得处理该事件的Event Handler,也就是由DoTrigger泛型方法来处理该指定的事件:

view sourceprint?
01
public
void
RegisterTrigger(Control control,
string
eventName)
02
{
03
try
04
{
05
EventInfo  eventInfo = control.GetType().GetEvent(eventName,
06
BindingFlags.Public | BindingFlags.Instance);
07
08
Delegate d =
this
.GetEventHandler(control, eventInfo);
09
eventInfo.AddEventHandler(control,d);
10
}
11
catch
12
{
13
throw
;
14
}
15
}
最后,在使用的时候,代码就简单啦:

view sourceprint?
1
private
void
Form_Load (
object
sender, System.EventArgs e)
2
{
3
this
.RegisterTrigger (btn,
"Click"
);
4
this
.RegisterTrigger  (textBox1,
"TextChanged"
);
5
this
.RegisterTrigger  (textBox2,
"MouseDown"
);
6
}
现在,不管是btn被单击,还是textBox1里的文字被更改,还是在textBox2鼠标按钮被按下,都会直接触发DoTrigger函数,进而使得 “下一步”按钮变得可用(Enabled为true)。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: