好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

一个可携带附加消息的增强消息框MessageBoxEx

分享一个可携带附加消息的增强消息框messageboxex

--------------201507160917更新---------------

无意中发现标准消息框在windows7是有声音的,只是在windows server 2008(r2)无声,而我用的刚好是后者,所以误以为是messagebeep api在所有nt6系统都不工作造成~汗,有人在stackoverflow也提过这问题。但我仍然决定使用playsound api,不做修改
将声音处理交给processicon方法负责。之前考虑松耦合,所以将messageboxicon和声音分开处理,但其实声音就是根据前者而来,两者天然就是耦合的,分开处理多此一举

--------------201507091034更新---------------

首先感谢猿友e204在回复中的反馈。

解决双击【详细信息】按钮造成的checked状态改变问题,办法是让togglebutton忽略wm_lbuttondblclk消息
修正收起详细信息区逻辑,改为直接取用plattachzone.height。之前是取expandheight,会造成视觉体验问题

--------------201507082014原文(已更新)---------------

适用于:.net 2.0+的winform项目

样子:

有损录制+制图的原因不可能原样展示出真实效果,可至文章结尾下载demo体验。

功能和特点:

相对父窗体居中 可附带附加消息。附加消息可以是string和exception类型,【详细信息】按钮会根据是否传入附加信息显示和隐藏。传入exception实例时,呈现的是exception.tostring(),也就是可能携带stacktrace信息,所以如果你只是想呈现异常文本,还是老实传入ex.message 展开/收起附加信息时有动画效果。实用为王的你亦可设置enableanimate=false关闭动画效果 在windows server 2008 r2(未测试其它服务器系统)也有声音反馈。标准消息框在个人系统(xp/win7等)是有声音的,但在srv08却没有。同时亦提供了enablesound属性允许你关闭声音反馈 移除了标准messagebox提供的iwin32window、messageboxoptions和help相关参数,原因是我用不到,懒得实现 可拖拉改变消息框尺寸,消息文本和附加文本会随窗体大小重排。这是标准消息框未提供的能力。改变尺寸分两种情况有不同的行为:①详细信息未展开时,改变的是主消息区大小;②详细信息展开时,改变的是详细信息区的大小

总体来说, 此消息框比较适合用在需要反馈大量消息文本的场合, 用标准消息框的话,文本太多可能会使消息框超出屏幕大小,比如codeproject测试数据上这位老兄举的,由于标准消息框不具备改变窗体大小的能力,将导致部分消息无法让用户看到。而就算没有超出屏幕,一下子让用户面对那么多消息文字,体验也不地道。使用本消息框就可以解决此类问题,比如可以将扼要信息显示在主消息区,将大量的明细消息(例如批量处理中的单项处理情况)、次要消息、异常信息等放置在详细信息区,由用户或it支持人员自己去展开获取这些信息。同时,在没有附加消息的时候,你仍然可以像标准消息框一样使用它,所以,如果你跟我一样不会用到标准消息框的iwin32window、messageboxoptions和help相关参数的话, 基本上你可以在整个项目中全程用此消息框替换掉标准消息框, 别忘了相比标准消息框,它还具备了可缩放、相对父窗体居中等额外能力。总言之,你值得拥有。至于如果你担心性能问题,这个~我想这么说,我对自己的代码质量还是有点信心的。也希望能得大侠指出槽点,感激!

使用说明:

先看公开成员:

?

//静态属性

messageboxex.enableanimate

messageboxex.enablesound

 

//静态方法

messageboxex.show( string , string , string )

messageboxex.show( string , string , string , messageboxbuttons)

messageboxex.show( string , string , string , messageboxbuttons, messageboxicon)

messageboxex.show( string , string , string , messageboxbuttons, messageboxicon, messageboxdefaultbutton)

 

messageboxex.show( string , string , exception)

messageboxex.show( string , string , exception, messageboxbuttons)

messageboxex.show( string , string , exception, messageboxbuttons, messageboxicon)

messageboxex.show( string , string , exception, messageboxbuttons, messageboxicon, messageboxdefaultbutton)

属性enableanimate和enablesound上面提过,分别是用来启用/关闭动画、声音效果的,默认是都启用。俩属性影响范围是全局的,比如设置enableanimate = false后,之后弹出的messageboxex都没有动画效果,直到重新设为true,enablesound亦然。最佳实践是将它俩与用户偏好设置相关联,允许用户自主控制

方法则只有一个:show(),从重载列表你大概都能知道如何使用。其中第3个参数就是附加消息,可接受string和exception类的实例,其余参数的位置和意义与标准消息框一致。简要示例如下:

?

messageboxex.show( "主消息" , "标题" , "附加消息" , messageboxbuttons.ok, messageboxicon.none, messageboxdefaultbutton.button1);

messageboxex.show( "主消息" , "标题" , ex, messageboxbuttons.ok, messageboxicon.none, messageboxdefaultbutton.button1);

前3个参数可以放心为null,内部有处理,后面的枚举你也null不了,如果传入无效枚举值,会抛异常

只有3个string参数的那个方法,后面俩参数是可选的。所以不讲究消息体验的你仍然可以这样使用:

?

messageboxex.show( "阿斯顿发" );

messageboxex.show( "阿斯顿发" , "士大夫" );

方案源码:

代码不少,原因自然是有的,有兴趣的童鞋请看后面的实现说明。另外,千万不要认为代码量跟性能有直接关系,有时候更多的代码恰恰是为了提升性能而存在,有时候则是为了健壮性。

 

?

using system;

using system测试数据ponentmodel;

using system.drawing;

using system.io;

using system.runtime.interopservices;

using system.threading;

using system.windows.forms;

 

namespace ahdung.winform

{

  /// <summary>

  /// 可以携带详细信息的消息框

  /// </summary>

  public static class messageboxex

  {

  //异常消息文本

  private const string invalidbuttonexstring = "按钮参数不是有效的枚举项!" ;

  private const string invalidiconexstring = "图标参数不是有效的枚举项!" ;

  private const string invaliddfbuttonexstring = "默认按钮参数不是有效的枚举项!" ;

 

  /// <summary>

  /// 是否启用动画效果

  /// </summary>

  public static bool enableanimate { get ; set ; }

 

  /// <summary>

  /// 是否启用声音反馈

  /// </summary>

  public static bool enablesound { get ; set ; }

 

  //静态构造

  static messageboxex()

  {

   //默认启用动画+声音

   enableanimate = true ;

   enablesound = true ;

  }

 

  #region 公开方法

 

  /// <summary>

  /// 显示消息框

  /// </summary>

  /// <param name="message">消息文本</param>

  /// <param name="caption">消息框标题</param>

  /// <param name="attachmessage">附加消息</param>

  public static dialogresult show( string message, string caption = null , string attachmessage = null )

