Click here to Skip to main content
Click here to Skip to main content
Add your own
alternative version

Balloon Tips Galore!

, 14 Sep 2003
An article describing the various forms of Balloon Tips
using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;

/*
 * 
 *	NOTE 1 : This class and logic will work only and only if the
 *		following key in the registry is set
 *		HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced\EnableToolTips\
 * 
 *	NOTE 2 : There needs to be a manifest file in the output location.
 *		This is in order for the close button to work properly.
 *
 *	NOTE 3 : Needs WinXP.
 * 
*/

namespace BalloonCS
{
	/*
	 * commented to avoid multiple declarations
	 * in project
	 * 
	 * public enum TooltipIcon : int
	{
		None,
		Info,
		Warning,
		Error
	}*/

	public enum BalloonAlignment 
	{
		TopLeft,
		TopMiddle,
		TopRight,
		LeftMiddle,
		RightMiddle,
		BottomLeft,
		BottomMiddle,
		BottomRight,
	}

	public enum BalloonPosition
	{
		/// <summary>
		/// Positions using the exact co-ordinates.
		/// So if the co-ordinates are outside the screen,
		/// tip wont be shown.
		/// </summary>
		Absolute,

		/// <summary>
		/// Positions using the co-ordinates as a reference.
		/// Regardless of the co-ordinates, the tip will 
		/// always be shown on the screen.
		/// </summary>
		Track
	}

	public delegate void DeActivateEventHandler();

	internal class MessageTool : NativeWindow
	{
		private const int WM_LBUTTONDOWN = 0x0201;
		public event DeActivateEventHandler DeActivate;
		
		protected override void WndProc(ref System.Windows.Forms.Message m)
		{
			if( m.Msg == WM_LBUTTONDOWN )
			{
				System.Diagnostics.Debug.WriteLine(m);
				// allow the balloon to close if clicked upon
				if(DeActivate!=null)
				{
					DeActivate();
				}
			}

			base.WndProc(ref m);
		}
	}
	/// <summary>
	/// A sample class to manipulate ballon tooltips.
	/// Windows XP balloon-tips if used properly can 
	/// be very helpful.
	/// This class creates a balloon tooltip in the form of a message.
	/// This becomes useful for showing important information 
	/// quickly to the user.
	/// For example in a data-entry form full of 
	/// controls if an error is made somewhere in entering data
	/// use this to point the bad control.
	/// This helps in a shorter learning cycle of the 
	/// application.
	/// NOTE: the difference between this and HoverBalloon class
	/// is that this can be shown on demand.
	/// </summary>
	public class MessageBalloon : IDisposable
	{
		private MessageTool m_tool = null;
		private Control m_parent;
		private TOOLINFO ti;

		private int m_maxWidth = 250;
		private string m_text = "FMS Balloon Tooltip Control Display Message";
		private string m_title = "FMS Balloon Tooltip Message";
		private TooltipIcon m_titleIcon = TooltipIcon.None;
		private BalloonAlignment m_align = BalloonAlignment.TopRight;
		private bool m_absPosn = false;
		private bool m_centerStem = false;

		private const string TOOLTIPS_CLASS = "tooltips_class32";
		private const int WS_POPUP = unchecked((int)0x80000000);
		private const int WM_USER = 0x0400;
		private readonly IntPtr HWND_TOPMOST = new IntPtr(-1);		
		private const int SWP_NOSIZE = 0x0001;
		private const int SWP_NOMOVE = 0x0002;
		private const int SWP_NOACTIVATE = 0x0010;
		private const int SWP_NOZORDER = 0x0004;
		
		[DllImport("User32", SetLastError=true)]
		private static extern int SetWindowPos(
			IntPtr hWnd,
			IntPtr hWndInsertAfter,
			int X,
			int Y,
			int cx,
			int cy,
			int uFlags);

		[DllImport("User32", SetLastError=true)]
		private static extern int GetClientRect(
			IntPtr hWnd,
			ref RECT lpRect);

		[DllImport("User32", SetLastError=true)]
		private static extern int ClientToScreen(
			IntPtr hWnd,
			ref RECT lpRect);

		[DllImport("User32", SetLastError=true)]
		private static extern int SendMessage(
			IntPtr hWnd,
			int Msg,
			int wParam,
			IntPtr lParam);

		[StructLayout(LayoutKind.Sequential)]
		private struct RECT
		{
			public int left;
			public int top;
			public int right;
			public int bottom;
		}

		private const int TTS_ALWAYSTIP = 0x01;
		private const int TTS_NOPREFIX = 0x02;
		private const int TTS_BALLOON = 0x40;
		private const int TTS_CLOSE = 0x80;
		
		private const int TTM_TRACKPOSITION = WM_USER + 18;
		private const int TTM_SETMAXTIPWIDTH = WM_USER + 24;
		private const int TTM_TRACKACTIVATE = WM_USER + 17;
		private const int TTM_ADDTOOL = WM_USER + 50;
		private const int TTM_SETTITLE = WM_USER + 33;

