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);
90 SetStyle (ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint, true);
91 SetStyle (ControlStyles.ResizeRedraw | ControlStyles.Opaque, true);
94 #endregion // ToolTipWindow Class Constructor
96 #region ToolTipWindow Class Protected Instance Methods
97 protected override void OnCreateControl() {
98 base.OnCreateControl ();
99 XplatUI.SetTopmost(this.window.Handle, true);
102 protected override CreateParams CreateParams {
106 cp = base.CreateParams;
108 cp.Style = (int)WindowStyles.WS_POPUP;
109 cp.Style |= (int)WindowStyles.WS_CLIPSIBLINGS;
111 cp.ExStyle = (int)(WindowExStyles.WS_EX_TOOLWINDOW | WindowExStyles.WS_EX_TOPMOST);
117 protected override void OnPaint(PaintEventArgs pevent) {
118 // We don't do double-buffering on purpose:
119 // 1) we'd have to meddle with is_visible, it destroys the buffers if !visible
120 // 2) We don't draw much, no need to double buffer
121 base.OnPaint(pevent);
123 OnDraw (new DrawToolTipEventArgs (pevent.Graphics, associated_control, associated_control, ClientRectangle, this.Text, this.BackColor, this.ForeColor, this.Font));
126 protected override void OnTextChanged (EventArgs args)
129 base.OnTextChanged (args);
132 protected override void WndProc(ref Message m) {
133 if (m.Msg == (int)Msg.WM_SETFOCUS) {
134 if (m.WParam != IntPtr.Zero) {
135 XplatUI.SetFocus(m.WParam);
138 base.WndProc (ref m);
142 #endregion // ToolTipWindow Class Protected Instance Methods
144 #region ToolTipWindow Class Private Methods
145 internal virtual void OnDraw (DrawToolTipEventArgs e)
147 DrawToolTipEventHandler eh = (DrawToolTipEventHandler)(Events[DrawEvent]);
151 ThemeEngine.Current.DrawToolTip (e.Graphics, e.Bounds, this);
154 internal virtual void OnPopup (PopupEventArgs e)
156 PopupEventHandler eh = (PopupEventHandler)(Events[PopupEvent]);
160 e.ToolTipSize = ThemeEngine.Current.ToolTipSize (this, Text);
163 private void ToolTipWindow_VisibleChanged(object sender, EventArgs e) {
164 Control control = (Control)sender;
166 if (control.is_visible) {
167 XplatUI.SetTopmost(control.window.Handle, true);
169 XplatUI.SetTopmost(control.window.Handle, false);
172 #endregion // ToolTipWindow Class Protected Instance Methods
174 #region Internal Properties
175 internal override bool ActivateOnShow { get { return false; } }
178 // This Present is used when we are using the expicit Show methods for 2.0.
179 // It will not reposition the window.
180 public void PresentModal (Control control, string text)
186 XplatUI.GetDisplaySize (out display_size);
188 associated_control = control;
192 PopupEventArgs pea = new PopupEventArgs (control, control, false, Size.Empty);
198 Size = pea.ToolTipSize;
203 public void Present (Control control, string text)
209 XplatUI.GetDisplaySize (out display_size);
212 associated_control = control;
217 PopupEventArgs pea = new PopupEventArgs (control, control, false, Size.Empty);
223 Size size = pea.ToolTipSize;
226 Height = size.Height;
228 int cursor_w, cursor_h, hot_x, hot_y;
229 XplatUI.GetCursorInfo (control.Cursor.Handle, out cursor_w, out cursor_h, out hot_x, out hot_y);
230 Point loc = Control.MousePosition;
231 loc.Y += (cursor_h - hot_y);
233 if ((loc.X + Width) > display_size.Width)
234 loc.X = display_size.Width - Width;
236 if ((loc.Y + Height) > display_size.Height)
237 loc.Y = Control.MousePosition.Y - Height - hot_y;
244 #region Internal Events
245 static object DrawEvent = new object ();
246 static object PopupEvent = new object ();
248 public event DrawToolTipEventHandler Draw {
249 add { Events.AddHandler (DrawEvent, value); }
250 remove { Events.RemoveHandler (DrawEvent, value); }
253 public event PopupEventHandler Popup {
254 add { Events.AddHandler (PopupEvent, value); }
255 remove { Events.RemoveHandler (PopupEvent, value); }
259 #endregion // ToolTipWindow Class
261 #region Public Constructors & Destructors
266 automatic_delay = 500;
267 autopop_delay = 5000;
271 back_color = SystemColors.Info;
272 fore_color = SystemColors.InfoText;
276 stripAmpersands = false;
280 tooltip_strings = new Hashtable(5);
281 controls = new ArrayList(5);
283 tooltip_window = new ToolTipWindow();
284 tooltip_window.MouseLeave += new EventHandler(control_MouseLeave);
285 tooltip_window.Draw += new DrawToolTipEventHandler (tooltip_window_Draw);
286 tooltip_window.Popup += new PopupEventHandler (tooltip_window_Popup);
289 timer.Enabled = false;
290 timer.Tick +=new EventHandler(timer_Tick);
293 public ToolTip(System.ComponentModel.IContainer cont) : this() {
299 #endregion // Public Constructors & Destructors
301 #region Public Instance Properties
302 [DefaultValue (true)]
309 if (is_active != value) {
312 if (tooltip_window.Visible) {
313 tooltip_window.Visible = false;
314 active_control = null;
321 [RefreshProperties (RefreshProperties.All)]
322 public int AutomaticDelay {
324 return automatic_delay;
328 if (automatic_delay != value) {
329 automatic_delay = value;
330 autopop_delay = automatic_delay * 10;
331 initial_delay = automatic_delay;
332 re_show_delay = automatic_delay / 5;
337 [RefreshProperties (RefreshProperties.All)]
338 public int AutoPopDelay {
340 return autopop_delay;
344 if (autopop_delay != value) {
345 autopop_delay = value;
351 [DefaultValue ("Color [Info]")]
352 public Color BackColor {
353 get { return this.back_color; }
354 set { this.back_color = value; tooltip_window.BackColor = value; }
357 [DefaultValue ("Color [InfoText]")]
358 public Color ForeColor
360 get { return this.fore_color; }
361 set { this.fore_color = value; tooltip_window.ForeColor = value; }
365 [RefreshProperties (RefreshProperties.All)]
366 public int InitialDelay {
368 return initial_delay;
372 if (initial_delay != value) {
373 initial_delay = value;
379 [DefaultValue (false)]
380 public bool OwnerDraw {
381 get { return this.owner_draw; }
382 set { this.owner_draw = value; }
386 [RefreshProperties (RefreshProperties.All)]
387 public int ReshowDelay {
389 return re_show_delay;
393 if (re_show_delay != value) {
394 re_show_delay = value;
399 [DefaultValue (false)]
400 public bool ShowAlways {
406 if (show_always != value) {
414 [DefaultValue (false)]
415 public bool IsBalloon {
416 get { return isBalloon; }
417 set { isBalloon = value; }
421 [DefaultValue (false)]
422 public bool StripAmpersands {
423 get { return stripAmpersands; }
424 set { stripAmpersands = value; }
427 [Localizable (false)]
429 [TypeConverter (typeof (StringConverter))]
430 [DefaultValue (null)]
436 [DefaultValue (ToolTipIcon.None)]
437 public ToolTipIcon ToolTipIcon {
438 get { return this.tool_tip_icon; }
439 set { this.tool_tip_icon = value; }
443 public string ToolTipTitle {
444 get { return this.tool_tip_title; }
445 set { this.tool_tip_title = value; }
449 [DefaultValue (true)]
450 public bool UseAnimation {
451 get { return useAnimation; }
452 set { useAnimation = value; }
456 [DefaultValue (true)]
457 public bool UseFading {
458 get { return useFading; }
459 set { useFading = value; }
463 #endregion // Public Instance Properties
465 #region Protected Properties
467 protected virtual CreateParams CreateParams
471 CreateParams cp = new CreateParams ();
481 #region Public Instance Methods
482 public bool CanExtend(object target) {
487 [Editor ("System.ComponentModel.Design.MultilineStringEditor, " + Consts.AssemblySystem_Design,
488 "System.Drawing.Design.UITypeEditor, " + Consts.AssemblySystem_Drawing)]
492 public string GetToolTip (Control control)
494 string tooltip = (string)tooltip_strings[control];
500 public void RemoveAll() {
501 tooltip_strings.Clear();
505 public void SetToolTip(Control control, string caption) {
506 tooltip_strings[control] = caption;
508 // no need for duplicates
509 if (!controls.Contains(control)) {
510 control.MouseEnter += new EventHandler(control_MouseEnter);
511 control.MouseMove += new MouseEventHandler(control_MouseMove);
512 control.MouseLeave += new EventHandler(control_MouseLeave);
513 control.MouseDown += new MouseEventHandler (control_MouseDown);
514 controls.Add(control);
517 // if SetToolTip is called from a control and the mouse is currently over that control,
518 // make sure that tooltip_window.Text gets updated if it's being shown,
519 // or show the tooltip for it if is not
520 if (active_control == control && caption != null && state == TipState.Show) {
521 Size size = ThemeEngine.Current.ToolTipSize(tooltip_window, caption);
522 tooltip_window.Width = size.Width;
523 tooltip_window.Height = size.Height;
524 tooltip_window.Text = caption;
527 } else if (control.IsHandleCreated && MouseInControl (control, false))
528 ShowTooltip (control);
531 public override string ToString() {
532 return base.ToString() + " InitialDelay: " + initial_delay + ", ShowAlways: " + show_always;
536 public void Show (string text, IWin32Window window)
538 Show (text, window, 0);
541 public void Show (string text, IWin32Window window, int duration)
544 throw new ArgumentNullException ("window");
546 throw new ArgumentOutOfRangeException ("duration", "duration cannot be less than zero");
553 Control c = (Control)window;
555 XplatUI.SetOwner (tooltip_window.Handle, c.TopLevelControl.Handle);
557 // If the mouse is in the requested window, use that position
558 // Else, center in the requested window
559 if (c.ClientRectangle.Contains (c.PointToClient (Control.MousePosition))) {
560 tooltip_window.Location = Control.MousePosition;
561 tooltip_strings[c] = text;
562 HookupControlEvents (c);
565 tooltip_window.Location = c.PointToScreen (new Point (c.Width / 2, c.Height / 2));
567 // We need to hide our tooltip if the form loses focus, is closed, or is minimized
568 HookupFormEvents ((Form)c.TopLevelControl);
570 tooltip_window.PresentModal ((Control)window, text);
572 state = TipState.Show;
575 timer.Interval = duration;
580 public void Show (string text, IWin32Window window, Point point)
582 Show (text, window, point, 0);
585 public void Show (string text, IWin32Window window, int x, int y)
587 Show (text, window, new Point (x, y), 0);
590 public void Show (string text, IWin32Window window, Point point, int duration)
593 throw new ArgumentNullException ("window");
595 throw new ArgumentOutOfRangeException ("duration", "duration cannot be less than zero");
602 Control c = (Control)window;
604 Point display_point = c.PointToScreen (Point.Empty);
605 display_point.X += point.X;
606 display_point.Y += point.Y;
608 XplatUI.SetOwner (tooltip_window.Handle, c.TopLevelControl.Handle);
610 // We need to hide our tooltip if the form loses focus, is closed, or is minimized
611 HookupFormEvents ((Form)c.TopLevelControl);
613 tooltip_window.Location = display_point;
614 tooltip_window.PresentModal ((Control)window, text);
616 state = TipState.Show;
619 timer.Interval = duration;
624 public void Show (string text, IWin32Window window, int x, int y, int duration)
626 Show (text, window, new Point (x, y), duration);
633 void Hide (IWin32Window win)
636 state = TipState.Initial;
639 tooltip_window.Visible = false;
641 #endregion // Public Instance Methods
643 #region Protected Instance Methods
644 protected override void Dispose(bool disposing) {
646 // Mop up the mess; or should we wait for the GC to kick in?
650 // Not sure if we should clean up tooltip_window
651 tooltip_window.Dispose();
653 tooltip_strings.Clear();
659 protected void StopTimer ()
663 #endregion // Protected Instance Methods
671 TipState state = TipState.Initial;
673 #region Private Methods
675 private void HookupFormEvents (Form form)
679 form.Deactivate += new EventHandler (Form_Deactivate);
680 form.Closed += new EventHandler (Form_Closed);
681 form.Resize += new EventHandler (Form_Resize);
684 private void UnhookFormEvents ()
686 if (hooked_form == null)
689 hooked_form.Deactivate -= new EventHandler (Form_Deactivate);
690 hooked_form.Closed -= new EventHandler (Form_Closed);
691 hooked_form.Resize -= new EventHandler (Form_Resize);
696 private void HookupControlEvents (Control control)
698 if (!controls.Contains (control)) {
699 control.MouseEnter += new EventHandler (control_MouseEnter);
700 control.MouseMove += new MouseEventHandler (control_MouseMove);
701 control.MouseLeave += new EventHandler (control_MouseLeave);
702 control.MouseDown += new MouseEventHandler (control_MouseDown);
703 controls.Add (control);
707 private void UnhookControlEvents (Control control)
709 control.MouseEnter -= new EventHandler (control_MouseEnter);
710 control.MouseMove -= new MouseEventHandler (control_MouseMove);
711 control.MouseLeave -= new EventHandler (control_MouseLeave);
712 control.MouseDown -= new MouseEventHandler (control_MouseDown);
715 private void Form_Resize (object sender, EventArgs e)
717 Form f = (Form)sender;
719 if (f.WindowState == FormWindowState.Minimized)
720 tooltip_window.Visible = false;
723 private void Form_Closed (object sender, EventArgs e)
725 tooltip_window.Visible = false;
728 private void Form_Deactivate (object sender, EventArgs e)
730 tooltip_window.Visible = false;
733 internal void Present (Control control, string text)
735 tooltip_window.Present (control, text);
738 private void control_MouseEnter (object sender, EventArgs e)
740 ShowTooltip (sender as Control);
743 private void ShowTooltip (Control control)
745 last_control = control;
747 // Whatever we're displaying right now, we don't want it anymore
748 tooltip_window.Visible = false;
750 state = TipState.Initial;
756 IContainerControl cc = last_control.GetContainerControl ();
757 if ((cc == null) || (cc.ActiveControl == null)) {
762 string text = (string)tooltip_strings[control];
763 if (text != null && text.Length > 0) {
764 if (active_control == null) {
765 timer.Interval = initial_delay;
767 timer.Interval = Math.Max (re_show_delay, 1);
770 active_control = control;
775 private void timer_Tick(object sender, EventArgs e) {
779 case TipState.Initial:
780 if (active_control == null)
782 tooltip_window.Present (active_control, (string)tooltip_strings[active_control]);
783 state = TipState.Show;
784 timer.Interval = autopop_delay;
789 tooltip_window.Visible = false;
790 state = TipState.Down;
794 throw new Exception ("Timer shouldn't be running in state: " + state);
798 private void tooltip_window_Popup (object sender, PopupEventArgs e)
800 e.ToolTipSize = ThemeEngine.Current.ToolTipSize (tooltip_window, tooltip_window.Text);
804 private void tooltip_window_Draw (object sender, DrawToolTipEventArgs e)
811 ThemeEngine.Current.DrawToolTip (e.Graphics, e.Bounds, tooltip_window);
814 private bool MouseInControl (Control control, bool fuzzy) {
819 if (control == null) {
823 m = Control.MousePosition;
824 c = new Point(control.Bounds.X, control.Bounds.Y);
825 if (control.Parent != null) {
826 c = control.Parent.PointToScreen(c);
828 cw = control.ClientSize;
831 Rectangle rect = new Rectangle (c, cw);
834 // We won't get mouse move events on all platforms with the exact same
835 // frequency, so cheat a bit.
839 return rect.Contains (m);
842 private void control_MouseLeave(object sender, EventArgs e)
846 active_control = null;
847 tooltip_window.Visible = false;
849 if (last_control == sender)
854 void control_MouseDown (object sender, MouseEventArgs e)
858 active_control = null;
859 tooltip_window.Visible = false;
861 if (last_control == sender)
865 private void Hide (Control sender)
869 if (!MouseInControl (tooltip_window, true) && !MouseInControl (active_control, true)) {
870 active_control = null;
871 tooltip_window.Visible = false;
874 if (last_control == sender)
878 private void control_MouseMove(object sender, MouseEventArgs e) {
879 if (state != TipState.Down) {
885 internal void OnDraw (DrawToolTipEventArgs e)
887 DrawToolTipEventHandler eh = (DrawToolTipEventHandler)(Events[DrawEvent]);
892 internal void OnPopup (PopupEventArgs e)
894 PopupEventHandler eh = (PopupEventHandler) (Events [PopupEvent]);
899 internal bool Visible {
900 get { return tooltip_window.Visible; }
902 #endregion // Private Methods
905 static object PopupEvent = new object ();
906 static object DrawEvent = new object ();
911 event PopupEventHandler Popup {
912 add { Events.AddHandler (PopupEvent, value); }
913 remove { Events.RemoveHandler (PopupEvent, value); }
919 event DrawToolTipEventHandler Draw {
920 add { Events.AddHandler (DrawEvent, value); }
921 remove { Events.RemoveHandler (DrawEvent, value); }