程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> 【C#】分享一個帶附加消息的增強消息框MessageBoxEx,

【C#】分享一個帶附加消息的增強消息框MessageBoxEx,

編輯:C#入門知識

【C#】分享一個帶附加消息的增強消息框MessageBoxEx,


--------------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關閉動畫效果
  • 根據傳入的MessageBoxIcon,有不同的聲音反饋。這個是NT5的消息框固有的能力,但NT6的消息框卻沒有聲音,猜想可能MS在NT6+有棄用MessageBeep這一API的打算。本消息框通過使用PlaySound API重新讓消息有了聲音反饋,同時亦提供了EnableSound屬性允許你關閉聲音反饋
  • 移除了標准MessageBox提供的IWin32Window、MessageBoxOptions和Help相關參數,原因是我用不到,懶得實現^_^
  • 可拖拉改變消息框尺寸,消息文本和附加文本會隨窗體大小重排。這是標准消息框未提供的能力。改變尺寸分兩種情況有不同的行為:①詳細信息未展開時,改變的是主消息區大小;②詳細信息展開時,改變的是詳細信息區的大小

總體來說,此消息框比較適合用在需要反饋大量消息文本的場合,用標准消息框的話,文本太多可能會使消息框超出屏幕大小,比如codeproject.com上這位老兄舉的例子,由於標准消息框不具備改變窗體大小的能力,將導致部分消息無法讓用戶看到。而就算沒有超出屏幕,一下子讓用戶面對那麼多消息文字,體驗也不地道。使用本消息框就可以解決此類問題,比如可以將扼要信息顯示在主消息區,將大量的明細消息(例如批量處理中的單項處理情況)、次要消息、異常信息等放置在詳細信息區,由用戶或IT支持人員自己去展開獲取這些信息。同時,在沒有附加消息的時候,你仍然可以像標准消息框一樣使用它,所以,如果你跟我一樣不會用到標准消息框的IWin32Window、MessageBoxOptions和Help相關參數的話,基本上你可以在整個項目中全程用此消息框替換掉標准消息框,別忘了相比標准消息框,它還具備了可縮放、NT6下有聲音、相對父窗體居中等額外能力。總言之,你值得擁有。至於如果你擔心性能問題,這個~我想這麼說,我對自己的代碼質量還是有點信心的。也希望能得大俠指出槽點,感激!

使用說明:

先看公開成員:

//靜態屬性
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.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; 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) { PlayMessageSound(this.MessageIcon); } 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> /// <remarks>之所以不在此處順便把Sound處理了是為了松耦合</remarks> private void ProcessIcon() { switch (MessageIcon) { //MessageBoxIcon.Information同樣 case MessageBoxIcon.Asterisk: lbMsg.Icon = SystemIcons.Information; break; //MessageBoxIcon.Hand、MessageBoxIcon.Stop同樣 case MessageBoxIcon.Error: lbMsg.Icon = SystemIcons.Error; break; //MessageBoxIcon.Warning同樣 case MessageBoxIcon.Exclamation: lbMsg.Icon = SystemIcons.Warning; break; case MessageBoxIcon.Question: lbMsg.Icon = SystemIcons.Question; break; default: lbMsg.Icon = null; 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是因為這貨在NT6上不出聲,所以用PlaySound代替</remarks> private static void PlayMessageSound(MessageBoxIcon msgType) { string eventString; switch (msgType) { case MessageBoxIcon.None: eventString = "SystemDefault"; break; //Question原本是沒聲音的,此實現讓它蹭一下Information的 case MessageBoxIcon.Question: //MessageBoxIcon.Information同樣 case MessageBoxIcon.Asterisk: eventString = "SystemAsterisk"; break; //MessageBoxIcon.Hand、MessageBoxIcon.Stop同樣 case MessageBoxIcon.Error: eventString = "SystemHand"; break; //MessageBoxIcon.Warning同樣 case MessageBoxIcon.Exclamation: eventString = "SystemExclamation"; break; default: throw new ArgumentOutOfRangeException(); } PlaySound(eventString, 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.Font.Height) : this.Font.Height) + 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); int lineHeight = TextRenderer.MeasureText(" ", this.Font, new Size(int.MaxValue, 0), textFlags).Height;//單行高,Font.Height不靠譜 wellSize = Convert.ToSingle(size.Width) / size.Height > PreferredScale //過於寬扁的情況 ? Size.Ceiling(GetSameSizeWithNewScale(size, PreferredScale)) : size; //湊齊整行高,確保尾行顯示 wellSize.Height = Convert.ToInt32(Math.Ceiling(wellSize.Height / Convert.ToDouble(lineHeight))) * lineHeight; } 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 } } } MessageBoxEx.cs

實現說明:

以下內容獻給愛“八卦”和蛋疼的童鞋。這裡先貼個概要類圖,詳細的後面有完整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控件也如此,所以暫時留著吧,哪有沒缺憾的人生呢
  • 至於消息框的聲音,NT6上的標准消息框原本就是沒有聲音的,所以照常用MessageBeep API,無聲就無聲,也無不可,但我考慮即便是盡量靠向原生,也不代表啥都要照搬,取其精華棄其糟泊才是正道,所以用了PlaySound API,讓消息聲回到NT6的世界,同時XP表現不變~人家原本就有聲音
  • 最後,【詳細信息】按鈕上那倆圖標(展開、收起各一個)是我畫的,本來想揀mstsc.exe上的,但發現效果不如意,還不如自己畫

說了這麼多,自以為很理想的實現,可能槽點也不少,再次懇請路過大俠指點,謝謝。

最後,Demo在此,裡面有個Tester供你體驗:

-文畢-

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved