1 // hardcodet.net NotifyIcon for WPF
2 // Copyright (c) 2009 Philipp Sumi
3 // Contact and Information: http://www.hardcodet.net
5 // This library is free software; you can redistribute it and/or
6 // modify it under the terms of the Code Project Open License (CPOL);
7 // either version 1.0 of the License, or (at your option) any later
10 // The above copyright notice and this permission notice shall be
11 // included in all copies or substantial portions of the Software.
13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
14 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
15 // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
16 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
17 // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
18 // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
20 // OTHER DEALINGS IN THE SOFTWARE.
22 // THIS COPYRIGHT NOTICE MAY NOT BE REMOVED FROM THIS FILE
26 using System.ComponentModel;
27 using System.Diagnostics;
29 using System.Threading;
31 using System.Windows.Controls;
32 using System.Windows.Controls.Primitives;
33 using System.Windows.Interop;
34 using System.Windows.Threading;
35 using Hardcodet.Wpf.TaskbarNotification.Interop;
36 using Point=Hardcodet.Wpf.TaskbarNotification.Interop.Point;
40 namespace Hardcodet.Wpf.TaskbarNotification
43 /// A WPF proxy to for a taskbar icon (NotifyIcon) that sits in the system's
44 /// taskbar notification area ("system tray").
46 public partial class TaskbarIcon : FrameworkElement, IDisposable
51 /// Represents the current icon data.
53 private NotifyIconData iconData;
56 /// Receives messages from the taskbar icon.
58 private readonly WindowMessageSink messageSink;
61 /// An action that is being invoked if the
62 /// <see cref="singleClickTimer"/> fires.
64 private Action delayedTimerAction;
67 /// A timer that is used to differentiate between single
68 /// and double clicks.
70 private readonly Timer singleClickTimer;
73 /// A timer that is used to close open balloon tooltips.
75 private readonly Timer balloonCloseTimer;
78 /// Indicates whether the taskbar icon has been created or not.
80 public bool IsTaskbarIconCreated { get; private set; }
83 /// Indicates whether custom tooltips are supported, which depends
84 /// on the OS. Windows Vista or higher is required in order to
85 /// support this feature.
87 public bool SupportsCustomToolTips
89 get { return messageSink.Version == NotifyIconVersion.Vista; }
95 /// Checks whether a non-tooltip popup is currently opened.
97 private bool IsPopupOpen
101 var popup = TrayPopupResolved;
102 var menu = ContextMenu;
103 var balloon = CustomBalloon;
105 return popup != null && popup.IsOpen ||
106 menu != null && menu.IsOpen ||
107 balloon != null && balloon.IsOpen;
118 /// Inits the taskbar icon and registers a message listener
119 /// in order to receive events from the taskbar area.
123 //using dummy sink in design mode
124 messageSink = Util.IsDesignMode
125 ? WindowMessageSink.CreateEmpty()
126 : new WindowMessageSink(NotifyIconVersion.Win95);
128 //init icon data structure
129 iconData = NotifyIconData.CreateDefault(messageSink.MessageWindowHandle);
131 //create the taskbar icon
134 //register event listeners
135 messageSink.MouseEventReceived += OnMouseEvent;
136 messageSink.TaskbarCreated += OnTaskbarCreated;
137 messageSink.ChangeToolTipStateRequest += OnToolTipChange;
138 messageSink.BalloonToolTipChanged += OnBalloonToolTipChanged;
140 //init single click / balloon timers
141 singleClickTimer = new Timer(DoSingleClickAction);
142 balloonCloseTimer = new Timer(CloseBalloonCallback);
144 //register listener in order to get notified when the application closes
145 if (Application.Current != null) Application.Current.Exit += OnExit;
151 #region Custom Balloons
154 /// Shows a custom control as a tooltip in the tray location.
156 /// <param name="balloon"></param>
157 /// <param name="animation">An optional animation for the popup.</param>
158 /// <param name="timeout">The time after which the popup is being closed.
159 /// Submit null in order to keep the balloon open inde
161 /// <exception cref="ArgumentNullException">If <paramref name="balloon"/>
162 /// is a null reference.</exception>
163 public void ShowCustomBalloon(UIElement balloon, PopupAnimation animation, int? timeout)
165 Dispatcher dispatcher = this.GetDispatcher();
166 if (!dispatcher.CheckAccess())
168 var action = new Action(() => ShowCustomBalloon(balloon, animation, timeout));
169 dispatcher.Invoke(DispatcherPriority.Normal, action);
173 if (balloon == null) throw new ArgumentNullException("balloon");
174 if (timeout.HasValue && timeout < 500)
176 string msg = "Invalid timeout of {0} milliseconds. Timeout must be at least 500 ms";
177 msg = String.Format(msg, timeout);
178 throw new ArgumentOutOfRangeException("timeout", msg);
183 //make sure we don't have an open balloon
189 //create an invisible popup that hosts the UIElement
190 Popup popup = new Popup();
191 popup.AllowsTransparency = true;
193 //provide the popup with the taskbar icon's data context
194 UpdateDataContext(popup, null, DataContext);
196 //don't animate by default - devs can use attached
198 popup.PopupAnimation = animation;
200 popup.Child = balloon;
202 //don't set the PlacementTarget as it causes the popup to become hidden if the
203 //TaskbarIcon's parent is hidden, too...
204 //popup.PlacementTarget = this;
206 popup.Placement = PlacementMode.AbsolutePoint;
207 popup.StaysOpen = true;
209 Point position = TrayInfo.GetTrayLocation();
210 popup.HorizontalOffset = position.X -1;
211 popup.VerticalOffset = position.Y -1;
216 SetCustomBalloon(popup);
219 //assign this instance as an attached property
220 SetParentTaskbarIcon(balloon, this);
222 //fire attached event
223 RaiseBalloonShowingEvent(balloon, this);
228 if (timeout.HasValue)
230 //register timer to close the popup
231 balloonCloseTimer.Change(timeout.Value, Timeout.Infinite);
237 /// Resets the closing timeout, which effectively
238 /// keeps a displayed balloon message open until
239 /// it is either closed programmatically through
240 /// <see cref="CloseBalloon"/> or due to a new
241 /// message being displayed.
243 public void ResetBalloonCloseTimer()
245 if (IsDisposed) return;
249 //reset timer in any case
250 balloonCloseTimer.Change(Timeout.Infinite, Timeout.Infinite);
256 /// Closes the current <see cref="CustomBalloon"/>, if the
259 public void CloseBalloon()
261 if (IsDisposed) return;
263 Dispatcher dispatcher = this.GetDispatcher();
264 if (!dispatcher.CheckAccess())
266 Action action = CloseBalloon;
267 dispatcher.Invoke(DispatcherPriority.Normal, action);
273 //reset timer in any case
274 balloonCloseTimer.Change(Timeout.Infinite, Timeout.Infinite);
276 //reset old popup, if we still have one
277 Popup popup = CustomBalloon;
280 UIElement element = popup.Child;
283 RoutedEventArgs eventArgs = RaiseBalloonClosingEvent(element, this);
284 if (!eventArgs.Handled)
286 //if the event was handled, clear the reference to the popup,
287 //but don't close it - the handling code has to manage this stuff now
290 popup.IsOpen = false;
292 //reset attached property
293 if (element != null) SetParentTaskbarIcon(element, null);
296 //remove custom balloon anyway
297 SetCustomBalloon(null);
304 /// Timer-invoke event which closes the currently open balloon and
305 /// resets the <see cref="CustomBalloon"/> dependency property.
307 private void CloseBalloonCallback(object state)
309 if (IsDisposed) return;
311 //switch to UI thread
312 Action action = CloseBalloon;
313 this.GetDispatcher().Invoke(action);
318 #region Process Incoming Mouse Events
321 /// Processes mouse events, which are bubbled
322 /// through the class' routed events, trigger
323 /// certain actions (e.g. show a popup), or
326 /// <param name="me">Event flag.</param>
327 private void OnMouseEvent(MouseEvent me)
329 if (IsDisposed) return;
333 case MouseEvent.MouseMove:
334 RaiseTrayMouseMoveEvent();
335 //immediately return - there's nothing left to evaluate
337 case MouseEvent.IconRightMouseDown:
338 RaiseTrayRightMouseDownEvent();
340 case MouseEvent.IconLeftMouseDown:
341 RaiseTrayLeftMouseDownEvent();
343 case MouseEvent.IconRightMouseUp:
344 RaiseTrayRightMouseUpEvent();
346 case MouseEvent.IconLeftMouseUp:
347 RaiseTrayLeftMouseUpEvent();
349 case MouseEvent.IconMiddleMouseDown:
350 RaiseTrayMiddleMouseDownEvent();
352 case MouseEvent.IconMiddleMouseUp:
353 RaiseTrayMiddleMouseUpEvent();
355 case MouseEvent.IconDoubleClick:
356 //cancel single click timer
357 singleClickTimer.Change(Timeout.Infinite, Timeout.Infinite);
359 RaiseTrayMouseDoubleClickEvent();
361 case MouseEvent.BalloonToolTipClicked:
362 RaiseTrayBalloonTipClickedEvent();
365 throw new ArgumentOutOfRangeException("me", "Missing handler for mouse event flag: " + me);
369 //get mouse coordinates
370 Point cursorPosition = new Point();
371 WinApi.GetCursorPos(ref cursorPosition);
373 bool isLeftClickCommandInvoked = false;
375 //show popup, if requested
376 if (me.IsMatch(PopupActivation))
378 if (me == MouseEvent.IconLeftMouseUp)
380 //show popup once we are sure it's not a double click
381 delayedTimerAction = () =>
383 LeftClickCommand.ExecuteIfEnabled(LeftClickCommandParameter, LeftClickCommandTarget ?? this);
384 ShowTrayPopup(cursorPosition);
386 singleClickTimer.Change(WinApi.GetDoubleClickTime(), Timeout.Infinite);
387 isLeftClickCommandInvoked = true;
391 //show popup immediately
392 ShowTrayPopup(cursorPosition);
397 //show context menu, if requested
398 if (me.IsMatch(MenuActivation))
400 if (me == MouseEvent.IconLeftMouseUp)
402 //show context menu once we are sure it's not a double click
403 delayedTimerAction = () =>
405 LeftClickCommand.ExecuteIfEnabled(LeftClickCommandParameter, LeftClickCommandTarget ?? this);
406 ShowContextMenu(cursorPosition);
408 singleClickTimer.Change(WinApi.GetDoubleClickTime(), Timeout.Infinite);
409 isLeftClickCommandInvoked = true;
413 //show context menu immediately
414 ShowContextMenu(cursorPosition);
418 //make sure the left click command is invoked on mouse clicks
419 if (me == MouseEvent.IconLeftMouseUp && !isLeftClickCommandInvoked)
421 //show context menu once we are sure it's not a double click
422 delayedTimerAction = () => LeftClickCommand.ExecuteIfEnabled(LeftClickCommandParameter, LeftClickCommandTarget ?? this);
423 singleClickTimer.Change(WinApi.GetDoubleClickTime(), Timeout.Infinite);
433 /// Displays a custom tooltip, if available. This method is only
434 /// invoked for Windows Vista and above.
436 /// <param name="visible">Whether to show or hide the tooltip.</param>
437 private void OnToolTipChange(bool visible)
439 //if we don't have a tooltip, there's nothing to do here...
440 if (TrayToolTipResolved == null) return;
446 //ignore if we are already displaying something down there
450 var args = RaisePreviewTrayToolTipOpenEvent();
451 if (args.Handled) return;
453 TrayToolTipResolved.IsOpen = true;
455 //raise attached event first
456 if (TrayToolTip != null) RaiseToolTipOpenedEvent(TrayToolTip);
458 //bubble routed event
459 RaiseTrayToolTipOpenEvent();
463 var args = RaisePreviewTrayToolTipCloseEvent();
464 if (args.Handled) return;
466 //raise attached event first
467 if (TrayToolTip != null) RaiseToolTipCloseEvent(TrayToolTip);
469 TrayToolTipResolved.IsOpen = false;
472 RaiseTrayToolTipCloseEvent();
478 /// Creates a <see cref="ToolTip"/> control that either
479 /// wraps the currently set <see cref="TrayToolTip"/>
480 /// control or the <see cref="ToolTipText"/> string.<br/>
481 /// If <see cref="TrayToolTip"/> itself is already
482 /// a <see cref="ToolTip"/> instance, it will be used directly.
484 /// <remarks>We use a <see cref="ToolTip"/> rather than
485 /// <see cref="Popup"/> because there was no way to prevent a
486 /// popup from causing cyclic open/close commands if it was
487 /// placed under the mouse. ToolTip internally uses a Popup of
488 /// its own, but takes advance of Popup's internal <see cref="Popup.HitTestable"/>
489 /// property which prevents this issue.</remarks>
490 private void CreateCustomToolTip()
492 //check if the item itself is a tooltip
493 ToolTip tt = TrayToolTip as ToolTip;
495 if (tt == null && TrayToolTip != null)
497 //create an invisible tooltip that hosts the UIElement
499 tt.Placement = PlacementMode.Mouse;
501 //do *not* set the placement target, as it causes the popup to become hidden if the
502 //TaskbarIcon's parent is hidden, too. At runtime, the parent can be resolved through
503 //the ParentTaskbarIcon attached dependency property:
504 //tt.PlacementTarget = this;
506 //make sure the tooltip is invisible
507 tt.HasDropShadow = false;
508 tt.BorderThickness = new Thickness(0);
509 tt.Background = System.Windows.Media.Brushes.Transparent;
513 tt.Content = TrayToolTip;
515 else if (tt == null && !String.IsNullOrEmpty(ToolTipText))
517 //create a simple tooltip for the string
519 tt.Content = ToolTipText;
522 //the tooltip explicitly gets the DataContext of this instance.
523 //If there is no DataContext, the TaskbarIcon assigns itself
526 UpdateDataContext(tt, null, DataContext);
529 //store a reference to the used tooltip
530 SetTrayToolTipResolved(tt);
535 /// Sets tooltip settings for the class depending on defined
536 /// dependency properties and OS support.
538 private void WriteToolTipSettings()
540 const IconDataMembers flags = IconDataMembers.Tip;
541 iconData.ToolTipText = ToolTipText;
543 if (messageSink.Version == NotifyIconVersion.Vista)
545 //we need to set a tooltip text to get tooltip events from the
547 if (String.IsNullOrEmpty(iconData.ToolTipText) && TrayToolTipResolved != null)
549 //if we have not tooltip text but a custom tooltip, we
550 //need to set a dummy value (we're displaying the ToolTip control, not the string)
551 iconData.ToolTipText = "ToolTip";
555 //update the tooltip text
556 Util.WriteIconData(ref iconData, NotifyCommand.Modify, flags);
564 /// Creates a <see cref="ToolTip"/> control that either
565 /// wraps the currently set <see cref="TrayToolTip"/>
566 /// control or the <see cref="ToolTipText"/> string.<br/>
567 /// If <see cref="TrayToolTip"/> itself is already
568 /// a <see cref="ToolTip"/> instance, it will be used directly.
570 /// <remarks>We use a <see cref="ToolTip"/> rather than
571 /// <see cref="Popup"/> because there was no way to prevent a
572 /// popup from causing cyclic open/close commands if it was
573 /// placed under the mouse. ToolTip internally uses a Popup of
574 /// its own, but takes advance of Popup's internal <see cref="Popup.HitTestable"/>
575 /// property which prevents this issue.</remarks>
576 private void CreatePopup()
578 //check if the item itself is a popup
579 Popup popup = TrayPopup as Popup;
581 if (popup == null && TrayPopup != null)
583 //create an invisible popup that hosts the UIElement
585 popup.AllowsTransparency = true;
587 //don't animate by default - devs can use attached
589 popup.PopupAnimation = PopupAnimation.None;
591 //the CreateRootPopup method outputs binding errors in the debug window because
592 //it tries to bind to "Popup-specific" properties in case they are provided by the child.
593 //We don't need that so just assign the control as the child.
594 popup.Child = TrayPopup;
596 //do *not* set the placement target, as it causes the popup to become hidden if the
597 //TaskbarIcon's parent is hidden, too. At runtime, the parent can be resolved through
598 //the ParentTaskbarIcon attached dependency property:
599 //popup.PlacementTarget = this;
601 popup.Placement = PlacementMode.AbsolutePoint;
602 popup.StaysOpen = false;
605 //the popup explicitly gets the DataContext of this instance.
606 //If there is no DataContext, the TaskbarIcon assigns itself
609 UpdateDataContext(popup, null, DataContext);
612 //store a reference to the used tooltip
613 SetTrayPopupResolved(popup);
618 /// Displays the <see cref="TrayPopup"/> control if
621 private void ShowTrayPopup(Point cursorPosition)
623 if (IsDisposed) return;
625 //raise preview event no matter whether popup is currently set
626 //or not (enables client to set it on demand)
627 var args = RaisePreviewTrayPopupOpenEvent();
628 if (args.Handled) return;
630 if (TrayPopup != null)
632 //use absolute position, but place the popup centered above the icon
633 TrayPopupResolved.Placement = PlacementMode.AbsolutePoint;
634 TrayPopupResolved.HorizontalOffset = cursorPosition.X;
635 TrayPopupResolved.VerticalOffset = cursorPosition.Y;
638 TrayPopupResolved.IsOpen = true;
641 IntPtr handle = IntPtr.Zero;
642 if (TrayPopupResolved.Child != null)
644 //try to get a handle on the popup itself (via its child)
645 HwndSource source = (HwndSource)PresentationSource.FromVisual(TrayPopupResolved.Child);
646 if (source != null) handle = source.Handle;
649 //if we don't have a handle for the popup, fall back to the message sink
650 if (handle == IntPtr.Zero) handle = messageSink.MessageWindowHandle;
652 //activate either popup or message sink to track deactivation.
653 //otherwise, the popup does not close if the user clicks somewhere else
654 WinApi.SetForegroundWindow(handle);
656 //raise attached event - item should never be null unless developers
657 //changed the CustomPopup directly...
658 if (TrayPopup != null) RaisePopupOpenedEvent(TrayPopup);
660 //bubble routed event
661 RaiseTrayPopupOpenEvent();
670 /// Displays the <see cref="ContextMenu"/> if
673 private void ShowContextMenu(Point cursorPosition)
675 if (IsDisposed) return;
677 //raise preview event no matter whether context menu is currently set
678 //or not (enables client to set it on demand)
679 var args = RaisePreviewTrayContextMenuOpenEvent();
680 if (args.Handled) return;
682 if (ContextMenu != null)
684 //use absolute position
685 ContextMenu.Placement = PlacementMode.AbsolutePoint;
686 ContextMenu.HorizontalOffset = cursorPosition.X;
687 ContextMenu.VerticalOffset = cursorPosition.Y;
688 ContextMenu.IsOpen = true;
690 //activate the message window to track deactivation - otherwise, the context menu
691 //does not close if the user clicks somewhere else
692 WinApi.SetForegroundWindow(messageSink.MessageWindowHandle);
695 RaiseTrayContextMenuOpenEvent();
704 /// Bubbles events if a balloon ToolTip was displayed
707 /// <param name="visible">Whether the ToolTip was just displayed
708 /// or removed.</param>
709 private void OnBalloonToolTipChanged(bool visible)
713 RaiseTrayBalloonTipShownEvent();
717 RaiseTrayBalloonTipClosedEvent();
722 /// Displays a balloon tip with the specified title,
723 /// text, and icon in the taskbar for the specified time period.
725 /// <param name="title">The title to display on the balloon tip.</param>
726 /// <param name="message">The text to display on the balloon tip.</param>
727 /// <param name="symbol">A symbol that indicates the severity.</param>
728 public void ShowBalloonTip(string title, string message, BalloonIcon symbol)
732 ShowBalloonTip(title, message, symbol.GetBalloonFlag(), IntPtr.Zero);
738 /// Displays a balloon tip with the specified title,
739 /// text, and a custom icon in the taskbar for the specified time period.
741 /// <param name="title">The title to display on the balloon tip.</param>
742 /// <param name="message">The text to display on the balloon tip.</param>
743 /// <param name="customIcon">A custom icon.</param>
744 /// <exception cref="ArgumentNullException">If <paramref name="customIcon"/>
745 /// is a null reference.</exception>
746 public void ShowBalloonTip(string title, string message, Icon customIcon)
748 if (customIcon == null) throw new ArgumentNullException("customIcon");
752 ShowBalloonTip(title, message, BalloonFlags.User, customIcon.Handle);
758 /// Invokes <see cref="WinApi.Shell_NotifyIcon"/> in order to display
759 /// a given balloon ToolTip.
761 /// <param name="title">The title to display on the balloon tip.</param>
762 /// <param name="message">The text to display on the balloon tip.</param>
763 /// <param name="flags">Indicates what icon to use.</param>
764 /// <param name="balloonIconHandle">A handle to a custom icon, if any, or
765 /// <see cref="IntPtr.Zero"/>.</param>
766 private void ShowBalloonTip(string title, string message, BalloonFlags flags, IntPtr balloonIconHandle)
770 iconData.BalloonText = message ?? String.Empty;
771 iconData.BalloonTitle = title ?? String.Empty;
773 iconData.BalloonFlags = flags;
774 iconData.CustomBalloonIconHandle = balloonIconHandle;
775 Util.WriteIconData(ref iconData, NotifyCommand.Modify, IconDataMembers.Info | IconDataMembers.Icon);
780 /// Hides a balloon ToolTip, if any is displayed.
782 public void HideBalloonTip()
786 //reset balloon by just setting the info to an empty string
787 iconData.BalloonText = iconData.BalloonTitle = String.Empty;
788 Util.WriteIconData(ref iconData, NotifyCommand.Modify, IconDataMembers.Info);
793 #region Single Click Timer event
796 /// Performs a delayed action if the user requested an action
797 /// based on a single click of the left mouse.<br/>
798 /// This method is invoked by the <see cref="singleClickTimer"/>.
800 private void DoSingleClickAction(object state)
802 if (IsDisposed) return;
805 Action action = delayedTimerAction;
809 delayedTimerAction = null;
811 //switch to UI thread
812 this.GetDispatcher().Invoke(action);
818 #region Set Version (API)
821 /// Sets the version flag for the <see cref="iconData"/>.
823 private void SetVersion()
825 iconData.VersionOrTimeout = (uint) NotifyIconVersion.Vista;
826 bool status = WinApi.Shell_NotifyIcon(NotifyCommand.SetVersion, ref iconData);
830 iconData.VersionOrTimeout = (uint) NotifyIconVersion.Win2000;
831 status = Util.WriteIconData(ref iconData, NotifyCommand.SetVersion);
836 iconData.VersionOrTimeout = (uint) NotifyIconVersion.Win95;
837 status = Util.WriteIconData(ref iconData, NotifyCommand.SetVersion);
842 Debug.Fail("Could not set version");
848 #region Create / Remove Taskbar Icon
851 /// Recreates the taskbar icon if the whole taskbar was
852 /// recreated (e.g. because Explorer was shut down).
854 private void OnTaskbarCreated()
856 IsTaskbarIconCreated = false;
862 /// Creates the taskbar icon. This message is invoked during initialization,
863 /// if the taskbar is restarted, and whenever the icon is displayed.
865 private void CreateTaskbarIcon()
869 if (!IsTaskbarIconCreated)
871 const IconDataMembers members = IconDataMembers.Message
872 | IconDataMembers.Icon
873 | IconDataMembers.Tip;
875 //write initial configuration
876 var status = Util.WriteIconData(ref iconData, NotifyCommand.Add, members);
879 throw new Win32Exception("Could not create icon data");
882 //set to most recent version
884 messageSink.Version = (NotifyIconVersion) iconData.VersionOrTimeout;
886 IsTaskbarIconCreated = true;
893 /// Closes the taskbar icon if required.
895 private void RemoveTaskbarIcon()
899 if (IsTaskbarIconCreated)
901 Util.WriteIconData(ref iconData, NotifyCommand.Delete, IconDataMembers.Message);
902 IsTaskbarIconCreated = false;
909 #region Dispose / Exit
912 /// Set to true as soon as <see cref="Dispose"/>
913 /// has been invoked.
915 public bool IsDisposed { get; private set; }
919 /// Checks if the object has been disposed and
920 /// raises a <see cref="ObjectDisposedException"/> in case
921 /// the <see cref="IsDisposed"/> flag is true.
923 private void EnsureNotDisposed()
925 if (IsDisposed) throw new ObjectDisposedException(Name ?? GetType().FullName);
930 /// Disposes the class if the application exits.
932 private void OnExit(object sender, EventArgs e)
939 /// This destructor will run only if the <see cref="Dispose()"/>
940 /// method does not get called. This gives this base class the
941 /// opportunity to finalize.
943 /// Important: Do not provide destructors in types derived from
954 /// Disposes the object.
956 /// <remarks>This method is not virtual by design. Derived classes
957 /// should override <see cref="Dispose(bool)"/>.
959 public void Dispose()
963 // This object will be cleaned up by the Dispose method.
964 // Therefore, you should call GC.SupressFinalize to
965 // take this object off the finalization queue
966 // and prevent finalization code for this object
967 // from executing a second time.
968 GC.SuppressFinalize(this);
973 /// Closes the tray and releases all resources.
976 /// <c>Dispose(bool disposing)</c> executes in two distinct scenarios.
977 /// If disposing equals <c>true</c>, the method has been called directly
978 /// or indirectly by a user's code. Managed and unmanaged resources
981 /// <param name="disposing">If disposing equals <c>false</c>, the method
982 /// has been called by the runtime from inside the finalizer and you
983 /// should not reference other objects. Only unmanaged resources can
984 /// be disposed.</param>
985 /// <remarks>Check the <see cref="IsDisposed"/> property to determine whether
986 /// the method has already been called.</remarks>
987 private void Dispose(bool disposing)
989 //don't do anything if the component is already disposed
990 if (IsDisposed || !disposing) return;
996 //deregister application event listener
997 if (Application.Current != null)
999 Application.Current.Exit -= OnExit;
1003 singleClickTimer.Dispose();
1004 balloonCloseTimer.Dispose();
1006 //dispose message sink
1007 messageSink.Dispose();
1010 RemoveTaskbarIcon();