using System; using System.Collections; using System.ComponentModel; using System.Drawing; using System.Runtime.InteropServices; using System.Windows.Forms; namespace Eugen.ESystem.Windows.Forms { /// /// An advanced MessageBox that supports customizations like Font, Icon, /// Buttons and Saved Responses /// internal class MessageBoxExForm : System.Windows.Forms.Form { #region Constants private const int LEFT_PADDING = 12; private const int RIGHT_PADDING = 12; private const int TOP_PADDING = 12; private const int BOTTOM_PADDING = 12; private const int BUTTON_LEFT_PADDING = 4; private const int BUTTON_RIGHT_PADDING = 4; private const int BUTTON_TOP_PADDING = 4; private const int BUTTON_BOTTOM_PADDING = 4; private const int MIN_BUTTON_HEIGHT = 23; private const int MIN_BUTTON_WIDTH = 74; private const int ITEM_PADDING = 10; private const int ICON_MESSAGE_PADDING = 15; private const int BUTTON_PADDING = 5; private const int CHECKBOX_WIDTH = 20; private const int IMAGE_INDEX_EXCLAMATION = 0; private const int IMAGE_INDEX_QUESTION = 1; private const int IMAGE_INDEX_STOP = 2; private const int IMAGE_INDEX_INFORMATION = 3; #endregion #region Fields private System.ComponentModel.IContainer components; private System.Windows.Forms.CheckBox chbSaveResponse; private System.Windows.Forms.ImageList imageListIcons; private System.Windows.Forms.ToolTip buttonToolTip; private ArrayList _buttons = new ArrayList(); private bool _allowSaveResponse; private bool _playAlert = true; private MessageBoxExButton _cancelButton = null; private Button _defaultButtonControl = null; private int _maxLayoutWidth; private int _maxLayoutHeight; private int _maxWidth; private int _maxHeight; private bool _allowCancel = true; private string _result = null; /// /// Used to determine the alert sound to play /// private MessageBoxIcon _standardIcon = MessageBoxIcon.None; private Icon _iconImage = null; private Timer timerTimeout = null; private int _timeout = 0; private TimeoutResult _timeoutResult = TimeoutResult.Default; private System.Windows.Forms.Panel panelIcon; private System.Windows.Forms.RichTextBox rtbMessage; /// /// Maps MessageBoxEx buttons to Button controls /// private Hashtable _buttonControlsTable = new Hashtable(); #endregion #region Properties public string Message { set{ rtbMessage.Text = value; } } public string Caption { set{ this.Text = value; } } public Font CustomFont { set{ this.Font = value; } } public ArrayList Buttons { get{ return _buttons; } } public bool AllowSaveResponse { get{ return _allowSaveResponse; } set{ _allowSaveResponse = value; } } public bool SaveResponse { get{ return chbSaveResponse.Checked; } } public string SaveResponseText { set{ chbSaveResponse.Text = value; } } public MessageBoxIcon StandardIcon { set{ SetStandardIcon(value); } } public Icon CustomIcon { set { _standardIcon = MessageBoxIcon.None; _iconImage = value; } } public MessageBoxExButton CustomCancelButton { set{ _cancelButton = value; } } public string Result { get{ return _result; } } public bool PlayAlertSound { get{ return _playAlert; } set{ _playAlert = value; } } public int Timeout { get{ return _timeout; } set{ _timeout = value; } } public TimeoutResult TimeoutResult { get{ return _timeoutResult; } set{ _timeoutResult = value; } } #endregion #region Ctor/Dtor public MessageBoxExForm() { // // Required for Windows Form Designer support // InitializeComponent(); _maxWidth = (int)(SystemInformation.WorkingArea.Width * 0.60); _maxHeight = (int)(SystemInformation.WorkingArea.Height * 0.90); } /// /// Clean up any resources being used. /// protected override void Dispose( bool disposing ) { if( disposing ) { if(components != null) { components.Dispose(); } } base.Dispose( disposing ); } #endregion #region Windows Form Designer generated code /// /// Required method for Designer support - do not modify /// the contents of this method with the code editor. /// private void InitializeComponent() { this.components = new System.ComponentModel.Container(); System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MessageBoxExForm)); this.panelIcon = new System.Windows.Forms.Panel(); this.chbSaveResponse = new System.Windows.Forms.CheckBox(); this.imageListIcons = new System.Windows.Forms.ImageList(this.components); this.buttonToolTip = new System.Windows.Forms.ToolTip(this.components); this.rtbMessage = new System.Windows.Forms.RichTextBox(); this.SuspendLayout(); // // panelIcon // resources.ApplyResources(this.panelIcon, "panelIcon"); this.panelIcon.BackColor = System.Drawing.Color.Transparent; this.panelIcon.Name = "panelIcon"; this.buttonToolTip.SetToolTip(this.panelIcon, resources.GetString("panelIcon.ToolTip")); // // chbSaveResponse // resources.ApplyResources(this.chbSaveResponse, "chbSaveResponse"); this.chbSaveResponse.Name = "chbSaveResponse"; this.buttonToolTip.SetToolTip(this.chbSaveResponse, resources.GetString("chbSaveResponse.ToolTip")); // // imageListIcons // this.imageListIcons.ImageStream = ((System.Windows.Forms.ImageListStreamer)(resources.GetObject("imageListIcons.ImageStream"))); this.imageListIcons.TransparentColor = System.Drawing.Color.Transparent; this.imageListIcons.Images.SetKeyName(0, ""); this.imageListIcons.Images.SetKeyName(1, ""); this.imageListIcons.Images.SetKeyName(2, ""); this.imageListIcons.Images.SetKeyName(3, ""); // // rtbMessage // resources.ApplyResources(this.rtbMessage, "rtbMessage"); this.rtbMessage.BackColor = System.Drawing.SystemColors.Control; this.rtbMessage.BorderStyle = System.Windows.Forms.BorderStyle.None; this.rtbMessage.Name = "rtbMessage"; this.rtbMessage.ReadOnly = true; this.buttonToolTip.SetToolTip(this.rtbMessage, resources.GetString("rtbMessage.ToolTip")); // // MessageBoxExForm // resources.ApplyResources(this, "$this"); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.Controls.Add(this.rtbMessage); this.Controls.Add(this.chbSaveResponse); this.Controls.Add(this.panelIcon); this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; this.MaximizeBox = false; this.MinimizeBox = false; this.Name = "MessageBoxExForm"; this.buttonToolTip.SetToolTip(this, resources.GetString("$this.ToolTip")); this.ResumeLayout(false); } #endregion #region Overrides /// /// This will get called everytime we call ShowDialog on the form /// /// protected override void OnLoad(EventArgs e) { //Reset result _result = null; this.Size = new Size(_maxWidth, _maxHeight); //This is the rectangle in which all items will be layed out _maxLayoutWidth = this.ClientSize.Width - LEFT_PADDING - RIGHT_PADDING; _maxLayoutHeight = this.ClientSize.Height - TOP_PADDING - BOTTOM_PADDING; AddOkButtonIfNoButtonsPresent(); DisableCloseIfMultipleButtonsAndNoCancelButton(); SetIconSizeAndVisibility(); SetMessageSizeAndVisibility(); SetCheckboxSizeAndVisibility(); SetOptimumSize(); LayoutControls(); CenterForm(); PlayAlert(); SelectDefaultButton(); StartTimerIfTimeoutGreaterThanZero(); base.OnLoad (e); } protected override bool ProcessCmdKey(ref Message msg, Keys keyData) { if((int)keyData == (int)(Keys.Alt | Keys.F4) && !_allowCancel) { return true; } return base.ProcessCmdKey(ref msg, keyData); } protected override void OnClosing(CancelEventArgs e) { if(_result == null) { if(_allowCancel) { _result = _cancelButton.Value; } else { e.Cancel = true; return; } } if(timerTimeout != null) { timerTimeout.Stop(); } base.OnClosing (e); } protected override void OnPaint(PaintEventArgs e) { base.OnPaint (e); if(_iconImage != null) { e.Graphics.DrawIcon(_iconImage, new Rectangle(panelIcon.Location, new Size(32, 32))); } } #endregion #region Methods /// /// Measures a string using the Graphics object for this form with /// the specified font /// /// The string to measure /// The maximum width available to display the string /// The font with which to measure the string /// private Size MeasureString(string str, int maxWidth, Font font) { Graphics g = this.CreateGraphics(); SizeF strRectSizeF = g.MeasureString(str, font, maxWidth); g.Dispose(); return new Size((int)Math.Ceiling(strRectSizeF.Width), (int)Math.Ceiling(strRectSizeF.Height)); } /// /// Measures a string using the Graphics object for this form and the /// font of this form /// /// /// /// private Size MeasureString(string str, int maxWidth) { return MeasureString(str, maxWidth, this.Font); } /// /// Gets the longest button text /// /// private string GetLongestButtonText() { int maxLen = 0; string maxStr = null; foreach(MessageBoxExButton button in _buttons) { if(button.Text != null && button.Text.Length > maxLen) { maxLen = button.Text.Length; maxStr = button.Text; } } return maxStr; } /// /// Sets the size and visibility of the Message /// private void SetMessageSizeAndVisibility() { if(rtbMessage.Text == null || rtbMessage.Text.Trim().Length == 0) { rtbMessage.Size = Size.Empty; rtbMessage.Visible = false; } else { int maxWidth = _maxLayoutWidth; if(panelIcon.Size.Width != 0) { maxWidth = maxWidth - (panelIcon.Size.Width + ICON_MESSAGE_PADDING); } //We need to account for scroll bar width and height, otherwise for certains //kinds of text the scroll bar shows up unnecessarily maxWidth = maxWidth - SystemInformation.VerticalScrollBarWidth; Size messageRectSize = MeasureString(rtbMessage.Text, maxWidth); messageRectSize.Width += SystemInformation.VerticalScrollBarWidth; messageRectSize.Height = Math.Max(panelIcon.Height, messageRectSize.Height) + SystemInformation.HorizontalScrollBarHeight; rtbMessage.Size = messageRectSize; rtbMessage.Visible = true; } } /// /// Sets the size and visibility of the Icon /// private void SetIconSizeAndVisibility() { if(_iconImage == null) { panelIcon.Visible = false; panelIcon.Size = Size.Empty; } else { panelIcon.Size = new Size(32,32); panelIcon.Visible = true; } } /// /// Sets the size and visibility of the save response checkbox /// private void SetCheckboxSizeAndVisibility() { if(!AllowSaveResponse) { chbSaveResponse.Visible = false; chbSaveResponse.Size = Size.Empty; } else { Size saveResponseTextSize = MeasureString(chbSaveResponse.Text, _maxLayoutWidth); saveResponseTextSize.Width += CHECKBOX_WIDTH; chbSaveResponse.Size = saveResponseTextSize; chbSaveResponse.Visible= true; } } /// /// Calculates the button size based on the text of the longest /// button text /// /// private Size GetButtonSize() { string longestButtonText = GetLongestButtonText(); if(longestButtonText == null) { //TODO:Handle this case } Size buttonTextSize = MeasureString(longestButtonText, _maxLayoutWidth); Size buttonSize = new Size(buttonTextSize.Width + BUTTON_LEFT_PADDING + BUTTON_RIGHT_PADDING, buttonTextSize.Height + BUTTON_TOP_PADDING + BUTTON_BOTTOM_PADDING); if(buttonSize.Width < MIN_BUTTON_WIDTH) buttonSize.Width = MIN_BUTTON_WIDTH; if(buttonSize.Height < MIN_BUTTON_HEIGHT) buttonSize.Height = MIN_BUTTON_HEIGHT; return buttonSize; } /// /// Set the icon /// /// private void SetStandardIcon(MessageBoxIcon icon) { _standardIcon = icon; switch(icon) { case MessageBoxIcon.Asterisk: _iconImage = SystemIcons.Asterisk; break; case MessageBoxIcon.Error: _iconImage = SystemIcons.Error; break; case MessageBoxIcon.Exclamation: _iconImage = SystemIcons.Exclamation; break; // case MessageBoxIcon.Hand: // _iconImage = SystemIcons.Hand; // break; // case MessageBoxIcon.Information: // _iconImage = SystemIcons.Information; // break; case MessageBoxIcon.Question: _iconImage = SystemIcons.Question; break; // case MessageBoxIcon.Stop: // _iconImage = SystemIcons.Stop; // break; // case MessageBoxIcon.Warning: // _iconImage = SystemIcons.Warning; // break; case MessageBoxIcon.None: _iconImage = null; break; } } private void AddOkButtonIfNoButtonsPresent() { if(_buttons.Count == 0) { MessageBoxExButton okButton = new MessageBoxExButton(); okButton.Text = MessageBoxExButtons.Ok.ToString(); okButton.Value = MessageBoxExButtons.Ok.ToString(); _buttons.Add(okButton); } } /// /// Centers the form on the screen /// private void CenterForm() { int x = (SystemInformation.WorkingArea.Width - this.Width ) / 2; int y = (SystemInformation.WorkingArea.Height - this.Height ) / 2; this.Location = new Point(x,y); } /// /// Sets the optimum size for the form based on the controls that /// need to be displayed /// private void SetOptimumSize() { int ncWidth = this.Width - this.ClientSize.Width; int ncHeight = this.Height - this.ClientSize.Height; int iconAndMessageRowWidth = rtbMessage.Width + ICON_MESSAGE_PADDING + panelIcon.Width; int saveResponseRowWidth = chbSaveResponse.Width + (int)(panelIcon.Width / 2); int buttonsRowWidth = GetWidthOfAllButtons(); int captionWidth = GetCaptionSize().Width; int maxItemWidth = Math.Max(saveResponseRowWidth, Math.Max(iconAndMessageRowWidth, buttonsRowWidth)); int requiredWidth = LEFT_PADDING + maxItemWidth + RIGHT_PADDING + ncWidth; //Since Caption width is not client width, we do the check here if(requiredWidth < captionWidth) requiredWidth = captionWidth; int requiredHeight = TOP_PADDING + Math.Max(rtbMessage.Height,panelIcon.Height) + ITEM_PADDING + chbSaveResponse.Height + ITEM_PADDING + GetButtonSize().Height + BOTTOM_PADDING + ncHeight; //Fix the bug where if the message text is huge then the buttons are overwritten. //Incase the required height is more than the max height then adjust that in the //message height if(requiredHeight > _maxHeight) { rtbMessage.Height -= requiredHeight - _maxHeight; } int height = Math.Min(requiredHeight, _maxHeight); int width = Math.Min(requiredWidth, _maxWidth); this.Size = new Size(width, height); } /// /// Returns the width that will be occupied by all buttons including /// the inter-button padding /// private int GetWidthOfAllButtons() { Size buttonSize = GetButtonSize(); int allButtonsWidth = buttonSize.Width*_buttons.Count + BUTTON_PADDING*(_buttons.Count-1); return allButtonsWidth; } /// /// Gets the width of the caption /// private Size GetCaptionSize() { Font captionFont = GetCaptionFont(); if(captionFont == null) { //some error occured while determining system font captionFont = new Font("Tahoma",11); } int availableWidth = _maxWidth - SystemInformation.CaptionButtonSize.Width - SystemInformation.Border3DSize.Width * 2 ; Size captionSize = MeasureString(this.Text, availableWidth, captionFont); captionSize.Width += SystemInformation.CaptionButtonSize.Width + SystemInformation.Border3DSize.Width * 2; return captionSize; } /// /// Layout all the controls /// private void LayoutControls() { panelIcon.Location = new Point(LEFT_PADDING, TOP_PADDING); rtbMessage.Location = new Point(LEFT_PADDING + panelIcon.Width + ICON_MESSAGE_PADDING * (panelIcon.Width == 0 ? 0 : 1) , TOP_PADDING); chbSaveResponse.Location = new Point(LEFT_PADDING + (int)(panelIcon.Width / 2), TOP_PADDING + Math.Max(panelIcon.Height, rtbMessage.Height) + ITEM_PADDING); Size buttonSize = GetButtonSize(); int allButtonsWidth = GetWidthOfAllButtons(); int firstButtonX = ((int)(this.ClientSize.Width - allButtonsWidth) / 2); int firstButtonY = this.ClientSize.Height - BOTTOM_PADDING - buttonSize.Height; Point nextButtonLocation = new Point(firstButtonX,firstButtonY); bool foundDefaultButton = false; foreach(MessageBoxExButton button in _buttons) { Button buttonCtrl = GetButton(button, buttonSize, nextButtonLocation); if(!foundDefaultButton) { _defaultButtonControl = buttonCtrl; foundDefaultButton = true; } nextButtonLocation.X += buttonSize.Width + BUTTON_PADDING; } } /// /// Gets the button control for the specified MessageBoxExButton, if the /// control has not been created this method creates the control /// /// /// /// /// private Button GetButton(MessageBoxExButton button, Size size, Point location) { Button buttonCtrl = null; if(_buttonControlsTable.ContainsKey(button)) { buttonCtrl = _buttonControlsTable[button] as Button; buttonCtrl.Size = size; buttonCtrl.Location = location; } else { buttonCtrl = CreateButton(button, size, location); _buttonControlsTable[button] = buttonCtrl; this.Controls.Add(buttonCtrl); } return buttonCtrl; } /// /// Creates a button control based on info from MessageBoxExButton /// /// /// /// /// private Button CreateButton(MessageBoxExButton button, Size size, Point location) { Button buttonCtrl = new Button(); buttonCtrl.Size = size; buttonCtrl.Text = button.Text; buttonCtrl.TextAlign = ContentAlignment.MiddleCenter; buttonCtrl.FlatStyle = FlatStyle.System; if(button.HelpText != null && button.HelpText.Trim().Length != 0) { buttonToolTip.SetToolTip(buttonCtrl, button.HelpText); } buttonCtrl.Location = location; buttonCtrl.Click += new EventHandler(OnButtonClicked); buttonCtrl.Tag = button.Value; return buttonCtrl; } private void DisableCloseIfMultipleButtonsAndNoCancelButton() { if(_buttons.Count > 1) { if(_cancelButton != null) return; //See if standard cancel button is present foreach(MessageBoxExButton button in _buttons) { if(button.Text == MessageBoxExButtons.Cancel.ToString() && button.Value == MessageBoxExButtons.Cancel.ToString()) { _cancelButton = button; return; } } //Standard cancel button is not present, Disable //close button DisableCloseButton(this); _allowCancel = false; } else if(_buttons.Count == 1) { _cancelButton = _buttons[0] as MessageBoxExButton; } else { //This condition should never get called _allowCancel = false; } } /// /// Plays the alert sound based on the icon set for the message box /// private void PlayAlert() { if(_playAlert) { if(_standardIcon != MessageBoxIcon.None) { MessageBeep((uint)_standardIcon); } else { MessageBeep(0 /*MB_OK*/); } } } private void SelectDefaultButton() { if(_defaultButtonControl != null) { _defaultButtonControl.Select(); } } private void StartTimerIfTimeoutGreaterThanZero() { if(_timeout > 0) { if(timerTimeout == null) { timerTimeout = new Timer(this.components); timerTimeout.Tick += new EventHandler(timerTimeout_Tick); } if(!timerTimeout.Enabled) { timerTimeout.Interval = _timeout; timerTimeout.Start(); } } } private void SetResultAndClose(string result) { _result = result; this.DialogResult = DialogResult.OK; } #endregion #region Event Handlers private void OnButtonClicked(object sender, EventArgs e) { Button btn = sender as Button; if(btn == null || btn.Tag == null) return; string result = btn.Tag as string; SetResultAndClose(result); } private void timerTimeout_Tick(object sender, EventArgs e) { timerTimeout.Stop(); switch(_timeoutResult) { case TimeoutResult.Default: _defaultButtonControl.PerformClick(); break; case TimeoutResult.Cancel: if(_cancelButton != null) { SetResultAndClose(_cancelButton.Value); } else { _defaultButtonControl.PerformClick(); } break; case TimeoutResult.Timeout: SetResultAndClose(MessageBoxExResult.Timeout); break; } } #endregion #region P/Invoke - SystemParametersInfo, GetSystemMenu, EnableMenuItem, MessageBeep private Font GetCaptionFont() { NONCLIENTMETRICS ncm = new NONCLIENTMETRICS(); ncm.cbSize = Marshal.SizeOf(typeof(NONCLIENTMETRICS)); try { bool result = SystemParametersInfo(SPI_GETNONCLIENTMETRICS, ncm.cbSize, ref ncm, 0); if(result) { return Font.FromLogFont(ncm.lfCaptionFont); } else { int lastError = Marshal.GetLastWin32Error(); return null; } } catch(Exception /*ex*/) { //System.Console.WriteLine(ex.Message); } return null; } private const int SPI_GETNONCLIENTMETRICS = 41; private const int LF_FACESIZE = 32; [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Auto)] private struct LOGFONT { public int lfHeight; public int lfWidth; public int lfEscapement; public int lfOrientation; public int lfWeight; public byte lfItalic; public byte lfUnderline; public byte lfStrikeOut; public byte lfCharSet; public byte lfOutPrecision; public byte lfClipPrecision; public byte lfQuality; public byte lfPitchAndFamily; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] public string lfFaceSize; } [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Auto)] private struct NONCLIENTMETRICS { public int cbSize; public int iBorderWidth; public int iScrollWidth; public int iScrollHeight; public int iCaptionWidth; public int iCaptionHeight; public LOGFONT lfCaptionFont; public int iSmCaptionWidth; public int iSmCaptionHeight; public LOGFONT lfSmCaptionFont; public int iMenuWidth; public int iMenuHeight; public LOGFONT lfMenuFont; public LOGFONT lfStatusFont; public LOGFONT lfMessageFont; } [DllImport("user32.dll", SetLastError=true, CharSet=CharSet.Auto)] private static extern bool SystemParametersInfo(int uiAction, int uiParam, ref NONCLIENTMETRICS ncMetrics, int fWinIni); [DllImport("user32.dll", CharSet=CharSet.Auto)] private static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert); [DllImport("user32.dll", CharSet=CharSet.Auto)] private static extern bool EnableMenuItem(IntPtr hMenu, uint uIDEnableItem, uint uEnable); private const int SC_CLOSE = 0xF060; private const int MF_BYCOMMAND = 0x0; private const int MF_GRAYED = 0x1; private const int MF_ENABLED = 0x0; private void DisableCloseButton(Form form) { try { EnableMenuItem(GetSystemMenu(form.Handle, false), SC_CLOSE, MF_BYCOMMAND | MF_GRAYED); } catch(Exception /*ex*/) { //System.Console.WriteLine(ex.Message); } } [DllImport("user32.dll", CharSet=CharSet.Auto)] private static extern bool MessageBeep(uint type); #endregion } }