root / trunk / NotifyIconWpf / TaskbarIcon.cs @ 71374945
History | View | Annotate | Download (32 kB)
1 | 96e5791c | pkanavos | // hardcodet.net NotifyIcon for WPF |
---|---|---|---|
2 | 96e5791c | pkanavos | // Copyright (c) 2009 Philipp Sumi |
3 | 96e5791c | pkanavos | // Contact and Information: http://www.hardcodet.net |
4 | 96e5791c | pkanavos | // |
5 | 96e5791c | pkanavos | // This library is free software; you can redistribute it and/or |
6 | 96e5791c | pkanavos | // modify it under the terms of the Code Project Open License (CPOL); |
7 | 96e5791c | pkanavos | // either version 1.0 of the License, or (at your option) any later |
8 | 96e5791c | pkanavos | // version. |
9 | 96e5791c | pkanavos | // |
10 | 96e5791c | pkanavos | // The above copyright notice and this permission notice shall be |
11 | 96e5791c | pkanavos | // included in all copies or substantial portions of the Software. |
12 | 96e5791c | pkanavos | // |
13 | 96e5791c | pkanavos | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
14 | 96e5791c | pkanavos | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES |
15 | 96e5791c | pkanavos | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
16 | 96e5791c | pkanavos | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT |
17 | 96e5791c | pkanavos | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, |
18 | 96e5791c | pkanavos | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
19 | 96e5791c | pkanavos | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR |
20 | 96e5791c | pkanavos | // OTHER DEALINGS IN THE SOFTWARE. |
21 | 96e5791c | pkanavos | // |
22 | 96e5791c | pkanavos | // THIS COPYRIGHT NOTICE MAY NOT BE REMOVED FROM THIS FILE |
23 | 96e5791c | pkanavos | |
24 | 96e5791c | pkanavos | |
25 | 96e5791c | pkanavos | using System; |
26 | 96e5791c | pkanavos | using System.ComponentModel; |
27 | 96e5791c | pkanavos | using System.Diagnostics; |
28 | 96e5791c | pkanavos | using System.Drawing; |
29 | 96e5791c | pkanavos | using System.Threading; |
30 | 96e5791c | pkanavos | using System.Windows; |
31 | 96e5791c | pkanavos | using System.Windows.Controls; |
32 | 96e5791c | pkanavos | using System.Windows.Controls.Primitives; |
33 | 96e5791c | pkanavos | using System.Windows.Interop; |
34 | 96e5791c | pkanavos | using System.Windows.Threading; |
35 | 96e5791c | pkanavos | using Hardcodet.Wpf.TaskbarNotification.Interop; |
36 | 96e5791c | pkanavos | using Point=Hardcodet.Wpf.TaskbarNotification.Interop.Point; |
37 | 96e5791c | pkanavos | |
38 | 96e5791c | pkanavos | |
39 | 96e5791c | pkanavos | |
40 | 96e5791c | pkanavos | namespace Hardcodet.Wpf.TaskbarNotification |
41 | 96e5791c | pkanavos | { |
42 | 96e5791c | pkanavos | /// <summary> |
43 | 96e5791c | pkanavos | /// A WPF proxy to for a taskbar icon (NotifyIcon) that sits in the system's |
44 | 96e5791c | pkanavos | /// taskbar notification area ("system tray"). |
45 | 96e5791c | pkanavos | /// </summary> |
46 | 96e5791c | pkanavos | public partial class TaskbarIcon : FrameworkElement, IDisposable |
47 | 96e5791c | pkanavos | { |
48 | 96e5791c | pkanavos | #region Members |
49 | 96e5791c | pkanavos | |
50 | 96e5791c | pkanavos | /// <summary> |
51 | 96e5791c | pkanavos | /// Represents the current icon data. |
52 | 96e5791c | pkanavos | /// </summary> |
53 | 96e5791c | pkanavos | private NotifyIconData iconData; |
54 | 96e5791c | pkanavos | |
55 | 96e5791c | pkanavos | /// <summary> |
56 | 96e5791c | pkanavos | /// Receives messages from the taskbar icon. |
57 | 96e5791c | pkanavos | /// </summary> |
58 | 96e5791c | pkanavos | private readonly WindowMessageSink messageSink; |
59 | 96e5791c | pkanavos | |
60 | 96e5791c | pkanavos | /// <summary> |
61 | 96e5791c | pkanavos | /// An action that is being invoked if the |
62 | 96e5791c | pkanavos | /// <see cref="singleClickTimer"/> fires. |
63 | 96e5791c | pkanavos | /// </summary> |
64 | 96e5791c | pkanavos | private Action delayedTimerAction; |
65 | 96e5791c | pkanavos | |
66 | 96e5791c | pkanavos | /// <summary> |
67 | 96e5791c | pkanavos | /// A timer that is used to differentiate between single |
68 | 96e5791c | pkanavos | /// and double clicks. |
69 | 96e5791c | pkanavos | /// </summary> |
70 | 96e5791c | pkanavos | private readonly Timer singleClickTimer; |
71 | 96e5791c | pkanavos | |
72 | 96e5791c | pkanavos | /// <summary> |
73 | 96e5791c | pkanavos | /// A timer that is used to close open balloon tooltips. |
74 | 96e5791c | pkanavos | /// </summary> |
75 | 96e5791c | pkanavos | private readonly Timer balloonCloseTimer; |
76 | 96e5791c | pkanavos | |
77 | 96e5791c | pkanavos | /// <summary> |
78 | 96e5791c | pkanavos | /// Indicates whether the taskbar icon has been created or not. |
79 | 96e5791c | pkanavos | /// </summary> |
80 | 96e5791c | pkanavos | public bool IsTaskbarIconCreated { get; private set; } |
81 | 96e5791c | pkanavos | |
82 | 96e5791c | pkanavos | /// <summary> |
83 | 96e5791c | pkanavos | /// Indicates whether custom tooltips are supported, which depends |
84 | 96e5791c | pkanavos | /// on the OS. Windows Vista or higher is required in order to |
85 | 96e5791c | pkanavos | /// support this feature. |
86 | 96e5791c | pkanavos | /// </summary> |
87 | 96e5791c | pkanavos | public bool SupportsCustomToolTips |
88 | 96e5791c | pkanavos | { |
89 | 96e5791c | pkanavos | get { return messageSink.Version == NotifyIconVersion.Vista; } |
90 | 96e5791c | pkanavos | } |
91 | 96e5791c | pkanavos | |
92 | 96e5791c | pkanavos | |
93 | 96e5791c | pkanavos | |
94 | 96e5791c | pkanavos | /// <summary> |
95 | 96e5791c | pkanavos | /// Checks whether a non-tooltip popup is currently opened. |
96 | 96e5791c | pkanavos | /// </summary> |
97 | 96e5791c | pkanavos | private bool IsPopupOpen |
98 | 96e5791c | pkanavos | { |
99 | 96e5791c | pkanavos | get |
100 | 96e5791c | pkanavos | { |
101 | 96e5791c | pkanavos | var popup = TrayPopupResolved; |
102 | 96e5791c | pkanavos | var menu = ContextMenu; |
103 | 96e5791c | pkanavos | var balloon = CustomBalloon; |
104 | 96e5791c | pkanavos | |
105 | 96e5791c | pkanavos | return popup != null && popup.IsOpen || |
106 | 96e5791c | pkanavos | menu != null && menu.IsOpen || |
107 | 96e5791c | pkanavos | balloon != null && balloon.IsOpen; |
108 | 96e5791c | pkanavos | |
109 | 96e5791c | pkanavos | } |
110 | 96e5791c | pkanavos | } |
111 | 96e5791c | pkanavos | |
112 | 96e5791c | pkanavos | #endregion |
113 | 96e5791c | pkanavos | |
114 | 96e5791c | pkanavos | |
115 | 96e5791c | pkanavos | #region Construction |
116 | 96e5791c | pkanavos | |
117 | 96e5791c | pkanavos | /// <summary> |
118 | 96e5791c | pkanavos | /// Inits the taskbar icon and registers a message listener |
119 | 96e5791c | pkanavos | /// in order to receive events from the taskbar area. |
120 | 96e5791c | pkanavos | /// </summary> |
121 | 96e5791c | pkanavos | public TaskbarIcon() |
122 | 96e5791c | pkanavos | { |
123 | 96e5791c | pkanavos | //using dummy sink in design mode |
124 | 96e5791c | pkanavos | messageSink = Util.IsDesignMode |
125 | 96e5791c | pkanavos | ? WindowMessageSink.CreateEmpty() |
126 | 96e5791c | pkanavos | : new WindowMessageSink(NotifyIconVersion.Win95); |
127 | 96e5791c | pkanavos | |
128 | 96e5791c | pkanavos | //init icon data structure |
129 | 96e5791c | pkanavos | iconData = NotifyIconData.CreateDefault(messageSink.MessageWindowHandle); |
130 | 96e5791c | pkanavos | |
131 | 96e5791c | pkanavos | //create the taskbar icon |
132 | 96e5791c | pkanavos | CreateTaskbarIcon(); |
133 | 96e5791c | pkanavos | |
134 | 96e5791c | pkanavos | //register event listeners |
135 | 96e5791c | pkanavos | messageSink.MouseEventReceived += OnMouseEvent; |
136 | 96e5791c | pkanavos | messageSink.TaskbarCreated += OnTaskbarCreated; |
137 | 96e5791c | pkanavos | messageSink.ChangeToolTipStateRequest += OnToolTipChange; |
138 | 96e5791c | pkanavos | messageSink.BalloonToolTipChanged += OnBalloonToolTipChanged; |
139 | 96e5791c | pkanavos | |
140 | 96e5791c | pkanavos | //init single click / balloon timers |
141 | 96e5791c | pkanavos | singleClickTimer = new Timer(DoSingleClickAction); |
142 | 96e5791c | pkanavos | balloonCloseTimer = new Timer(CloseBalloonCallback); |
143 | 96e5791c | pkanavos | |
144 | 96e5791c | pkanavos | //register listener in order to get notified when the application closes |
145 | 96e5791c | pkanavos | if (Application.Current != null) Application.Current.Exit += OnExit; |
146 | 96e5791c | pkanavos | } |
147 | 96e5791c | pkanavos | |
148 | 96e5791c | pkanavos | #endregion |
149 | 96e5791c | pkanavos | |
150 | 96e5791c | pkanavos | |
151 | 96e5791c | pkanavos | #region Custom Balloons |
152 | 96e5791c | pkanavos | |
153 | 96e5791c | pkanavos | /// <summary> |
154 | 96e5791c | pkanavos | /// Shows a custom control as a tooltip in the tray location. |
155 | 96e5791c | pkanavos | /// </summary> |
156 | 96e5791c | pkanavos | /// <param name="balloon"></param> |
157 | 96e5791c | pkanavos | /// <param name="animation">An optional animation for the popup.</param> |
158 | 96e5791c | pkanavos | /// <param name="timeout">The time after which the popup is being closed. |
159 | 96e5791c | pkanavos | /// Submit null in order to keep the balloon open inde |
160 | 96e5791c | pkanavos | /// </param> |
161 | 96e5791c | pkanavos | /// <exception cref="ArgumentNullException">If <paramref name="balloon"/> |
162 | 96e5791c | pkanavos | /// is a null reference.</exception> |
163 | 96e5791c | pkanavos | public void ShowCustomBalloon(UIElement balloon, PopupAnimation animation, int? timeout) |
164 | 96e5791c | pkanavos | { |
165 | 96e5791c | pkanavos | Dispatcher dispatcher = this.GetDispatcher(); |
166 | 96e5791c | pkanavos | if (!dispatcher.CheckAccess()) |
167 | 96e5791c | pkanavos | { |
168 | 96e5791c | pkanavos | var action = new Action(() => ShowCustomBalloon(balloon, animation, timeout)); |
169 | 96e5791c | pkanavos | dispatcher.Invoke(DispatcherPriority.Normal, action); |
170 | 96e5791c | pkanavos | return; |
171 | 96e5791c | pkanavos | } |
172 | 96e5791c | pkanavos | |
173 | 96e5791c | pkanavos | if (balloon == null) throw new ArgumentNullException("balloon"); |
174 | 96e5791c | pkanavos | if (timeout.HasValue && timeout < 500) |
175 | 96e5791c | pkanavos | { |
176 | 96e5791c | pkanavos | string msg = "Invalid timeout of {0} milliseconds. Timeout must be at least 500 ms"; |
177 | 96e5791c | pkanavos | msg = String.Format(msg, timeout); |
178 | 96e5791c | pkanavos | throw new ArgumentOutOfRangeException("timeout", msg); |
179 | 96e5791c | pkanavos | } |
180 | 96e5791c | pkanavos | |
181 | 96e5791c | pkanavos | EnsureNotDisposed(); |
182 | 96e5791c | pkanavos | |
183 | 96e5791c | pkanavos | //make sure we don't have an open balloon |
184 | 96e5791c | pkanavos | lock (this) |
185 | 96e5791c | pkanavos | { |
186 | 96e5791c | pkanavos | CloseBalloon(); |
187 | 96e5791c | pkanavos | } |
188 | 96e5791c | pkanavos | |
189 | 96e5791c | pkanavos | //create an invisible popup that hosts the UIElement |
190 | 96e5791c | pkanavos | Popup popup = new Popup(); |
191 | 96e5791c | pkanavos | popup.AllowsTransparency = true; |
192 | 96e5791c | pkanavos | |
193 | 96e5791c | pkanavos | //provide the popup with the taskbar icon's data context |
194 | 96e5791c | pkanavos | UpdateDataContext(popup, null, DataContext); |
195 | 96e5791c | pkanavos | |
196 | 96e5791c | pkanavos | //don't animate by default - devs can use attached |
197 | 96e5791c | pkanavos | //events or override |
198 | 96e5791c | pkanavos | popup.PopupAnimation = animation; |
199 | 96e5791c | pkanavos | |
200 | 96e5791c | pkanavos | popup.Child = balloon; |
201 | 96e5791c | pkanavos | |
202 | 96e5791c | pkanavos | //don't set the PlacementTarget as it causes the popup to become hidden if the |
203 | 96e5791c | pkanavos | //TaskbarIcon's parent is hidden, too... |
204 | 96e5791c | pkanavos | //popup.PlacementTarget = this; |
205 | 96e5791c | pkanavos | |
206 | 96e5791c | pkanavos | popup.Placement = PlacementMode.AbsolutePoint; |
207 | 96e5791c | pkanavos | popup.StaysOpen = true; |
208 | 96e5791c | pkanavos | |
209 | 96e5791c | pkanavos | Point position = TrayInfo.GetTrayLocation(); |
210 | 96e5791c | pkanavos | popup.HorizontalOffset = position.X -1; |
211 | 96e5791c | pkanavos | popup.VerticalOffset = position.Y -1; |
212 | 96e5791c | pkanavos | |
213 | 96e5791c | pkanavos | //store reference |
214 | 96e5791c | pkanavos | lock (this) |
215 | 96e5791c | pkanavos | { |
216 | 96e5791c | pkanavos | SetCustomBalloon(popup); |
217 | 96e5791c | pkanavos | } |
218 | 96e5791c | pkanavos | |
219 | 96e5791c | pkanavos | //assign this instance as an attached property |
220 | 96e5791c | pkanavos | SetParentTaskbarIcon(balloon, this); |
221 | 96e5791c | pkanavos | |
222 | 96e5791c | pkanavos | //fire attached event |
223 | 96e5791c | pkanavos | RaiseBalloonShowingEvent(balloon, this); |
224 | 96e5791c | pkanavos | |
225 | 96e5791c | pkanavos | //display item |
226 | 96e5791c | pkanavos | popup.IsOpen = true; |
227 | 96e5791c | pkanavos | |
228 | 96e5791c | pkanavos | if (timeout.HasValue) |
229 | 96e5791c | pkanavos | { |
230 | 96e5791c | pkanavos | //register timer to close the popup |
231 | 96e5791c | pkanavos | balloonCloseTimer.Change(timeout.Value, Timeout.Infinite); |
232 | 96e5791c | pkanavos | } |
233 | 96e5791c | pkanavos | } |
234 | 96e5791c | pkanavos | |
235 | 96e5791c | pkanavos | |
236 | 96e5791c | pkanavos | /// <summary> |
237 | 96e5791c | pkanavos | /// Resets the closing timeout, which effectively |
238 | 96e5791c | pkanavos | /// keeps a displayed balloon message open until |
239 | 96e5791c | pkanavos | /// it is either closed programmatically through |
240 | 96e5791c | pkanavos | /// <see cref="CloseBalloon"/> or due to a new |
241 | 96e5791c | pkanavos | /// message being displayed. |
242 | 96e5791c | pkanavos | /// </summary> |
243 | 96e5791c | pkanavos | public void ResetBalloonCloseTimer() |
244 | 96e5791c | pkanavos | { |
245 | 96e5791c | pkanavos | if (IsDisposed) return; |
246 | 96e5791c | pkanavos | |
247 | 96e5791c | pkanavos | lock (this) |
248 | 96e5791c | pkanavos | { |
249 | 96e5791c | pkanavos | //reset timer in any case |
250 | 96e5791c | pkanavos | balloonCloseTimer.Change(Timeout.Infinite, Timeout.Infinite); |
251 | 96e5791c | pkanavos | } |
252 | 96e5791c | pkanavos | } |
253 | 96e5791c | pkanavos | |
254 | 96e5791c | pkanavos | |
255 | 96e5791c | pkanavos | /// <summary> |
256 | 96e5791c | pkanavos | /// Closes the current <see cref="CustomBalloon"/>, if the |
257 | 96e5791c | pkanavos | /// property is set. |
258 | 96e5791c | pkanavos | /// </summary> |
259 | 96e5791c | pkanavos | public void CloseBalloon() |
260 | 96e5791c | pkanavos | { |
261 | 96e5791c | pkanavos | if (IsDisposed) return; |
262 | 96e5791c | pkanavos | |
263 | 96e5791c | pkanavos | Dispatcher dispatcher = this.GetDispatcher(); |
264 | 96e5791c | pkanavos | if (!dispatcher.CheckAccess()) |
265 | 96e5791c | pkanavos | { |
266 | 96e5791c | pkanavos | Action action = CloseBalloon; |
267 | 96e5791c | pkanavos | dispatcher.Invoke(DispatcherPriority.Normal, action); |
268 | 96e5791c | pkanavos | return; |
269 | 96e5791c | pkanavos | } |
270 | 96e5791c | pkanavos | |
271 | 96e5791c | pkanavos | lock (this) |
272 | 96e5791c | pkanavos | { |
273 | 96e5791c | pkanavos | //reset timer in any case |
274 | 96e5791c | pkanavos | balloonCloseTimer.Change(Timeout.Infinite, Timeout.Infinite); |
275 | 96e5791c | pkanavos | |
276 | 96e5791c | pkanavos | //reset old popup, if we still have one |
277 | 96e5791c | pkanavos | Popup popup = CustomBalloon; |
278 | 96e5791c | pkanavos | if (popup != null) |
279 | 96e5791c | pkanavos | { |
280 | 96e5791c | pkanavos | UIElement element = popup.Child; |
281 | 96e5791c | pkanavos | |
282 | 96e5791c | pkanavos | //announce closing |
283 | 96e5791c | pkanavos | RoutedEventArgs eventArgs = RaiseBalloonClosingEvent(element, this); |
284 | 96e5791c | pkanavos | if (!eventArgs.Handled) |
285 | 96e5791c | pkanavos | { |
286 | 96e5791c | pkanavos | //if the event was handled, clear the reference to the popup, |
287 | 96e5791c | pkanavos | //but don't close it - the handling code has to manage this stuff now |
288 | 96e5791c | pkanavos | |
289 | 96e5791c | pkanavos | //close the popup |
290 | 96e5791c | pkanavos | popup.IsOpen = false; |
291 | 96e5791c | pkanavos | |
292 | 96e5791c | pkanavos | //reset attached property |
293 | 96e5791c | pkanavos | if (element != null) SetParentTaskbarIcon(element, null); |
294 | 96e5791c | pkanavos | } |
295 | 96e5791c | pkanavos | |
296 | 96e5791c | pkanavos | //remove custom balloon anyway |
297 | 96e5791c | pkanavos | SetCustomBalloon(null); |
298 | 96e5791c | pkanavos | } |
299 | 96e5791c | pkanavos | } |
300 | 96e5791c | pkanavos | } |
301 | 96e5791c | pkanavos | |
302 | 96e5791c | pkanavos | |
303 | 96e5791c | pkanavos | /// <summary> |
304 | 96e5791c | pkanavos | /// Timer-invoke event which closes the currently open balloon and |
305 | 96e5791c | pkanavos | /// resets the <see cref="CustomBalloon"/> dependency property. |
306 | 96e5791c | pkanavos | /// </summary> |
307 | 96e5791c | pkanavos | private void CloseBalloonCallback(object state) |
308 | 96e5791c | pkanavos | { |
309 | 96e5791c | pkanavos | if (IsDisposed) return; |
310 | 96e5791c | pkanavos | |
311 | 96e5791c | pkanavos | //switch to UI thread |
312 | 96e5791c | pkanavos | Action action = CloseBalloon; |
313 | 96e5791c | pkanavos | this.GetDispatcher().Invoke(action); |
314 | 96e5791c | pkanavos | } |
315 | 96e5791c | pkanavos | |
316 | 96e5791c | pkanavos | #endregion |
317 | 96e5791c | pkanavos | |
318 | 96e5791c | pkanavos | #region Process Incoming Mouse Events |
319 | 96e5791c | pkanavos | |
320 | 96e5791c | pkanavos | /// <summary> |
321 | 96e5791c | pkanavos | /// Processes mouse events, which are bubbled |
322 | 96e5791c | pkanavos | /// through the class' routed events, trigger |
323 | 96e5791c | pkanavos | /// certain actions (e.g. show a popup), or |
324 | 96e5791c | pkanavos | /// both. |
325 | 96e5791c | pkanavos | /// </summary> |
326 | 96e5791c | pkanavos | /// <param name="me">Event flag.</param> |
327 | 96e5791c | pkanavos | private void OnMouseEvent(MouseEvent me) |
328 | 96e5791c | pkanavos | { |
329 | 96e5791c | pkanavos | if (IsDisposed) return; |
330 | 96e5791c | pkanavos | |
331 | 96e5791c | pkanavos | switch (me) |
332 | 96e5791c | pkanavos | { |
333 | 96e5791c | pkanavos | case MouseEvent.MouseMove: |
334 | 96e5791c | pkanavos | RaiseTrayMouseMoveEvent(); |
335 | 96e5791c | pkanavos | //immediately return - there's nothing left to evaluate |
336 | 96e5791c | pkanavos | return; |
337 | 96e5791c | pkanavos | case MouseEvent.IconRightMouseDown: |
338 | 96e5791c | pkanavos | RaiseTrayRightMouseDownEvent(); |
339 | 96e5791c | pkanavos | break; |
340 | 96e5791c | pkanavos | case MouseEvent.IconLeftMouseDown: |
341 | 96e5791c | pkanavos | RaiseTrayLeftMouseDownEvent(); |
342 | 96e5791c | pkanavos | break; |
343 | 96e5791c | pkanavos | case MouseEvent.IconRightMouseUp: |
344 | 96e5791c | pkanavos | RaiseTrayRightMouseUpEvent(); |
345 | 96e5791c | pkanavos | break; |
346 | 96e5791c | pkanavos | case MouseEvent.IconLeftMouseUp: |
347 | 96e5791c | pkanavos | RaiseTrayLeftMouseUpEvent(); |
348 | 96e5791c | pkanavos | break; |
349 | 96e5791c | pkanavos | case MouseEvent.IconMiddleMouseDown: |
350 | 96e5791c | pkanavos | RaiseTrayMiddleMouseDownEvent(); |
351 | 96e5791c | pkanavos | break; |
352 | 96e5791c | pkanavos | case MouseEvent.IconMiddleMouseUp: |
353 | 96e5791c | pkanavos | RaiseTrayMiddleMouseUpEvent(); |
354 | 96e5791c | pkanavos | break; |
355 | 96e5791c | pkanavos | case MouseEvent.IconDoubleClick: |
356 | 96e5791c | pkanavos | //cancel single click timer |
357 | 96e5791c | pkanavos | singleClickTimer.Change(Timeout.Infinite, Timeout.Infinite); |
358 | 96e5791c | pkanavos | //bubble event |
359 | 96e5791c | pkanavos | RaiseTrayMouseDoubleClickEvent(); |
360 | 96e5791c | pkanavos | break; |
361 | 96e5791c | pkanavos | case MouseEvent.BalloonToolTipClicked: |
362 | 96e5791c | pkanavos | RaiseTrayBalloonTipClickedEvent(); |
363 | 96e5791c | pkanavos | break; |
364 | 96e5791c | pkanavos | default: |
365 | 96e5791c | pkanavos | throw new ArgumentOutOfRangeException("me", "Missing handler for mouse event flag: " + me); |
366 | 96e5791c | pkanavos | } |
367 | 96e5791c | pkanavos | |
368 | 96e5791c | pkanavos | |
369 | 96e5791c | pkanavos | //get mouse coordinates |
370 | 96e5791c | pkanavos | Point cursorPosition = new Point(); |
371 | 96e5791c | pkanavos | WinApi.GetCursorPos(ref cursorPosition); |
372 | 96e5791c | pkanavos | |
373 | 96e5791c | pkanavos | bool isLeftClickCommandInvoked = false; |
374 | 96e5791c | pkanavos | |
375 | 96e5791c | pkanavos | //show popup, if requested |
376 | 96e5791c | pkanavos | if (me.IsMatch(PopupActivation)) |
377 | 96e5791c | pkanavos | { |
378 | 96e5791c | pkanavos | if (me == MouseEvent.IconLeftMouseUp) |
379 | 96e5791c | pkanavos | { |
380 | 96e5791c | pkanavos | //show popup once we are sure it's not a double click |
381 | 96e5791c | pkanavos | delayedTimerAction = () => |
382 | 96e5791c | pkanavos | { |
383 | 96e5791c | pkanavos | LeftClickCommand.ExecuteIfEnabled(LeftClickCommandParameter, LeftClickCommandTarget ?? this); |
384 | 96e5791c | pkanavos | ShowTrayPopup(cursorPosition); |
385 | 96e5791c | pkanavos | }; |
386 | 96e5791c | pkanavos | singleClickTimer.Change(WinApi.GetDoubleClickTime(), Timeout.Infinite); |
387 | 96e5791c | pkanavos | isLeftClickCommandInvoked = true; |
388 | 96e5791c | pkanavos | } |
389 | 96e5791c | pkanavos | else |
390 | 96e5791c | pkanavos | { |
391 | 96e5791c | pkanavos | //show popup immediately |
392 | 96e5791c | pkanavos | ShowTrayPopup(cursorPosition); |
393 | 96e5791c | pkanavos | } |
394 | 96e5791c | pkanavos | } |
395 | 96e5791c | pkanavos | |
396 | 96e5791c | pkanavos | |
397 | 96e5791c | pkanavos | //show context menu, if requested |
398 | 96e5791c | pkanavos | if (me.IsMatch(MenuActivation)) |
399 | 96e5791c | pkanavos | { |
400 | 96e5791c | pkanavos | if (me == MouseEvent.IconLeftMouseUp) |
401 | 96e5791c | pkanavos | { |
402 | 96e5791c | pkanavos | //show context menu once we are sure it's not a double click |
403 | 96e5791c | pkanavos | delayedTimerAction = () => |
404 | 96e5791c | pkanavos | { |
405 | 96e5791c | pkanavos | LeftClickCommand.ExecuteIfEnabled(LeftClickCommandParameter, LeftClickCommandTarget ?? this); |
406 | 96e5791c | pkanavos | ShowContextMenu(cursorPosition); |
407 | 96e5791c | pkanavos | }; |
408 | 96e5791c | pkanavos | singleClickTimer.Change(WinApi.GetDoubleClickTime(), Timeout.Infinite); |
409 | 96e5791c | pkanavos | isLeftClickCommandInvoked = true; |
410 | 96e5791c | pkanavos | } |
411 | 96e5791c | pkanavos | else |
412 | 96e5791c | pkanavos | { |
413 | 96e5791c | pkanavos | //show context menu immediately |
414 | 96e5791c | pkanavos | ShowContextMenu(cursorPosition); |
415 | 96e5791c | pkanavos | } |
416 | 96e5791c | pkanavos | } |
417 | 96e5791c | pkanavos | |
418 | 96e5791c | pkanavos | //make sure the left click command is invoked on mouse clicks |
419 | 96e5791c | pkanavos | if (me == MouseEvent.IconLeftMouseUp && !isLeftClickCommandInvoked) |
420 | 96e5791c | pkanavos | { |
421 | 96e5791c | pkanavos | //show context menu once we are sure it's not a double click |
422 | 96e5791c | pkanavos | delayedTimerAction = () => LeftClickCommand.ExecuteIfEnabled(LeftClickCommandParameter, LeftClickCommandTarget ?? this); |
423 | 96e5791c | pkanavos | singleClickTimer.Change(WinApi.GetDoubleClickTime(), Timeout.Infinite); |
424 | 96e5791c | pkanavos | } |
425 | 96e5791c | pkanavos | |
426 | 96e5791c | pkanavos | } |
427 | 96e5791c | pkanavos | |
428 | 96e5791c | pkanavos | #endregion |
429 | 96e5791c | pkanavos | |
430 | 96e5791c | pkanavos | #region ToolTips |
431 | 96e5791c | pkanavos | |
432 | 96e5791c | pkanavos | /// <summary> |
433 | 96e5791c | pkanavos | /// Displays a custom tooltip, if available. This method is only |
434 | 96e5791c | pkanavos | /// invoked for Windows Vista and above. |
435 | 96e5791c | pkanavos | /// </summary> |
436 | 96e5791c | pkanavos | /// <param name="visible">Whether to show or hide the tooltip.</param> |
437 | 96e5791c | pkanavos | private void OnToolTipChange(bool visible) |
438 | 96e5791c | pkanavos | { |
439 | 96e5791c | pkanavos | //if we don't have a tooltip, there's nothing to do here... |
440 | 96e5791c | pkanavos | if (TrayToolTipResolved == null) return; |
441 | 96e5791c | pkanavos | |
442 | 96e5791c | pkanavos | if (visible) |
443 | 96e5791c | pkanavos | { |
444 | 96e5791c | pkanavos | if (IsPopupOpen) |
445 | 96e5791c | pkanavos | { |
446 | 96e5791c | pkanavos | //ignore if we are already displaying something down there |
447 | 96e5791c | pkanavos | return; |
448 | 96e5791c | pkanavos | } |
449 | 96e5791c | pkanavos | |
450 | 96e5791c | pkanavos | var args = RaisePreviewTrayToolTipOpenEvent(); |
451 | 96e5791c | pkanavos | if (args.Handled) return; |
452 | 96e5791c | pkanavos | |
453 | 96e5791c | pkanavos | TrayToolTipResolved.IsOpen = true; |
454 | 96e5791c | pkanavos | |
455 | 96e5791c | pkanavos | //raise attached event first |
456 | 96e5791c | pkanavos | if (TrayToolTip != null) RaiseToolTipOpenedEvent(TrayToolTip); |
457 | 96e5791c | pkanavos | |
458 | 96e5791c | pkanavos | //bubble routed event |
459 | 96e5791c | pkanavos | RaiseTrayToolTipOpenEvent(); |
460 | 96e5791c | pkanavos | } |
461 | 96e5791c | pkanavos | else |
462 | 96e5791c | pkanavos | { |
463 | 96e5791c | pkanavos | var args = RaisePreviewTrayToolTipCloseEvent(); |
464 | 96e5791c | pkanavos | if (args.Handled) return; |
465 | 96e5791c | pkanavos | |
466 | 96e5791c | pkanavos | //raise attached event first |
467 | 96e5791c | pkanavos | if (TrayToolTip != null) RaiseToolTipCloseEvent(TrayToolTip); |
468 | 96e5791c | pkanavos | |
469 | 96e5791c | pkanavos | TrayToolTipResolved.IsOpen = false; |
470 | 96e5791c | pkanavos | |
471 | 96e5791c | pkanavos | //bubble event |
472 | 96e5791c | pkanavos | RaiseTrayToolTipCloseEvent(); |
473 | 96e5791c | pkanavos | } |
474 | 96e5791c | pkanavos | } |
475 | 96e5791c | pkanavos | |
476 | 96e5791c | pkanavos | |
477 | 96e5791c | pkanavos | /// <summary> |
478 | 96e5791c | pkanavos | /// Creates a <see cref="ToolTip"/> control that either |
479 | 96e5791c | pkanavos | /// wraps the currently set <see cref="TrayToolTip"/> |
480 | 96e5791c | pkanavos | /// control or the <see cref="ToolTipText"/> string.<br/> |
481 | 96e5791c | pkanavos | /// If <see cref="TrayToolTip"/> itself is already |
482 | 96e5791c | pkanavos | /// a <see cref="ToolTip"/> instance, it will be used directly. |
483 | 96e5791c | pkanavos | /// </summary> |
484 | 96e5791c | pkanavos | /// <remarks>We use a <see cref="ToolTip"/> rather than |
485 | 96e5791c | pkanavos | /// <see cref="Popup"/> because there was no way to prevent a |
486 | 96e5791c | pkanavos | /// popup from causing cyclic open/close commands if it was |
487 | 96e5791c | pkanavos | /// placed under the mouse. ToolTip internally uses a Popup of |
488 | 96e5791c | pkanavos | /// its own, but takes advance of Popup's internal <see cref="Popup.HitTestable"/> |
489 | 96e5791c | pkanavos | /// property which prevents this issue.</remarks> |
490 | 96e5791c | pkanavos | private void CreateCustomToolTip() |
491 | 96e5791c | pkanavos | { |
492 | 96e5791c | pkanavos | //check if the item itself is a tooltip |
493 | 96e5791c | pkanavos | ToolTip tt = TrayToolTip as ToolTip; |
494 | 96e5791c | pkanavos | |
495 | 96e5791c | pkanavos | if (tt == null && TrayToolTip != null) |
496 | 96e5791c | pkanavos | { |
497 | 96e5791c | pkanavos | //create an invisible tooltip that hosts the UIElement |
498 | 96e5791c | pkanavos | tt = new ToolTip(); |
499 | 96e5791c | pkanavos | tt.Placement = PlacementMode.Mouse; |
500 | 96e5791c | pkanavos | |
501 | 96e5791c | pkanavos | //do *not* set the placement target, as it causes the popup to become hidden if the |
502 | 96e5791c | pkanavos | //TaskbarIcon's parent is hidden, too. At runtime, the parent can be resolved through |
503 | 96e5791c | pkanavos | //the ParentTaskbarIcon attached dependency property: |
504 | 96e5791c | pkanavos | //tt.PlacementTarget = this; |
505 | 96e5791c | pkanavos | |
506 | 96e5791c | pkanavos | //make sure the tooltip is invisible |
507 | 96e5791c | pkanavos | tt.HasDropShadow = false; |
508 | 96e5791c | pkanavos | tt.BorderThickness = new Thickness(0); |
509 | 96e5791c | pkanavos | tt.Background = System.Windows.Media.Brushes.Transparent; |
510 | 96e5791c | pkanavos | |
511 | 96e5791c | pkanavos | //setting the |
512 | 96e5791c | pkanavos | tt.StaysOpen = true; |
513 | 96e5791c | pkanavos | tt.Content = TrayToolTip; |
514 | 96e5791c | pkanavos | } |
515 | 96e5791c | pkanavos | else if (tt == null && !String.IsNullOrEmpty(ToolTipText)) |
516 | 96e5791c | pkanavos | { |
517 | 96e5791c | pkanavos | //create a simple tooltip for the string |
518 | 96e5791c | pkanavos | tt = new ToolTip(); |
519 | 96e5791c | pkanavos | tt.Content = ToolTipText; |
520 | 96e5791c | pkanavos | } |
521 | 96e5791c | pkanavos | |
522 | 96e5791c | pkanavos | //the tooltip explicitly gets the DataContext of this instance. |
523 | 96e5791c | pkanavos | //If there is no DataContext, the TaskbarIcon assigns itself |
524 | 96e5791c | pkanavos | if (tt != null) |
525 | 96e5791c | pkanavos | { |
526 | 96e5791c | pkanavos | UpdateDataContext(tt, null, DataContext); |
527 | 96e5791c | pkanavos | } |
528 | 96e5791c | pkanavos | |
529 | 96e5791c | pkanavos | //store a reference to the used tooltip |
530 | 96e5791c | pkanavos | SetTrayToolTipResolved(tt); |
531 | 96e5791c | pkanavos | } |
532 | 96e5791c | pkanavos | |
533 | 96e5791c | pkanavos | |
534 | 96e5791c | pkanavos | /// <summary> |
535 | 96e5791c | pkanavos | /// Sets tooltip settings for the class depending on defined |
536 | 96e5791c | pkanavos | /// dependency properties and OS support. |
537 | 96e5791c | pkanavos | /// </summary> |
538 | 96e5791c | pkanavos | private void WriteToolTipSettings() |
539 | 96e5791c | pkanavos | { |
540 | 96e5791c | pkanavos | const IconDataMembers flags = IconDataMembers.Tip; |
541 | 96e5791c | pkanavos | iconData.ToolTipText = ToolTipText; |
542 | 96e5791c | pkanavos | |
543 | 96e5791c | pkanavos | if (messageSink.Version == NotifyIconVersion.Vista) |
544 | 96e5791c | pkanavos | { |
545 | 96e5791c | pkanavos | //we need to set a tooltip text to get tooltip events from the |
546 | 96e5791c | pkanavos | //taskbar icon |
547 | 96e5791c | pkanavos | if (String.IsNullOrEmpty(iconData.ToolTipText) && TrayToolTipResolved != null) |
548 | 96e5791c | pkanavos | { |
549 | 96e5791c | pkanavos | //if we have not tooltip text but a custom tooltip, we |
550 | 96e5791c | pkanavos | //need to set a dummy value (we're displaying the ToolTip control, not the string) |
551 | 96e5791c | pkanavos | iconData.ToolTipText = "ToolTip"; |
552 | 96e5791c | pkanavos | } |
553 | 96e5791c | pkanavos | } |
554 | 96e5791c | pkanavos | |
555 | 96e5791c | pkanavos | //update the tooltip text |
556 | 96e5791c | pkanavos | Util.WriteIconData(ref iconData, NotifyCommand.Modify, flags); |
557 | 96e5791c | pkanavos | } |
558 | 96e5791c | pkanavos | |
559 | 96e5791c | pkanavos | #endregion |
560 | 96e5791c | pkanavos | |
561 | 96e5791c | pkanavos | #region Custom Popup |
562 | 96e5791c | pkanavos | |
563 | 96e5791c | pkanavos | /// <summary> |
564 | 96e5791c | pkanavos | /// Creates a <see cref="ToolTip"/> control that either |
565 | 96e5791c | pkanavos | /// wraps the currently set <see cref="TrayToolTip"/> |
566 | 96e5791c | pkanavos | /// control or the <see cref="ToolTipText"/> string.<br/> |
567 | 96e5791c | pkanavos | /// If <see cref="TrayToolTip"/> itself is already |
568 | 96e5791c | pkanavos | /// a <see cref="ToolTip"/> instance, it will be used directly. |
569 | 96e5791c | pkanavos | /// </summary> |
570 | 96e5791c | pkanavos | /// <remarks>We use a <see cref="ToolTip"/> rather than |
571 | 96e5791c | pkanavos | /// <see cref="Popup"/> because there was no way to prevent a |
572 | 96e5791c | pkanavos | /// popup from causing cyclic open/close commands if it was |
573 | 96e5791c | pkanavos | /// placed under the mouse. ToolTip internally uses a Popup of |
574 | 96e5791c | pkanavos | /// its own, but takes advance of Popup's internal <see cref="Popup.HitTestable"/> |
575 | 96e5791c | pkanavos | /// property which prevents this issue.</remarks> |
576 | 96e5791c | pkanavos | private void CreatePopup() |
577 | 96e5791c | pkanavos | { |
578 | 96e5791c | pkanavos | //check if the item itself is a popup |
579 | 96e5791c | pkanavos | Popup popup = TrayPopup as Popup; |
580 | 96e5791c | pkanavos | |
581 | 96e5791c | pkanavos | if (popup == null && TrayPopup != null) |
582 | 96e5791c | pkanavos | { |
583 | 96e5791c | pkanavos | //create an invisible popup that hosts the UIElement |
584 | 96e5791c | pkanavos | popup = new Popup(); |
585 | 96e5791c | pkanavos | popup.AllowsTransparency = true; |
586 | 96e5791c | pkanavos | |
587 | 96e5791c | pkanavos | //don't animate by default - devs can use attached |
588 | 96e5791c | pkanavos | //events or override |
589 | 96e5791c | pkanavos | popup.PopupAnimation = PopupAnimation.None; |
590 | 96e5791c | pkanavos | |
591 | 96e5791c | pkanavos | //the CreateRootPopup method outputs binding errors in the debug window because |
592 | 96e5791c | pkanavos | //it tries to bind to "Popup-specific" properties in case they are provided by the child. |
593 | 96e5791c | pkanavos | //We don't need that so just assign the control as the child. |
594 | 96e5791c | pkanavos | popup.Child = TrayPopup; |
595 | 96e5791c | pkanavos | |
596 | 96e5791c | pkanavos | //do *not* set the placement target, as it causes the popup to become hidden if the |
597 | 96e5791c | pkanavos | //TaskbarIcon's parent is hidden, too. At runtime, the parent can be resolved through |
598 | 96e5791c | pkanavos | //the ParentTaskbarIcon attached dependency property: |
599 | 96e5791c | pkanavos | //popup.PlacementTarget = this; |
600 | 96e5791c | pkanavos | |
601 | 96e5791c | pkanavos | popup.Placement = PlacementMode.AbsolutePoint; |
602 | 96e5791c | pkanavos | popup.StaysOpen = false; |
603 | 96e5791c | pkanavos | } |
604 | 96e5791c | pkanavos | |
605 | 96e5791c | pkanavos | //the popup explicitly gets the DataContext of this instance. |
606 | 96e5791c | pkanavos | //If there is no DataContext, the TaskbarIcon assigns itself |
607 | 96e5791c | pkanavos | if (popup != null) |
608 | 96e5791c | pkanavos | { |
609 | 96e5791c | pkanavos | UpdateDataContext(popup, null, DataContext); |
610 | 96e5791c | pkanavos | } |
611 | 96e5791c | pkanavos | |
612 | 96e5791c | pkanavos | //store a reference to the used tooltip |
613 | 96e5791c | pkanavos | SetTrayPopupResolved(popup); |
614 | 96e5791c | pkanavos | } |
615 | 96e5791c | pkanavos | |
616 | 96e5791c | pkanavos | |
617 | 96e5791c | pkanavos | /// <summary> |
618 | 96e5791c | pkanavos | /// Displays the <see cref="TrayPopup"/> control if |
619 | 96e5791c | pkanavos | /// it was set. |
620 | 96e5791c | pkanavos | /// </summary> |
621 | 96e5791c | pkanavos | private void ShowTrayPopup(Point cursorPosition) |
622 | 96e5791c | pkanavos | { |
623 | 96e5791c | pkanavos | if (IsDisposed) return; |
624 | 96e5791c | pkanavos | |
625 | 96e5791c | pkanavos | //raise preview event no matter whether popup is currently set |
626 | 96e5791c | pkanavos | //or not (enables client to set it on demand) |
627 | 96e5791c | pkanavos | var args = RaisePreviewTrayPopupOpenEvent(); |
628 | 96e5791c | pkanavos | if (args.Handled) return; |
629 | 96e5791c | pkanavos | |
630 | 96e5791c | pkanavos | if (TrayPopup != null) |
631 | 96e5791c | pkanavos | { |
632 | 96e5791c | pkanavos | //use absolute position, but place the popup centered above the icon |
633 | 96e5791c | pkanavos | TrayPopupResolved.Placement = PlacementMode.AbsolutePoint; |
634 | 96e5791c | pkanavos | TrayPopupResolved.HorizontalOffset = cursorPosition.X; |
635 | 96e5791c | pkanavos | TrayPopupResolved.VerticalOffset = cursorPosition.Y; |
636 | 96e5791c | pkanavos | |
637 | 96e5791c | pkanavos | //open popup |
638 | 96e5791c | pkanavos | TrayPopupResolved.IsOpen = true; |
639 | 96e5791c | pkanavos | |
640 | 96e5791c | pkanavos | |
641 | 96e5791c | pkanavos | IntPtr handle = IntPtr.Zero; |
642 | 96e5791c | pkanavos | if (TrayPopupResolved.Child != null) |
643 | 96e5791c | pkanavos | { |
644 | 96e5791c | pkanavos | //try to get a handle on the popup itself (via its child) |
645 | 96e5791c | pkanavos | HwndSource source = (HwndSource)PresentationSource.FromVisual(TrayPopupResolved.Child); |
646 | 96e5791c | pkanavos | if (source != null) handle = source.Handle; |
647 | 96e5791c | pkanavos | } |
648 | 96e5791c | pkanavos | |
649 | 96e5791c | pkanavos | //if we don't have a handle for the popup, fall back to the message sink |
650 | 96e5791c | pkanavos | if (handle == IntPtr.Zero) handle = messageSink.MessageWindowHandle; |
651 | 96e5791c | pkanavos | |
652 | 96e5791c | pkanavos | //activate either popup or message sink to track deactivation. |
653 | 96e5791c | pkanavos | //otherwise, the popup does not close if the user clicks somewhere else |
654 | 96e5791c | pkanavos | WinApi.SetForegroundWindow(handle); |
655 | 96e5791c | pkanavos | |
656 | 96e5791c | pkanavos | //raise attached event - item should never be null unless developers |
657 | 96e5791c | pkanavos | //changed the CustomPopup directly... |
658 | 96e5791c | pkanavos | if (TrayPopup != null) RaisePopupOpenedEvent(TrayPopup); |
659 | 96e5791c | pkanavos | |
660 | 96e5791c | pkanavos | //bubble routed event |
661 | 96e5791c | pkanavos | RaiseTrayPopupOpenEvent(); |
662 | 96e5791c | pkanavos | } |
663 | 96e5791c | pkanavos | } |
664 | 96e5791c | pkanavos | |
665 | 96e5791c | pkanavos | #endregion |
666 | 96e5791c | pkanavos | |
667 | 96e5791c | pkanavos | #region Context Menu |
668 | 96e5791c | pkanavos | |
669 | 96e5791c | pkanavos | /// <summary> |
670 | 96e5791c | pkanavos | /// Displays the <see cref="ContextMenu"/> if |
671 | 96e5791c | pkanavos | /// it was set. |
672 | 96e5791c | pkanavos | /// </summary> |
673 | 96e5791c | pkanavos | private void ShowContextMenu(Point cursorPosition) |
674 | 96e5791c | pkanavos | { |
675 | 96e5791c | pkanavos | if (IsDisposed) return; |
676 | 96e5791c | pkanavos | |
677 | 96e5791c | pkanavos | //raise preview event no matter whether context menu is currently set |
678 | 96e5791c | pkanavos | //or not (enables client to set it on demand) |
679 | 96e5791c | pkanavos | var args = RaisePreviewTrayContextMenuOpenEvent(); |
680 | 96e5791c | pkanavos | if (args.Handled) return; |
681 | 96e5791c | pkanavos | |
682 | 96e5791c | pkanavos | if (ContextMenu != null) |
683 | 96e5791c | pkanavos | { |
684 | 96e5791c | pkanavos | //use absolute position |
685 | 96e5791c | pkanavos | ContextMenu.Placement = PlacementMode.AbsolutePoint; |
686 | 96e5791c | pkanavos | ContextMenu.HorizontalOffset = cursorPosition.X; |
687 | 96e5791c | pkanavos | ContextMenu.VerticalOffset = cursorPosition.Y; |
688 | 96e5791c | pkanavos | ContextMenu.IsOpen = true; |
689 | 96e5791c | pkanavos | |
690 | 96e5791c | pkanavos | //activate the message window to track deactivation - otherwise, the context menu |
691 | 96e5791c | pkanavos | //does not close if the user clicks somewhere else |
692 | 96e5791c | pkanavos | WinApi.SetForegroundWindow(messageSink.MessageWindowHandle); |
693 | 96e5791c | pkanavos | |
694 | 96e5791c | pkanavos | //bubble event |
695 | 96e5791c | pkanavos | RaiseTrayContextMenuOpenEvent(); |
696 | 96e5791c | pkanavos | } |
697 | 96e5791c | pkanavos | } |
698 | 96e5791c | pkanavos | |
699 | 96e5791c | pkanavos | #endregion |
700 | 96e5791c | pkanavos | |
701 | 96e5791c | pkanavos | #region Balloon Tips |
702 | 96e5791c | pkanavos | |
703 | 96e5791c | pkanavos | /// <summary> |
704 | 96e5791c | pkanavos | /// Bubbles events if a balloon ToolTip was displayed |
705 | 96e5791c | pkanavos | /// or removed. |
706 | 96e5791c | pkanavos | /// </summary> |
707 | 96e5791c | pkanavos | /// <param name="visible">Whether the ToolTip was just displayed |
708 | 96e5791c | pkanavos | /// or removed.</param> |
709 | 96e5791c | pkanavos | private void OnBalloonToolTipChanged(bool visible) |
710 | 96e5791c | pkanavos | { |
711 | 96e5791c | pkanavos | if (visible) |
712 | 96e5791c | pkanavos | { |
713 | 96e5791c | pkanavos | RaiseTrayBalloonTipShownEvent(); |
714 | 96e5791c | pkanavos | } |
715 | 96e5791c | pkanavos | else |
716 | 96e5791c | pkanavos | { |
717 | 96e5791c | pkanavos | RaiseTrayBalloonTipClosedEvent(); |
718 | 96e5791c | pkanavos | } |
719 | 96e5791c | pkanavos | } |
720 | 96e5791c | pkanavos | |
721 | 96e5791c | pkanavos | /// <summary> |
722 | 96e5791c | pkanavos | /// Displays a balloon tip with the specified title, |
723 | 96e5791c | pkanavos | /// text, and icon in the taskbar for the specified time period. |
724 | 96e5791c | pkanavos | /// </summary> |
725 | 96e5791c | pkanavos | /// <param name="title">The title to display on the balloon tip.</param> |
726 | 96e5791c | pkanavos | /// <param name="message">The text to display on the balloon tip.</param> |
727 | 96e5791c | pkanavos | /// <param name="symbol">A symbol that indicates the severity.</param> |
728 | 96e5791c | pkanavos | public void ShowBalloonTip(string title, string message, BalloonIcon symbol) |
729 | 96e5791c | pkanavos | { |
730 | 96e5791c | pkanavos | lock (this) |
731 | 96e5791c | pkanavos | { |
732 | 96e5791c | pkanavos | ShowBalloonTip(title, message, symbol.GetBalloonFlag(), IntPtr.Zero); |
733 | 96e5791c | pkanavos | } |
734 | 96e5791c | pkanavos | } |
735 | 96e5791c | pkanavos | |
736 | 96e5791c | pkanavos | |
737 | 96e5791c | pkanavos | /// <summary> |
738 | 96e5791c | pkanavos | /// Displays a balloon tip with the specified title, |
739 | 96e5791c | pkanavos | /// text, and a custom icon in the taskbar for the specified time period. |
740 | 96e5791c | pkanavos | /// </summary> |
741 | 96e5791c | pkanavos | /// <param name="title">The title to display on the balloon tip.</param> |
742 | 96e5791c | pkanavos | /// <param name="message">The text to display on the balloon tip.</param> |
743 | 96e5791c | pkanavos | /// <param name="customIcon">A custom icon.</param> |
744 | 96e5791c | pkanavos | /// <exception cref="ArgumentNullException">If <paramref name="customIcon"/> |
745 | 96e5791c | pkanavos | /// is a null reference.</exception> |
746 | 96e5791c | pkanavos | public void ShowBalloonTip(string title, string message, Icon customIcon) |
747 | 96e5791c | pkanavos | { |
748 | 96e5791c | pkanavos | if (customIcon == null) throw new ArgumentNullException("customIcon"); |
749 | 96e5791c | pkanavos | |
750 | 96e5791c | pkanavos | lock (this) |
751 | 96e5791c | pkanavos | { |
752 | 96e5791c | pkanavos | ShowBalloonTip(title, message, BalloonFlags.User, customIcon.Handle); |
753 | 96e5791c | pkanavos | } |
754 | 96e5791c | pkanavos | } |
755 | 96e5791c | pkanavos | |
756 | 96e5791c | pkanavos | |
757 | 96e5791c | pkanavos | /// <summary> |
758 | 96e5791c | pkanavos | /// Invokes <see cref="WinApi.Shell_NotifyIcon"/> in order to display |
759 | 96e5791c | pkanavos | /// a given balloon ToolTip. |
760 | 96e5791c | pkanavos | /// </summary> |
761 | 96e5791c | pkanavos | /// <param name="title">The title to display on the balloon tip.</param> |
762 | 96e5791c | pkanavos | /// <param name="message">The text to display on the balloon tip.</param> |
763 | 96e5791c | pkanavos | /// <param name="flags">Indicates what icon to use.</param> |
764 | 96e5791c | pkanavos | /// <param name="balloonIconHandle">A handle to a custom icon, if any, or |
765 | 96e5791c | pkanavos | /// <see cref="IntPtr.Zero"/>.</param> |
766 | 96e5791c | pkanavos | private void ShowBalloonTip(string title, string message, BalloonFlags flags, IntPtr balloonIconHandle) |
767 | 96e5791c | pkanavos | { |
768 | 96e5791c | pkanavos | EnsureNotDisposed(); |
769 | 96e5791c | pkanavos | |
770 | 96e5791c | pkanavos | iconData.BalloonText = message ?? String.Empty; |
771 | 96e5791c | pkanavos | iconData.BalloonTitle = title ?? String.Empty; |
772 | 96e5791c | pkanavos | |
773 | 96e5791c | pkanavos | iconData.BalloonFlags = flags; |
774 | 96e5791c | pkanavos | iconData.CustomBalloonIconHandle = balloonIconHandle; |
775 | 96e5791c | pkanavos | Util.WriteIconData(ref iconData, NotifyCommand.Modify, IconDataMembers.Info | IconDataMembers.Icon); |
776 | 96e5791c | pkanavos | } |
777 | 96e5791c | pkanavos | |
778 | 96e5791c | pkanavos | |
779 | 96e5791c | pkanavos | /// <summary> |
780 | 96e5791c | pkanavos | /// Hides a balloon ToolTip, if any is displayed. |
781 | 96e5791c | pkanavos | /// </summary> |
782 | 96e5791c | pkanavos | public void HideBalloonTip() |
783 | 96e5791c | pkanavos | { |
784 | 96e5791c | pkanavos | EnsureNotDisposed(); |
785 | 96e5791c | pkanavos | |
786 | 96e5791c | pkanavos | //reset balloon by just setting the info to an empty string |
787 | 96e5791c | pkanavos | iconData.BalloonText = iconData.BalloonTitle = String.Empty; |
788 | 96e5791c | pkanavos | Util.WriteIconData(ref iconData, NotifyCommand.Modify, IconDataMembers.Info); |
789 | 96e5791c | pkanavos | } |
790 | 96e5791c | pkanavos | |
791 | 96e5791c | pkanavos | #endregion |
792 | 96e5791c | pkanavos | |
793 | 96e5791c | pkanavos | #region Single Click Timer event |
794 | 96e5791c | pkanavos | |
795 | 96e5791c | pkanavos | /// <summary> |
796 | 96e5791c | pkanavos | /// Performs a delayed action if the user requested an action |
797 | 96e5791c | pkanavos | /// based on a single click of the left mouse.<br/> |
798 | 96e5791c | pkanavos | /// This method is invoked by the <see cref="singleClickTimer"/>. |
799 | 96e5791c | pkanavos | /// </summary> |
800 | 96e5791c | pkanavos | private void DoSingleClickAction(object state) |
801 | 96e5791c | pkanavos | { |
802 | 96e5791c | pkanavos | if (IsDisposed) return; |
803 | 96e5791c | pkanavos | |
804 | 96e5791c | pkanavos | //run action |
805 | 96e5791c | pkanavos | Action action = delayedTimerAction; |
806 | 96e5791c | pkanavos | if (action != null) |
807 | 96e5791c | pkanavos | { |
808 | 96e5791c | pkanavos | //cleanup action |
809 | 96e5791c | pkanavos | delayedTimerAction = null; |
810 | 96e5791c | pkanavos | |
811 | 96e5791c | pkanavos | //switch to UI thread |
812 | 96e5791c | pkanavos | this.GetDispatcher().Invoke(action); |
813 | 96e5791c | pkanavos | } |
814 | 96e5791c | pkanavos | } |
815 | 96e5791c | pkanavos | |
816 | 96e5791c | pkanavos | #endregion |
817 | 96e5791c | pkanavos | |
818 | 96e5791c | pkanavos | #region Set Version (API) |
819 | 96e5791c | pkanavos | |
820 | 96e5791c | pkanavos | /// <summary> |
821 | 96e5791c | pkanavos | /// Sets the version flag for the <see cref="iconData"/>. |
822 | 96e5791c | pkanavos | /// </summary> |
823 | 96e5791c | pkanavos | private void SetVersion() |
824 | 96e5791c | pkanavos | { |
825 | 96e5791c | pkanavos | iconData.VersionOrTimeout = (uint) NotifyIconVersion.Vista; |
826 | 96e5791c | pkanavos | bool status = WinApi.Shell_NotifyIcon(NotifyCommand.SetVersion, ref iconData); |
827 | 96e5791c | pkanavos | |
828 | 96e5791c | pkanavos | if (!status) |
829 | 96e5791c | pkanavos | { |
830 | 96e5791c | pkanavos | iconData.VersionOrTimeout = (uint) NotifyIconVersion.Win2000; |
831 | 96e5791c | pkanavos | status = Util.WriteIconData(ref iconData, NotifyCommand.SetVersion); |
832 | 96e5791c | pkanavos | } |
833 | 96e5791c | pkanavos | |
834 | 96e5791c | pkanavos | if (!status) |
835 | 96e5791c | pkanavos | { |
836 | 96e5791c | pkanavos | iconData.VersionOrTimeout = (uint) NotifyIconVersion.Win95; |
837 | 96e5791c | pkanavos | status = Util.WriteIconData(ref iconData, NotifyCommand.SetVersion); |
838 | 96e5791c | pkanavos | } |
839 | 96e5791c | pkanavos | |
840 | 96e5791c | pkanavos | if (!status) |
841 | 96e5791c | pkanavos | { |
842 | 96e5791c | pkanavos | Debug.Fail("Could not set version"); |
843 | 96e5791c | pkanavos | } |
844 | 96e5791c | pkanavos | } |
845 | 96e5791c | pkanavos | |
846 | 96e5791c | pkanavos | #endregion |
847 | 96e5791c | pkanavos | |
848 | 96e5791c | pkanavos | #region Create / Remove Taskbar Icon |
849 | 96e5791c | pkanavos | |
850 | 96e5791c | pkanavos | /// <summary> |
851 | 96e5791c | pkanavos | /// Recreates the taskbar icon if the whole taskbar was |
852 | 96e5791c | pkanavos | /// recreated (e.g. because Explorer was shut down). |
853 | 96e5791c | pkanavos | /// </summary> |
854 | 96e5791c | pkanavos | private void OnTaskbarCreated() |
855 | 96e5791c | pkanavos | { |
856 | 96e5791c | pkanavos | IsTaskbarIconCreated = false; |
857 | 96e5791c | pkanavos | CreateTaskbarIcon(); |
858 | 96e5791c | pkanavos | } |
859 | 96e5791c | pkanavos | |
860 | 96e5791c | pkanavos | |
861 | 96e5791c | pkanavos | /// <summary> |
862 | 96e5791c | pkanavos | /// Creates the taskbar icon. This message is invoked during initialization, |
863 | 96e5791c | pkanavos | /// if the taskbar is restarted, and whenever the icon is displayed. |
864 | 96e5791c | pkanavos | /// </summary> |
865 | 96e5791c | pkanavos | private void CreateTaskbarIcon() |
866 | 96e5791c | pkanavos | { |
867 | 96e5791c | pkanavos | lock (this) |
868 | 96e5791c | pkanavos | { |
869 | 96e5791c | pkanavos | if (!IsTaskbarIconCreated) |
870 | 96e5791c | pkanavos | { |
871 | 96e5791c | pkanavos | const IconDataMembers members = IconDataMembers.Message |
872 | 96e5791c | pkanavos | | IconDataMembers.Icon |
873 | 96e5791c | pkanavos | | IconDataMembers.Tip; |
874 | 96e5791c | pkanavos | |
875 | 96e5791c | pkanavos | //write initial configuration |
876 | 96e5791c | pkanavos | var status = Util.WriteIconData(ref iconData, NotifyCommand.Add, members); |
877 | 96e5791c | pkanavos | if (!status) |
878 | 96e5791c | pkanavos | { |
879 | 96e5791c | pkanavos | //throw new Win32Exception("Could not create icon data"); |
880 | 96e5791c | pkanavos | } |
881 | 96e5791c | pkanavos | |
882 | 96e5791c | pkanavos | //set to most recent version |
883 | 96e5791c | pkanavos | SetVersion(); |
884 | 96e5791c | pkanavos | messageSink.Version = (NotifyIconVersion) iconData.VersionOrTimeout; |
885 | 96e5791c | pkanavos | |
886 | 96e5791c | pkanavos | IsTaskbarIconCreated = true; |
887 | 96e5791c | pkanavos | } |
888 | 96e5791c | pkanavos | } |
889 | 96e5791c | pkanavos | } |
890 | 96e5791c | pkanavos | |
891 | 96e5791c | pkanavos | |
892 | 96e5791c | pkanavos | /// <summary> |
893 | 96e5791c | pkanavos | /// Closes the taskbar icon if required. |
894 | 96e5791c | pkanavos | /// </summary> |
895 | 96e5791c | pkanavos | private void RemoveTaskbarIcon() |
896 | 96e5791c | pkanavos | { |
897 | 96e5791c | pkanavos | lock (this) |
898 | 96e5791c | pkanavos | { |
899 | 96e5791c | pkanavos | if (IsTaskbarIconCreated) |
900 | 96e5791c | pkanavos | { |
901 | 96e5791c | pkanavos | Util.WriteIconData(ref iconData, NotifyCommand.Delete, IconDataMembers.Message); |
902 | 96e5791c | pkanavos | IsTaskbarIconCreated = false; |
903 | 96e5791c | pkanavos | } |
904 | 96e5791c | pkanavos | } |
905 | 96e5791c | pkanavos | } |
906 | 96e5791c | pkanavos | |
907 | 96e5791c | pkanavos | #endregion |
908 | 96e5791c | pkanavos | |
909 | 96e5791c | pkanavos | #region Dispose / Exit |
910 | 96e5791c | pkanavos | |
911 | 96e5791c | pkanavos | /// <summary> |
912 | 96e5791c | pkanavos | /// Set to true as soon as <see cref="Dispose"/> |
913 | 96e5791c | pkanavos | /// has been invoked. |
914 | 96e5791c | pkanavos | /// </summary> |
915 | 96e5791c | pkanavos | public bool IsDisposed { get; private set; } |
916 | 96e5791c | pkanavos | |
917 | 96e5791c | pkanavos | |
918 | 96e5791c | pkanavos | /// <summary> |
919 | 96e5791c | pkanavos | /// Checks if the object has been disposed and |
920 | 96e5791c | pkanavos | /// raises a <see cref="ObjectDisposedException"/> in case |
921 | 96e5791c | pkanavos | /// the <see cref="IsDisposed"/> flag is true. |
922 | 96e5791c | pkanavos | /// </summary> |
923 | 96e5791c | pkanavos | private void EnsureNotDisposed() |
924 | 96e5791c | pkanavos | { |
925 | 96e5791c | pkanavos | if (IsDisposed) throw new ObjectDisposedException(Name ?? GetType().FullName); |
926 | 96e5791c | pkanavos | } |
927 | 96e5791c | pkanavos | |
928 | 96e5791c | pkanavos | |
929 | 96e5791c | pkanavos | /// <summary> |
930 | 96e5791c | pkanavos | /// Disposes the class if the application exits. |
931 | 96e5791c | pkanavos | /// </summary> |
932 | 96e5791c | pkanavos | private void OnExit(object sender, EventArgs e) |
933 | 96e5791c | pkanavos | { |
934 | 96e5791c | pkanavos | Dispose(); |
935 | 96e5791c | pkanavos | } |
936 | 96e5791c | pkanavos | |
937 | 96e5791c | pkanavos | |
938 | 96e5791c | pkanavos | /// <summary> |
939 | 96e5791c | pkanavos | /// This destructor will run only if the <see cref="Dispose()"/> |
940 | 96e5791c | pkanavos | /// method does not get called. This gives this base class the |
941 | 96e5791c | pkanavos | /// opportunity to finalize. |
942 | 96e5791c | pkanavos | /// <para> |
943 | 96e5791c | pkanavos | /// Important: Do not provide destructors in types derived from |
944 | 96e5791c | pkanavos | /// this class. |
945 | 96e5791c | pkanavos | /// </para> |
946 | 96e5791c | pkanavos | /// </summary> |
947 | 96e5791c | pkanavos | ~TaskbarIcon() |
948 | 96e5791c | pkanavos | { |
949 | 96e5791c | pkanavos | Dispose(false); |
950 | 96e5791c | pkanavos | } |
951 | 96e5791c | pkanavos | |
952 | 96e5791c | pkanavos | |
953 | 96e5791c | pkanavos | /// <summary> |
954 | 96e5791c | pkanavos | /// Disposes the object. |
955 | 96e5791c | pkanavos | /// </summary> |
956 | 96e5791c | pkanavos | /// <remarks>This method is not virtual by design. Derived classes |
957 | 96e5791c | pkanavos | /// should override <see cref="Dispose(bool)"/>. |
958 | 96e5791c | pkanavos | /// </remarks> |
959 | 96e5791c | pkanavos | public void Dispose() |
960 | 96e5791c | pkanavos | { |
961 | 96e5791c | pkanavos | Dispose(true); |
962 | 96e5791c | pkanavos | |
963 | 96e5791c | pkanavos | // This object will be cleaned up by the Dispose method. |
964 | 96e5791c | pkanavos | // Therefore, you should call GC.SupressFinalize to |
965 | 96e5791c | pkanavos | // take this object off the finalization queue |
966 | 96e5791c | pkanavos | // and prevent finalization code for this object |
967 | 96e5791c | pkanavos | // from executing a second time. |
968 | 96e5791c | pkanavos | GC.SuppressFinalize(this); |
969 | 96e5791c | pkanavos | } |
970 | 96e5791c | pkanavos | |
971 | 96e5791c | pkanavos | |
972 | 96e5791c | pkanavos | /// <summary> |
973 | 96e5791c | pkanavos | /// Closes the tray and releases all resources. |
974 | 96e5791c | pkanavos | /// </summary> |
975 | 96e5791c | pkanavos | /// <summary> |
976 | 96e5791c | pkanavos | /// <c>Dispose(bool disposing)</c> executes in two distinct scenarios. |
977 | 96e5791c | pkanavos | /// If disposing equals <c>true</c>, the method has been called directly |
978 | 96e5791c | pkanavos | /// or indirectly by a user's code. Managed and unmanaged resources |
979 | 96e5791c | pkanavos | /// can be disposed. |
980 | 96e5791c | pkanavos | /// </summary> |
981 | 96e5791c | pkanavos | /// <param name="disposing">If disposing equals <c>false</c>, the method |
982 | 96e5791c | pkanavos | /// has been called by the runtime from inside the finalizer and you |
983 | 96e5791c | pkanavos | /// should not reference other objects. Only unmanaged resources can |
984 | 96e5791c | pkanavos | /// be disposed.</param> |
985 | 96e5791c | pkanavos | /// <remarks>Check the <see cref="IsDisposed"/> property to determine whether |
986 | 96e5791c | pkanavos | /// the method has already been called.</remarks> |
987 | 96e5791c | pkanavos | private void Dispose(bool disposing) |
988 | 96e5791c | pkanavos | { |
989 | 96e5791c | pkanavos | //don't do anything if the component is already disposed |
990 | 96e5791c | pkanavos | if (IsDisposed || !disposing) return; |
991 | 96e5791c | pkanavos | |
992 | 96e5791c | pkanavos | lock (this) |
993 | 96e5791c | pkanavos | { |
994 | 96e5791c | pkanavos | IsDisposed = true; |
995 | 96e5791c | pkanavos | |
996 | 96e5791c | pkanavos | //deregister application event listener |
997 | 96e5791c | pkanavos | if (Application.Current != null) |
998 | 96e5791c | pkanavos | { |
999 | 96e5791c | pkanavos | Application.Current.Exit -= OnExit; |
1000 | 96e5791c | pkanavos | } |
1001 | 96e5791c | pkanavos | |
1002 | 96e5791c | pkanavos | //stop timers |
1003 | 96e5791c | pkanavos | singleClickTimer.Dispose(); |
1004 | 96e5791c | pkanavos | balloonCloseTimer.Dispose(); |
1005 | 96e5791c | pkanavos | |
1006 | 96e5791c | pkanavos | //dispose message sink |
1007 | 96e5791c | pkanavos | messageSink.Dispose(); |
1008 | 96e5791c | pkanavos | |
1009 | 96e5791c | pkanavos | //remove icon |
1010 | 96e5791c | pkanavos | RemoveTaskbarIcon(); |
1011 | 96e5791c | pkanavos | } |
1012 | 96e5791c | pkanavos | } |
1013 | 96e5791c | pkanavos | |
1014 | 96e5791c | pkanavos | #endregion |
1015 | 96e5791c | pkanavos | } |
1016 | 9bae55d1 | Panagiotis Kanavos | } |