/* * ToolTipControl - A limited wrapper around a Windows tooltip control * * For some reason, the ToolTip class in the .NET framework is implemented in a significantly * different manner to other controls. For our purposes, the worst of these problems * is that we cannot get the Handle, so we cannot send Windows level messages to the control. * * Author: Phillip Piper * Date: 2009-05-17 7:22PM * * Change log: * v2.3 * 2009-06-13 JPP - Moved ToolTipShowingEventArgs to Events.cs * v2.2 * 2009-06-06 JPP - Fixed some Vista specific problems * 2009-05-17 JPP - Initial version * * TO DO: * * Copyright (C) 2006-2014 Phillip Piper * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you wish to use this code in a closed source application, please contact phillip_piper@bigfoot.com. */ using System; using System.Collections; using System.ComponentModel; using System.Drawing; using System.Runtime.InteropServices; using System.Windows.Forms; using System.Security.Permissions; namespace BrightIdeasSoftware { /// /// A limited wrapper around a Windows tooltip window. /// public class ToolTipControl : NativeWindow { #region Constants /// /// These are the standard icons that a tooltip can display. /// public enum StandardIcons { /// /// No icon /// None = 0, /// /// Info /// Info = 1, /// /// Warning /// Warning = 2, /// /// Error /// Error = 3, /// /// Large info (Vista and later only) /// InfoLarge = 4, /// /// Large warning (Vista and later only) /// WarningLarge = 5, /// /// Large error (Vista and later only) /// ErrorLarge = 6 } const int GWL_STYLE = -16; const int WM_GETFONT = 0x31; const int WM_SETFONT = 0x30; const int WS_BORDER = 0x800000; const int WS_EX_TOPMOST = 8; const int TTM_ADDTOOL = 0x432; const int TTM_ADJUSTRECT = 0x400 + 31; const int TTM_DELTOOL = 0x433; const int TTM_GETBUBBLESIZE = 0x400 + 30; const int TTM_GETCURRENTTOOL = 0x400 + 59; const int TTM_GETTIPBKCOLOR = 0x400 + 22; const int TTM_GETTIPTEXTCOLOR = 0x400 + 23; const int TTM_GETDELAYTIME = 0x400 + 21; const int TTM_NEWTOOLRECT = 0x400 + 52; const int TTM_POP = 0x41c; const int TTM_SETDELAYTIME = 0x400 + 3; const int TTM_SETMAXTIPWIDTH = 0x400 + 24; const int TTM_SETTIPBKCOLOR = 0x400 + 19; const int TTM_SETTIPTEXTCOLOR = 0x400 + 20; const int TTM_SETTITLE = 0x400 + 33; const int TTM_SETTOOLINFO = 0x400 + 54; const int TTF_IDISHWND = 1; //const int TTF_ABSOLUTE = 0x80; const int TTF_CENTERTIP = 2; const int TTF_RTLREADING = 4; const int TTF_SUBCLASS = 0x10; //const int TTF_TRACK = 0x20; //const int TTF_TRANSPARENT = 0x100; const int TTF_PARSELINKS = 0x1000; const int TTS_NOPREFIX = 2; const int TTS_BALLOON = 0x40; const int TTS_USEVISUALSTYLE = 0x100; const int TTN_FIRST = -520; /// /// /// public const int TTN_SHOW = (TTN_FIRST - 1); /// /// /// public const int TTN_POP = (TTN_FIRST - 2); /// /// /// public const int TTN_LINKCLICK = (TTN_FIRST - 3); /// /// /// public const int TTN_GETDISPINFO = (TTN_FIRST - 10); const int TTDT_AUTOMATIC = 0; const int TTDT_RESHOW = 1; const int TTDT_AUTOPOP = 2; const int TTDT_INITIAL = 3; #endregion #region Properties /// /// Get or set if the style of the tooltip control /// internal int WindowStyle { get { return (int)NativeMethods.GetWindowLong(this.Handle, GWL_STYLE); } set { NativeMethods.SetWindowLong(this.Handle, GWL_STYLE, value); } } /// /// Get or set if the tooltip should be shown as a ballon /// public bool IsBalloon { get { return (this.WindowStyle & TTS_BALLOON) == TTS_BALLOON; } set { if (this.IsBalloon == value) return; int windowStyle = this.WindowStyle; if (value) { windowStyle |= (TTS_BALLOON | TTS_USEVISUALSTYLE); // On XP, a border makes the ballon look wrong if (!ObjectListView.IsVistaOrLater) windowStyle &= ~WS_BORDER; } else { windowStyle &= ~(TTS_BALLOON | TTS_USEVISUALSTYLE); if (!ObjectListView.IsVistaOrLater) { if (this.hasBorder) windowStyle |= WS_BORDER; else windowStyle &= ~WS_BORDER; } } this.WindowStyle = windowStyle; } } /// /// Get or set if the tooltip should be shown as a ballon /// public bool HasBorder { get { return this.hasBorder; } set { if (this.hasBorder == value) return; if (value) { this.WindowStyle |= WS_BORDER; } else { this.WindowStyle &= ~WS_BORDER; } } } private bool hasBorder = true; /// /// Get or set the background color of the tooltip /// public Color BackColor { get { int color = (int)NativeMethods.SendMessage(this.Handle, TTM_GETTIPBKCOLOR, 0, 0); return ColorTranslator.FromWin32(color); } set { // For some reason, setting the color fails on Vista and messes up later ops. // So we don't even try to set it. if (!ObjectListView.IsVistaOrLater) { int color = ColorTranslator.ToWin32(value); NativeMethods.SendMessage(this.Handle, TTM_SETTIPBKCOLOR, color, 0); //int x2 = Marshal.GetLastWin32Error(); } } } /// /// Get or set the color of the text and border on the tooltip. /// public Color ForeColor { get { int color = (int)NativeMethods.SendMessage(this.Handle, TTM_GETTIPTEXTCOLOR, 0, 0); return ColorTranslator.FromWin32(color); } set { // For some reason, setting the color fails on Vista and messes up later ops. // So we don't even try to set it. if (!ObjectListView.IsVistaOrLater) { int color = ColorTranslator.ToWin32(value); NativeMethods.SendMessage(this.Handle, TTM_SETTIPTEXTCOLOR, color, 0); } } } /// /// Get or set the title that will be shown on the tooltip. /// public string Title { get { return this.title; } set { if (String.IsNullOrEmpty(value)) this.title = String.Empty; else if (value.Length >= 100) this.title = value.Substring(0, 99); else this.title = value; NativeMethods.SendMessageString(this.Handle, TTM_SETTITLE, (int)this.standardIcon, this.title); } } private string title; /// /// Get or set the icon that will be shown on the tooltip. /// public StandardIcons StandardIcon { get { return this.standardIcon; } set { this.standardIcon = value; NativeMethods.SendMessageString(this.Handle, TTM_SETTITLE, (int)this.standardIcon, this.title); } } private StandardIcons standardIcon; /// /// Gets or sets the font that will be used to draw this control. /// is still. /// /// Setting this to null reverts to the default font. public Font Font { get { IntPtr hfont = NativeMethods.SendMessage(this.Handle, WM_GETFONT, 0, 0); if (hfont == IntPtr.Zero) return Control.DefaultFont; else return Font.FromHfont(hfont); } set { Font newFont = value ?? Control.DefaultFont; if (newFont == this.font) return; this.font = newFont; IntPtr hfont = this.font.ToHfont(); // THINK: When should we delete this hfont? NativeMethods.SendMessage(this.Handle, WM_SETFONT, hfont, 0); } } private Font font; /// /// Gets or sets how many milliseconds the tooltip will remain visible while the mouse /// is still. /// public int AutoPopDelay { get { return this.GetDelayTime(TTDT_AUTOPOP); } set { this.SetDelayTime(TTDT_AUTOPOP, value); } } /// /// Gets or sets how many milliseconds the mouse must be still before the tooltip is shown. /// public int InitialDelay { get { return this.GetDelayTime(TTDT_INITIAL); } set { this.SetDelayTime(TTDT_INITIAL, value); } } /// /// Gets or sets how many milliseconds the mouse must be still before the tooltip is shown again. /// public int ReshowDelay { get { return this.GetDelayTime(TTDT_RESHOW); } set { this.SetDelayTime(TTDT_RESHOW, value); } } private int GetDelayTime(int which) { return (int)NativeMethods.SendMessage(this.Handle, TTM_GETDELAYTIME, which, 0); } private void SetDelayTime(int which, int value) { NativeMethods.SendMessage(this.Handle, TTM_SETDELAYTIME, which, value); } #endregion #region Commands /// /// Create the underlying control. /// /// The parent of the tooltip /// This does nothing if the control has already been created public void Create(IntPtr parentHandle) { if (this.Handle != IntPtr.Zero) return; CreateParams cp = new CreateParams(); cp.ClassName = "tooltips_class32"; cp.Style = TTS_NOPREFIX; cp.ExStyle = WS_EX_TOPMOST; cp.Parent = parentHandle; this.CreateHandle(cp); // Ensure that multiline tooltips work correctly this.SetMaxWidth(); } /// /// Take a copy of the current settings and restore them when the /// tooltip is poppped. /// /// /// This call cannot be nested. Subsequent calls to this method will be ignored /// until PopSettings() is called. /// public void PushSettings() { // Ignore any nested calls if (this.settings != null) return; this.settings = new Hashtable(); this.settings["IsBalloon"] = this.IsBalloon; this.settings["HasBorder"] = this.HasBorder; this.settings["BackColor"] = this.BackColor; this.settings["ForeColor"] = this.ForeColor; this.settings["Title"] = this.Title; this.settings["StandardIcon"] = this.StandardIcon; this.settings["AutoPopDelay"] = this.AutoPopDelay; this.settings["InitialDelay"] = this.InitialDelay; this.settings["ReshowDelay"] = this.ReshowDelay; this.settings["Font"] = this.Font; } private Hashtable settings; /// /// Restore the settings of the tooltip as they were when PushSettings() /// was last called. /// public void PopSettings() { if (this.settings == null) return; this.IsBalloon = (bool)this.settings["IsBalloon"]; this.HasBorder = (bool)this.settings["HasBorder"]; this.BackColor = (Color)this.settings["BackColor"]; this.ForeColor = (Color)this.settings["ForeColor"]; this.Title = (string)this.settings["Title"]; this.StandardIcon = (StandardIcons)this.settings["StandardIcon"]; this.AutoPopDelay = (int)this.settings["AutoPopDelay"]; this.InitialDelay = (int)this.settings["InitialDelay"]; this.ReshowDelay = (int)this.settings["ReshowDelay"]; this.Font = (Font)this.settings["Font"]; this.settings = null; } /// /// Add the given window to those for whom this tooltip will show tips /// /// The window public void AddTool(IWin32Window window) { NativeMethods.TOOLINFO lParam = this.MakeToolInfoStruct(window); NativeMethods.SendMessageTOOLINFO(this.Handle, TTM_ADDTOOL, 0, lParam); } /// /// Hide any currently visible tooltip /// /// public void PopToolTip(IWin32Window window) { NativeMethods.SendMessage(this.Handle, TTM_POP, 0, 0); } //public void Munge() { // NativeMethods.TOOLINFO tool = new NativeMethods.TOOLINFO(); // IntPtr result = NativeMethods.SendMessageTOOLINFO(this.Handle, TTM_GETCURRENTTOOL, 0, tool); // System.Diagnostics.Trace.WriteLine("-"); // System.Diagnostics.Trace.WriteLine(result); // result = NativeMethods.SendMessageTOOLINFO(this.Handle, TTM_GETBUBBLESIZE, 0, tool); // System.Diagnostics.Trace.WriteLine(String.Format("{0} {1}", result.ToInt32() >> 16, result.ToInt32() & 0xFFFF)); // NativeMethods.ChangeSize(this, result.ToInt32() & 0xFFFF, result.ToInt32() >> 16); // //NativeMethods.RECT r = new NativeMethods.RECT(); // //r.right // //IntPtr x = NativeMethods.SendMessageRECT(this.Handle, TTM_ADJUSTRECT, true, ref r); // //System.Diagnostics.Trace.WriteLine(String.Format("{0} {1} {2} {3}", r.left, r.top, r.right, r.bottom)); //} /// /// Remove the given window from those managed by this tooltip /// /// public void RemoveToolTip(IWin32Window window) { NativeMethods.TOOLINFO lParam = this.MakeToolInfoStruct(window); NativeMethods.SendMessageTOOLINFO(this.Handle, TTM_DELTOOL, 0, lParam); } /// /// Set the maximum width of a tooltip string. /// public void SetMaxWidth() { this.SetMaxWidth(SystemInformation.MaxWindowTrackSize.Width); } /// /// Set the maximum width of a tooltip string. /// /// Setting this ensures that line breaks in the tooltip are honoured. public void SetMaxWidth(int maxWidth) { NativeMethods.SendMessage(this.Handle, TTM_SETMAXTIPWIDTH, 0, maxWidth); } #endregion #region Implementation /// /// Make a TOOLINFO structure for the given window /// /// /// A filled in TOOLINFO private NativeMethods.TOOLINFO MakeToolInfoStruct(IWin32Window window) { NativeMethods.TOOLINFO toolinfo_tooltip = new NativeMethods.TOOLINFO(); toolinfo_tooltip.hwnd = window.Handle; toolinfo_tooltip.uFlags = TTF_IDISHWND | TTF_SUBCLASS; toolinfo_tooltip.uId = window.Handle; toolinfo_tooltip.lpszText = (IntPtr)(-1); // LPSTR_TEXTCALLBACK return toolinfo_tooltip; } /// /// Handle a WmNotify message /// /// The msg /// True if the message has been handled protected virtual bool HandleNotify(ref Message msg) { //THINK: What do we have to do here? Nothing it seems :) //NativeMethods.NMHEADER nmheader = (NativeMethods.NMHEADER)msg.GetLParam(typeof(NativeMethods.NMHEADER)); //System.Diagnostics.Trace.WriteLine("HandleNotify"); //System.Diagnostics.Trace.WriteLine(nmheader.nhdr.code); //switch (nmheader.nhdr.code) { //} return false; } /// /// Handle a get display info message /// /// The msg /// True if the message has been handled public virtual bool HandleGetDispInfo(ref Message msg) { //System.Diagnostics.Trace.WriteLine("HandleGetDispInfo"); this.SetMaxWidth(); ToolTipShowingEventArgs args = new ToolTipShowingEventArgs(); args.ToolTipControl = this; this.OnShowing(args); if (String.IsNullOrEmpty(args.Text)) return false; this.ApplyEventFormatting(args); NativeMethods.NMTTDISPINFO dispInfo = (NativeMethods.NMTTDISPINFO)msg.GetLParam(typeof(NativeMethods.NMTTDISPINFO)); dispInfo.lpszText = args.Text; dispInfo.hinst = IntPtr.Zero; if (args.RightToLeft == RightToLeft.Yes) dispInfo.uFlags |= TTF_RTLREADING; Marshal.StructureToPtr(dispInfo, msg.LParam, false); return true; } private void ApplyEventFormatting(ToolTipShowingEventArgs args) { if (!args.IsBalloon.HasValue && !args.BackColor.HasValue && !args.ForeColor.HasValue && args.Title == null && !args.StandardIcon.HasValue && !args.AutoPopDelay.HasValue && args.Font == null) return; this.PushSettings(); if (args.IsBalloon.HasValue) this.IsBalloon = args.IsBalloon.Value; if (args.BackColor.HasValue) this.BackColor = args.BackColor.Value; if (args.ForeColor.HasValue) this.ForeColor = args.ForeColor.Value; if (args.StandardIcon.HasValue) this.StandardIcon = args.StandardIcon.Value; if (args.AutoPopDelay.HasValue) this.AutoPopDelay = args.AutoPopDelay.Value; if (args.Font != null) this.Font = args.Font; if (args.Title != null) this.Title = args.Title; } /// /// Handle a TTN_LINKCLICK message /// /// The msg /// True if the message has been handled /// This cannot call base.WndProc() since the msg may have come from another control. public virtual bool HandleLinkClick(ref Message msg) { //System.Diagnostics.Trace.WriteLine("HandleLinkClick"); return false; } /// /// Handle a TTN_POP message /// /// The msg /// True if the message has been handled /// This cannot call base.WndProc() since the msg may have come from another control. public virtual bool HandlePop(ref Message msg) { //System.Diagnostics.Trace.WriteLine("HandlePop"); this.PopSettings(); return true; } /// /// Handle a TTN_SHOW message /// /// The msg /// True if the message has been handled /// This cannot call base.WndProc() since the msg may have come from another control. public virtual bool HandleShow(ref Message msg) { //System.Diagnostics.Trace.WriteLine("HandleShow"); return false; } /// /// Handle a reflected notify message /// /// The msg /// True if the message has been handled protected virtual bool HandleReflectNotify(ref Message msg) { NativeMethods.NMHEADER nmheader = (NativeMethods.NMHEADER)msg.GetLParam(typeof(NativeMethods.NMHEADER)); switch (nmheader.nhdr.code) { case TTN_SHOW: //System.Diagnostics.Trace.WriteLine("reflect TTN_SHOW"); if (this.HandleShow(ref msg)) return true; break; case TTN_POP: //System.Diagnostics.Trace.WriteLine("reflect TTN_POP"); if (this.HandlePop(ref msg)) return true; break; case TTN_LINKCLICK: //System.Diagnostics.Trace.WriteLine("reflect TTN_LINKCLICK"); if (this.HandleLinkClick(ref msg)) return true; break; case TTN_GETDISPINFO: //System.Diagnostics.Trace.WriteLine("reflect TTN_GETDISPINFO"); if (this.HandleGetDispInfo(ref msg)) return true; break; } return false; } /// /// Mess with the basic message pump of the tooltip /// /// [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)] override protected void WndProc(ref Message msg) { //System.Diagnostics.Trace.WriteLine(String.Format("xx {0:x}", msg.Msg)); switch (msg.Msg) { case 0x4E: // WM_NOTIFY if (!this.HandleNotify(ref msg)) return; break; case 0x204E: // WM_REFLECT_NOTIFY if (!this.HandleReflectNotify(ref msg)) return; break; } base.WndProc(ref msg); } #endregion #region Events /// /// Tell the world that a tooltip is about to show /// public event EventHandler Showing; /// /// Tell the world that a tooltip is about to disappear /// public event EventHandler Pop; /// /// /// /// protected virtual void OnShowing(ToolTipShowingEventArgs e) { if (this.Showing != null) this.Showing(this, e); } /// /// /// /// protected virtual void OnPop(EventArgs e) { if (this.Pop != null) this.Pop(this, e); } #endregion } }