Statistics
| Branch: | Revision:

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
}