  {

   return showcore(message, caption, attachmessage, messageboxbuttons.ok, messageboxicon.none, messageboxdefaultbutton.button1);

  }

 

  /*下面这仨弄成重载而不是可选方法是为了避免不必要的参数检查*/

 

  /// <summary>

  /// 显示消息框

  /// </summary>

  /// <param name="message">消息文本</param>

  /// <param name="caption">消息框标题</param>

  /// <param name="attachmessage">附加消息</param>

  /// <param name="buttons">按钮组合</param>

  public static dialogresult show( string message, string caption, string attachmessage, messageboxbuttons buttons)

  {

   if (! enum .isdefined( typeof (messageboxbuttons), buttons)) { throw new invalidenumargumentexception(invalidbuttonexstring); }

 

   return showcore(message, caption, attachmessage, buttons, messageboxicon.none, messageboxdefaultbutton.button1);

  }

 

  /// <summary>

  /// 显示消息框

  /// </summary>

  /// <param name="message">消息文本</param>

  /// <param name="caption">消息框标题</param>

  /// <param name="attachmessage">附加消息</param>

  /// <param name="buttons">按钮组合</param>

  /// <param name="icon">图标</param>

  public static dialogresult show( string message, string caption, string attachmessage, messageboxbuttons buttons, messageboxicon icon)

  {

   if (! enum .isdefined( typeof (messageboxbuttons), buttons)) { throw new invalidenumargumentexception(invalidbuttonexstring); }

   if (! enum .isdefined( typeof (messageboxicon), icon)) { throw new invalidenumargumentexception(invalidiconexstring); }

 

   return showcore(message, caption, attachmessage, buttons, icon, messageboxdefaultbutton.button1);

  }

 

  /// <summary>

  /// 显示消息框

  /// </summary>

  /// <param name="message">消息文本</param>

  /// <param name="caption">消息框标题</param>

  /// <param name="attachmessage">附加消息</param>

  /// <param name="buttons">按钮组合</param>

  /// <param name="icon">图标</param>

  /// <param name="defaultbutton">默认按钮</param>

  public static dialogresult show( string message, string caption, string attachmessage, messageboxbuttons buttons, messageboxicon icon, messageboxdefaultbutton defaultbutton)

  {

   if (! enum .isdefined( typeof (messageboxbuttons), buttons)) { throw new invalidenumargumentexception(invalidbuttonexstring); }

   if (! enum .isdefined( typeof (messageboxicon), icon)) { throw new invalidenumargumentexception(invalidiconexstring); }

   if (! enum .isdefined( typeof (messageboxdefaultbutton), defaultbutton)) { throw new invalidenumargumentexception(invaliddfbuttonexstring); }

 

   return showcore(message, caption, attachmessage, buttons, icon, defaultbutton);

  }

 

  /********传入异常的重载********/

 

  /// <summary>

  /// 显示消息框

  /// </summary>

  /// <param name="message">消息文本</param>

  /// <param name="caption">消息框标题</param>

  /// <param name="exception">异常实例</param>

  public static dialogresult show( string message, string caption, exception exception)

  {

   return show(message, caption, exception == null ? string .empty : exception.tostring());

  }

 

  /// <summary>

  /// 显示消息框

  /// </summary>

  /// <param name="message">消息文本</param>

  /// <param name="caption">消息框标题</param>

  /// <param name="exception">异常实例</param>

  /// <param name="buttons">按钮组合</param>

  public static dialogresult show( string message, string caption, exception exception, messageboxbuttons buttons)

  {

   return show(message, caption, exception == null ? string .empty : exception.tostring(), buttons);

  }

 

  /// <summary>

  /// 显示消息框

  /// </summary>

  /// <param name="message">消息文本</param>

  /// <param name="caption">消息框标题</param>

  /// <param name="exception">异常实例</param>

  /// <param name="buttons">按钮组合</param>

  /// <param name="icon">图标</param>

  public static dialogresult show( string message, string caption, exception exception, messageboxbuttons buttons, messageboxicon icon)

  {

   return show(message, caption, exception == null ? string .empty : exception.tostring(), buttons, icon);

  }

 

  /// <summary>

  /// 显示消息框

  /// </summary>

  /// <param name="message">消息文本</param>

  /// <param name="caption">消息框标题</param>

  /// <param name="exception">异常实例</param>

  /// <param name="buttons">按钮组合</param>

  /// <param name="icon">图标</param>

  /// <param name="defaultbutton">默认按钮</param>

  public static dialogresult show( string message, string caption, exception exception, messageboxbuttons buttons, messageboxicon icon, messageboxdefaultbutton defaultbutton)

  {

   return show(message, caption, exception == null ? string .empty : exception.tostring(), buttons, icon, defaultbutton);

  }

 

  #endregion

 

  //内部方法,不检查参数有效性

  private static dialogresult showcore( string message, string caption, string attachmessage, messageboxbuttons buttons, messageboxicon icon, messageboxdefaultbutton defaultbutton)

  {

   using (messageform f = new messageform(message, caption, buttons, icon, defaultbutton, attachmessage, enableanimate, enablesound))

   {

   return f.showdialog();

   }

  }

 

 

  /*----------------

   下面是消息窗体相关

   ---------------*/

 

  /// <summary>

  /// 消息窗体

  /// </summary>

  /// <remarks>参数有效性由messageboxex负责</remarks>

  private class messageform : form

  {

   /* todo 存在问题:

   * 当消息区文本非常非常多时,且反复进行改变消息框窗口大小、位置、展开收起的操作,那么在某次展开时

   详细信息文本框可能会在原位置(即消息区内某rect)瞬闪一下,

   原因是文本框控件在显示时总会在原位置wm_ncpaint + wm_erasebkgnd一下,暂无解决办法。

   实际应用中碰到的几率很小,就算碰到,影响也可以忽略。

   */

 

   #region 控件初始化

 

   /// <summary>

   /// 必需的设计器变量。

   /// </summary>

   private system测试数据ponentmodel.icontainer components = null ;

 

   /// <summary>

   /// 清理所有正在使用的资源。

   /// </summary>

   /// <param name="disposing">如果应释放托管资源,为 true;否则为 false。</param>

   protected override void dispose( bool disposing)

   {

   if (disposing && (components != null ))

   {

    components.dispose();

   }

   base .dispose(disposing);

   }

 

   #region windows 窗体设计器生成的代码

 

   /// <summary>

   /// 设计器支持所需的方法 - 不要

   /// 使用代码编辑器修改此方法的内容。

   /// </summary>

   private void initializecomponent()