		private const int TTF_IDISHWND = 0x0001;
		private const int TTF_SUBCLASS = 0x0010;
		private const int TTF_TRACK = 0x0020;
		private const int TTF_ABSOLUTE = 0x0080;
		private const int TTF_TRANSPARENT = 0x0100;
		private const int TTF_CENTERTIP = 0x0002;
		private const int TTF_PARSELINKS = 0x1000;

		[StructLayout(LayoutKind.Sequential)]
		private struct TOOLINFO
		{
			public int cbSize;
			public int uFlags;
			public IntPtr hwnd;
			public IntPtr uId;
			public RECT rect;
			public IntPtr hinst;
			[MarshalAs(UnmanagedType.LPTStr)] 
			public string lpszText;
			public uint lParam;
		}
		
		/// <summary>
		/// Creates a new instance of the MessageBalloon.
		/// </summary>
		public MessageBalloon()
		{
			m_tool = new MessageTool();
			m_tool.DeActivate += new DeActivateEventHandler(this.Hide);
		}

		/// <summary>
		/// Creates a new instance of the MessageBalloon.
		/// </summary>
		/// <param name="parent">Set the parent control which will display.</param>
		public MessageBalloon(Control parent)
		{
			m_parent = parent;
			m_tool = new MessageTool();
			m_tool.DeActivate += new DeActivateEventHandler(this.Hide);

		}

		~MessageBalloon()
		{
			Dispose(false);
		}

		private bool disposed = false;
		public void Dispose()
		{
			Dispose(true);
			// Take yourself off the Finalization queue 
			// to prevent finalization code for this object
			// from executing a second time.
			GC.SuppressFinalize(this);
		}

		protected virtual void Dispose(bool disposing)
		{
			if(!this.disposed)
			{
				if(disposing)
				{
					// release managed resources if any
				}
				
				// release unmanaged resource
				Hide();

				// Note that this is not thread safe.
				// Another thread could start disposing the object
				// after the managed resources are disposed,
				// but before the disposed flag is set to true.
				// If thread safety is necessary, it must be
				// implemented by the client.
			}
			disposed = true;
		}

		private void CreateTool()
		{
			System.Diagnostics.Debug.Assert(
				m_parent.Handle!=IntPtr.Zero, 
				"parent hwnd is null", "SetToolTip");

			CreateParams cp = new CreateParams();
			cp.ClassName = TOOLTIPS_CLASS;
			cp.Style = 
				WS_POPUP | 
				TTS_BALLOON | 
				TTS_NOPREFIX |	
				TTS_ALWAYSTIP |
				TTS_CLOSE;

			// create the tool
			m_tool.CreateHandle(cp);

			// create and fill in the tool tip info
			ti = new TOOLINFO();
			ti.cbSize = Marshal.SizeOf(ti);

			ti.uFlags = TTF_TRACK |
				TTF_IDISHWND |
				TTF_TRANSPARENT | 
				TTF_SUBCLASS |
				TTF_PARSELINKS;

			// absolute is used tooltip maynot be shown 
			// if coords exceed the corners of the screen
			if(m_absPosn)
			{
				ti.uFlags |= TTF_ABSOLUTE;
			}

			if(m_centerStem)
			{
				ti.uFlags |= TTF_CENTERTIP;
			}

			ti.uId = m_tool.Handle;
			ti.lpszText = m_text;
			ti.hwnd = m_parent.Handle;

			GetClientRect(m_parent.Handle, ref ti.rect);
			ClientToScreen(m_parent.Handle, ref ti.rect);

			// make sure we make it the top level window
			SetWindowPos(
				m_tool.Handle, 
				HWND_TOPMOST, 
				0, 0, 0, 0,
				SWP_NOACTIVATE | 
				SWP_NOMOVE | 
				SWP_NOSIZE);

			// add the tool tip
			IntPtr ptrStruct = Marshal.AllocHGlobal(Marshal.SizeOf(ti));
			Marshal.StructureToPtr(ti, ptrStruct, true);

			SendMessage(
				m_tool.Handle, TTM_ADDTOOL, 0, ptrStruct);

			ti = (TOOLINFO)Marshal.PtrToStructure(ptrStruct, 
				typeof(TOOLINFO));

			SendMessage(
				m_tool.Handle, TTM_SETMAXTIPWIDTH, 
				0, new IntPtr(m_maxWidth));

			IntPtr ptrTitle = Marshal.StringToHGlobalAuto(m_title);

			SendMessage(
				m_tool.Handle, TTM_SETTITLE, 
				(int)m_titleIcon, ptrTitle);

			SetBalloonPosition(ti.rect);

			Marshal.FreeHGlobal(ptrStruct);
			Marshal.FreeHGlobal(ptrTitle);
		}
		
