首页 > 编程 > C# > 正文

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

2020-01-24 00:42:26
字体:
来源:转载
供稿:网友

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

使用说明:

先看公开成员:

//静态属性MessageBoxEx.EnableAnimateMessageBoxEx.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.ComponentModel;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.ComponentModel.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 =@"iVBORw0KGgoAAAANSUhEUgAAACAAAAAQCAYAAAB3AH1ZAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA3NJREFUeNqklVlPFEEQx/8zPccue6gorMd6gBegeCAQD4w+oCx+AInxIB4EfTK8+g2MQUUTcBU8En0wmvigEkyMxgcTjRrUqHFVUBRQQaJGl2WPmbG6dzCLWUiESf7T0739666urqqVDjVcxT9PAWkfqZKUY491ktpIzaRXGPv5L15J+dZIRx26dqAwf56c48+Cx+1CzDDR//13/seevvx3HZ8OxmLxMzSvjhT5Z+Nx8UoKfHOu31e+qWwZPBkOMBkwTAvRuAE21QuvJwNz5s6U25++rv365dtC+4SxifJsfeVWvsCJ2TOzqyo2FsHt1OBSFeiqTItIsOhHw7JgGBZM+s72TcOvX+GccHgwk7qttgHj5slOLNE0tXZNSQGYJEEhiDEJusLoW4ZMfZnGJVv0QmHhYuiaup+zE+W5Aftyc/xMURRhacJIKpowqDVhkhu5LCspiY6k0OIL5s9mdrCNyp9sDKL+6PExeW5AwOebigRNiiVMkoFIPIFwlLcGhuIm4mRI3DRpAQg38oPMmD6Nuz4wGn+koRGH64/hxr1HuHjl2qg8D8JcZ4ZTRCtLSDjT1Ijz51rS5lfVzj2o2rWXXCzDPcnNh3L5K5WntdHYdAqng6cwa/EK+AuK8SDUSx65gUAlxR1ZkcqLLDBpkJ+SR8yOvbXw+vx4GOoZsXlZyQqsK10pNlDpjlVZDPMs0FL55mATLl04C39+EWblFf3l2zs+w7jZii1bKkfw3IDOcDiS5/G4yLjknQcCAbrPW3j8plvMWlu8XGwOsblMASYjFh3i3S4SS+W3Vddg++6apJ8tOwN4HHH/p+G5AW3f+gbyvB632DwGHigSyjdvpn4b9ElZWF9aJE6uMAanJsOlK3jdNcAXuE2y0vEQrcXfyeCT0vPcES0funoNRTJpgixSRUQsLbapogIbVq8S47rKCORShQvbX7437NI6Km8Ol9sxeG7Ai2g0Fnz2PAQ3TcjQGBw02UGWOqig8L7bweB1qCSFxHD3/nMMDkWDnJ0oP1yK6z529y1i8ovydaVLwXOaXxl3W7K4yKKykY/Rdq8dofe9d+x6jonyw6WYu+Pyj5/hzLedPcU61dDJLh1T3E4BRgYjCHV04/qdJ+bn/h+naW41KZpiwLh5Kc3fMS+vNXaRybVT7YMdcM2228d6/ov/I8AAPfkI7yO+mM8AAAAASUVORK5CYII=";  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供你体验:

-文毕-

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

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表