   {

   this .button3 = new system.windows.forms.button();

   this .txbattach = new textboxunselectallable();

   this .button2 = new system.windows.forms.button();

   this .button1 = new system.windows.forms.button();

   this .plbuttonszone = new ahdung.winform.messageboxex.messageform.panelbasic();

   this .ckbtoggle = new ahdung.winform.messageboxex.messageform.togglebutton( this .useanimate);

   this .plattachzone = new ahdung.winform.messageboxex.messageform.panelbasic();

   this .lbmsg = new ahdung.winform.messageboxex.messageform.messageviewer();

   this .plbuttonszone.suspendlayout();

   this .plattachzone.suspendlayout();

   this .suspendlayout();

   //

   // button3

   //

   this .button3.anchor = system.windows.forms.anchorstyles.top | system.windows.forms.anchorstyles.right;

   this .button3.location = new system.drawing.point(320, 8);

   this .button3.margin = new system.windows.forms.padding(3, 2, 3, 2);

   this .button3.name = "button3" ;

   this .button3.size = new system.drawing.size(85, 27);

   this .button3.tabindex = 2;

   //

   // txbattach

   //

   this .txbattach.anchor = ((system.windows.forms.anchorstyles.top | system.windows.forms.anchorstyles.bottom)

       | system.windows.forms.anchorstyles.left)

       | system.windows.forms.anchorstyles.right;

   this .txbattach.location = new system.drawing.point(10, 7);

   this .txbattach.margin = new system.windows.forms.padding(3, 1, 3, 1);

   this .txbattach.name = "txbattach" ;

   this .txbattach. readonly = true ;

   this .txbattach.multiline = true ;

   this .txbattach.scrollbars = system.windows.forms.scrollbars.vertical;

   this .txbattach.size = new system.drawing.size(395, 105);

   this .txbattach.tabindex = 0;

   //

   // button2

   //

   this .button2.anchor = system.windows.forms.anchorstyles.top | system.windows.forms.anchorstyles.right;

   this .button2.location = new system.drawing.point(229, 8);

   this .button2.margin = new system.windows.forms.padding(3, 2, 3, 2);

   this .button2.name = "button2" ;

   this .button2.size = new system.drawing.size(85, 27);

   this .button2.tabindex = 1;

   //

   // button1

   //

   this .button1.anchor = system.windows.forms.anchorstyles.top | system.windows.forms.anchorstyles.right;

   this .button1.location = new system.drawing.point(138, 8);

   this .button1.margin = new system.windows.forms.padding(3, 2, 3, 2);

   this .button1.name = "button1" ;

   this .button1.size = new system.drawing.size(85, 27);

   this .button1.tabindex = 0;

   //

   // plbuttonszone

   //

   this .plbuttonszone.controls.add( this .ckbtoggle);

   this .plbuttonszone.controls.add( this .button1);

   this .plbuttonszone.controls.add( this .button2);

   this .plbuttonszone.controls.add( this .button3);

   this .plbuttonszone.dock = system.windows.forms.dockstyle.bottom;

   this .plbuttonszone.location = new system.drawing.point(0, 96);

   this .plbuttonszone.margin = new system.windows.forms.padding(3, 1, 3, 1);

   this .plbuttonszone.name = "plbuttonszone" ;

   this .plbuttonszone.size = new system.drawing.size(415, 36);

   this .plbuttonszone.tabindex = 1;

   //

   // ckbtoggle

   //

   this .ckbtoggle.location = new system.drawing.point(10, 8);

   this .ckbtoggle.name = "ckbtoggle" ;

   this .ckbtoggle.size = new system.drawing.size(93, 27);

   this .ckbtoggle.tabindex = 3;

   this .ckbtoggle.text = "详细信息(&d)" ;

   this .ckbtoggle.checkedchanged += this .ckbtoggle_checkedchanged;

   //

   // plattachzone

   //

   this .plattachzone.controls.add( this .txbattach);

   this .plattachzone.dock = system.windows.forms.dockstyle.fill;

   this .plattachzone.location = new system.drawing.point(0, 130);

   this .plattachzone.margin = new system.windows.forms.padding(3, 2, 3, 2);

   this .plattachzone.name = "plattachzone" ;

   this .plattachzone.size = new system.drawing.size(415, 114);

   this .plattachzone.tabindex = 2;

   this .plattachzone.visible = false ;

   //

   // lbmsg

   //

   this .lbmsg.dock = system.windows.forms.dockstyle.fill;

   this .lbmsg.icon = null ;

   this .lbmsg.location = new system.drawing.point(0, 0);

   this .lbmsg.name = "lbmsg" ;

   this .lbmsg.padding = new system.windows.forms.padding(21, 18, 21, 18);

   //this.lbmsg.size = new system.drawing.size(415, 96);

   this .lbmsg.tabindex = 0;

   //

   // fmmsg

   //

   this .autoscalemode = system.windows.forms.autoscalemode.none;

   //this.clientsize = new system.drawing.size(415, 261);

   this .controls.add( this .lbmsg);

   this .controls.add( this .plbuttonszone);

   this .controls.add( this .plattachzone);

   this .doublebuffered = true ;

   this .maximizebox = false ;

   this .name = "messageform" ;

   this .padding = new system.windows.forms.padding(0, 0, 0, 17);

   this .showicon = false ;

   this .showintaskbar = false ;

   this .sizegripstyle = system.windows.forms.sizegripstyle.show;

   this .plbuttonszone.resumelayout( false );

   this .plattachzone.resumelayout( false );

   this .plattachzone.performlayout();

   this .resumelayout( false );

   }

 

   #endregion

 

   private togglebutton ckbtoggle;

   private textboxunselectallable txbattach;

   private messageviewer lbmsg;

   private system.windows.forms.button button2;

   private system.windows.forms.button button1;

   private panelbasic plbuttonszone;

   private panelbasic plattachzone;

   private system.windows.forms.button button3;

 

   #endregion

 

   /// <summary>

   /// 最大默认窗体客户区宽度

   /// </summary>

   const int maxclientwidth = 700;

 

   string messagesound; //存储供playsound api使用的系统消息音别名,在processicon中赋值,onshown中取用

 

   int expandheight;

   /// <summary>

   /// 详细信息区展开高度

   /// </summary>

   private int expandheight

   {

   get { return expandheight < 150 ? 150 : expandheight; }

   set { expandheight = value; }

   }

 

   #region 属性

 

   /// <summary>

   /// 是否启用动画效果

   /// </summary>

   /// <remarks>此处还弄该属性是为了保证窗体类的独立性</remarks>

   private bool useanimate { get ; set ; }

 

   /// <summary>

   /// 是否启用声音反馈

   /// </summary>

   /// <remarks>此处还弄该属性是为了保证窗体类的独立性</remarks>

   private bool usesound { get ; set ; }

 

   /// <summary>

   /// 消息按钮

   /// </summary>

   private messageboxbuttons messagebuttons { get ; set ; }

 

   /// <summary>

   /// 消息图标

   /// </summary>

   private messageboxicon messageicon { get ; set ; }

 

   /// <summary>

   /// 默认按钮

   /// </summary>

   private messageboxdefaultbutton defaultbutton { get ; set ; }

 

   #endregion

 

   /// <summary>

   /// 创建消息窗体

   /// </summary>

   private messageform( bool enableanimate)

   {

   this .useanimate = enableanimate; //须尽早设置,要供展开按钮初始化用

   initializecomponent();

   this .startposition = form.activeform == null ? formstartposition.centerscreen : formstartposition.centerparent;

   this .font = systemfonts.messageboxfont;

 

   //注册事件

   this .button1.click += button_click;

   this .button2.click += button_click;

   this .button3.click += button_click;

   this .plattachzone.resize += plattachzone_resize;

   }

 

   /// <summary>

   /// 创建消息窗体

   /// </summary>

   public messageform( string message, string caption, messageboxbuttons buttons, messageboxicon icon, messageboxdefaultbutton defaultbutton, string attachmessage, bool enableanimate, bool enablesound)

   : this (enableanimate)

   {

   this .lbmsg.text = message;

   this .text = caption;

   this .txbattach.text = attachmessage;

   this .messagebuttons = buttons;

   this .messageicon = icon;

   this .defaultbutton = defaultbutton;

   this .usesound = enablesound;

   }

 

   #region 重写基类方法

 

   protected override void onload(eventargs e)

   {

   //须在计算各种尺寸前搞掂

   processicon();

   processbuttons();

 

   this .minimumsize = sizefromclientsize( new size(getpanelbuttonminwidth(), getclientminheight()));

 

   //参数意义定为客户区最大大小,所以需刨掉非客户区高度后传入

   this .clientsize = this .getpreferredsize( new size(maxclientwidth, screen.primaryscreen.workingarea.height - ( this .height - this .clientsize.height)));

 

   base .onload(e);

   }

 

   protected override void onshown(eventargs e)

   {

   //设置默认按钮焦点。须在onshown中设置按钮焦点才有用

   button dfbtn;

   if ((dfbtn = this .acceptbutton as button) != null )

   {

    dfbtn.focus();

   }

 

   //播放消息提示音

   if ( this .usesound) { playsystemsound( this .messagesound); }

 

   base .onshown(e);

   }

 

   //重写窗体参数

   protected override createparams createparams

   {

   get

   {

    createparams prms = base .createparams;

 

    if ((convert.toint32( this .messagebuttons) & 1) == 0) //没有cancel按钮时屏蔽关闭按钮,刚好在偶数项

    {

    prms.classstyle |= 0x200;

    }

 

    return prms;

   }

   }

 

   /// <summary>

   /// 计算合适的窗口尺寸

   /// </summary>

   /// <param name="proposedsize">该参数此处定义为客户区可设置的最大尺寸</param>

   public override size getpreferredsize(size proposedsize)

   {

   int reservedheight = plbuttonszone.height + padding.bottom;

   size size = lbmsg.getpreferredsize( new size(proposedsize.width, proposedsize.height - reservedheight));

   size.height += reservedheight;

   return size;

   }

 

   #endregion

 

   #region 事件处理方法

 

   //展开收起

   private void ckbtoggle_checkedchanged( object sender, eventargs e)

   {

   this .suspendlayout();

 

   if (ckbtoggle. checked )

   {

    plbuttonszone.sendtoback();

    lbmsg.sendtoback();

 

    lbmsg.dock = dockstyle.top;

    plbuttonszone.dock = dockstyle.top;

 

    changeformheight(expandheight);

    plattachzone.visible = true ;

   }

   else

   {

    expandheight = plattachzone.height; //为再次展开记忆高度

    plattachzone.visible = false ;

    changeformheight(-plattachzone.height); //收起时直接取pl高度,不要取expandheight

 

    plbuttonszone.sendtoback();

 

    plbuttonszone.dock = dockstyle.bottom;

    lbmsg.dock = dockstyle.fill;

   }

 

   this .resumelayout();

   }

 

   //按钮事件

   private void button_click( object sender, eventargs e)

   {

   this .dialogresult = (dialogresult)((sender as button).tag);

   }

 

   //用户手工收完详细区则触发折叠

   private void plattachzone_resize( object sender, eventargs e)

   {

   if (ckbtoggle. checked && plattachzone.height == 0)

   {

    ckbtoggle. checked = false ;

   }

   }

 

   #endregion

 

   #region 辅助+私有方法

 

   /// <summary>

   /// 处理按钮相关

   /// </summary>

   private void processbuttons()

   {

   this .ckbtoggle.visible = txbattach.text.trim().length != 0; //无详细信息就不显示展开按钮

 

   int btncount = 3; //按钮数量

 

   switch (messagebuttons) //老实用case,可读点

   {

    case messageboxbuttons.abortretryignore:

    button1.text = "中止(&a)" ; button1.tag = dialogresult.abort;

    button2.text = "重试(&r)" ; button2.tag = dialogresult.retry;

    button3.text = "忽略(&i)" ; button3.tag = dialogresult.ignore;

    break ;

    case messageboxbuttons.ok:

    button1.visible = false ;

    button2.visible = false ;

    button3.text = "确定" ; button3.tag = dialogresult.ok;

    btncount = 1;

    break ;

    case messageboxbuttons.okcancel:

    button1.visible = false ;

    button2.text = "确定" ; button2.tag = dialogresult.ok;

    button3.text = "取消" ; button3.tag = dialogresult.cancel;

    btncount = 2;

    break ;

    case messageboxbuttons.retrycancel:

    button1.visible = false ;

    button2.text = "重试(&r)" ; button2.tag = dialogresult.retry;

    button3.text = "取消" ; button3.tag = dialogresult.cancel;

    btncount = 2;

    break ;

    case messageboxbuttons.yesno:

    button1.visible = false ;

    button2.text = "是(&y)" ; button2.tag = dialogresult.yes;

    button3.text = "否(&n)" ; button3.tag = dialogresult.no;

    btncount = 2;

    break ;

    case messageboxbuttons.yesnocancel:

    button1.text = "是(&y)" ; button1.tag = dialogresult.yes;

    button2.text = "否(&n)" ; button2.tag = dialogresult.no;

    button3.text = "取消" ; button3.tag = dialogresult.cancel;

    break ;

    default : break ;

   }

 

   //仅有ok和有取消按钮时设cancelbutton

   if (( int )messagebuttons == 0 || (( int )messagebuttons & 1) == 1)

   {

    this .cancelbutton = button3;

   }

 

   //处理默认按钮

   if (btncount == 1)

   {

    this .acceptbutton = button3;

   }

   else if (btncount == 2)

   {

    this .acceptbutton = defaultbutton == messageboxdefaultbutton.button2 ? button3 : button2;

   }

   else

   {

    button[] btnarray = { button1, button2, button3 };

    this .acceptbutton = btnarray[convert.toint32(defaultbutton) / 0x100];

   }

   }

 

