zoukankan      html  css  js  c++  java
  • 使用winform自己做一个消息框, 以取代系统提供的MessageBox

    摘要
    1.是什么导致我们需要提供一个自定义的消息框?
    2.说说我的大致思路
    3.你的亮点在哪里?
    4.难道就是这些吗?

    下载本文讲述的项目源码包

    是什么导致我们需要提供一个自定义的消息框?

    最初产生这样一个需求是源于项目经理的近乎白痴般的要求,有一天,他告诉我说那个弹出来的消息框太小了。我告诉他,这是系统自带的东西,大小随着给定文本内容的长度自动变化,他说不行,给弄大点,而且字体也太小,换个字体吧!我的天,难道架构师都是从来不真实写代码的吗?

    接 到这个要求,想想也许不大难吧。先看看系统给了我什么接口:MessageBox位于System.Windows.Forms命名空间内,是一个被密封了的静态类,能够使用的公共方法也就是MessageBox.Show,倒是有很多重载版本,也有一个选项参数,但该参数却没提供我真正感兴趣的东西。

    碰 到问题我还是老规矩,首先请教Google,然后直接上codeproject或sourceforge查找,一般的问题都可以解决。一看,原来大家都曾 有过这样的需求啊;再来看别人的解决方法。大部分人使用Hook,也就是系统消息拦截,获取消息框的句柄,然后使用winApi篡改该窗体的某些特征;这样的示例非常多,而且有着非常有特色的特征:可以在上面添加个comboBox,告诉它Don't show this in the next time;可以赋予按钮自己想要的文本值;也能修改一些文本的字体和字号属性;

    看起来这就是我要的解决方案,因为它几乎能做一切;作为 windows上的新手,我于是迫切需要去弄弄Hook,这东西也不难;可问题在于,我发现无论怎么去尝试修改那些作者使用Hook做的示例,最后的结果仍然是:该消息框上所有使用文本的地方最终都是同一个变化!也就是说你如果把Message Text部分的字体弄成Vadana,按钮上的文本的字体也会变为Vadana,这居然是一体的!

    很好,我正不想弄Hook,这东西我就没弄成功过,别人例子都行,我就是不行!

    也罢,逼急了,我自己来搞一个。


    说说我的大致思路

    .NET下画个窗体不难,事实上拖下鼠标就行了;问题在于一样东西总是牵扯到其它的很多东西。
    ok,看看我自己的MessageBox究竟需要什么,其实需要的就是尽可能模仿系统自带的消息框,最好就是完全取代之。

    必须能够自定义按钮的数目,给出不同的提示图标(错误,询问,信息等),最好还能发出相应的系统声音,消息框消失后获取按钮的某种枚举值,必须能够为其指定一个父窗体(作为此上的模式窗体),必须能够安全的在非UI线程下使用。大概就是这些吧。

    自定义按钮的数目
    按钮的数目会影响它们在窗体上的位置布局,于是需要知道单个按钮的宽度,相邻2个按钮之间的位置差。
    在源代码中我提供了这样一个枚举:

     

    public enum MsgBoxButtons
    {
        
    /// <summary>
        
    /// OK按钮
        
    /// </summary>

        OK = 0,
        
    /// <summary>
        
    /// OK按钮以及Cancel按钮
        
    /// </summary>

        OKCancel,
        
    /// <summary>
        
    /// OK按钮, No按钮, 以及Cancel按钮
        
    /// </summary>

        OKNoCancel,
    }

     

    因为我发现大多数情况下,这样的按钮组合足够应付需要了。

    我发现我无法获取系统默认的按钮的大小,于是我作了个假设:

     

    const int DefBtnWidth = 75;  // TODO -- this may not be right
    const int GapTwoBtns = 150;
    const int GapTriBtns = 100;


    以上同时包含了按钮之间的间距值。

    自定义错误提示图标
    目的是一眼看出来发生了什么:错误?提示性信息?询问选择?
    为此我拖了一个imageList组件,嵌入了一组图标,并定义了如下枚举:

     

    public enum MsgBoxIcons
    {
        
    // NOTE: notice the order, corresponds to their images' order

        
    /// <summary>
        
    /// 成功标志
        
    /// </summary>

        Good = 0,
        
    /// <summary>
        
    /// 错误标志
        
    /// </summary>

        Error,
        
    /// <summary>
        
    /// 提示性标志
        
    /// </summary>

        Info,
        
    /// <summary>
        
    /// 询问标志
        
    /// </summary>

        Question,
        
    /// <summary>
        
    /// 不要出现任何标志
        
    /// </summary>

        None,
    }



    需要注意的是:这些枚举值与我的

    imageList中的image的顺序是一一对应的,如果你要添加你自己的图标,你就应该保持这种对应关系。

    弹出窗体的时候发出声音

    这个功能完全是模仿来的,看代码吧

     

    internal static void DoBeep(MsgBoxIcons icon)
    {
        
    // Play the associated SystemSound
        switch (icon)
        
    {
            
    case MsgBoxIcons.Error:
                SystemSounds.Hand.Play();
                
    break;
            
    case MsgBoxIcons.Good:
            
    case MsgBoxIcons.Info:
                SystemSounds.Asterisk.Play();
                
    break;
            
    case MsgBoxIcons.Question:
                SystemSounds.Question.Play();
                
    break;
            
    default:
                SystemSounds.Beep.Play();
                
    break;
        }

    }

     

    获取窗体关闭时的DialogResult

    这个值很重要,它将反馈给调用者,以辅助其作出某种相应的决策。在这里我直接使用了系统框架类中的DialogResult枚举,在窗体中写死了每个按钮的DialogResult值。

    为之提供父窗体参数

    这个本身很简单,任何窗体的ShowDialog都有一个owner参数,我也提供就行了。可是你将会发现,如果你身处该父窗体所在的UI线程中,这样调用没有任何的不适,你甚至不需要为之提供owner参数,缺省的是将当前窗体作为父窗体。一切都运行良好;但是在非UI线程中,你就不能这么做了。看下文。

    UI线程中使用它

    假定我们已经提供了ShowDialog方法,事实上我提供了该方法的很多重载版本,其易用性比系统自带的还好。在非UI线程中使用它时,你有2个选择:为之提供owner参数或者不提供;如果不提供,它就无法和你的UI主窗体联系起来,它不会挡在任何窗体的前面,成为其之上的模式窗体,这让人感觉很不爽,我发现尤其要解决这一点;那就提供这个参数就可以了嘛,把主窗体的Handle给它不就完了嘛,哈,这样一来,你的程序只能偶尔的工作。大家应该都知道,这就是跨线程访问控件的问题,原来问题在于,如果需要在A窗体上显示一个模式窗体b,这个任务必须委托给A窗体UI线程执行才行!请注意这句话,我花了很长时间才明确这一点!

    ok,你明白了,你需要一个委托来做这件事情,很好,你在A窗体的非UI线程的代码中定义一个委托对象,使用某个方法(b窗体的ShowDialog方法)将其实例化,然后通过FormA.Invoke()来委托给AUI线程来执行。

    没错,这就是标准的解决方案,大家一直都是这样做的,.NETMSDN就告诉并建议我们这样做。可是我们需要的消息框是一个独立的组件,你难道要求每个使用该组件的程序都去提供一个委托来在非UI线程中显示这样一个消息框吗?很麻烦不是吗?我就希望在任何线程中,我给你一个Handle,你给我挡在这个Handle的窗体前面显示就行了。于是这个问题成为这个小项目的难点。

    你的亮点在哪里?

    这是本文最重要的一个部分。我花了很长时间领悟到,我希望你只需要不到1分钟就能理解并记住。

    上面说了,.NET的这种跨线程调用机制要求我们必须使用委托来实现:如果需要在A窗体上显示一个模式窗体b,那么请将该任务委托给AUI线程来执行!

    委托对象放在调用者方会带来额外的编码,于是我把它放进了我自己的组件里,也就是这个自定义的消息框中!

     

    // 跨线程使用之必须
    private delegate DialogResult ShowItDelegate(Form owner);
    private static DialogResult ShowIt(Form owner, MsgBox mbx)
    {
        
    if (owner != null
            
    && owner.InvokeRequired)
        
    {
            ShowItDelegate d 
    = new ShowItDelegate(mbx.ShowDialog);
            
    return (DialogResult)owner.Invoke(d, owner);
        }


        
    return mbx.ShowDialog(owner);
    }

     

    难道就是这些吗?

    这样看起来,你的消息框确实比系统的要强,可以在非UI线程下方便的使用;但也就是一个很基本的消息框,那些Hook示例所展示的似乎比你的还要吸引人。

    没错,以上Hook示例中展示了很多很有用的技术:添加comboBox,让窗体在一段时间没有被点击后自动消失(同时返回某个默认的DialogResult值),可以替换按钮上的文本字体。

    可是这一切对于一个完全由你自己开发的窗体程序而言,是个难题么?太简单了,当然我把一些东西做死了,其实如果你想获得高度自定义的功能,你为自己的Show方法提供一个MsgBoxOptions参数就可以了,源代码在你自己手里,没有什么是不能做的!

    当然这取决于你的需要了,就我而言,这已经非常足够了,我一直在用我自己编写的消息框,太方便了!赞一个自己先 :-) 

    后记

    最初我使用的是Singleton模式,可是在很多地方可能先后都要求弹出一个窗体,而先前的这个窗体尚未消失,这就带来了一个问题,.NET抛出了这样一个异常:已经显示的模式窗体不能够再次被显示(大意如此)。经过自己观察,我发现系统自带的消息框类似于每次需要时创建,然后丢弃之,也就是说每个使用的地方都是一个新的消息框。于是就演化成了现在的这样一个版本。

    谢谢各位大虾和菜鸟的阅读,祝您使用愉快!

    (全文完,页面上会提供源码包下载)

  • 相关阅读:
    链表中倒数第K个结点
    关于栈的经典问题---判断一个栈的出栈序列是不是有效的
    剑指Offer-用两个栈实现队列
    Netty与NIO
    牛客-反转数字
    N叉树的最大深度-DFS
    version can neither be null, empty nor blank
    剑指 Offer 16. 数值的整数次方
    Vue基础语法与指令
    ES6常用语法
  • 原文地址:https://www.cnblogs.com/zhwl/p/3124210.html
Copyright © 2011-2022 走看看