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
30 using System
.Collections
;
31 using System
.ComponentModel
;
34 namespace System
.Windows
.Forms
{
35 [ProvideProperty ("ToolTip", typeof(System
.Windows
.Forms
.Control
))]
36 [ToolboxItemFilter("System.Windows.Forms", ToolboxItemFilterType
.Allow
)]
41 class ToolTip
: System
.ComponentModel
.Component
, System
.ComponentModel
.IExtenderProvider
{
42 #region Local variables
43 internal bool is_active
;
44 internal int automatic_delay
;
45 internal int autopop_delay
;
46 internal int initial_delay
;
47 internal int re_show_delay
;
48 internal bool show_always
;
50 internal ToolTipWindow tooltip_window
; // The actual tooltip window
51 internal Hashtable tooltip_strings
; // List of strings for each control, indexed by control
52 internal ArrayList controls
;
53 internal Control active_control
; // Control for which the tooltip is currently displayed
54 internal Control last_control
; // last control the mouse was in
55 internal Timer timer
; // Used for the various intervals
56 #endregion // Local variables
58 #region ToolTipWindow Class
59 internal class ToolTipWindow
: Control
{
60 #region ToolTipWindow Class Local Variables
61 internal StringFormat string_format
;
62 #endregion // ToolTipWindow Class Local Variables
64 #region ToolTipWindow Class Constructor
65 internal ToolTipWindow() {
67 string_format
= new StringFormat();
68 string_format
.LineAlignment
= StringAlignment
.Center
;
69 string_format
.Alignment
= StringAlignment
.Center
;
70 string_format
.FormatFlags
= StringFormatFlags
.NoWrap
;
73 Size
= new Size(100, 20);
74 ForeColor
= ThemeEngine
.Current
.ColorInfoText
;
75 BackColor
= ThemeEngine
.Current
.ColorInfo
;
77 VisibleChanged
+= new EventHandler(ToolTipWindow_VisibleChanged
);
79 SetStyle (ControlStyles
.UserPaint
| ControlStyles
.AllPaintingInWmPaint
, true);
80 SetStyle (ControlStyles
.ResizeRedraw
| ControlStyles
.Opaque
, true);
83 #endregion // ToolTipWindow Class Constructor
85 #region ToolTipWindow Class Protected Instance Methods
86 protected override void OnCreateControl() {
87 base.OnCreateControl ();
88 XplatUI
.SetTopmost(this.window
.Handle
, IntPtr
.Zero
, true);
91 protected override CreateParams CreateParams
{
95 cp
= base.CreateParams
;
97 cp
.Style
= (int)WindowStyles
.WS_POPUP
;
98 cp
.Style
|= (int)WindowStyles
.WS_CLIPSIBLINGS
;
100 cp
.ExStyle
= (int)(WindowExStyles
.WS_EX_TOOLWINDOW
| WindowExStyles
.WS_EX_TOPMOST
);
106 protected override void OnPaint(PaintEventArgs pevent
) {
107 // We don't do double-buffering on purpose:
108 // 1) we'd have to meddle with is_visible, it destroys the buffers if !visible
109 // 2) We don't draw much, no need to double buffer
110 ThemeEngine
.Current
.DrawToolTip(pevent
.Graphics
, ClientRectangle
, this);
112 base.OnPaint(pevent
);
115 protected override void Dispose(bool disposing
) {
117 this.string_format
.Dispose();
119 base.Dispose (disposing
);
122 protected override void WndProc(ref Message m
) {
123 if (m
.Msg
== (int)Msg
.WM_SETFOCUS
) {
124 if (m
.WParam
!= IntPtr
.Zero
) {
125 XplatUI
.SetFocus(m
.WParam
);
128 base.WndProc (ref m
);
132 #endregion // ToolTipWindow Class Protected Instance Methods
134 #region ToolTipWindow Class Private Methods
135 private void ToolTipWindow_VisibleChanged(object sender
, EventArgs e
) {
136 Control control
= (Control
)sender
;
138 if (control
.Visible
) {
139 XplatUI
.SetTopmost(control
.window
.Handle
, IntPtr
.Zero
, true);
141 XplatUI
.SetTopmost(control
.window
.Handle
, IntPtr
.Zero
, false);
144 #endregion // ToolTipWindow Class Protected Instance Methods
146 public void Present (Control control
, string text
)
152 XplatUI
.GetDisplaySize (out display_size
);
154 Size size
= ThemeEngine
.Current
.ToolTipSize (this, text
);
156 Height
= size
.Height
;
159 int cursor_w
, cursor_h
, hot_x
, hot_y
;
160 XplatUI
.GetCursorInfo (control
.Cursor
.Handle
, out cursor_w
, out cursor_h
, out hot_x
, out hot_y
);
161 Point loc
= Control
.MousePosition
;
162 loc
.Y
+= (cursor_h
- hot_y
);
164 if ((loc
.X
+ Width
) > display_size
.Width
)
165 loc
.X
= display_size
.Width
- Width
;
167 if ((loc
.Y
+ Height
) > display_size
.Height
)
168 loc
.Y
= Control
.MousePosition
.Y
- Height
- hot_y
;
174 #endregion // ToolTipWindow Class
176 #region Public Constructors & Destructors
181 automatic_delay
= 500;
182 autopop_delay
= 5000;
187 tooltip_strings
= new Hashtable(5);
188 controls
= new ArrayList(5);
190 tooltip_window
= new ToolTipWindow();
191 tooltip_window
.MouseLeave
+= new EventHandler(control_MouseLeave
);
194 timer
.Enabled
= false;
195 timer
.Tick
+=new EventHandler(timer_Tick
);
198 public ToolTip(System
.ComponentModel
.IContainer cont
) : this() {
204 #endregion // Public Constructors & Destructors
206 #region Public Instance Properties
207 [DefaultValue (true)]
214 if (is_active
!= value) {
217 if (tooltip_window
.Visible
) {
218 tooltip_window
.Visible
= false;
219 active_control
= null;
226 [RefreshProperties (RefreshProperties
.All
)]
227 public int AutomaticDelay
{
229 return automatic_delay
;
233 if (automatic_delay
!= value) {
234 automatic_delay
= value;
235 autopop_delay
= automatic_delay
* 10;
236 initial_delay
= automatic_delay
;
237 re_show_delay
= automatic_delay
/ 5;
242 [RefreshProperties (RefreshProperties
.All
)]
243 public int AutoPopDelay
{
245 return autopop_delay
;
249 if (autopop_delay
!= value) {
250 autopop_delay
= value;
255 [RefreshProperties (RefreshProperties
.All
)]
256 public int InitialDelay
{
258 return initial_delay
;
262 if (initial_delay
!= value) {
263 initial_delay
= value;
268 [RefreshProperties (RefreshProperties
.All
)]
269 public int ReshowDelay
{
271 return re_show_delay
;
275 if (re_show_delay
!= value) {
276 re_show_delay
= value;
281 [DefaultValue (false)]
282 public bool ShowAlways
{
288 if (show_always
!= value) {
293 #endregion // Public Instance Properties
295 #region Public Instance Methods
296 public bool CanExtend(object target
) {
302 public string GetToolTip(Control control
) {
303 string tooltip
= (string)tooltip_strings
[control
];
309 public void RemoveAll() {
310 tooltip_strings
.Clear();
314 public void SetToolTip(Control control
, string caption
) {
315 tooltip_strings
[control
] = caption
;
317 // no need for duplicates
318 if (!controls
.Contains(control
)) {
319 control
.MouseEnter
+= new EventHandler(control_MouseEnter
);
320 control
.MouseMove
+= new MouseEventHandler(control_MouseMove
);
321 control
.MouseLeave
+= new EventHandler(control_MouseLeave
);
322 controls
.Add(control
);
325 // if SetToolTip is called from a control and the mouse is currently over that control,
326 // make sure that tooltip_window.Text gets updated
327 if (caption
!= null && last_control
== control
) {
328 Size size
= ThemeEngine
.Current
.ToolTipSize(tooltip_window
, caption
);
329 tooltip_window
.Width
= size
.Width
;
330 tooltip_window
.Height
= size
.Height
;
331 tooltip_window
.Text
= caption
;
335 public override string ToString() {
336 return base.ToString() + " InitialDelay: " + initial_delay
+ ", ShowAlways: " + show_always
;
338 #endregion // Public Instance Methods
340 #region Protected Instance Methods
341 protected override void Dispose(bool disposing
) {
343 // Mop up the mess; or should we wait for the GC to kick in?
347 // Not sure if we should clean up tooltip_window
348 tooltip_window
.Dispose();
350 tooltip_strings
.Clear();
355 #endregion // Protected Instance Methods
363 TipState state
= TipState
.Initial
;
365 #region Private Methods
366 private void control_MouseEnter(object sender
, EventArgs e
)
368 last_control
= (Control
)sender
;
370 // Whatever we're displaying right now, we don't want it anymore
371 tooltip_window
.Visible
= false;
373 state
= TipState
.Initial
;
375 // if we're in the same control as before (how'd that happen?) or if we're not active, leave
376 if (!is_active
|| (active_control
== (Control
)sender
)) {
381 IContainerControl cc
= last_control
.GetContainerControl ();
382 if ((cc
== null) || (cc
.ActiveControl
== null)) {
387 string text
= (string)tooltip_strings
[sender
];
388 if (text
!= null && text
.Length
> 0) {
390 if (active_control
== null) {
391 timer
.Interval
= initial_delay
;
393 timer
.Interval
= re_show_delay
;
396 active_control
= (Control
)sender
;
401 private void timer_Tick(object sender
, EventArgs e
) {
405 case TipState
.Initial
:
406 if (active_control
== null)
408 tooltip_window
.Present (active_control
, (string)tooltip_strings
[active_control
]);
409 state
= TipState
.Show
;
410 timer
.Interval
= autopop_delay
;
415 tooltip_window
.Visible
= false;
416 state
= TipState
.Down
;
420 throw new Exception ("Timer shouldn't be running in state: " + state
);
425 private bool MouseInControl(Control control
) {
430 if (control
== null) {
434 m
= Control
.MousePosition
;
435 c
= new Point(control
.Bounds
.X
, control
.Bounds
.Y
);
436 if (control
.Parent
!= null) {
437 c
= control
.Parent
.PointToScreen(c
);
439 cw
= control
.ClientSize
;
441 if (c
.X
<=m
.X
&& m
.X
<(c
.X
+cw
.Width
) &&
442 c
.Y
<=m
.Y
&& m
.Y
<(c
.Y
+cw
.Height
)) {
448 private void control_MouseLeave(object sender
, EventArgs e
) {
451 if (!MouseInControl(tooltip_window
) && !MouseInControl(active_control
)) {
452 active_control
= null;
453 tooltip_window
.Visible
= false;
456 if (last_control
== (Control
)sender
)
460 private void control_MouseMove(object sender
, MouseEventArgs e
) {
461 if (state
!= TipState
.Down
) {
466 #endregion // Private Methods