   /// <summary>

   /// 处理图标(含声音)

   /// </summary>

   private void processicon()

   {

   switch (messageicon)

   {

    //messageboxicon.information同样

    case messageboxicon.asterisk:

    lbmsg.icon = systemicons.information;

    messagesound = "systemasterisk" ;

    break ;

 

    //messageboxicon.hand、messageboxicon.stop同样

    case messageboxicon.error:

    lbmsg.icon = systemicons.error;

    messagesound = "systemhand" ;

    break ;

 

    //messageboxicon.warning同样

    case messageboxicon.exclamation:

    lbmsg.icon = systemicons.warning;

    messagesound = "systemexclamation" ;

    break ;

 

    case messageboxicon.question:

    lbmsg.icon = systemicons.question;

    messagesound = "systemasterisk" ; //question原本是没声音的,此实现让它蹭一下information的

    break ;

 

    default : //messageboxicon.none

    lbmsg.icon = null ;

    messagesound = "systemdefault" ;

    break ;

   }

   }

 

   /// <summary>

   /// 计算窗体客户区最小高度

   /// </summary>

   private int getclientminheight()

   {

   return lbmsg.minimumheight + plbuttonszone.height + padding.bottom;

   }

 

   /// <summary>

   /// 计算按钮区最小宽度

   /// </summary>

   private int getpanelbuttonminwidth()

   {

   int r = 20 /*左右padding*/ , visiblecount = -1 /*因为两个以上才会有间距*/ ;

 

   if (ckbtoggle.visible) { r += ckbtoggle.width; visiblecount++; }

   if (button1.visible) { r += button1.width * 3; visiblecount += 3; }

   else if (button2.visible) { r += button2.width * 2; visiblecount += 2; }

   else { r += button3.width; visiblecount++; }

 

   if (visiblecount != -1) { r += visiblecount * 6; } //按钮间距

 

   return r;

   }

 

   /// <summary>

   /// 改变窗体高度。内部有动画处理

   /// </summary>

   /// <param name="increment">增量(负数即为减小高度)</param>

   private void changeformheight( int increment)

   {

   int finalheight = this .height + increment; //正确的目标高度

 

   if (! this .useanimate) //不使用动画

   {

    this .height = finalheight;

    return ;

   }

 

   const int step = 8; //帧数

 

   for ( int i = 0; i < step; i++)

   {

    if (i == step - 1) //最后一步直达目标

    {

    this .height = finalheight;

    return ;

    }

 

    this .height += increment / step;

 

    application.doevents(); //必要

    thread.sleep(10);

   }

   }

 

   /// <summary>

   /// 播放系统事件声音

   /// </summary>

   /// <remarks>之所以不用messagebeep api是因为这货在srv08上不出声,所以用playsound代替</remarks>

   private static void playsystemsound( string soundalias)

   {

   playsound(soundalias, intptr.zero, 0x10000 /*snd_alias*/ | 0x1 /*snd_async*/ );

   }

 

   [dllimport( "winmm.dll" , charset = charset.auto)]

   private static extern bool playsound([marshalas(unmanagedtype.lpwstr)] string soundname, intptr hmod, int soundflags);

 

   #endregion

 

   #region 嵌套类

 

   /// <summary>

   /// 基础面板

   /// </summary>

   private class panelbasic : control

   {

   public panelbasic()

   {

    setstyle(controlstyles.allpaintinginwmpaint, false ); //关键,不然其上的toolbar不正常

    setstyle(controlstyles.optimizeddoublebuffer, true ); //重要。不设置的话控件绘制不正常

    setstyle(controlstyles.containercontrol, true );

    setstyle(controlstyles.selectable, false );

   }

 

   protected override void wndproc( ref message m)

   {

    //屏蔽wm_erasebkgnd。防止显示时在原位置快闪

    //不能通过controlstyles.allpaintinginwmpaint=true屏蔽

    //会影响其上的toolbar

    if (m.msg == 0x14) { return ; }

 

    base .wndproc( ref m);

   }

 

   protected override void setboundscore( int x, int y, int width, int height, boundsspecified specified)

   {

    //防dock时面板短暂滞留在原位置

    base .setboundscore(x, y, width, height, specified | boundsspecified.y | boundsspecified.width);

   }

   }

 

   /// <summary>

   /// 消息呈现控件

   /// </summary>

   private class messageviewer : control

   {

   const textformatflags textflags = textformatflags.endellipsis //未完省略号

        | textformatflags.wordbreak //允许换行

        | textformatflags.nopadding //无边距

        | textformatflags.externalleading //行间空白。nt5必须,不然文字挤在一起

        | textformatflags.textboxcontrol; //避免半行

 

   const int iconspace = 5; //图标与文本间距

 

   const float preferredscale = 13; //最佳文本区块比例(宽/高)

 

   /// <summary>

   /// 最小高度。不要重写minimumsize,那会在窗体移动和缩放时都会执行

   /// </summary>

   public int minimumheight

   {

    get

    {

    return ( this .icon != null ? math.max( this .icon.height, this .fontheight) : this .fontheight) + padding.vertical;

    }

   }

 

   /// <summary>

   /// 获取或设置图标

   /// </summary>

   public icon icon { get ; set ; }

 

   public messageviewer()

   {

    this .setstyle(controlstyles.cachetext, true );

    this .setstyle(controlstyles.userpaint, true );

    this .setstyle(controlstyles.allpaintinginwmpaint, true );

    this .setstyle(controlstyles.selectable, false );

    this .setstyle(controlstyles.resizeredraw, true ); //重要

 

    this .doublebuffered = true ; //双缓冲

    backcolor = environment.osversion.version.major == 5 ? systemcolors.control : color.white;

   }

 

   //防dock改变尺寸

   protected override void setboundscore( int x, int y, int width, int height, boundsspecified specified)

   {

    base .setboundscore(x, y, width, height, specified | boundsspecified.size);

   }

 

   /// <summary>

   /// 计算合适的消息区尺寸

   /// </summary>

   /// <param name="proposedsize">该参数此处定义为此控件可设置的最大尺寸</param>

   /// <remarks>该方法对太长的单行文本有做比例优化处理,避免用户摆头幅度过大扭到脖子</remarks>

   public override size getpreferredsize(size proposedsize)