		private void SetBalloonPosition(RECT rect)
		{
			int x = 0, y = 0;

			// calculate cordinates depending upon aligment
			switch(m_align)
			{
				case BalloonAlignment.TopLeft:
					x = rect.left;
					y = rect.top;
					break;
				case BalloonAlignment.TopMiddle:
					x = rect.left + (rect.right / 2);
					y = rect.top;
					break;
				case BalloonAlignment.TopRight:
					x = rect.left + rect.right;
					y = rect.top;
					break;
				case BalloonAlignment.LeftMiddle:
					x = rect.left;
					y = rect.top + (rect.bottom / 2);
					break;
				case BalloonAlignment.RightMiddle:
					x = rect.left + rect.right;
					y = rect.top + (rect.bottom / 2);
					break;
				case BalloonAlignment.BottomLeft:
					x = rect.left;
					y = rect.top + rect.bottom;
					break;
				case BalloonAlignment.BottomMiddle:
					x = rect.left + (rect.right / 2);
					y = rect.top + rect.bottom;
					break;
				case BalloonAlignment.BottomRight:
					x = rect.left + rect.right;
					y = rect.top + rect.bottom;
					break;
				default:
					System.Diagnostics.Debug.Assert(false, "undefined enum", "default case reached");
					break;
			}

			//int pt = MAKELONG(ti.rect.left, ti.rect.top);
			int pt = MAKELONG(x, y);
			IntPtr ptr = new IntPtr(pt);

			SendMessage(
				m_tool.Handle, TTM_TRACKPOSITION,
				0, ptr);
				
		}

		/// <summary>
		/// Shows or hides the tool.
		/// </summary>
		/// <param name="show">0 to hide, -1 to show</param>
		private void Display(int show)
		{
			IntPtr ptrStruct = Marshal.AllocHGlobal(Marshal.SizeOf(ti));
			Marshal.StructureToPtr(ti, ptrStruct, true);

			SendMessage(
				m_tool.Handle, TTM_TRACKACTIVATE,
				show, ptrStruct);

			Marshal.FreeHGlobal(ptrStruct);
		}

		/// <summary>
		/// Hides the message if visible.
		/// </summary>
		public void Hide()
		{
			Display(0);
			m_tool.DestroyHandle();
		}

		private int MAKELONG(int loWord, int hiWord)
		{
			return (hiWord << 16) | (loWord & 0xffff);
		}

		/// <summary>
		/// Sets or gets the Title.
		/// </summary>
		public string Title
		{
			get
			{
				return m_title;
			}
			set
			{
				m_title = value;
			}
		}

		/// <summary>
		/// Sets or gets the display icon.
		/// </summary>
		public TooltipIcon TitleIcon
		{
			get
			{
				return m_titleIcon;
			}
			set
			{
				m_titleIcon = value;
			}
		}

		/// <summary>
		/// Sets or get the display text.
		/// </summary>
		public string Text
		{
			get
			{
				return m_text;
			}
			set
			{
				m_text = value;
			}
		}

		/// <summary>
		/// Sets or gets the parent.
		/// </summary>
		public Control Parent
		{
			get
			{
				return m_parent;
			}
			set
			{
				m_parent = value;
			}
		}

		/// <summary>
		/// Sets or gets the placement of the balloon.
		/// </summary>
		public BalloonAlignment Align
		{
			get
			{
				return m_align;
			}
			set
			{
				m_align = value;
			}
		}

		/// <summary>
		/// Sets or gets the positioning of the balloon.
		/// TRUE : Positions using the exact co-ordinates,
		/// if the co-ordinates are outside the screen, tip wont be shown.
		/// FALSE : Positions using the co-ordinates as a reference.
		/// Regardless of the co-ordinates, the tip will 
		/// always be shown on the screen.
		/// </summary>
		public bool UseAbsolutePositioning
		{
			get
			{
				return m_absPosn;
			}
			set
			{
				m_absPosn = value;
			}
		}

		/// <summary>
		/// Sets or gets the stem position 
		/// in the tip. 
		/// TRUE : The stem of the tip is set to center.
		/// An attempt is made to show the tip with the stem
		/// centered, if that would make the tip to be 
		/// hidden partly, stem is not centered.
		/// FALSE: Stem is not centered.
		/// </summary>
		public bool CenterStem
		{
			get
			{
				return m_centerStem;
			}
			set
			{
				m_centerStem = value;
			}
		}

		/// <summary>
		/// Show the Message in a balloon tooltip.
		/// </summary>
		public void Show()
		{
			// recreate window always
			Hide();

			CreateTool();
			Display(-1);
		}

	}
}

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 has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

Ramesh Shrivastav
Web Developer
United States United States
Ramesh is very much into Microsoft technologies, and silently marvels at the power of Windows SDK.
He juggles his time between his family, work and his computer.

| Advertise | Privacy | Mobile
Web02 | 2.8.141015.1 | Last Updated 15 Sep 2003
Article Copyright 2003 by Ramesh Shrivastav
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid