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 {
34 [DefaultEvent ("Popup")]
36 [ProvideProperty ("ToolTip", typeof(System.Windows.Forms.Control))]
37 [ToolboxItemFilter("System.Windows.Forms", ToolboxItemFilterType.Allow)]
42 class ToolTip : System.ComponentModel.Component, System.ComponentModel.IExtenderProvider {
43 #region Local variables
44 internal bool is_active;
45 internal int automatic_delay;
46 internal int autopop_delay;
47 internal int initial_delay;
48 internal int re_show_delay;
49 internal bool show_always;
51 internal Color back_color;
52 internal Color fore_color;
54 internal ToolTipWindow tooltip_window; // The actual tooltip window
55 internal Hashtable tooltip_strings; // List of strings for each control, indexed by control
56 internal ArrayList controls;
57 internal Control active_control; // Control for which the tooltip is currently displayed
58 internal Control last_control; // last control the mouse was in
59 internal Timer timer; // Used for the various intervals
60 private Form hooked_form;
63 private bool isBalloon;
64 private bool owner_draw;
65 private bool stripAmpersands;
66 private ToolTipIcon tool_tip_icon;
67 private string tool_tip_title;
68 private bool useAnimation;
69 private bool useFading;
73 #endregion // Local variables
75 #region ToolTipWindow Class
76 internal class ToolTipWindow : Control {
77 #region ToolTipWindow Class Local Variables
78 private Control associated_control;
79 #endregion // ToolTipWindow Class Local Variables
81 #region ToolTipWindow Class Constructor
82 internal ToolTipWindow() {
84 Size = new Size(100, 20);
85 ForeColor = ThemeEngine.Current.ColorInfoText;
86 BackColor = ThemeEngine.Current.ColorInfo;
88 VisibleChanged += new EventHandler(ToolTipWindow_VisibleChanged);
91 // UIA Framework: Used to generate UnPopup
92 VisibleChanged += new EventHandler (OnUIAToolTip_VisibleChanged);
95 SetStyle (ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint, true);
96 SetStyle (ControlStyles.ResizeRedraw, true);
97 if (ThemeEngine.Current.ToolTipTransparentBackground) {
98 SetStyle (ControlStyles.SupportsTransparentBackColor, true);
99 BackColor = Color.Transparent;
101 SetStyle (ControlStyles.Opaque, true);
104 #endregion // ToolTipWindow Class Constructor
106 #region ToolTipWindow Class Protected Instance Methods
107 protected override void OnCreateControl() {
108 base.OnCreateControl ();
109 XplatUI.SetTopmost(this.window.Handle, true);
112 protected override CreateParams CreateParams {
116 cp = base.CreateParams;
118 cp.Style = (int)WindowStyles.WS_POPUP;
119 cp.Style |= (int)WindowStyles.WS_CLIPSIBLINGS;
121 cp.ExStyle = (int)(WindowExStyles.WS_EX_TOOLWINDOW | WindowExStyles.WS_EX_TOPMOST);
127 protected override void OnPaint(PaintEventArgs pevent) {
128 // We don't do double-buffering on purpose:
129 // 1) we'd have to meddle with is_visible, it destroys the buffers if !visible
130 // 2) We don't draw much, no need to double buffer
131 base.OnPaint(pevent);
133 OnDraw (new DrawToolTipEventArgs (pevent.Graphics, associated_control, associated_control, ClientRectangle, this.Text, this.BackColor, this.ForeColor, this.Font));
136 protected override void OnTextChanged (EventArgs args)
139 base.OnTextChanged (args);
142 protected override void WndProc(ref Message m) {
143 if (m.Msg == (int)Msg.WM_SETFOCUS) {
144 if (m.WParam != IntPtr.Zero) {
145 XplatUI.SetFocus(m.WParam);
148 base.WndProc (ref m);
152 #endregion // ToolTipWindow Class Protected Instance Methods
154 #region ToolTipWindow Class Private Methods
155 internal virtual void OnDraw (DrawToolTipEventArgs e)
157 DrawToolTipEventHandler eh = (DrawToolTipEventHandler)(Events[DrawEvent]);
161 ThemeEngine.Current.DrawToolTip (e.Graphics, e.Bounds, this);
164 internal virtual void OnPopup (PopupEventArgs e)
166 PopupEventHandler eh = (PopupEventHandler)(Events[PopupEvent]);
170 e.ToolTipSize = ThemeEngine.Current.ToolTipSize (this, Text);
173 private void ToolTipWindow_VisibleChanged(object sender, EventArgs e) {
174 Control control = (Control)sender;
176 if (control.is_visible) {
177 XplatUI.SetTopmost(control.window.Handle, true);
179 XplatUI.SetTopmost(control.window.Handle, false);
185 private void OnUIAToolTip_VisibleChanged (object sender, EventArgs e)
187 if (Visible == false)
188 OnUnPopup (new PopupEventArgs (associated_control, associated_control, false, Size.Empty));
191 private void OnUnPopup (PopupEventArgs e)
193 PopupEventHandler eh = (PopupEventHandler) (Events [UnPopupEvent]);
200 #endregion // ToolTipWindow Class Protected Instance Methods
202 #region Internal Properties
203 internal override bool ActivateOnShow { get { return false; } }
206 // This Present is used when we are using the expicit Show methods for 2.0.
207 // It will not reposition the window.
208 public void PresentModal (Control control, string text)
214 XplatUI.GetDisplaySize (out display_size);
216 associated_control = control;
220 PopupEventArgs pea = new PopupEventArgs (control, control, false, Size.Empty);
226 Size = pea.ToolTipSize;
231 public void Present (Control control, string text)
237 XplatUI.GetDisplaySize (out display_size);
240 associated_control = control;
245 PopupEventArgs pea = new PopupEventArgs (control, control, false, Size.Empty);
251 Size size = pea.ToolTipSize;
254 Height = size.Height;
256 int cursor_w, cursor_h, hot_x, hot_y;
257 XplatUI.GetCursorInfo (control.Cursor.Handle, out cursor_w, out cursor_h, out hot_x, out hot_y);
258 Point loc = Control.MousePosition;
259 loc.Y += (cursor_h - hot_y);
261 if ((loc.X + Width) > display_size.Width)
262 loc.X = display_size.Width - Width;
264 if ((loc.Y + Height) > display_size.Height)
265 loc.Y = Control.MousePosition.Y - Height - hot_y;
272 #region Internal Events
273 static object DrawEvent = new object ();
274 static object PopupEvent = new object ();
278 static object UnPopupEvent = new object ();
281 public event DrawToolTipEventHandler Draw {
282 add { Events.AddHandler (DrawEvent, value); }
283 remove { Events.RemoveHandler (DrawEvent, value); }
286 public event PopupEventHandler Popup {
287 add { Events.AddHandler (PopupEvent, value); }
288 remove { Events.RemoveHandler (PopupEvent, value); }
292 public event PopupEventHandler UnPopup {
293 add { Events.AddHandler (UnPopupEvent, value); }
294 remove { Events.RemoveHandler (UnPopupEvent, value); }
299 #endregion // ToolTipWindow Class
301 #region Public Constructors & Destructors
306 automatic_delay = 500;
307 autopop_delay = 5000;
311 back_color = SystemColors.Info;
312 fore_color = SystemColors.InfoText;
316 stripAmpersands = false;
320 tooltip_strings = new Hashtable(5);
321 controls = new ArrayList(5);
323 tooltip_window = new ToolTipWindow();
324 tooltip_window.MouseLeave += new EventHandler(control_MouseLeave);
325 tooltip_window.Draw += new DrawToolTipEventHandler (tooltip_window_Draw);
326 tooltip_window.Popup += new PopupEventHandler (tooltip_window_Popup);
329 // UIA Framework: Static event handlers
330 tooltip_window.UnPopup += delegate (object sender, PopupEventArgs args) {
333 UnPopup += new PopupEventHandler (OnUIAUnPopup);
337 timer.Enabled = false;
338 timer.Tick +=new EventHandler(timer_Tick);
343 #region UIA Framework: Events, Delegates and Methods
346 // We are using Reflection to add/remove internal events.
347 // Class ToolTipListener uses the events.
349 // - UIAUnPopup. Event used to generate ChildRemoved in ToolTip
350 // - UIAToolTipHookUp. Event used to keep track of associated controls
351 // - UIAToolTipUnhookUp. Event used to remove track of associated controls
352 static object UnPopupEvent = new object ();
354 internal event PopupEventHandler UnPopup {
355 add { Events.AddHandler (UnPopupEvent, value); }
356 remove { Events.RemoveHandler (UnPopupEvent, value); }
359 internal static event PopupEventHandler UIAUnPopup;
360 internal static event ControlEventHandler UIAToolTipHookUp;
361 internal static event ControlEventHandler UIAToolTipUnhookUp;
363 internal static void OnUIAUnPopup (object sender, PopupEventArgs args)
365 if (UIAUnPopup != null)
366 UIAUnPopup (sender, args);
369 internal static void OnUIAToolTipHookUp (object sender, ControlEventArgs args)
371 if (UIAToolTipHookUp != null)
372 UIAToolTipHookUp (sender, args);
375 internal static void OnUIAToolTipUnhookUp (object sender, ControlEventArgs args)
377 if (UIAToolTipUnhookUp != null)
378 UIAToolTipUnhookUp (sender, args);
384 public ToolTip(System.ComponentModel.IContainer cont) : this() {
390 #endregion // Public Constructors & Destructors
392 #region Public Instance Properties
393 [DefaultValue (true)]
400 if (is_active != value) {
403 if (tooltip_window.Visible) {
404 tooltip_window.Visible = false;
405 active_control = null;
412 [RefreshProperties (RefreshProperties.All)]
413 public int AutomaticDelay {
415 return automatic_delay;
419 if (automatic_delay != value) {
420 automatic_delay = value;
421 autopop_delay = automatic_delay * 10;
422 initial_delay = automatic_delay;
423 re_show_delay = automatic_delay / 5;
428 [RefreshProperties (RefreshProperties.All)]
429 public int AutoPopDelay {
431 return autopop_delay;
435 if (autopop_delay != value) {
436 autopop_delay = value;
442 [DefaultValue ("Color [Info]")]
443 public Color BackColor {
444 get { return this.back_color; }
445 set { this.back_color = value; tooltip_window.BackColor = value; }
448 [DefaultValue ("Color [InfoText]")]
449 public Color ForeColor
451 get { return this.fore_color; }
452 set { this.fore_color = value; tooltip_window.ForeColor = value; }
456 [RefreshProperties (RefreshProperties.All)]
457 public int InitialDelay {
459 return initial_delay;
463 if (initial_delay != value) {
464 initial_delay = value;
470 [DefaultValue (false)]
471 public bool OwnerDraw {
472 get { return this.owner_draw; }
473 set { this.owner_draw = value; }
477 [RefreshProperties (RefreshProperties.All)]
478 public int ReshowDelay {
480 return re_show_delay;
484 if (re_show_delay != value) {
485 re_show_delay = value;
490 [DefaultValue (false)]
491 public bool ShowAlways {
497 if (show_always != value) {
505 [DefaultValue (false)]
506 public bool IsBalloon {
507 get { return isBalloon; }
508 set { isBalloon = value; }
512 [DefaultValue (false)]
513 public bool StripAmpersands {
514 get { return stripAmpersands; }
515 set { stripAmpersands = value; }
518 [Localizable (false)]
520 [TypeConverter (typeof (StringConverter))]
521 [DefaultValue (null)]
527 [DefaultValue (ToolTipIcon.None)]
528 public ToolTipIcon ToolTipIcon {
529 get { return this.tool_tip_icon; }
530 set { this.tool_tip_icon = value; }
534 public string ToolTipTitle {
535 get { return this.tool_tip_title; }
536 set { this.tool_tip_title = value; }
540 [DefaultValue (true)]
541 public bool UseAnimation {
542 get { return useAnimation; }
543 set { useAnimation = value; }
547 [DefaultValue (true)]
548 public bool UseFading {
549 get { return useFading; }
550 set { useFading = value; }
554 #endregion // Public Instance Properties
556 #region Protected Properties
558 protected virtual CreateParams CreateParams
562 CreateParams cp = new CreateParams ();
572 #region Public Instance Methods
573 public bool CanExtend(object target) {
578 [Editor ("System.ComponentModel.Design.MultilineStringEditor, " + Consts.AssemblySystem_Design,
579 "System.Drawing.Design.UITypeEditor, " + Consts.AssemblySystem_Drawing)]
583 public string GetToolTip (Control control)
585 string tooltip = (string)tooltip_strings[control];
591 public void RemoveAll() {
592 tooltip_strings.Clear();
594 //UIA Framework: ToolTip isn't associated anymore
595 foreach (Control control in controls)
596 OnUIAToolTipUnhookUp (this, new ControlEventArgs (control));
602 public void SetToolTip(Control control, string caption) {
605 OnUIAToolTipHookUp (this, new ControlEventArgs (control));
607 tooltip_strings[control] = caption;
609 // no need for duplicates
610 if (!controls.Contains(control)) {
611 control.MouseEnter += new EventHandler(control_MouseEnter);
612 control.MouseMove += new MouseEventHandler(control_MouseMove);
613 control.MouseLeave += new EventHandler(control_MouseLeave);
614 control.MouseDown += new MouseEventHandler (control_MouseDown);
615 controls.Add(control);
618 // if SetToolTip is called from a control and the mouse is currently over that control,
619 // make sure that tooltip_window.Text gets updated if it's being shown,
620 // or show the tooltip for it if is not
621 if (active_control == control && caption != null && state == TipState.Show) {
622 Size size = ThemeEngine.Current.ToolTipSize(tooltip_window, caption);
623 tooltip_window.Width = size.Width;
624 tooltip_window.Height = size.Height;
625 tooltip_window.Text = caption;
628 } else if (control.IsHandleCreated && MouseInControl (control, false))
629 ShowTooltip (control);
632 public override string ToString() {
633 return base.ToString() + " InitialDelay: " + initial_delay + ", ShowAlways: " + show_always;
637 public void Show (string text, IWin32Window window)
639 Show (text, window, 0);
642 public void Show (string text, IWin32Window window, int duration)
645 throw new ArgumentNullException ("window");
647 throw new ArgumentOutOfRangeException ("duration", "duration cannot be less than zero");
654 Control c = (Control)window;
656 XplatUI.SetOwner (tooltip_window.Handle, c.TopLevelControl.Handle);
658 // If the mouse is in the requested window, use that position
659 // Else, center in the requested window
660 if (c.ClientRectangle.Contains (c.PointToClient (Control.MousePosition))) {
661 tooltip_window.Location = Control.MousePosition;
662 tooltip_strings[c] = text;
663 HookupControlEvents (c);
666 tooltip_window.Location = c.PointToScreen (new Point (c.Width / 2, c.Height / 2));
668 // We need to hide our tooltip if the form loses focus, is closed, or is minimized
669 HookupFormEvents ((Form)c.TopLevelControl);
671 tooltip_window.PresentModal ((Control)window, text);
673 state = TipState.Show;
676 timer.Interval = duration;
681 public void Show (string text, IWin32Window window, Point point)
683 Show (text, window, point, 0);
686 public void Show (string text, IWin32Window window, int x, int y)
688 Show (text, window, new Point (x, y), 0);
691 public void Show (string text, IWin32Window window, Point point, int duration)
694 throw new ArgumentNullException ("window");
696 throw new ArgumentOutOfRangeException ("duration", "duration cannot be less than zero");
703 Control c = (Control)window;
705 Point display_point = c.PointToScreen (Point.Empty);
706 display_point.X += point.X;
707 display_point.Y += point.Y;
709 XplatUI.SetOwner (tooltip_window.Handle, c.TopLevelControl.Handle);
711 // We need to hide our tooltip if the form loses focus, is closed, or is minimized
712 HookupFormEvents ((Form)c.TopLevelControl);
714 tooltip_window.Location = display_point;
715 tooltip_window.PresentModal ((Control)window, text);
717 state = TipState.Show;
720 timer.Interval = duration;
725 public void Show (string text, IWin32Window window, int x, int y, int duration)
727 Show (text, window, new Point (x, y), duration);
734 void Hide (IWin32Window win)
737 state = TipState.Initial;
740 tooltip_window.Visible = false;
742 #endregion // Public Instance Methods
744 #region Protected Instance Methods
745 protected override void Dispose(bool disposing) {
747 // Mop up the mess; or should we wait for the GC to kick in?
751 // Not sure if we should clean up tooltip_window
752 tooltip_window.Dispose();
754 tooltip_strings.Clear();
757 //UIA Framework: ToolTip isn't associated anymore
758 foreach (Control control in controls)
759 OnUIAToolTipUnhookUp (this, new ControlEventArgs (control));
766 protected void StopTimer ()
771 #endregion // Protected Instance Methods
779 TipState state = TipState.Initial;
781 #region Private Methods
784 private void HookupFormEvents (Form form)
788 form.Deactivate += new EventHandler (Form_Deactivate);
789 form.Closed += new EventHandler (Form_Closed);
790 form.Resize += new EventHandler (Form_Resize);
793 private void HookupControlEvents (Control control)
795 if (!controls.Contains (control)) {
796 control.MouseEnter += new EventHandler (control_MouseEnter);
797 control.MouseMove += new MouseEventHandler (control_MouseMove);
798 control.MouseLeave += new EventHandler (control_MouseLeave);
799 control.MouseDown += new MouseEventHandler (control_MouseDown);
800 controls.Add (control);
804 private void UnhookControlEvents (Control control)
806 control.MouseEnter -= new EventHandler (control_MouseEnter);
807 control.MouseMove -= new MouseEventHandler (control_MouseMove);
808 control.MouseLeave -= new EventHandler (control_MouseLeave);
809 control.MouseDown -= new MouseEventHandler (control_MouseDown);
812 private void UnhookFormEvents ()
814 if (hooked_form == null)
817 hooked_form.Deactivate -= new EventHandler (Form_Deactivate);
818 hooked_form.Closed -= new EventHandler (Form_Closed);
819 hooked_form.Resize -= new EventHandler (Form_Resize);
825 private void Form_Resize (object sender, EventArgs e)
827 Form f = (Form)sender;
829 if (f.WindowState == FormWindowState.Minimized)
830 tooltip_window.Visible = false;
833 private void Form_Closed (object sender, EventArgs e)
835 tooltip_window.Visible = false;
838 private void Form_Deactivate (object sender, EventArgs e)
840 tooltip_window.Visible = false;
843 internal void Present (Control control, string text)
845 tooltip_window.Present (control, text);
848 private void control_MouseEnter (object sender, EventArgs e)
850 ShowTooltip (sender as Control);
853 private void ShowTooltip (Control control)
855 last_control = control;
857 // Whatever we're displaying right now, we don't want it anymore
858 tooltip_window.Visible = false;
860 state = TipState.Initial;
866 IContainerControl cc = last_control.GetContainerControl ();
867 if ((cc == null) || (cc.ActiveControl == null)) {
872 string text = (string)tooltip_strings[control];
873 if (text != null && text.Length > 0) {
874 if (active_control == null) {
875 timer.Interval = initial_delay;
877 timer.Interval = Math.Max (re_show_delay, 1);
880 active_control = control;
885 private void timer_Tick(object sender, EventArgs e) {
889 case TipState.Initial:
890 if (active_control == null)
892 tooltip_window.Present (active_control, (string)tooltip_strings[active_control]);
893 state = TipState.Show;
894 timer.Interval = autopop_delay;
899 tooltip_window.Visible = false;
900 state = TipState.Down;
904 throw new Exception ("Timer shouldn't be running in state: " + state);
908 private void tooltip_window_Popup (object sender, PopupEventArgs e)
910 e.ToolTipSize = ThemeEngine.Current.ToolTipSize (tooltip_window, tooltip_window.Text);
914 private void tooltip_window_Draw (object sender, DrawToolTipEventArgs e)
921 ThemeEngine.Current.DrawToolTip (e.Graphics, e.Bounds, tooltip_window);
924 private bool MouseInControl (Control control, bool fuzzy) {
929 if (control == null) {
933 m = Control.MousePosition;
934 c = new Point(control.Bounds.X, control.Bounds.Y);
935 if (control.Parent != null) {
936 c = control.Parent.PointToScreen(c);
938 cw = control.ClientSize;
941 Rectangle rect = new Rectangle (c, cw);
944 // We won't get mouse move events on all platforms with the exact same
945 // frequency, so cheat a bit.
949 return rect.Contains (m);
952 private void control_MouseLeave(object sender, EventArgs e)
956 active_control = null;
957 tooltip_window.Visible = false;
959 if (last_control == sender)
964 void control_MouseDown (object sender, MouseEventArgs e)
968 active_control = null;
969 tooltip_window.Visible = false;
971 if (last_control == sender)
977 private void Hide (Control sender)
981 if (!MouseInControl (tooltip_window, true) && !MouseInControl (active_control, true)) {
982 active_control = null;
983 tooltip_window.Visible = false;
986 if (last_control == sender)
992 private void control_MouseMove(object sender, MouseEventArgs e) {
993 if (state != TipState.Down) {
999 internal void OnDraw (DrawToolTipEventArgs e)
1001 DrawToolTipEventHandler eh = (DrawToolTipEventHandler)(Events[DrawEvent]);
1006 internal void OnPopup (PopupEventArgs e)
1008 PopupEventHandler eh = (PopupEventHandler) (Events [PopupEvent]);
1014 internal void OnUnPopup (PopupEventArgs e)
1016 PopupEventHandler eh = (PopupEventHandler) (Events [UnPopupEvent]);
1022 internal bool Visible {
1023 get { return tooltip_window.Visible; }
1025 #endregion // Private Methods
1028 static object PopupEvent = new object ();
1029 static object DrawEvent = new object ();
1034 event PopupEventHandler Popup {
1035 add { Events.AddHandler (PopupEvent, value); }
1036 remove { Events.RemoveHandler (PopupEvent, value); }
1042 event DrawToolTipEventHandler Draw {
1043 add { Events.AddHandler (DrawEvent, value); }
1044 remove { Events.RemoveHandler (DrawEvent, value); }