   {

    if (proposedsize.width < 10) { proposedsize.width = int .maxvalue; }

    if (proposedsize.height < 10) { proposedsize.height = int .maxvalue; }

 

    int reservedwidth = padding.horizontal + ( this .icon == null ? 0 : ( this .icon.width + iconspace));

 

    size wellsize = size.empty;

    if (! string .isnullorempty( this .text))

    {

    //优化文本块宽高比例

    size size = textrenderer.measuretext( this .text, this .font, new size(proposedsize.width - reservedwidth, 0), textflags); //用指定宽度测量文本面积

    wellsize = convert.tosingle(size.width) / size.height > preferredscale //过于宽扁的情况

     ? size.ceiling(getsamesizewithnewscale(size, preferredscale))

     : size;

 

    //凑齐整行高,确保尾行显示

    int lineheight = textrenderer.measuretext( " " , this .font, new size( int .maxvalue, 0), textflags).height; //单行高,font.height不靠谱

    int differ;

    wellsize.height += (differ = wellsize.height % lineheight) == 0 ? 0 : (lineheight - differ);

    }

    if ( this .icon != null )

    {

    wellsize.width += this .icon.width + iconspace;

    wellsize.height = math.max( this .icon.height, wellsize.height);

    }

    wellsize += padding.size;

 

    //不应超过指定尺寸。宽度在上面已确保不会超过

    if (wellsize.height > proposedsize.height) { wellsize.height = proposedsize.height; }

 

    return wellsize;

   }

 

   /// <summary>

   /// 重绘

   /// </summary>

   protected override void onpaint(painteventargs e)

   {

    graphics g = e.graphics;

    rectangle rect = getpaddedrectangle();

 

    //绘制图标

    if ( this .icon != null )

    {

    g.drawicon( this .icon, padding.left, padding.top);

 

    //右移文本区

    rect.x += this .icon.width + iconspace;

    rect.width -= this .icon.width + iconspace;

 

    //若文字太少,则与图标垂直居中

    if ( this .text.length < 100)

    {

     size textsize = textrenderer.measuretext(g, this .text, this .font, rect.size, textflags);

     if (textsize.height <= this .icon.height)

     {

     rect.y += ( this .icon.height - textsize.height) / 2;

     }

    }

    }

 

    //g.fillrectangle(brushes.gainsboro, rect);//test

 

    //绘制文本

    textrenderer.drawtext(g, this .text, this .font, rect, color.black, textflags);

 

    base .onpaint(e);

   }

 

   /// <summary>

   /// 根据原尺寸,得到相同面积、且指定比例的新尺寸

   /// </summary>

   /// <param name="src">原尺寸</param>

   /// <param name="scale">新尺寸比例。需是width/height</param>

   private static sizef getsamesizewithnewscale(size src, float scale)

   {

    int sqr = src.width * src.height; //原面积

    double w = math.sqrt(sqr * scale); //新面积宽

    return new sizef(convert.tosingle(w), convert.tosingle(sqr / w));

   }

 

   /// <summary>

   /// 获取刨去padding的内容区

   /// </summary>

   private rectangle getpaddedrectangle()

   {

    rectangle r = this .clientrectangle;

    r.x += this .padding.left;

    r.y += this .padding.top;

    r.width -= this .padding.horizontal;

    r.height -= this .padding.vertical;

    return r;

   }

   }

 

   /// <summary>

   /// 屏蔽全选消息的文本框

   /// </summary>

   private class textboxunselectallable : textbox

   {

   protected override void wndproc( ref message m)

   {

    //em_setsel

    if (m.msg == 0xb1) { return ; }

 

    base .wndproc( ref m);

   }

   }

 

   /// <summary>

   /// 包装toolbarbutton为单一控件

   /// </summary>

   private class togglebutton : control

   {

   /// <summary>

   /// 展开/收起图标数据

   /// </summary>

   const string imgdatabase64 =

@"ivborw0kggoaaaansuheugaaacaaaaaqcayaaab3ah1zaaaagxrfwhrtb2z0d2fyzqbbzg9izsbj

bwfnzvjlywr5ccllpaaaa3njrefuenqklvlpfeeqx/8zpccue6gormd6gbegecaqd4w+ocx+ainx

ib4eftk8+g2mquutcbu8en0wmvigekymxgctjrruqhfvubrqqajgl2wpmbg6dzclwuiesf7t0739

666urqqvdjvcxt9pawkfqzkuy491ktpizarxgpv5l15j+dzirx26dqawf56c48+cx+1czddr//13

/seevvx3hz8oxmlxmzsvjht5z+nx8uokfhou31e+qwwzpbkombkwtavruae21quvjwnz5s6u25++

rv365dtc+4sxifjsfevwvscj2tozqyo2fsht1obsfeiqtitisohhw7jggbzm+s72tcovx+gcchgw

k7qttghj5slolne0txznsqgyjeehidejuslow4zmfzngjvv0qmhhyuiaup+ze+w5aftyc/xmurrh

acjikpowqdvhkhu5lcspiy6k0oil5s9mdrcnyp9sdkl+6pexew5awoebigrniivmkofipifwllcg

huim4mri3drpaqg38opmmd6nuz4wgn+korgh64/hxr1huhjl2qg8d8jcz4ztrctlsdjt1ijz51rs

5lfvzj2o2rwxxczdpcnnh3l5k5wntdhydaqng6cwa/ek+auk8sdusx65gualxr1zkcqlldbpkj+s

r8yovbxw+vx4goozsxlzyqqsk10pnldpjlvzdpms0fl55matll04c39+ewblff3l2zs+w7jzii1b

kkfw3idocdis5/g4yljknqccabrpw3j8plvmwlu8xgwosblmasyjfh3i3s4ss+w3vddg++6apj8t

own4hhh/p+g5aw3f+gbyvb632dwghigsyjdvpn4b9elzwf9aje6umaanjsolk3jdncaxue2y0veq

rcxfyect0vpces0funonrtjpgixsruqslbapogibvq8s47rkcorshqvbx7437ni6km8ol9sxeg7a

i2g0fnz2paq3tcjqgbw02ugwoqig8l7bweb1qcsfxhd3/nmmdkwdnj0op1yk6z529y1i8ovydavl

wxoaxxl3w7k4ykkyky/rdq8dofe9d+x6jonyw6wyu+pyj5/hzledpcu61ddjlh1t3e4brgyjchv0

4/qdj+bn/h+naw41kzpiwlh5kc3fms+vnxarybvt7ymdcm2228d6/ov/i8aapfki7yo+mm8aaaaa

suvork5cyii=" ;

 

   readonly bool istogglemode;

   bool ischecked;

   bool useanimate;

   readonly imagelist imglist;

 

   /// <summary>

   /// checked改变后

   /// </summary>

   public event eventhandler checkedchanged;

 

   /// <summary>

   /// 使用动画按钮效果

   /// </summary>

   private bool useanimate

   {

    get { return useanimate; }

    set

    {

    if (useanimate == value) { return ; }

 

    useanimate = value;

    if (ishandlecreated) { this .createhandle(); }

    }

   }

 

   /// <summary>

