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;
263 #region Internal Events
264 static object DrawEvent = new object ();
265 static object PopupEvent = new object ();
268 static object UnPopupEvent = new object ();
270 public event DrawToolTipEventHandler Draw {
271 add { Events.AddHandler (DrawEvent, value); }
272 remove { Events.RemoveHandler (DrawEvent, value); }
275 public event PopupEventHandler Popup {
276 add { Events.AddHandler (PopupEvent, value); }
277 remove { Events.RemoveHandler (PopupEvent, value); }
280 internal event PopupEventHandler UnPopup {
281 add { Events.AddHandler (UnPopupEvent, value); }
282 remove { Events.RemoveHandler (UnPopupEvent, value); }
286 #endregion // ToolTipWindow Class
288 #region Public Constructors & Destructors
293 automatic_delay = 500;
294 autopop_delay = 5000;
298 back_color = SystemColors.Info;
299 fore_color = SystemColors.InfoText;
302 stripAmpersands = false;
305 tooltip_strings = new Hashtable(5);
306 controls = new ArrayList(5);
308 tooltip_window = new ToolTipWindow();
309 tooltip_window.MouseLeave += new EventHandler(control_MouseLeave);
310 tooltip_window.Draw += new DrawToolTipEventHandler (tooltip_window_Draw);
311 tooltip_window.Popup += new PopupEventHandler (tooltip_window_Popup);
313 // UIA Framework: Static event handlers
314 tooltip_window.UnPopup += delegate (object sender, PopupEventArgs args) {
317 UnPopup += new PopupEventHandler (OnUIAUnPopup);
320 timer.Enabled = false;
321 timer.Tick +=new EventHandler(timer_Tick);
326 #region UIA Framework: Events, Delegates and Methods
328 // We are using Reflection to add/remove internal events.
329 // Class ToolTipListener uses the events.
331 // - UIAUnPopup. Event used to generate ChildRemoved in ToolTip
332 // - UIAToolTipHookUp. Event used to keep track of associated controls
333 // - UIAToolTipUnhookUp. Event used to remove track of associated controls
334 static object UnPopupEvent = new object ();
336 internal event PopupEventHandler UnPopup {
337 add { Events.AddHandler (UnPopupEvent, value); }
338 remove { Events.RemoveHandler (UnPopupEvent, value); }
341 internal static event PopupEventHandler UIAUnPopup;
342 internal static event ControlEventHandler UIAToolTipHookUp;
343 internal static event ControlEventHandler UIAToolTipUnhookUp;
345 internal Rectangle UIAToolTipRectangle {
346 get { return tooltip_window.Bounds; }
349 internal static void OnUIAUnPopup (object sender, PopupEventArgs args)
351 if (UIAUnPopup != null)
352 UIAUnPopup (sender, args);
355 internal static void OnUIAToolTipHookUp (object sender, ControlEventArgs args)
357 if (UIAToolTipHookUp != null)
358 UIAToolTipHookUp (sender, args);
361 internal static void OnUIAToolTipUnhookUp (object sender, ControlEventArgs args)
363 if (UIAToolTipUnhookUp != null)
364 UIAToolTipUnhookUp (sender, args);
369 public ToolTip(System.ComponentModel.IContainer cont) : this() {
375 #endregion // Public Constructors & Destructors
377 #region Public Instance Properties
378 [DefaultValue (true)]
385 if (is_active != value) {
388 if (tooltip_window.Visible) {
389 tooltip_window.Visible = false;
390 active_control = null;
397 [RefreshProperties (RefreshProperties.All)]
398 public int AutomaticDelay {
400 return automatic_delay;
404 if (automatic_delay != value) {
405 automatic_delay = value;
406 autopop_delay = automatic_delay * 10;
407 initial_delay = automatic_delay;
408 re_show_delay = automatic_delay / 5;
413 [RefreshProperties (RefreshProperties.All)]
414 public int AutoPopDelay {
416 return autopop_delay;
420 if (autopop_delay != value) {
421 autopop_delay = value;
426 [DefaultValue ("Color [Info]")]
427 public Color BackColor {
428 get { return this.back_color; }
429 set { this.back_color = value; tooltip_window.BackColor = value; }
432 [DefaultValue ("Color [InfoText]")]
433 public Color ForeColor
435 get { return this.fore_color; }
436 set { this.fore_color = value; tooltip_window.ForeColor = value; }
439 [RefreshProperties (RefreshProperties.All)]
440 public int InitialDelay {
442 return initial_delay;
446 if (initial_delay != value) {
447 initial_delay = value;
452 [DefaultValue (false)]
453 public bool OwnerDraw {
454 get { return this.owner_draw; }
455 set { this.owner_draw = value; }
458 [RefreshProperties (RefreshProperties.All)]
459 public int ReshowDelay {
461 return re_show_delay;
465 if (re_show_delay != value) {
466 re_show_delay = value;
471 [DefaultValue (false)]
472 public bool ShowAlways {
478 if (show_always != value) {
485 [DefaultValue (false)]
486 public bool IsBalloon {
487 get { return isBalloon; }
488 set { isBalloon = value; }
492 [DefaultValue (false)]
493 public bool StripAmpersands {
494 get { return stripAmpersands; }
495 set { stripAmpersands = value; }
498 [Localizable (false)]
500 [TypeConverter (typeof (StringConverter))]
501 [DefaultValue (null)]
507 [DefaultValue (ToolTipIcon.None)]
508 public ToolTipIcon ToolTipIcon {
509 get { return this.tool_tip_icon; }
512 case ToolTipIcon.None:
513 tooltip_window.icon = null;
515 case ToolTipIcon.Error:
516 tooltip_window.icon = SystemIcons.Error;
518 case ToolTipIcon.Warning:
519 tooltip_window.icon = SystemIcons.Warning;
521 case ToolTipIcon.Info:
522 tooltip_window.icon = SystemIcons.Information;
526 tool_tip_icon = value;
531 public string ToolTipTitle {
532 get { return tooltip_window.title; }
535 value = String.Empty;
537 tooltip_window.title = value;
542 [DefaultValue (true)]
543 public bool UseAnimation {
544 get { return useAnimation; }
545 set { useAnimation = value; }
549 [DefaultValue (true)]
550 public bool UseFading {
551 get { return useFading; }
552 set { useFading = value; }
555 #endregion // Public Instance Properties
557 #region Protected Properties
558 protected virtual CreateParams CreateParams
562 CreateParams cp = new CreateParams ();
571 #region Public Instance Methods
572 public bool CanExtend(object target) {
576 [Editor ("System.ComponentModel.Design.MultilineStringEditor, " + Consts.AssemblySystem_Design,
577 "System.Drawing.Design.UITypeEditor, " + Consts.AssemblySystem_Drawing)]
580 public string GetToolTip (Control control)
582 string tooltip = (string)tooltip_strings[control];
588 public void RemoveAll() {
589 tooltip_strings.Clear();
590 //UIA Framework: ToolTip isn't associated anymore
591 foreach (Control control in controls)
592 OnUIAToolTipUnhookUp (this, new ControlEventArgs (control));
597 public void SetToolTip(Control control, string caption) {
599 OnUIAToolTipHookUp (this, new ControlEventArgs (control));
600 tooltip_strings[control] = caption;
602 // no need for duplicates
603 if (!controls.Contains(control)) {
604 control.MouseEnter += new EventHandler(control_MouseEnter);
605 control.MouseMove += new MouseEventHandler(control_MouseMove);
606 control.MouseLeave += new EventHandler(control_MouseLeave);
607 control.MouseDown += new MouseEventHandler (control_MouseDown);
608 controls.Add(control);
611 // if SetToolTip is called from a control and the mouse is currently over that control,
612 // make sure that tooltip_window.Text gets updated if it's being shown,
613 // or show the tooltip for it if is not
614 if (active_control == control && caption != null && state == TipState.Show) {
615 Size size = ThemeEngine.Current.ToolTipSize(tooltip_window, caption);
616 tooltip_window.Width = size.Width;
617 tooltip_window.Height = size.Height;
618 tooltip_window.Text = caption;
621 } else if (control.IsHandleCreated && MouseInControl (control, false))
622 ShowTooltip (control);
625 public override string ToString() {
626 return base.ToString() + " InitialDelay: " + initial_delay + ", ShowAlways: " + show_always;
629 public void Show (string text, IWin32Window window)
631 Show (text, window, 0);
634 public void Show (string text, IWin32Window window, int duration)
637 throw new ArgumentNullException ("window");
639 throw new ArgumentOutOfRangeException ("duration", "duration cannot be less than zero");
646 Control c = (Control)window;
648 XplatUI.SetOwner (tooltip_window.Handle, c.TopLevelControl.Handle);
650 // If the mouse is in the requested window, use that position
651 // Else, center in the requested window
652 if (c.ClientRectangle.Contains (c.PointToClient (Control.MousePosition))) {
653 tooltip_window.Location = Control.MousePosition;
654 tooltip_strings[c] = text;
655 HookupControlEvents (c);
658 tooltip_window.Location = c.PointToScreen (new Point (c.Width / 2, c.Height / 2));
660 // We need to hide our tooltip if the form loses focus, is closed, or is minimized
661 HookupFormEvents ((Form)c.TopLevelControl);
663 tooltip_window.PresentModal ((Control)window, text);
665 state = TipState.Show;
668 timer.Interval = duration;
673 public void Show (string text, IWin32Window window, Point point)
675 Show (text, window, point, 0);
678 public void Show (string text, IWin32Window window, int x, int y)
680 Show (text, window, new Point (x, y), 0);
683 public void Show (string text, IWin32Window window, Point point, int duration)
686 throw new ArgumentNullException ("window");
688 throw new ArgumentOutOfRangeException ("duration", "duration cannot be less than zero");
695 Control c = (Control)window;
697 Point display_point = c.PointToScreen (Point.Empty);
698 display_point.X += point.X;
699 display_point.Y += point.Y;
701 XplatUI.SetOwner (tooltip_window.Handle, c.TopLevelControl.Handle);
703 // We need to hide our tooltip if the form loses focus, is closed, or is minimized
704 HookupFormEvents ((Form)c.TopLevelControl);
706 tooltip_window.Location = display_point;
707 tooltip_window.PresentModal ((Control)window, text);
709 state = TipState.Show;
712 timer.Interval = duration;
717 public void Show (string text, IWin32Window window, int x, int y, int duration)
719 Show (text, window, new Point (x, y), duration);
722 public void Hide (IWin32Window win)
725 state = TipState.Initial;
728 tooltip_window.Visible = false;
730 #endregion // Public Instance Methods
732 #region Protected Instance Methods
733 protected override void Dispose(bool disposing) {
734 // call the base impl first to avoid conflicts with any parent's events
735 base.Dispose (disposing);
738 // Mop up the mess; or should we wait for the GC to kick in?
742 // Not sure if we should clean up tooltip_window
743 tooltip_window.Dispose();
745 tooltip_strings.Clear();
747 //UIA Framework: ToolTip isn't associated anymore
748 foreach (Control control in controls)
749 OnUIAToolTipUnhookUp (this, new ControlEventArgs (control));
754 protected void StopTimer ()
758 #endregion // Protected Instance Methods
760 internal enum TipState {
766 TipState state = TipState.Initial;
768 #region Private Methods
770 private void HookupFormEvents (Form form)
774 form.Deactivate += new EventHandler (Form_Deactivate);
775 form.Closed += new EventHandler (Form_Closed);
776 form.Resize += new EventHandler (Form_Resize);
779 private void HookupControlEvents (Control control)
781 if (!controls.Contains (control)) {
782 control.MouseEnter += new EventHandler (control_MouseEnter);
783 control.MouseMove += new MouseEventHandler (control_MouseMove);
784 control.MouseLeave += new EventHandler (control_MouseLeave);
785 control.MouseDown += new MouseEventHandler (control_MouseDown);
786 controls.Add (control);
790 private void UnhookControlEvents (Control control)
792 control.MouseEnter -= new EventHandler (control_MouseEnter);
793 control.MouseMove -= new MouseEventHandler (control_MouseMove);
794 control.MouseLeave -= new EventHandler (control_MouseLeave);
795 control.MouseDown -= new MouseEventHandler (control_MouseDown);
797 private void UnhookFormEvents ()
799 if (hooked_form == null)
802 hooked_form.Deactivate -= new EventHandler (Form_Deactivate);
803 hooked_form.Closed -= new EventHandler (Form_Closed);
804 hooked_form.Resize -= new EventHandler (Form_Resize);
810 private void Form_Resize (object sender, EventArgs e)
812 Form f = (Form)sender;
814 if (f.WindowState == FormWindowState.Minimized)
815 tooltip_window.Visible = false;
818 private void Form_Closed (object sender, EventArgs e)
820 tooltip_window.Visible = false;
823 private void Form_Deactivate (object sender, EventArgs e)
825 tooltip_window.Visible = false;
828 internal void Present (Control control, string text)
830 tooltip_window.Present (control, text);
833 private void control_MouseEnter (object sender, EventArgs e)
835 ShowTooltip (sender as Control);
838 private void ShowTooltip (Control control)
840 last_control = control;
842 // Whatever we're displaying right now, we don't want it anymore
843 tooltip_window.Visible = false;
845 state = TipState.Initial;
850 // ShowAlways controls whether the controls in non-active forms
851 // can display its tooltips, even if they are not current active control.
852 if (!show_always && control.FindForm () != Form.ActiveForm)
855 string text = (string)tooltip_strings[control];
856 if (text != null && text.Length > 0) {
857 if (active_control == null) {
858 timer.Interval = Math.Max (initial_delay, 1);
860 timer.Interval = Math.Max (re_show_delay, 1);
863 active_control = control;
868 private void timer_Tick(object sender, EventArgs e) {
872 case TipState.Initial:
873 if (active_control == null)
875 tooltip_window.Present (active_control, (string)tooltip_strings[active_control]);
876 state = TipState.Show;
877 timer.Interval = autopop_delay;
882 tooltip_window.Visible = false;
883 state = TipState.Down;
887 throw new Exception ("Timer shouldn't be running in state: " + state);
891 private void tooltip_window_Popup (object sender, PopupEventArgs e)
893 e.ToolTipSize = ThemeEngine.Current.ToolTipSize (tooltip_window, tooltip_window.Text);
897 private void tooltip_window_Draw (object sender, DrawToolTipEventArgs e)
902 ThemeEngine.Current.DrawToolTip (e.Graphics, e.Bounds, tooltip_window);
905 private bool MouseInControl (Control control, bool fuzzy) {
910 if (control == null) {
914 m = Control.MousePosition;
915 c = new Point(control.Bounds.X, control.Bounds.Y);
916 if (control.Parent != null) {
917 c = control.Parent.PointToScreen(c);
919 cw = control.ClientSize;
922 Rectangle rect = new Rectangle (c, cw);
925 // We won't get mouse move events on all platforms with the exact same
926 // frequency, so cheat a bit.
930 return rect.Contains (m);
933 private void control_MouseLeave(object sender, EventArgs e)
937 active_control = null;
938 tooltip_window.Visible = false;
940 if (last_control == sender)
945 void control_MouseDown (object sender, MouseEventArgs e)
949 active_control = null;
950 tooltip_window.Visible = false;
952 if (last_control == sender)
956 private void control_MouseMove(object sender, MouseEventArgs e) {
957 if (state != TipState.Down) {
963 internal void OnDraw (DrawToolTipEventArgs e)
965 DrawToolTipEventHandler eh = (DrawToolTipEventHandler)(Events[DrawEvent]);
970 internal void OnPopup (PopupEventArgs e)
972 PopupEventHandler eh = (PopupEventHandler) (Events [PopupEvent]);
977 internal void OnUnPopup (PopupEventArgs e)
979 PopupEventHandler eh = (PopupEventHandler) (Events [UnPopupEvent]);
984 internal bool Visible {
985 get { return tooltip_window.Visible; }
987 #endregion // Private Methods
990 static object PopupEvent = new object ();
991 static object DrawEvent = new object ();
993 public event PopupEventHandler Popup {
994 add { Events.AddHandler (PopupEvent, value); }
995 remove { Events.RemoveHandler (PopupEvent, value); }
998 public event DrawToolTipEventHandler Draw {
999 add { Events.AddHandler (DrawEvent, value); }
1000 remove { Events.RemoveHandler (DrawEvent, value); }