dax.net

Software on Dynamics AX, .NET and DDD ......
posts - 152, comments - 855, trackbacks - 0, articles - 0
  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理

这个题目想了半天,不太好用一句话描述。这样,举个简单的应用场景:在用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)。

标签: .NET, C#, WinForm

Feedback

#1楼  回复 引用 查看   

2010-03-19 16:43 by 吴峰      
要是想textBox1和textBox2同时有值才能下一步怎么办?
再加个条件吧,要求textBox2里输入合法的email或有一定强度的密码。

#2楼[楼主]  回复 引用 查看   

2010-03-19 16:52 by dax.net      
引用吴峰:
要是想textBox1和textBox2同时有值才能下一步怎么办?
再加个条件吧,要求textBox2里输入合法的email或有一定强度的密码。


判断条件是Rule方面的内容,这里的确没有做介绍,本文仅仅是介绍触发事件的方法。在事件触发以后,当然还要做进一步的判断,来最终确定到底是否让btnNext.Enabled为true。如果有兴趣,我接下来再介绍。呵呵。

#3楼  回复 引用 查看   

2010-03-19 17:30 by 不若相忘于江湖      

一半代码看不懂... 水平太差的说.

#4楼  回复 引用 查看   

2010-03-20 11:31 by Linq.C#      

额。。我觉得 DoTrigger 方法里面的处理函数 应该 可以支持由调用者 补充。

或许 可以重载 一个 protected void DoTrigger<T>(object sender, T eventArgs) ;

#5楼  回复 引用 查看   

2010-03-20 11:33 by Linq.C#      
protected void DoTrigger<T>(object sender, T eventArgs,CallBack callback) ;

提供一个回调...?


呵呵..个人看法..不知道有没有顺着楼主的思路..^_^

#6楼[楼主]  回复 引用 查看   

2010-03-20 20:23 by dax.net      
引用Linq.C#:
额。。我觉得 DoTrigger 方法里面的处理函数 应该 可以支持由调用者 补充。

或许 可以重载 一个 protected void DoTrigger<T>(object sender, T eventArgs) ;


是的,在我自己的应用中,调用者可以使用AddRule方法,使用委托实现规则自定义,同时,会公布两个事件,以便调用者能够订阅这两个事件分别处理“规则校验成功”和“规则校验失败”的情况。于是,在这两个事件的处理过程中,即可设置btnNext的Enabled属性,或者做其他更多的事情。

本文仅仅介绍了侦听任意控件的任意事件的方法。如果网友们对这个有兴趣,我会继续介绍向导的实现方式。
发表评论

昵称: [登录] [注册]

主页:

邮箱:(仅博主可见)

评论内容:

  登录  注册

[使用Ctrl+Enter键快速提交评论]

0 1689838 We0eth8o8+w=