   /// 获取或设置按钮是否处于按下状态

   /// </summary>

   [description( "获取或设置按钮是否处于按下状态" ), defaultvalue( false )]

   public bool checked

   {

    get

    {

    if (ishandlecreated)

    {

     //保证ischecked与实情吻合。tb_isbuttonchecked

     ischecked = convert.toboolean(sendmessage( this .handle, 0x40a, intptr.zero, intptr.zero).toint32());

    }

    return ischecked;

    }

    set

    {

    if (ischecked == value || !istogglemode) { return ; }

 

    ischecked = value;

 

    if (ishandlecreated)

    {

     //tb_checkbutton

     sendmessage( this .handle, 0x402, intptr.zero, new intptr(convert.toint32(value)));

    }

 

    oncheckedchanged(eventargs.empty);

    }

   }

 

   /// <summary>

   /// 创建toolbarbuttoncontrol

   /// </summary>

   public togglebutton( bool useanimate)

   {

    setstyle(controlstyles.userpaint, false );

    setstyle(controlstyles.allpaintinginwmpaint, true );

    setstyle(controlstyles.optimizeddoublebuffer, true );

    setstyle(controlstyles.resizeredraw, true );

 

    this .istogglemode = true ; //写死好了,独立版才提供设置

    this .useanimate = useanimate;

 

    //将图标加入imagelist

    imglist = new imagelist { imagesize = new system.drawing.size(16, 16), colordepth = colordepth.depth32bit };

    using (memorystream ms = new memorystream(convert.frombase64string(imgdatabase64)))

    {

    imglist.images.addstrip(image.fromstream(ms));

    }

   }

 

   /// <summary>

   /// 执行左键单击

   /// </summary>

   public void performclick()

   {

    sendmessage( this .handle, 0x201, new intptr(0x1), intptr.zero); //wm_lbuttondown

    application.doevents();

    sendmessage( this .handle, 0x202, intptr.zero, intptr.zero); //wm_lbuttonup

   }

 

   protected override void wndproc( ref message m)

   {

    //忽略鼠标双击消息,wm_lbuttondblclk

    if (m.msg == 0x203) { return ; }

 

    //有节操的响应鼠标动作

    if ((m.msg == 0x201 || m.msg == 0x202) && (! this .enabled || ! this .visible))

    {

    return ;

    }

    base .wndproc( ref m);

   }

 

   //创建toolbar

   protected override createparams createparams

   {

    get

    {

    createparams prms = base .createparams;

    prms.classname = "toolbarwindow32" ;

    prms.style = 0x40000000

     | 0x10000000

     //| 0x2000000 //ws_clipchildren

     //| 0x8000

     | 0x1

     | 0x4

     | 0x8

     | 0x40

     | 0x1000 //tbstyle_list,图标文本横排

     ;

    if (useanimate) { prms.style |= 0x800; } //tbstyle_flat。flat模式在nt6.x下,按钮按下会有动画效果

 

    prms.exstyle = 0;

 

    return prms;

    }

   }

 

   protected override void onhandlecreated(eventargs e)

   {

    base .onhandlecreated(e);

 

    //设置imglist

    sendmessage( this .handle, 0x430, intptr.zero, imglist.handle); //tb_setimagelist

 

    //准备添加按钮

    int btnstructsize = marshal. sizeof ( typeof (tbbutton));

    sendmessage( this .handle, 0x41e, new intptr(btnstructsize), intptr.zero); //tb_buttonstructsize,必须在添加按钮前

 

    //构建按钮信息

    tbbutton btnstruct = new tbbutton

    {

    //ibitmap = 0,

    //idcommand = 0,

    fsstate = 0x4, //tbstate_enabled

    istring = sendmessage( this .handle, 0x44d, 0, this .text + '\0' ) //tb_addstring

    };

    if ( this .istogglemode) { btnstruct.fsstyle = 0x2; } //btns_check。作为切换按钮时

 

    intptr btnstructstart = intptr.zero;

    try

    {

    btnstructstart = marshal.allochglobal(btnstructsize); //在非托管区创建一个指针

    marshal.structuretoptr(btnstruct, btnstructstart, true ); //把结构体塞到上述指针

 

    //添加按钮

    sendmessage( this .handle, 0x444, new intptr(1) /*按钮数量*/ , btnstructstart); //tb_addbuttons。从指针取按钮信息

 

    //设置按钮尺寸刚好为toolbar尺寸

    adjustbuttonsize();

    }

    finally

    {

    if (btnstructstart != intptr.zero) { marshal.freehglobal(btnstructstart); }

    }

   }

 

   protected override bool processcmdkey( ref message m, keys keydata)

   {

    //将空格和回车作为鼠标单击处理

    if (m.msg == 0x100 && (keydata == keys.enter || keydata == keys.space))

    {

    performclick();

    return true ;

    }

 

    return base .processcmdkey( ref m, keydata);

   }

 

   /// <summary>

   /// 处理助记键

   /// </summary>

   protected override bool processmnemonic( char charcode)

   {

    if (ismnemonic(charcode, this .text))

    {

    performclick();

    return true ;

    }

 

    return base .processmnemonic(charcode);

   }

 

   protected override void onclick(eventargs e)

   {

    //忽略鼠标右键

    mouseeventargs me = e as mouseeventargs;

    if (me != null && me.button != system.windows.forms.mousebuttons.left)

    { return ; }

 

    //若是切换模式,直接引发checked事件(不要通过设置checked属性引发,因为onclick发送之前就已经check了)

    //存在理论上的不可靠,但暂无更好办法

    if (istogglemode)

    { this .oncheckedchanged(eventargs.empty); }

 

    base .onclick(e);

   }

 

   //重绘后重设按钮尺寸

   protected override void oninvalidated(invalidateeventargs e)

   {

    base .oninvalidated(e);

    adjustbuttonsize();

   }

 

   /// <summary>

   /// 引发checkedchanged事件

   /// </summary>

   protected virtual void oncheckedchanged(eventargs e)

   {

    setimageindex( this . checked ? 1 : 0);

 

    if (checkedchanged != null ) { checkedchanged( this , e); }

   }

 

   /// <summary>

   /// 设置图标索引

   /// </summary>

   private void setimageindex( int index)

   {

    //tb_changebitmap

    sendmessage( this .handle, 0x42b, intptr.zero, new intptr(index));

   }

 

   /// <summary>

   /// 调整按钮尺寸刚好为toolbar尺寸

   /// </summary>

   private void adjustbuttonsize()

   {

    intptr lparam = new intptr(( this .width & 0xffff) | ( this .height << 0x10)); //makelparam手法

    sendmessage( this .handle, 0x41f, intptr.zero, lparam); //tb_setbuttonsize

   }

 

   #region win32 api

 

   [dllimport( "user32.dll" , charset = charset.auto)]

