温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

C#中如何实现可携带附加消息的增强消息框

发布时间:2021-09-09 16:54:36 阅读:169 作者:小新 栏目:编程语言
开发者测试专用服务器限时活动,0元免费领,库存有限,领完即止! 点击查看>>

小编给大家分享一下C#中如何实现可携带附加消息的增强消息框,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!

样子:

有损录制+制图的原因不可能原样展示出真实效果。

C#中如何实现可携带附加消息的增强消息框

功能和特点:

  • 相对父窗体居中

  • 可附带附加消息。附加消息可以是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.EnableAnimate
MessageBoxEx.EnableSound

//静态方法
MessageBoxEx.Show(stringstringstring)
MessageBoxEx.Show(stringstringstring, MessageBoxButtons)
MessageBoxEx.Show(stringstringstring, MessageBoxButtons, MessageBoxIcon)
MessageBoxEx.Show(stringstringstring, MessageBoxButtons, MessageBoxIcon, MessageBoxDefaultButton)

MessageBoxEx.Show(stringstringException)
MessageBoxEx.Show(stringstringException, MessageBoxButtons)
MessageBoxEx.Show(stringstringException, MessageBoxButtons, MessageBoxIcon)
MessageBoxEx.Show(stringstringException, 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 { getset; }

 /// <summary>
 /// 是否启用声音反馈
 /// </summary>
 public static bool EnableSound { getset; }

 //静态构造
 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 = nullstring 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(3208);
  this.button3.Margin = new System.Windows.Forms.Padding(3232);
  this.button3.Name = "button3";
  this.button3.Size = new System.Drawing.Size(8527);
  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(107);
  this.txbAttach.Margin = new System.Windows.Forms.Padding(3131);
  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(395105);
  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(2298);
  this.button2.Margin = new System.Windows.Forms.Padding(3232);
  this.button2.Name = "button2";
  this.button2.Size = new System.Drawing.Size(8527);
  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(1388);
  this.button1.Margin = new System.Windows.Forms.Padding(3232);
  this.button1.Name = "button1";
  this.button1.Size = new System.Drawing.Size(8527);
  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(096);
  this.plButtonsZone.Margin = new System.Windows.Forms.Padding(3131);
  this.plButtonsZone.Name = "plButtonsZone";
  this.plButtonsZone.Size = new System.Drawing.Size(41536);
  this.plButtonsZone.TabIndex = 1;
  // 
  // ckbToggle
  // 
  this.ckbToggle.Location = new System.Drawing.Point(108);
  this.ckbToggle.Name = "ckbToggle";
  this.ckbToggle.Size = new System.Drawing.Size(9327);
  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(0130);
  this.plAttachZone.Margin = new System.Windows.Forms.Padding(3232);
  this.plAttachZone.Name = "plAttachZone";
  this.plAttachZone.Size = new System.Drawing.Size(415114);
  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(00);
  this.lbMsg.Name = "lbMsg";
  this.lbMsg.Padding = new System.Windows.Forms.Padding(21182118);
  //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(00017);
  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 { getset; }

  /// <summary>
  /// 是否启用声音反馈
  /// </summary>
  /// <remarks>此处还弄该属性是为了保证窗体类的独立性</remarks>
  private bool UseSound { getset; }

  /// <summary>
  /// 消息按钮
  /// </summary>
  private MessageBoxButtons MessageButtons { getset; }

  /// <summary>
  /// 消息图标
  /// </summary>
  private MessageBoxIcon MessageIcon { getset; }

  /// <summary>
  /// 默认按钮
  /// </summary>
  private MessageBoxDefaultButton DefaultButton { getset; }

  #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;
   defaultbreak;
  }

  //仅有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 { getset; }

  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
5lfVzj2o2rWXXCzDPcnNh4L5K5WntdHYdAqng6cwa/EK+AuK8SDUSx65gUAlxR1ZkcqLLDBpkJ+S
R8yOvbXw+vx4GOoZsXlZyQqsK10pNlDpjlVZDPMs0FL55mATLl04C39+EWblFf3l2zs+w7jZii1b
Kkfw3IDOcDiS5/G4yLjknQcCAbrPW3j8plvMWlu8XGwOsblMASYjFh4i3S4SS+W3Vddg++6apJ8t
OwN4HHH/p+G5AW3f+gbyvB632DwGHigSyjdvpn4b9ElZWF9aJE6uMAanJsOlK3jdNcAXuE2y0vEQ
rcXfyeCT0vPcES0funoNRTJpgixSRUQsLbapogIbVq8S47rKCORShQvbX7437NI6Km8Ol9sxeG7A
i2g0Fnz2PAQ3TcjQGBw02UGWOqig8L7bweB1qCSFxHD3/nMMDkWDnJ0oP1yK6z529y1i8ovydaVL
wXOaXxl3W7K4yKKykY/Rdq8dofe9d+x6jonyw6WYu+Pyj5/hzLedPcU61dDJLh2T3E4BRgYjCHV0
4/qdJ+bn/h+naW41KZpiwLh6Kc3fMS+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(1616), ColorDepth = ColorDepth.Depth42Bit };
   using (MemoryStream ms = new MemoryStream(Convert.FromBase64String(ImgDataBase64)))
   {
   imgList.Images.AddStrip(Image.FromStream(ms));
   }
  }

  /// <summary>
  /// 执行左键单击
  /// </summary>
  public void PerformClick()
  {
   SendMessage(this.Handle, 0x201new 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, 0x41Enew IntPtr(btnStructSize), IntPtr.Zero);//TB_BUTTONSTRUCTSIZE,必须在添加按钮前

   //构建按钮信息
   TBBUTTON btnStruct = new TBBUTTON
   {
   //iBitmap = 0,
   //idCommand = 0,
   fsState = 0x4//TBSTATE_ENABLED
   iString = SendMessage(this.Handle, 0x44D0this.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, 0x444new 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回去慢慢研究。

C#中如何实现可携带附加消息的增强消息框

若干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的这个效果:

C#中如何实现可携带附加消息的增强消息框

顺便说一点,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供你体验:

C#中如何实现可携带附加消息的增强消息框

看完了这篇文章,相信你对“C#中如何实现可携带附加消息的增强消息框”有了一定的了解,如果想了解更多相关知识,欢迎关注亿速云行业资讯频道,感谢各位的阅读!

亿速云「云服务器」,即开即用、新一代英特尔至强铂金CPU、三副本存储NVMe SSD云盘,价格低至29元/月。点击查看>>

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI

开发者交流群×