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 bool useAnimation;
68 private bool useFading;
72 #endregion // Local variables
74 #region ToolTipWindow Class
75 internal class ToolTipWindow : Control {
76 #region ToolTipWindow Class Local Variables
77 private Control associated_control;
79 internal string title = String.Empty;
80 internal Rectangle icon_rect;
81 internal Rectangle title_rect;
82 internal Rectangle text_rect;
83 #endregion // ToolTipWindow Class Local Variables
85 #region ToolTipWindow Class Constructor
86 internal ToolTipWindow() {
88 Size = new Size(100, 20);
89 ForeColor = ThemeEngine.Current.ColorInfoText;
90 BackColor = ThemeEngine.Current.ColorInfo;
92 VisibleChanged += new EventHandler(ToolTipWindow_VisibleChanged);
95 // UIA Framework: Used to generate UnPopup
96 VisibleChanged += new EventHandler (OnUIAToolTip_VisibleChanged);
99 SetStyle (ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint, true);
100 SetStyle (ControlStyles.ResizeRedraw, true);
101 if (ThemeEngine.Current.ToolTipTransparentBackground) {
102 SetStyle (ControlStyles.SupportsTransparentBackColor, true);
103 BackColor = Color.Transparent;
105 SetStyle (ControlStyles.Opaque, true);
108 #endregion // ToolTipWindow Class Constructor
110 #region ToolTipWindow Class Protected Instance Methods
111 protected override void OnCreateControl() {
112 base.OnCreateControl ();
113 XplatUI.SetTopmost(this.window.Handle, true);
116 protected override CreateParams CreateParams {
120 cp = base.CreateParams;
122 cp.Style = (int)WindowStyles.WS_POPUP;
123 cp.Style |= (int)WindowStyles.WS_CLIPSIBLINGS;
125 cp.ExStyle = (int)(WindowExStyles.WS_EX_TOOLWINDOW | WindowExStyles.WS_EX_TOPMOST);
131 protected override void OnPaint(PaintEventArgs pevent) {
132 // We don't do double-buffering on purpose:
133 // 1) we'd have to meddle with is_visible, it destroys the buffers if !visible
134 // 2) We don't draw much, no need to double buffer
135 base.OnPaint(pevent);
137 OnDraw (new DrawToolTipEventArgs (pevent.Graphics, associated_control, associated_control, ClientRectangle, this.Text, this.BackColor, this.ForeColor, this.Font));
140 protected override void OnTextChanged (EventArgs args)
143 base.OnTextChanged (args);
146 protected override void WndProc(ref Message m) {
147 if (m.Msg == (int)Msg.WM_SETFOCUS) {
148 if (m.WParam != IntPtr.Zero) {
149 XplatUI.SetFocus(m.WParam);
152 base.WndProc (ref m);
156 #endregion // ToolTipWindow Class Protected Instance Methods
158 #region ToolTipWindow Class Private Methods
159 internal virtual void OnDraw (DrawToolTipEventArgs e)
161 DrawToolTipEventHandler eh = (DrawToolTipEventHandler)(Events[DrawEvent]);
165 ThemeEngine.Current.DrawToolTip (e.Graphics, e.Bounds, this);
168 internal virtual void OnPopup (PopupEventArgs e)
170 PopupEventHandler eh = (PopupEventHandler)(Events[PopupEvent]);
174 e.ToolTipSize = ThemeEngine.Current.ToolTipSize (this, Text);
177 private void ToolTipWindow_VisibleChanged(object sender, EventArgs e) {
178 Control control = (Control)sender;
180 if (control.is_visible) {
181 XplatUI.SetTopmost(control.window.Handle, true);
183 XplatUI.SetTopmost(control.window.Handle, false);
189 private void OnUIAToolTip_VisibleChanged (object sender, EventArgs e)
191 if (Visible == false)
192 OnUnPopup (new PopupEventArgs (associated_control, associated_control, false, Size.Empty));
195 private void OnUnPopup (PopupEventArgs e)
197 PopupEventHandler eh = (PopupEventHandler) (Events [UnPopupEvent]);
204 #endregion // ToolTipWindow Class Protected Instance Methods
206 #region Internal Properties
207 internal override bool ActivateOnShow { get { return false; } }
210 // This Present is used when we are using the expicit Show methods for 2.0.
211 // It will not reposition the window.
212 public void PresentModal (Control control, string text)
218 XplatUI.GetDisplaySize (out display_size);
220 associated_control = control;
224 PopupEventArgs pea = new PopupEventArgs (control, control, false, Size.Empty);
230 Size = pea.ToolTipSize;
235 public void Present (Control control, string text)
241 XplatUI.GetDisplaySize (out display_size);
244 associated_control = control;
249 PopupEventArgs pea = new PopupEventArgs (control, control, false, Size.Empty);
255 Size size = pea.ToolTipSize;
258 Height = size.Height;
260 int cursor_w, cursor_h, hot_x, hot_y;
261 XplatUI.GetCursorInfo (control.Cursor.Handle, out cursor_w, out cursor_h, out hot_x, out hot_y);
262 Point loc = Control.MousePosition;
263 loc.Y += (cursor_h - hot_y);
265 if ((loc.X + Width) > display_size.Width)
266 loc.X = display_size.Width - Width;
268 if ((loc.Y + Height) > display_size.Height)
269 loc.Y = Control.MousePosition.Y - Height - hot_y;
276 #region Internal Events
277 static object DrawEvent = new object ();
278 static object PopupEvent = new object ();
282 static object UnPopupEvent = new object ();
285 public event DrawToolTipEventHandler Draw {
286 add { Events.AddHandler (DrawEvent, value); }
287 remove { Events.RemoveHandler (DrawEvent, value); }
290 public event PopupEventHandler Popup {
291 add { Events.AddHandler (PopupEvent, value); }
292 remove { Events.RemoveHandler (PopupEvent, value); }
296 internal event PopupEventHandler UnPopup {
297 add { Events.AddHandler (UnPopupEvent, value); }
298 remove { Events.RemoveHandler (UnPopupEvent, value); }
303 #endregion // ToolTipWindow Class
305 #region Public Constructors & Destructors
310 automatic_delay = 500;
311 autopop_delay = 5000;
315 back_color = SystemColors.Info;
316 fore_color = SystemColors.InfoText;
320 stripAmpersands = false;
324 tooltip_strings = new Hashtable(5);
325 controls = new ArrayList(5);
327 tooltip_window = new ToolTipWindow();
328 tooltip_window.MouseLeave += new EventHandler(control_MouseLeave);
329 tooltip_window.Draw += new DrawToolTipEventHandler (tooltip_window_Draw);
330 tooltip_window.Popup += new PopupEventHandler (tooltip_window_Popup);
333 // UIA Framework: Static event handlers
334 tooltip_window.UnPopup += delegate (object sender, PopupEventArgs args) {
337 UnPopup += new PopupEventHandler (OnUIAUnPopup);
341 timer.Enabled = false;
342 timer.Tick +=new EventHandler(timer_Tick);
347 #region UIA Framework: Events, Delegates and Methods
350 // We are using Reflection to add/remove internal events.
351 // Class ToolTipListener uses the events.
353 // - UIAUnPopup. Event used to generate ChildRemoved in ToolTip
354 // - UIAToolTipHookUp. Event used to keep track of associated controls
355 // - UIAToolTipUnhookUp. Event used to remove track of associated controls
356 static object UnPopupEvent = new object ();
358 internal event PopupEventHandler UnPopup {
359 add { Events.AddHandler (UnPopupEvent, value); }
360 remove { Events.RemoveHandler (UnPopupEvent, value); }
363 internal static event PopupEventHandler UIAUnPopup;
364 internal static event ControlEventHandler UIAToolTipHookUp;
365 internal static event ControlEventHandler UIAToolTipUnhookUp;
367 internal Rectangle UIAToolTipRectangle {
368 get { return tooltip_window.Bounds; }
371 internal static void OnUIAUnPopup (object sender, PopupEventArgs args)
373 if (UIAUnPopup != null)
374 UIAUnPopup (sender, args);
377 internal static void OnUIAToolTipHookUp (object sender, ControlEventArgs args)
379 if (UIAToolTipHookUp != null)
380 UIAToolTipHookUp (sender, args);
383 internal static void OnUIAToolTipUnhookUp (object sender, ControlEventArgs args)
385 if (UIAToolTipUnhookUp != null)
386 UIAToolTipUnhookUp (sender, args);
392 public ToolTip(System.ComponentModel.IContainer cont) : this() {
398 #endregion // Public Constructors & Destructors
400 #region Public Instance Properties
401 [DefaultValue (true)]
408 if (is_active != value) {
411 if (tooltip_window.Visible) {
412 tooltip_window.Visible = false;
413 active_control = null;
420 [RefreshProperties (RefreshProperties.All)]
421 public int AutomaticDelay {
423 return automatic_delay;
427 if (automatic_delay != value) {
428 automatic_delay = value;
429 autopop_delay = automatic_delay * 10;
430 initial_delay = automatic_delay;
431 re_show_delay = automatic_delay / 5;
436 [RefreshProperties (RefreshProperties.All)]
437 public int AutoPopDelay {
439 return autopop_delay;
443 if (autopop_delay != value) {
444 autopop_delay = value;
450 [DefaultValue ("Color [Info]")]
451 public Color BackColor {
452 get { return this.back_color; }
453 set { this.back_color = value; tooltip_window.BackColor = value; }
456 [DefaultValue ("Color [InfoText]")]
457 public Color ForeColor
459 get { return this.fore_color; }
460 set { this.fore_color = value; tooltip_window.ForeColor = value; }
464 [RefreshProperties (RefreshProperties.All)]
465 public int InitialDelay {
467 return initial_delay;
471 if (initial_delay != value) {
472 initial_delay = value;
478 [DefaultValue (false)]
479 public bool OwnerDraw {
480 get { return this.owner_draw; }
481 set { this.owner_draw = value; }
485 [RefreshProperties (RefreshProperties.All)]
486 public int ReshowDelay {
488 return re_show_delay;
492 if (re_show_delay != value) {
493 re_show_delay = value;
498 [DefaultValue (false)]
499 public bool ShowAlways {
505 if (show_always != value) {
513 [DefaultValue (false)]
514 public bool IsBalloon {
515 get { return isBalloon; }
516 set { isBalloon = value; }
520 [DefaultValue (false)]
521 public bool StripAmpersands {
522 get { return stripAmpersands; }
523 set { stripAmpersands = value; }
526 [Localizable (false)]
528 [TypeConverter (typeof (StringConverter))]
529 [DefaultValue (null)]
535 [DefaultValue (ToolTipIcon.None)]
536 public ToolTipIcon ToolTipIcon {
537 get { return this.tool_tip_icon; }
540 case ToolTipIcon.None:
541 tooltip_window.icon = null;
543 case ToolTipIcon.Error:
544 tooltip_window.icon = SystemIcons.Error;
546 case ToolTipIcon.Warning:
547 tooltip_window.icon = SystemIcons.Warning;
549 case ToolTipIcon.Info:
550 tooltip_window.icon = SystemIcons.Information;
554 tool_tip_icon = value;
559 public string ToolTipTitle {
560 get { return tooltip_window.title; }
563 value = String.Empty;
565 tooltip_window.title = value;
570 [DefaultValue (true)]
571 public bool UseAnimation {
572 get { return useAnimation; }
573 set { useAnimation = value; }
577 [DefaultValue (true)]
578 public bool UseFading {
579 get { return useFading; }
580 set { useFading = value; }
584 #endregion // Public Instance Properties
586 #region Protected Properties
588 protected virtual CreateParams CreateParams
592 CreateParams cp = new CreateParams ();
602 #region Public Instance Methods
603 public bool CanExtend(object target) {
608 [Editor ("System.ComponentModel.Design.MultilineStringEditor, " + Consts.AssemblySystem_Design,
609 "System.Drawing.Design.UITypeEditor, " + Consts.AssemblySystem_Drawing)]
613 public string GetToolTip (Control control)
615 string tooltip = (string)tooltip_strings[control];
621 public void RemoveAll() {
622 tooltip_strings.Clear();
624 //UIA Framework: ToolTip isn't associated anymore
625 foreach (Control control in controls)
626 OnUIAToolTipUnhookUp (this, new ControlEventArgs (control));
632 public void SetToolTip(Control control, string caption) {
635 OnUIAToolTipHookUp (this, new ControlEventArgs (control));
637 tooltip_strings[control] = caption;
639 // no need for duplicates
640 if (!controls.Contains(control)) {
641 control.MouseEnter += new EventHandler(control_MouseEnter);
642 control.MouseMove += new MouseEventHandler(control_MouseMove);
643 control.MouseLeave += new EventHandler(control_MouseLeave);
644 control.MouseDown += new MouseEventHandler (control_MouseDown);
645 controls.Add(control);
648 // if SetToolTip is called from a control and the mouse is currently over that control,
649 // make sure that tooltip_window.Text gets updated if it's being shown,
650 // or show the tooltip for it if is not
651 if (active_control == control && caption != null && state == TipState.Show) {
652 Size size = ThemeEngine.Current.ToolTipSize(tooltip_window, caption);
653 tooltip_window.Width = size.Width;
654 tooltip_window.Height = size.Height;
655 tooltip_window.Text = caption;
658 } else if (control.IsHandleCreated && MouseInControl (control, false))
659 ShowTooltip (control);
662 public override string ToString() {
663 return base.ToString() + " InitialDelay: " + initial_delay + ", ShowAlways: " + show_always;
667 public void Show (string text, IWin32Window window)
669 Show (text, window, 0);
672 public void Show (string text, IWin32Window window, int duration)
675 throw new ArgumentNullException ("window");
677 throw new ArgumentOutOfRangeException ("duration", "duration cannot be less than zero");
684 Control c = (Control)window;
686 XplatUI.SetOwner (tooltip_window.Handle, c.TopLevelControl.Handle);
688 // If the mouse is in the requested window, use that position
689 // Else, center in the requested window
690 if (c.ClientRectangle.Contains (c.PointToClient (Control.MousePosition))) {
691 tooltip_window.Location = Control.MousePosition;
692 tooltip_strings[c] = text;
693 HookupControlEvents (c);
696 tooltip_window.Location = c.PointToScreen (new Point (c.Width / 2, c.Height / 2));
698 // We need to hide our tooltip if the form loses focus, is closed, or is minimized
699 HookupFormEvents ((Form)c.TopLevelControl);
701 tooltip_window.PresentModal ((Control)window, text);
703 state = TipState.Show;
706 timer.Interval = duration;
711 public void Show (string text, IWin32Window window, Point point)
713 Show (text, window, point, 0);
716 public void Show (string text, IWin32Window window, int x, int y)
718 Show (text, window, new Point (x, y), 0);
721 public void Show (string text, IWin32Window window, Point point, int duration)
724 throw new ArgumentNullException ("window");
726 throw new ArgumentOutOfRangeException ("duration", "duration cannot be less than zero");
733 Control c = (Control)window;
735 Point display_point = c.PointToScreen (Point.Empty);
736 display_point.X += point.X;
737 display_point.Y += point.Y;
739 XplatUI.SetOwner (tooltip_window.Handle, c.TopLevelControl.Handle);
741 // We need to hide our tooltip if the form loses focus, is closed, or is minimized
742 HookupFormEvents ((Form)c.TopLevelControl);
744 tooltip_window.Location = display_point;
745 tooltip_window.PresentModal ((Control)window, text);
747 state = TipState.Show;
750 timer.Interval = duration;
755 public void Show (string text, IWin32Window window, int x, int y, int duration)
757 Show (text, window, new Point (x, y), duration);
764 void Hide (IWin32Window win)
767 state = TipState.Initial;
770 tooltip_window.Visible = false;
772 #endregion // Public Instance Methods
774 #region Protected Instance Methods
775 protected override void Dispose(bool disposing) {
776 // call the base impl first to avoid conflicts with any parent's events
777 base.Dispose (disposing);
780 // Mop up the mess; or should we wait for the GC to kick in?
784 // Not sure if we should clean up tooltip_window
785 tooltip_window.Dispose();
787 tooltip_strings.Clear();
790 //UIA Framework: ToolTip isn't associated anymore
791 foreach (Control control in controls)
792 OnUIAToolTipUnhookUp (this, new ControlEventArgs (control));
799 protected void StopTimer ()
804 #endregion // Protected Instance Methods
812 TipState state = TipState.Initial;
814 #region Private Methods
817 private void HookupFormEvents (Form form)
821 form.Deactivate += new EventHandler (Form_Deactivate);
822 form.Closed += new EventHandler (Form_Closed);
823 form.Resize += new EventHandler (Form_Resize);
826 private void HookupControlEvents (Control control)
828 if (!controls.Contains (control)) {
829 control.MouseEnter += new EventHandler (control_MouseEnter);
830 control.MouseMove += new MouseEventHandler (control_MouseMove);
831 control.MouseLeave += new EventHandler (control_MouseLeave);
832 control.MouseDown += new MouseEventHandler (control_MouseDown);
833 controls.Add (control);
837 private void UnhookControlEvents (Control control)
839 control.MouseEnter -= new EventHandler (control_MouseEnter);
840 control.MouseMove -= new MouseEventHandler (control_MouseMove);
841 control.MouseLeave -= new EventHandler (control_MouseLeave);
842 control.MouseDown -= new MouseEventHandler (control_MouseDown);
845 private void UnhookFormEvents ()
847 if (hooked_form == null)
850 hooked_form.Deactivate -= new EventHandler (Form_Deactivate);
851 hooked_form.Closed -= new EventHandler (Form_Closed);
852 hooked_form.Resize -= new EventHandler (Form_Resize);
858 private void Form_Resize (object sender, EventArgs e)
860 Form f = (Form)sender;
862 if (f.WindowState == FormWindowState.Minimized)
863 tooltip_window.Visible = false;
866 private void Form_Closed (object sender, EventArgs e)
868 tooltip_window.Visible = false;
871 private void Form_Deactivate (object sender, EventArgs e)
873 tooltip_window.Visible = false;
876 internal void Present (Control control, string text)
878 tooltip_window.Present (control, text);
881 private void control_MouseEnter (object sender, EventArgs e)
883 ShowTooltip (sender as Control);
886 private void ShowTooltip (Control control)
888 last_control = control;
890 // Whatever we're displaying right now, we don't want it anymore
891 tooltip_window.Visible = false;
893 state = TipState.Initial;
898 // ShowAlways controls whether the controls in non-active forms
899 // can display its tooltips, even if they are not current active control.
900 if (!show_always && control.FindForm () != Form.ActiveForm)
903 string text = (string)tooltip_strings[control];
904 if (text != null && text.Length > 0) {
905 if (active_control == null) {
906 timer.Interval = Math.Max (initial_delay, 1);
908 timer.Interval = Math.Max (re_show_delay, 1);
911 active_control = control;
916 private void timer_Tick(object sender, EventArgs e) {
920 case TipState.Initial:
921 if (active_control == null)
923 tooltip_window.Present (active_control, (string)tooltip_strings[active_control]);
924 state = TipState.Show;
925 timer.Interval = autopop_delay;
930 tooltip_window.Visible = false;
931 state = TipState.Down;
935 throw new Exception ("Timer shouldn't be running in state: " + state);
939 private void tooltip_window_Popup (object sender, PopupEventArgs e)
941 e.ToolTipSize = ThemeEngine.Current.ToolTipSize (tooltip_window, tooltip_window.Text);
945 private void tooltip_window_Draw (object sender, DrawToolTipEventArgs e)
952 ThemeEngine.Current.DrawToolTip (e.Graphics, e.Bounds, tooltip_window);
955 private bool MouseInControl (Control control, bool fuzzy) {
960 if (control == null) {
964 m = Control.MousePosition;
965 c = new Point(control.Bounds.X, control.Bounds.Y);
966 if (control.Parent != null) {
967 c = control.Parent.PointToScreen(c);
969 cw = control.ClientSize;
972 Rectangle rect = new Rectangle (c, cw);
975 // We won't get mouse move events on all platforms with the exact same
976 // frequency, so cheat a bit.
980 return rect.Contains (m);
983 private void control_MouseLeave(object sender, EventArgs e)
987 active_control = null;
988 tooltip_window.Visible = false;
990 if (last_control == sender)
995 void control_MouseDown (object sender, MouseEventArgs e)
999 active_control = null;
1000 tooltip_window.Visible = false;
1002 if (last_control == sender)
1003 last_control = null;
1008 private void Hide (Control sender)
1012 if (!MouseInControl (tooltip_window, true) && !MouseInControl (active_control, true)) {
1013 active_control = null;
1014 tooltip_window.Visible = false;
1017 if (last_control == sender)
1018 last_control = null;
1023 private void control_MouseMove(object sender, MouseEventArgs e) {
1024 if (state != TipState.Down) {
1030 internal void OnDraw (DrawToolTipEventArgs e)
1032 DrawToolTipEventHandler eh = (DrawToolTipEventHandler)(Events[DrawEvent]);
1037 internal void OnPopup (PopupEventArgs e)
1039 PopupEventHandler eh = (PopupEventHandler) (Events [PopupEvent]);
1045 internal void OnUnPopup (PopupEventArgs e)
1047 PopupEventHandler eh = (PopupEventHandler) (Events [UnPopupEvent]);
1053 internal bool Visible {
1054 get { return tooltip_window.Visible; }
1056 #endregion // Private Methods
1059 static object PopupEvent = new object ();
1060 static object DrawEvent = new object ();
1065 event PopupEventHandler Popup {
1066 add { Events.AddHandler (PopupEvent, value); }
1067 remove { Events.RemoveHandler (PopupEvent, value); }
1073 event DrawToolTipEventHandler Draw {
1074 add { Events.AddHandler (DrawEvent, value); }
1075 remove { Events.RemoveHandler (DrawEvent, value); }