1 // Permission is hereby granted, free of charge, to any person obtaining
2 // a copy of this software and associated documentation files (the
3 // "Software"), to deal in the Software without restriction, including
4 // without limitation the rights to use, copy, modify, merge, publish,
5 // distribute, sublicense, and/or sell copies of the Software, and to
6 // permit persons to whom the Software is furnished to do so, subject to
7 // the following conditions:
9 // The above copyright notice and this permission notice shall be
10 // included in all copies or substantial portions of the Software.
12 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
13 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
14 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
15 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
16 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
17 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
18 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20 // Copyright (c) 2004 Novell, Inc. (http://www.novell.com)
23 // Peter Bartok pbartok@novell.com
27 using System.Collections;
28 using System.ComponentModel;
30 using System.Drawing.Text;
32 namespace System.Windows.Forms {
33 [DefaultEvent ("Popup")]
34 [ProvideProperty ("ToolTip", typeof(System.Windows.Forms.Control))]
35 [ToolboxItemFilter("System.Windows.Forms", ToolboxItemFilterType.Allow)]
36 public class ToolTip : System.ComponentModel.Component, System.ComponentModel.IExtenderProvider {
37 #region Local variables
38 internal bool is_active;
39 internal int automatic_delay;
40 internal int autopop_delay;
41 internal int initial_delay;
42 internal int re_show_delay;
43 internal bool show_always;
45 internal Color back_color;
46 internal Color fore_color;
48 internal ToolTipWindow tooltip_window; // The actual tooltip window
49 internal Hashtable tooltip_strings; // List of strings for each control, indexed by control
50 internal ArrayList controls;
51 internal Control active_control; // Control for which the tooltip is currently displayed
52 internal Control last_control; // last control the mouse was in
53 internal Timer timer; // Used for the various intervals
54 private Form hooked_form;
56 private bool isBalloon;
57 private bool owner_draw;
58 private bool stripAmpersands;
59 private ToolTipIcon tool_tip_icon;
60 private bool useAnimation;
61 private bool useFading;
64 #endregion // Local variables
66 #region ToolTipWindow Class
67 internal class ToolTipWindow : Control {
68 #region ToolTipWindow Class Local Variables
69 private Control associated_control;
71 internal string title = String.Empty;
72 internal Rectangle icon_rect;
73 internal Rectangle title_rect;
74 internal Rectangle text_rect;
75 #endregion // ToolTipWindow Class Local Variables
77 #region ToolTipWindow Class Constructor
78 internal ToolTipWindow() {
80 Size = new Size(100, 20);
81 ForeColor = ThemeEngine.Current.ColorInfoText;
82 BackColor = ThemeEngine.Current.ColorInfo;
84 VisibleChanged += new EventHandler(ToolTipWindow_VisibleChanged);
86 // UIA Framework: Used to generate UnPopup
87 VisibleChanged += new EventHandler (OnUIAToolTip_VisibleChanged);
89 SetStyle (ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint, true);
90 SetStyle (ControlStyles.ResizeRedraw, true);
91 if (ThemeEngine.Current.ToolTipTransparentBackground) {
92 SetStyle (ControlStyles.SupportsTransparentBackColor, true);
93 BackColor = Color.Transparent;
95 SetStyle (ControlStyles.Opaque, true);
98 #endregion // ToolTipWindow Class Constructor
100 #region ToolTipWindow Class Protected Instance Methods
101 protected override void OnCreateControl() {
102 base.OnCreateControl ();
103 XplatUI.SetTopmost(this.window.Handle, true);
106 protected override CreateParams CreateParams {
110 cp = base.CreateParams;
112 cp.Style = (int)WindowStyles.WS_POPUP;
113 cp.Style |= (int)WindowStyles.WS_CLIPSIBLINGS;
115 cp.ExStyle = (int)(WindowExStyles.WS_EX_TOOLWINDOW | WindowExStyles.WS_EX_TOPMOST);
121 protected override void OnPaint(PaintEventArgs pevent) {
122 // We don't do double-buffering on purpose:
123 // 1) we'd have to meddle with is_visible, it destroys the buffers if !visible
124 // 2) We don't draw much, no need to double buffer
125 base.OnPaint(pevent);
127 OnDraw (new DrawToolTipEventArgs (pevent.Graphics, associated_control, associated_control, ClientRectangle, this.Text, this.BackColor, this.ForeColor, this.Font));
130 protected override void OnTextChanged (EventArgs args)
133 base.OnTextChanged (args);
136 protected override void WndProc(ref Message m) {
137 if (m.Msg == (int)Msg.WM_SETFOCUS) {
138 if (m.WParam != IntPtr.Zero) {
139 XplatUI.SetFocus(m.WParam);
142 base.WndProc (ref m);
146 #endregion // ToolTipWindow Class Protected Instance Methods
148 #region ToolTipWindow Class Private Methods
149 internal virtual void OnDraw (DrawToolTipEventArgs e)
151 DrawToolTipEventHandler eh = (DrawToolTipEventHandler)(Events[DrawEvent]);
155 ThemeEngine.Current.DrawToolTip (e.Graphics, e.Bounds, this);
158 internal virtual void OnPopup (PopupEventArgs e)
160 PopupEventHandler eh = (PopupEventHandler)(Events[PopupEvent]);
164 e.ToolTipSize = ThemeEngine.Current.ToolTipSize (this, Text);
167 private void ToolTipWindow_VisibleChanged(object sender, EventArgs e) {
168 Control control = (Control)sender;
170 if (control.is_visible) {
171 XplatUI.SetTopmost(control.window.Handle, true);
173 XplatUI.SetTopmost(control.window.Handle, false);
178 private void OnUIAToolTip_VisibleChanged (object sender, EventArgs e)
180 if (Visible == false)
181 OnUnPopup (new PopupEventArgs (associated_control, associated_control, false, Size.Empty));
184 private void OnUnPopup (PopupEventArgs e)
186 PopupEventHandler eh = (PopupEventHandler) (Events [UnPopupEvent]);
192 #endregion // ToolTipWindow Class Protected Instance Methods
194 #region Internal Properties
195 internal override bool ActivateOnShow { get { return false; } }
198 // This Present is used when we are using the expicit Show methods for 2.0.
199 // It will not reposition the window.
200 public void PresentModal (Control control, string text)
206 XplatUI.GetDisplaySize (out display_size);
208 associated_control = control;
212 PopupEventArgs pea = new PopupEventArgs (control, control, false, Size.Empty);
218 Size = pea.ToolTipSize;
223 public void Present (Control control, string text)
229 XplatUI.GetDisplaySize (out display_size);
231 associated_control = control;
235 PopupEventArgs pea = new PopupEventArgs (control, control, false, Size.Empty);
241 Size size = pea.ToolTipSize;
244 Height = size.Height;
246 int cursor_w, cursor_h, hot_x, hot_y;
247 XplatUI.GetCursorInfo (control.Cursor.Handle, out cursor_w, out cursor_h, out hot_x, out hot_y);
248 Point loc = Control.MousePosition;
249 loc.Y += (cursor_h - hot_y);
251 if ((loc.X + Width) > display_size.Width)
252 loc.X = display_size.Width - Width;
254 if ((loc.Y + Height) > display_size.Height)
255 loc.Y = Control.MousePosition.Y - Height - hot_y;
262 #region Internal Events
263 static object DrawEvent = new object ();
264 static object PopupEvent = new object ();
267 static object UnPopupEvent = new object ();
269 public event DrawToolTipEventHandler Draw {
270 add { Events.AddHandler (DrawEvent, value); }
271 remove { Events.RemoveHandler (DrawEvent, value); }
274 public event PopupEventHandler Popup {
275 add { Events.AddHandler (PopupEvent, value); }
276 remove { Events.RemoveHandler (PopupEvent, value); }
279 internal event PopupEventHandler UnPopup {
280 add { Events.AddHandler (UnPopupEvent, value); }
281 remove { Events.RemoveHandler (UnPopupEvent, value); }
285 #endregion // ToolTipWindow Class
287 #region Public Constructors & Destructors
292 automatic_delay = 500;
293 autopop_delay = 5000;
297 back_color = SystemColors.Info;
298 fore_color = SystemColors.InfoText;
301 stripAmpersands = false;
304 tooltip_strings = new Hashtable(5);
305 controls = new ArrayList(5);
307 tooltip_window = new ToolTipWindow();
308 tooltip_window.MouseLeave += new EventHandler(control_MouseLeave);
309 tooltip_window.Draw += new DrawToolTipEventHandler (tooltip_window_Draw);
310 tooltip_window.Popup += new PopupEventHandler (tooltip_window_Popup);
312 // UIA Framework: Static event handlers
313 tooltip_window.UnPopup += delegate (object sender, PopupEventArgs args) {
316 UnPopup += new PopupEventHandler (OnUIAUnPopup);
319 timer.Enabled = false;
320 timer.Tick +=new EventHandler(timer_Tick);
325 #region UIA Framework: Events, Delegates and Methods
327 // We are using Reflection to add/remove internal events.
328 // Class ToolTipListener uses the events.
330 // - UIAUnPopup. Event used to generate ChildRemoved in ToolTip
331 // - UIAToolTipHookUp. Event used to keep track of associated controls
332 // - UIAToolTipUnhookUp. Event used to remove track of associated controls
333 static object UnPopupEvent = new object ();
335 internal event PopupEventHandler UnPopup {
336 add { Events.AddHandler (UnPopupEvent, value); }
337 remove { Events.RemoveHandler (UnPopupEvent, value); }
340 internal static event PopupEventHandler UIAUnPopup;
341 internal static event ControlEventHandler UIAToolTipHookUp;
342 internal static event ControlEventHandler UIAToolTipUnhookUp;
344 internal Rectangle UIAToolTipRectangle {
345 get { return tooltip_window.Bounds; }
348 internal static void OnUIAUnPopup (object sender, PopupEventArgs args)
350 if (UIAUnPopup != null)
351 UIAUnPopup (sender, args);
354 internal static void OnUIAToolTipHookUp (object sender, ControlEventArgs args)
356 if (UIAToolTipHookUp != null)
357 UIAToolTipHookUp (sender, args);
360 internal static void OnUIAToolTipUnhookUp (object sender, ControlEventArgs args)
362 if (UIAToolTipUnhookUp != null)
363 UIAToolTipUnhookUp (sender, args);
368 public ToolTip(System.ComponentModel.IContainer cont) : this() {
374 #endregion // Public Constructors & Destructors
376 #region Public Instance Properties
377 [DefaultValue (true)]
384 if (is_active != value) {
387 if (tooltip_window.Visible) {
388 tooltip_window.Visible = false;
389 active_control = null;
396 [RefreshProperties (RefreshProperties.All)]
397 public int AutomaticDelay {
399 return automatic_delay;
403 if (automatic_delay != value) {
404 automatic_delay = value;
405 autopop_delay = automatic_delay * 10;
406 initial_delay = automatic_delay;
407 re_show_delay = automatic_delay / 5;
412 [RefreshProperties (RefreshProperties.All)]
413 public int AutoPopDelay {
415 return autopop_delay;
419 if (autopop_delay != value) {
420 autopop_delay = value;
425 [DefaultValue ("Color [Info]")]
426 public Color BackColor {
427 get { return this.back_color; }
428 set { this.back_color = value; tooltip_window.BackColor = value; }
431 [DefaultValue ("Color [InfoText]")]
432 public Color ForeColor
434 get { return this.fore_color; }
435 set { this.fore_color = value; tooltip_window.ForeColor = value; }
438 [RefreshProperties (RefreshProperties.All)]
439 public int InitialDelay {
441 return initial_delay;
445 if (initial_delay != value) {
446 initial_delay = value;
451 [DefaultValue (false)]
452 public bool OwnerDraw {
453 get { return this.owner_draw; }
454 set { this.owner_draw = value; }
457 [RefreshProperties (RefreshProperties.All)]
458 public int ReshowDelay {
460 return re_show_delay;
464 if (re_show_delay != value) {
465 re_show_delay = value;
470 [DefaultValue (false)]
471 public bool ShowAlways {
477 if (show_always != value) {
484 [DefaultValue (false)]
485 public bool IsBalloon {
486 get { return isBalloon; }
487 set { isBalloon = value; }
491 [DefaultValue (false)]
492 public bool StripAmpersands {
493 get { return stripAmpersands; }
494 set { stripAmpersands = value; }
497 [Localizable (false)]
499 [TypeConverter (typeof (StringConverter))]
500 [DefaultValue (null)]
506 [DefaultValue (ToolTipIcon.None)]
507 public ToolTipIcon ToolTipIcon {
508 get { return this.tool_tip_icon; }
511 case ToolTipIcon.None:
512 tooltip_window.icon = null;
514 case ToolTipIcon.Error:
515 tooltip_window.icon = SystemIcons.Error;
517 case ToolTipIcon.Warning:
518 tooltip_window.icon = SystemIcons.Warning;
520 case ToolTipIcon.Info:
521 tooltip_window.icon = SystemIcons.Information;
525 tool_tip_icon = value;
530 public string ToolTipTitle {
531 get { return tooltip_window.title; }
534 value = String.Empty;
536 tooltip_window.title = value;
541 [DefaultValue (true)]
542 public bool UseAnimation {
543 get { return useAnimation; }
544 set { useAnimation = value; }
548 [DefaultValue (true)]
549 public bool UseFading {
550 get { return useFading; }
551 set { useFading = value; }
554 #endregion // Public Instance Properties
556 #region Protected Properties
557 protected virtual CreateParams CreateParams
561 CreateParams cp = new CreateParams ();
570 #region Public Instance Methods
571 public bool CanExtend(object target) {
575 [Editor ("System.ComponentModel.Design.MultilineStringEditor, " + Consts.AssemblySystem_Design,
576 "System.Drawing.Design.UITypeEditor, " + Consts.AssemblySystem_Drawing)]
579 public string GetToolTip (Control control)
581 string tooltip = (string)tooltip_strings[control];
587 public void RemoveAll() {
588 tooltip_strings.Clear();
589 //UIA Framework: ToolTip isn't associated anymore
590 foreach (Control control in controls)
591 OnUIAToolTipUnhookUp (this, new ControlEventArgs (control));
596 public void SetToolTip(Control control, string caption) {
598 OnUIAToolTipHookUp (this, new ControlEventArgs (control));
599 tooltip_strings[control] = caption;
601 // no need for duplicates
602 if (!controls.Contains(control)) {
603 control.MouseEnter += new EventHandler(control_MouseEnter);
604 control.MouseMove += new MouseEventHandler(control_MouseMove);
605 control.MouseLeave += new EventHandler(control_MouseLeave);
606 control.MouseDown += new MouseEventHandler (control_MouseDown);
607 controls.Add(control);
610 // if SetToolTip is called from a control and the mouse is currently over that control,
611 // make sure that tooltip_window.Text gets updated if it's being shown,
612 // or show the tooltip for it if is not
613 if (active_control == control && caption != null && state == TipState.Show) {
614 Size size = ThemeEngine.Current.ToolTipSize(tooltip_window, caption);
615 tooltip_window.Width = size.Width;
616 tooltip_window.Height = size.Height;
617 tooltip_window.Text = caption;
620 } else if (control.IsHandleCreated && MouseInControl (control, false))
621 ShowTooltip (control);
624 public override string ToString() {
625 return base.ToString() + " InitialDelay: " + initial_delay + ", ShowAlways: " + show_always;
628 public void Show (string text, IWin32Window window)
630 Show (text, window, 0);
633 public void Show (string text, IWin32Window window, int duration)
636 throw new ArgumentNullException ("window");
638 throw new ArgumentOutOfRangeException ("duration", "duration cannot be less than zero");
645 Control c = (Control)window;
647 XplatUI.SetOwner (tooltip_window.Handle, c.TopLevelControl.Handle);
649 // If the mouse is in the requested window, use that position
650 // Else, center in the requested window
651 if (c.ClientRectangle.Contains (c.PointToClient (Control.MousePosition))) {
652 tooltip_window.Location = Control.MousePosition;
653 tooltip_strings[c] = text;
654 HookupControlEvents (c);
657 tooltip_window.Location = c.PointToScreen (new Point (c.Width / 2, c.Height / 2));
659 // We need to hide our tooltip if the form loses focus, is closed, or is minimized
660 HookupFormEvents ((Form)c.TopLevelControl);
662 tooltip_window.PresentModal ((Control)window, text);
664 state = TipState.Show;
667 timer.Interval = duration;
672 public void Show (string text, IWin32Window window, Point point)
674 Show (text, window, point, 0);
677 public void Show (string text, IWin32Window window, int x, int y)
679 Show (text, window, new Point (x, y), 0);
682 public void Show (string text, IWin32Window window, Point point, int duration)
685 throw new ArgumentNullException ("window");
687 throw new ArgumentOutOfRangeException ("duration", "duration cannot be less than zero");
694 Control c = (Control)window;
696 Point display_point = c.PointToScreen (Point.Empty);
697 display_point.X += point.X;
698 display_point.Y += point.Y;
700 XplatUI.SetOwner (tooltip_window.Handle, c.TopLevelControl.Handle);
702 // We need to hide our tooltip if the form loses focus, is closed, or is minimized
703 HookupFormEvents ((Form)c.TopLevelControl);
705 tooltip_window.Location = display_point;
706 tooltip_window.PresentModal ((Control)window, text);
708 state = TipState.Show;
711 timer.Interval = duration;
716 public void Show (string text, IWin32Window window, int x, int y, int duration)
718 Show (text, window, new Point (x, y), duration);
721 public void Hide (IWin32Window win)
724 state = TipState.Initial;
727 tooltip_window.Visible = false;
729 #endregion // Public Instance Methods
731 #region Protected Instance Methods
732 protected override void Dispose(bool disposing) {
733 // call the base impl first to avoid conflicts with any parent's events
734 base.Dispose (disposing);
737 // Mop up the mess; or should we wait for the GC to kick in?
741 // Not sure if we should clean up tooltip_window
742 tooltip_window.Dispose();
744 tooltip_strings.Clear();
746 //UIA Framework: ToolTip isn't associated anymore
747 foreach (Control control in controls)
748 OnUIAToolTipUnhookUp (this, new ControlEventArgs (control));
753 protected void StopTimer ()
757 #endregion // Protected Instance Methods
759 internal enum TipState {
765 TipState state = TipState.Initial;
767 #region Private Methods
769 private void HookupFormEvents (Form form)
773 form.Deactivate += new EventHandler (Form_Deactivate);
774 form.Closed += new EventHandler (Form_Closed);
775 form.Resize += new EventHandler (Form_Resize);
778 private void HookupControlEvents (Control control)
780 if (!controls.Contains (control)) {
781 control.MouseEnter += new EventHandler (control_MouseEnter);
782 control.MouseMove += new MouseEventHandler (control_MouseMove);
783 control.MouseLeave += new EventHandler (control_MouseLeave);
784 control.MouseDown += new MouseEventHandler (control_MouseDown);
785 controls.Add (control);
789 private void UnhookControlEvents (Control control)
791 control.MouseEnter -= new EventHandler (control_MouseEnter);
792 control.MouseMove -= new MouseEventHandler (control_MouseMove);
793 control.MouseLeave -= new EventHandler (control_MouseLeave);
794 control.MouseDown -= new MouseEventHandler (control_MouseDown);
796 private void UnhookFormEvents ()
798 if (hooked_form == null)
801 hooked_form.Deactivate -= new EventHandler (Form_Deactivate);
802 hooked_form.Closed -= new EventHandler (Form_Closed);
803 hooked_form.Resize -= new EventHandler (Form_Resize);
809 private void Form_Resize (object sender, EventArgs e)
811 Form f = (Form)sender;
813 if (f.WindowState == FormWindowState.Minimized)
814 tooltip_window.Visible = false;
817 private void Form_Closed (object sender, EventArgs e)
819 tooltip_window.Visible = false;
822 private void Form_Deactivate (object sender, EventArgs e)
824 tooltip_window.Visible = false;
827 internal void Present (Control control, string text)
829 tooltip_window.Present (control, text);
832 private void control_MouseEnter (object sender, EventArgs e)
834 ShowTooltip (sender as Control);
837 private void ShowTooltip (Control control)
839 last_control = control;
841 // Whatever we're displaying right now, we don't want it anymore
842 tooltip_window.Visible = false;
844 state = TipState.Initial;
849 // ShowAlways controls whether the controls in non-active forms
850 // can display its tooltips, even if they are not current active control.
851 if (!show_always && control.FindForm () != Form.ActiveForm)
854 string text = (string)tooltip_strings[control];
855 if (text != null && text.Length > 0) {
856 if (active_control == null) {
857 timer.Interval = Math.Max (initial_delay, 1);
859 timer.Interval = Math.Max (re_show_delay, 1);
862 active_control = control;
867 private void timer_Tick(object sender, EventArgs e) {
871 case TipState.Initial:
872 if (active_control == null)
874 tooltip_window.Present (active_control, (string)tooltip_strings[active_control]);
875 state = TipState.Show;
876 timer.Interval = autopop_delay;
881 tooltip_window.Visible = false;
882 state = TipState.Down;
886 throw new Exception ("Timer shouldn't be running in state: " + state);
890 private void tooltip_window_Popup (object sender, PopupEventArgs e)
892 e.ToolTipSize = ThemeEngine.Current.ToolTipSize (tooltip_window, tooltip_window.Text);
896 private void tooltip_window_Draw (object sender, DrawToolTipEventArgs e)
901 ThemeEngine.Current.DrawToolTip (e.Graphics, e.Bounds, tooltip_window);
904 private bool MouseInControl (Control control, bool fuzzy) {
909 if (control == null) {
913 m = Control.MousePosition;
914 c = new Point(control.Bounds.X, control.Bounds.Y);
915 if (control.Parent != null) {
916 c = control.Parent.PointToScreen(c);
918 cw = control.ClientSize;
921 Rectangle rect = new Rectangle (c, cw);
924 // We won't get mouse move events on all platforms with the exact same
925 // frequency, so cheat a bit.
929 return rect.Contains (m);
932 private void control_MouseLeave(object sender, EventArgs e)
936 active_control = null;
937 tooltip_window.Visible = false;
939 if (last_control == sender)
944 void control_MouseDown (object sender, MouseEventArgs e)
948 active_control = null;
949 tooltip_window.Visible = false;
951 if (last_control == sender)
955 private void control_MouseMove(object sender, MouseEventArgs e) {
956 if (state != TipState.Down) {
962 internal void OnDraw (DrawToolTipEventArgs e)
964 DrawToolTipEventHandler eh = (DrawToolTipEventHandler)(Events[DrawEvent]);
969 internal void OnPopup (PopupEventArgs e)
971 PopupEventHandler eh = (PopupEventHandler) (Events [PopupEvent]);
976 internal void OnUnPopup (PopupEventArgs e)
978 PopupEventHandler eh = (PopupEventHandler) (Events [UnPopupEvent]);
983 internal bool Visible {
984 get { return tooltip_window.Visible; }
986 #endregion // Private Methods
989 static object PopupEvent = new object ();
990 static object DrawEvent = new object ();
992 public event PopupEventHandler Popup {
993 add { Events.AddHandler (PopupEvent, value); }
994 remove { Events.RemoveHandler (PopupEvent, value); }
997 public event DrawToolTipEventHandler Draw {
998 add { Events.AddHandler (DrawEvent, value); }
999 remove { Events.RemoveHandler (DrawEvent, value); }