   private static extern intptr sendmessage(intptr hwnd, int msg, intptr wparam, intptr lparam);

 

   [dllimport( "user32.dll" , charset = charset.auto)]

   private static extern intptr sendmessage(intptr hwnd, int msg, int wparam, string lparam);

 

   [structlayout(layoutkind.sequential)]

   private struct tbbutton

   {

    public int ibitmap;

    public int idcommand;

    public byte fsstate;

    public byte fsstyle;

    public byte breserved0;

    public byte breserved1;

    public intptr dwdata;

    public intptr istring;

   }

 

   #endregion

   }

 

   #endregion

  }

  }

}

实现说明:

以下内容献给童鞋。这里先贴个概要类图,详细的后面有完整demo下载,你可以down回去慢慢研究。

若干show方法都是调用私有的showcore方法,这个是模仿标准messagebox的命名。至于意义,是因为公开方法要做参数检查,检查合格后的代码则可以重用。另外,几个存在参数检查的方法都是调用内部方法,而不是调参数最全的那个重载,也是因为要尽量避免无谓的参数检查,因为参数最全的那个公开方法,参数检查自然是做的最多的,那么少参方法本来已经能确保传入的是合法参数,却因为调它,就会造成无谓的检查,而调内部方法则可以避免,因为内部方法就应该设计为不做或少做参数检查的。啰嗦这个是想提醒初学者注意这些细节上的处理,性能要从细处抓起

静态类messageboxex内部维护着一个messageform窗体类(下文简称msgfm),每次show都会实例化一个msgfm,show完即释放。几乎所有能力都是由后者提供,前者只是简单的对其封装和暴露,所以下面主要说msgfm的事。另外,根据传入的messageboxbuttons有无cancel项,会启用/屏蔽窗体右上角的关闭按钮,因为单击关闭按钮的对话框结果始终是dialogresult.cancel,所以如果不屏蔽,在传入yesno这样的参数时候,调用者可能因为用户去点关闭按钮而得到yes、no以外的结果。标准消息框也是有这样的屏蔽处理的

msgfm由3个控件区构成,分别是 主消息区、按钮区、详细信息区 。

    主消息区 是一个单一控件:messageviewer,直接继承自control写成。一开始是考虑用现成的label控件,但发现后者的图文混排效果差强人意(不要扯这个成语本来的意思),它是把文字直接盖在图标上,呵呵,大概此控件的编写者本意就是要把image当backgroundimage用,所以不得已另写一个messageviewer。mv主要做了两个事,绘制(图标和文本)+根据内容确定自身尺寸,另外它还控制了最小高度,避免图标和文本整体被淹没
    按钮区 由一个容器类控件panelbasic托起4个按钮。pb同样是继承自control,没有直接选用panel的原因,主要是panel会在设置dock时跳一下,根源在control.setboundscore的specified参数通知了无谓的信息,所以干脆直接继承control重写该方法,顺便处理一下消息,解决瞬闪的问题,具体原因这里不细说,注释里有简短说明,总之相信我不是蛋疼就行了。此外按钮区会根据按钮可见情况控制最小宽度,它与上面的messageviewer的最小高度共同构成了整个对话框的最小尺寸minimumsize
    panelbasic 上的4个按钮分别是【详细信息】按钮和其它3个对话框命令按钮。仨按钮根据传入的messageboxbuttons参数动态处理(按钮文本、是否可见等),没什么好说的。【详细信息】按钮(togglebutton)则费了番功夫,该按钮从外观上就可以看出不是标准的button,事实上它是个工具栏按钮:toolbarbutton,属于toolbar上的item,本身不是独立的控件(直接继承自component)。这里扯一点,由于.net 2.0起ms就建议用新式的toolstrip代替toolbar,类似的还有menustrip代替mainmenu、statusstrip代替statusbar、contextmenustrip代替contextmenu,vs2010更是默认就不在工具箱显示这些[控件](有些不算控件),所以估计知道的新童鞋不多。后者都是原生的win32组件,前者则是纯.net实现的,有office2003的控件风格。总之对于有win32 native控的我来说,对这些被建议替代的老式控件有特别的情结。所以这个togglebutton实际上是由一个toolbar和一个toolbarbutton组成的看起来像一个单一控件的东西,那为什么它还是继承自control而不是直接用toolbar呢,我承认这里面有练手的原因(迟些我可能会写一篇【教你一步步封装一个win32原生控件】的文章),hmmm~也就这个原因了,但它虽然增加了代码量,但请务必相信性能不比直接用toolbar差,理论上还要好过,因为作为一个完备的toolbar,ms要考虑的情况相当多,显然处理也少不了,而我这个togglebutton由于只负责一个单一按钮的功能,所以其实很simple很lite~聪明的你会理解的。最后为什么要费事弄成toolbarbutton而不是直接用一个button,是因为我看上了mstsc.exe的这个效果:

顺便说一点,enableanimate属性有作用到该按钮,原理是当toolbar具有flat样式的时候,按钮按下和弹起就有动画效果,否则没有

     最后是 详细信息区 ,由一个panelbasic托起一个简单改造过的textbox构成。干嘛不单纯用一个textbox,而要在它底下垫一层呢,是因为在xp上的效果不好(控件狗要考虑的情况很多了啦好不好),xp窗口边框不如nt6粗,不加点衬料的话太单薄。话说回来,panelbasic上面已说过,而所谓改造过的这个textbox叫textboxunselectallable,就干一件事,忽略全选消息(em_setsel),避免焦点移进去的时候蓝莹莹一大片吓到观众。而为什么不用标准textbox的enter事件取消全选,一个字~太low

尚存在一个问题 ,这个注释里也有坦白,就是当主消息文本非常非常多时~大概整屏那么长(这其实是不正确的使用姿势,上面说过,大量信息应该放详细信息区),如果对对话框反复拖拉、展开/收起,那么在某次展开时,textboxunselectallable会瞬间在主消息区闪一下,这个问题在panelbasic得到了完美的解决,但textbox实在无能为力,尝试过直接用原生edit控件也如此,所以暂时留着吧,哪有没缺憾的人生呢

关于声音,由于messagebeep api在srv08系统无声,所以用了playsound api代替。另外,让原本没声音的messageboxicon.question蹭systemicons.information的声音,不能歧视人question

最后,【详细信息】按钮上那俩图标(展开、收起各一个)是我画的,本来想拣mstsc.exe上的,但发现效果不如意,还不如自己画

说了这么多,自以为很理想的实现,可能槽点也不少,再次恳请路过大侠指点,谢谢。

最后,demo 在此 ,里面有个tester供你体验:

-文毕-

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。

dy("nrwz");

查看更多关于一个可携带附加消息的增强消息框MessageBoxEx的详细内容...

  阅读:48次