Click here to Skip to main content
15,881,803 members
Articles / Programming Languages / C#

How to extend the native MessageBox dialog in .NET

Rate me:
Please Sign up or sign in to vote.
4.75/5 (13 votes)
4 Aug 2007CPOL8 min read 287.7K   1.2K   74  
This article introduces a way to extend the native MessageBox dialog without using Windows Hooks and message processing.
using System;
using System.ComponentModel;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Windows.Forms;
using System.Drawing;

namespace Babu.Windows.Forms
{
	/// <summary>
	/// An enhanced message box component. Based on the original System.Windows.Forms.MessageBox class,
	/// this component provides two more function. One is timeout, the other is an extra <see cref="System.Windows.Forms.CheckBox"/> control.
	/// </summary>
	[ToolboxBitmap(typeof(MessageBox), "Resources.MessageBox.bmp")]
	[Description("An enhanced message box.")]
	public partial class MessageBox : Component
	{
		#region Constants

		private const int CheckBoxVerticalSpace = 10;

		#endregion

		#region Constructors
		/// <summary>
		/// Initializes a new instance of the <see cref="MessageBox"/> class.
		/// </summary>
		public MessageBox()
		{
			InitializeComponent();
			InitializeInstance();
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="MessageBox"/> class with a container.
		/// </summary>
		/// <param name="container">Container of the component.</param>
		public MessageBox(IContainer container)
		{
			container.Add(this);

			InitializeComponent();
			InitializeInstance();
		}
		#endregion

		#region Fields

		MessageBoxChildCollection childWindowList;
		int seconds;

		#endregion

		#region Designable properties

		private string message;
		/// <summary>
		/// Gets or sets the text content in the message box.
		/// </summary>
		[Category("Behavior"), Description("The text to display in the message box.")]
		[Editor("System.ComponentModel.Design.MultilineStringEditor, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(System.Drawing.Design.UITypeEditor))]
		public string Message
		{
			get { return message; }
			set { message = value; }
		}

		private string caption;
		/// <summary>
		/// Gets or sets the text to display in the title bar of the message box.
		/// </summary>
		[Category("Behavior"), Description("The text to display in the title bar of the message box.")]
		public string Caption
		{
			get { return caption; }
			set { caption = value; }
		}

		private MessageBoxButtons buttons = MessageBoxButtons.OK;
		/// <summary>
		/// Gets or sets a value indicating which buttons to display in the message box. 
		/// </summary>
		[DefaultValue(typeof(MessageBoxButtons), "OK"), Category("Behavior")]
		[Description("Specifies which buttons to display in the message box. ")]
		public MessageBoxButtons Buttons
		{
			get { return buttons; }
			set { buttons = value; }
		}

		private MessageBoxIcon icon = MessageBoxIcon.None;
		/// <summary>
		/// Gets or sets a value indicating which icon to display in the message box.
		/// </summary>
		[DefaultValue(typeof(MessageBoxIcon), "None"), Category("Behavior")]
		[Description("Specifies which icon to display in the message box.")]
		public MessageBoxIcon Icon
		{
			get { return icon; }
			set { icon = value; }
		}

		private MessageBoxDefaultButton defaultButton = MessageBoxDefaultButton.Button1;
		/// <summary>
		/// Gets or sets a value indicating which button would be the default in the message box
		/// </summary>
		[DefaultValue(typeof(MessageBoxDefaultButton), "Button1"), Category("Behavior")]
		[Description("Specifies which button is the default button in the message box.")]
		public MessageBoxDefaultButton DefaultButton
		{
			get { return defaultButton; }
			set { defaultButton = value; }
		}


		private MessageBoxOptions? options;
		/// <summary>
		/// Gets or sets display and association options that will be used for the message box.
		/// </summary>
		[Category("Behavior")]
		[Description("Specifies which display and association options will be used for the message box. ")]
		public MessageBoxOptions? Options
		{
			get { return options; }
			set { options = value; }
		}

		/// <summary>
		/// Gets or sets a value indicating whether the CheckBox checked.
		/// </summary>
		[Category("CheckBox"), DefaultValue(false)]
		[Description("Specifies whether the CheckBox checked.")]
		public bool Checked
		{
			get { return this.checkBox.Checked; }
			set { this.checkBox.Checked = value; }
		}

		#region Timeout related properties
		private int timeout;
		/// <summary>
		/// Gets or sets the amount of seconds that the message box appears.
		/// Set the property to 0 to disable timeout function of <see cref="MessageBox"/>.
		/// </summary>
		/// <exception cref="System.ArgumentOutOfRangeException">Property value less than 0.</exception>
		[DefaultValue(0), Category("Timeout")]
		[Description("Specifies the amount of seconds that the message box appears. Set it to 0 to disable the function.")]
		public int Timeout
		{
			get { return timeout; }
			set
			{
				if (value < 0)
					throw new ArgumentOutOfRangeException("Timeout", "Timeout cannot be less than 0.");

				timeout = value;
			}
		}

		private bool displayTimeout = true;
		/// <summary>
		/// Gets or sets a value indicating whether the <see cref="MessageBox"/> will display a countdown message.
		/// </summary>
		[DefaultValue(true), Category("Timeout")]
		[Description("Indicate the message box to display a countdown message.")]
		public bool DisplayTimeout
		{
			get { return displayTimeout; }
			set { displayTimeout = value; }
		}

		private string timeoutTextFormat;
		/// <summary>
		/// Gets or sets the format of then countdown message. The format should only contains one argument.
		/// </summary>
		/// <exception cref="System.FormatException">Format string contains more than one arguments.</exception>
		[DefaultValue((string)null), Category("Timeout")]
		[Description("Specifies the countdown message format. The format should only contains one argument.")]
		public string TimeoutTextFormat
		{
			get { return timeoutTextFormat; }
			set
			{
				string saved = this.timeoutTextFormat;
				timeoutTextFormat = value;
				try
				{
					GetFormattedCountdownText(0, true);
				}
				catch (FormatException)
				{
					this.timeoutTextFormat = saved;
					throw;
				}
			}
		}

		#endregion

		#region Checkbox related properties

		private bool checkBoxEnabled;
		/// <summary>
		/// Gets or sets a value indicating the <see cref="MessageBox"/> to display a <see cref="CheckBox"/> control.
		/// </summary>
		[Category("CheckBox"), DefaultValue(false)]
		[Description("Indicates the message box to display a CheckBox control.")]
		public bool CheckBoxEnabled
		{
			get { return checkBoxEnabled; }
			set { checkBoxEnabled = value; }
		}

		private string checkBoxText;
		/// <summary>
		/// Gets or sets the text in the <see cref="CheckBox"/> control.
		/// </summary>
		[Category("CheckBox")]
		[Description("Specifies the text of the CheckBox control.")]
		public string CheckBoxText
		{
			get { return checkBoxText; }
			set { checkBoxText = value; }
		}

		#endregion

		#endregion

		#region Runtime properties

		private bool isTimedOut;
		/// <summary>
		/// Gets a value indicating whether the message box be closed manually or automaticly by timeout.
		/// </summary>
		[Browsable(false)]
		public bool IsTimedOut
		{
			get { return isTimedOut; }
			set { isTimedOut = value; }
		}

		private bool TimeoutEnabled
		{
			get { return this.timeout > 0; }
		}

		#endregion

		#region Methods

		private void InitializeInstance()
		{
			this.checkBox.Font = SystemFonts.MessageBoxFont;


			//checkBox.CheckedChanged += new EventHandler(checkBox_CheckedChanged);
			//checkBox.Disposed += new EventHandler(checkBox_Disposed);
		}

		//void checkBox_Disposed(object sender, EventArgs e)
		//{
		//    Console.WriteLine("Disposed");
		//}

		//void checkBox_CheckedChanged(object sender, EventArgs e)
		//{
		//    Console.WriteLine(checkBox.Checked);
		//}


		private string GetFormattedCountdownText(int remaining, bool throwError)
		{
			string format = this.timeoutTextFormat;
			string text;

			if (string.IsNullOrEmpty(format))
				format = Properties.Resources.MessageBox_TimeoutTextFormat;

			try
			{
				text = string.Format(format, remaining);
			}
			catch (FormatException fex)
			{
				if (throwError)
					throw fex;

				text = string.Format(Properties.Resources.MessageBox_TimeoutTextFormat);
			}

			return text;
		}

		private string GetCaption()
		{
			string caption = this.caption;

			if (string.IsNullOrEmpty(caption))
			{
				if (this.icon != MessageBoxIcon.None)
					caption = this.icon.ToString();
				else
					caption = Properties.Resources.MessageBox_Caption;
			}

			return caption;
		}

		/// <summary>
		/// Displays the message box without parent.
		/// </summary>
		/// <returns>Returns one of the <see cref="System.Windows.Forms.DialogResult"/> values.</returns>
		public DialogResult ShowDialog()
		{
			return this.ShowDialog(null);
		}

		/// <summary>
		/// Displays the message box in front of speicified window.
		/// </summary>
		/// <param name="owner">
		/// A implementation of <see cref="System.Windows.Forms.IWin32Window"/> that will own the modal dialog box.
		/// This parameter will be ignored when <see cref="System.Windows.Forms.MessageBoxOptions.ServiceNotification"/>
		/// option specified.
		/// </param>
		/// <returns></returns>
		public DialogResult ShowDialog(IWin32Window owner)
		{
			DialogResult dr;

			string caption = this.GetCaption();

			this.seconds = -1;
			this.timer1.Interval = 10;
			this.timer1.Start();
			this.isTimedOut = false;

			if (this.Options.HasValue)
			{
				if ((this.Options.Value | MessageBoxOptions.ServiceNotification) > 0 &&
					owner != null)
					owner = null;

				dr = global::System.Windows.Forms.MessageBox.Show(owner, this.Message, caption,
					this.Buttons, this.Icon, this.DefaultButton, this.Options.Value);
			}
			else
				dr = global::System.Windows.Forms.MessageBox.Show(owner, this.Message, caption,
					this.Buttons, this.Icon, this.DefaultButton);

			return dr;
		}

		private void timer1_Tick(object sender, EventArgs e)
		{
			Timer timer = (Timer)sender;
			IntPtr hMsgBox = NativeMethods.FindWindow(null, this.GetCaption());

			if (hMsgBox != IntPtr.Zero)
			{
				if (this.seconds == -1)
				{
					timer.Stop();
					DecorateMessageBox(hMsgBox);

					if (this.TimeoutEnabled)
					{
						timer.Interval = 1000;
						timer.Start();
					}
					else
						return;
				}

				string text = this.GetFormattedCountdownText(this.Timeout - ++seconds, false);
				if (this.label != null)
					label.Text = text;

				if (seconds >= this.Timeout)
				{
					this.isTimedOut = true;
					
					//NativeMethods.SendMessage(hMsgBox, NativeMethods.WM_CLOSE, IntPtr.Zero, IntPtr.Zero);

					MessageBoxChild bn = this.childWindowList.GetButton(this.defaultButton);
					NativeMethods.SendMessage(hMsgBox, NativeMethods.WM_COMMAND,
						NativeMethods.MakeWParam(bn.Id, NativeMethods.BN_CLICKED),
						bn.Handle);

					timer.Stop();
				}

			}
			else
			{
				timer.Stop();
			}
		}


		private void DecorateMessageBox(IntPtr hWndMsgBox)
		{
			if (this.childWindowList == null)
				this.childWindowList = new MessageBoxChildCollection();
			else
				this.childWindowList.Clear();

			NativeMethods.EnumChildWindows(hWndMsgBox, EnumChildren, IntPtr.Zero);

			if (this.childWindowList.Count > 0)
			{
				int y, heightIncreasement = 0;
				NativeMethods.RECT rect = new NativeMethods.RECT();
				NativeMethods.POINT point = new NativeMethods.POINT();

				// setup the checkbox control

				if (this.CheckBoxEnabled)
				{
					// move native buttons lower
					foreach (MessageBoxChild child in this.childWindowList.Buttons)
					{
						point.x = child.Rectangle.left;
						point.y = child.Rectangle.top;

						NativeMethods.ScreenToClient(hWndMsgBox, ref point);

						NativeMethods.SetWindowPos(child.Handle, IntPtr.Zero,
							point.x, point.y + CheckBoxVerticalSpace + this.checkBox.Height,
							child.Rectangle.Size.Width, child.Rectangle.Size.Height,
							NativeMethods.SWP_NOZORDER | NativeMethods.SWP_NOSIZE);
					}

					heightIncreasement += this.checkBox.Height + CheckBoxVerticalSpace;

					//this.checkBox = new CheckBox();
					//this.checkBox.Location = new Point(10, 20);
					//this.checkBox.Visible = true;
					if (string.IsNullOrEmpty(this.checkBoxText))
						this.checkBox.Text = Properties.Resources.MessageBox_CheckBoxText;
					else
						this.checkBox.Text = this.checkBoxText;

					y = point.y;

					if (this.childWindowList.Text != null)
					{
						NativeMethods.GetWindowRect(this.childWindowList.Text.Handle, ref rect);
						point.x = rect.left;
						point.y = rect.top;
						NativeMethods.ScreenToClient(hWndMsgBox, ref point);
						point.y = rect.Size.Width;
					}
					else
					{
						point.x = 20;
						point.y = 300;
					}

					NativeMethods.SetParent(this.checkBox.Handle, hWndMsgBox);
					this.checkBox.Location = new Point(point.x, y);
					this.checkBox.Width = point.y;


				}

				// setup the timeout label control

				if (this.DisplayTimeout && this.TimeoutEnabled)
				{
					//this.label = new Label();
					this.label.Visible = true;
					this.label.Font = SystemFonts.MessageBoxFont;

					NativeMethods.SetParent(this.label.Handle, hWndMsgBox);
					NativeMethods.GetClientRect(hWndMsgBox, ref rect);
					this.label.Top = rect.bottom + heightIncreasement;
					this.label.Width = rect.right;

					heightIncreasement += this.label.Height;
				}

				// adjusts MessageBox window size

				if (heightIncreasement > 0)
				{
					NativeMethods.GetWindowRect(hWndMsgBox, ref rect);

					NativeMethods.SetWindowPos(hWndMsgBox, IntPtr.Zero,
						rect.left, rect.top, rect.Size.Width, rect.Size.Height + heightIncreasement,
						NativeMethods.SWP_NOZORDER | NativeMethods.SWP_NOMOVE);
				}


			}
		}

		private bool EnumChildren(IntPtr hWnd, IntPtr param)
		{
			StringBuilder name = new StringBuilder(1024), caption = new StringBuilder(1024);
			NativeMethods.RECT rect = new NativeMethods.RECT();
			int style, id;

			NativeMethods.GetClassName(hWnd, name, 1024);
			NativeMethods.GetWindowText(hWnd, caption, 1024);
			NativeMethods.GetWindowRect(hWnd, ref rect);

			style = NativeMethods.GetWindowLong(hWnd, NativeMethods.GWL_STYLE);
			id = NativeMethods.GetWindowLong(hWnd, NativeMethods.GWL_ID);

			//Console.WriteLine("hWnd=0x{0:X8}, class={1}, caption={2}, rect={3}, id={4}", hWnd, name, caption, rect, id);

			this.childWindowList.Add(new MessageBoxChild(hWnd, name.ToString(), caption.ToString(), rect, style, id));

			return true;
		}

		#endregion

		#region Helper classes
		class MessageBoxChildCollection : List<MessageBoxChild>
		{
			public MessageBoxChildCollection()
				: base()
			{
				this.buttons = new List<MessageBoxChild>(4);
			}

			public new void Add(MessageBoxChild child)
			{
				switch (child.ClassName.ToUpper())
				{
				case NativeMethods.CLS_BUTTON:
					this.buttons.Add(child);
					break;
				case NativeMethods.CLS_STATIC:
					if ((child.Style & NativeMethods.SS_ICON) == NativeMethods.SS_ICON)
						this.icon = child;
					else
						this.text = child;
					break;
				default:
					break;
				}

				base.Add(child);
			}

			public new bool Remove(MessageBoxChild child)
			{
				if (child.Equals(icon)) icon = null;
				else if (child.Equals(text)) text = null;

				buttons.Remove(child);

				return base.Remove(child);
			}

			public new void Clear()
			{
				this.icon = null;
				this.text = null;
				this.buttons.Clear();

				base.Clear();
			}

			private MessageBoxChild icon;
			public MessageBoxChild Icon
			{
				get { return icon; }
			}

			private MessageBoxChild text;
			public MessageBoxChild Text
			{
				get { return text; }
			}

			private List<MessageBoxChild> buttons;
			public List<MessageBoxChild> Buttons
			{
				get { return buttons; }
			}

			public MessageBoxChild GetButton(DialogResult result)
			{
				int id;

				switch (result)
				{
				case DialogResult.Abort:
					id = NativeMethods.IDABORT;
					break;
				case DialogResult.Cancel:
					id = NativeMethods.IDCANCEL;
					break;
				case DialogResult.Ignore:
					id = NativeMethods.IDIGNORE;
					break;
				case DialogResult.No:
					id = NativeMethods.IDNO;
					break;
				case DialogResult.OK:
					id = NativeMethods.IDOK;
					break;
				case DialogResult.Retry:
					id = NativeMethods.IDRETRY;
					break;
				case DialogResult.Yes:
					id = NativeMethods.IDYES;
					break;
				default:
					id = 0;
					break;
				}

				if (id > 0)
				{
					foreach (MessageBoxChild bn in this.buttons)
					{
						if (bn.Id == id)
							return bn;
					}
				}

				return null;
			}

			public MessageBoxChild GetButton(MessageBoxDefaultButton button)
			{
				int index = 0;

				if (button == MessageBoxDefaultButton.Button2)
					index = 1;
				else if (button == MessageBoxDefaultButton.Button3)
					index = 2;

				if (index >= this.buttons.Count)
					index = 0;

				return this.buttons[index];
			}

		}

		class MessageBoxChild : IWin32Window
		{

			public MessageBoxChild(IntPtr handle, string className, string caption, NativeMethods.RECT rect, int style, int id)
			{
				this.handle = handle;
				this.className = className;
				this.caption = caption;
				this.rectangle = rect;
				this.style = style;
				this.id = id;
			}

			private int id;
			public int Id
			{
				get { return id; }
			}

			private int style;
			public int Style
			{
				get { return style; }
			}


			private IntPtr handle;
			public IntPtr Handle
			{
				get { return handle; }
			}


			private string className;
			public string ClassName
			{
				get { return className; }
			}

			private string caption;
			public string Caption
			{
				get { return caption; }
			}

			private NativeMethods.RECT rectangle;
			public NativeMethods.RECT Rectangle
			{
				get { return rectangle; }
			}


			// override object.Equals
			public override bool Equals(object obj)
			{

				//       
				// See the full list of guidelines at
				//   http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/cpconequals.asp    
				// and also the guidance for operator== at
				//   http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/cpconimplementingequalsoperator.asp
				//

				if (obj == null || GetType() != obj.GetType())
				{
					return false;
				}

				return ((MessageBoxChild)obj).Handle == this.Handle;
			}

			// override object.GetHashCode
			public override int GetHashCode()
			{
				return this.Handle.GetHashCode();
			}

			#region IWin32Window ��Ա

			IntPtr IWin32Window.Handle
			{
				get { return this.Handle; }
			}

			#endregion
		}

		#endregion

	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer
China China
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions