diff --git a/Source/NotifyIconWpf/TaskbarIcon.cs b/Source/NotifyIconWpf/TaskbarIcon.cs
index 1fde567..ec7eee2 100644
--- a/Source/NotifyIconWpf/TaskbarIcon.cs
+++ b/Source/NotifyIconWpf/TaskbarIcon.cs
@@ -32,1010 +32,1006 @@ using System.Windows.Controls.Primitives;
using System.Windows.Interop;
using System.Windows.Threading;
using Hardcodet.Wpf.TaskbarNotification.Interop;
-using Point=Hardcodet.Wpf.TaskbarNotification.Interop.Point;
-
+using Point = Hardcodet.Wpf.TaskbarNotification.Interop.Point;
namespace Hardcodet.Wpf.TaskbarNotification
{
- ///
- /// A WPF proxy to for a taskbar icon (NotifyIcon) that sits in the system's
- /// taskbar notification area ("system tray").
- ///
- public partial class TaskbarIcon : FrameworkElement, IDisposable
- {
- #region Members
-
///
- /// Represents the current icon data.
+ /// A WPF proxy to for a taskbar icon (NotifyIcon) that sits in the system's
+ /// taskbar notification area ("system tray").
///
- private NotifyIconData iconData;
-
- ///
- /// Receives messages from the taskbar icon.
- ///
- private readonly WindowMessageSink messageSink;
-
- ///
- /// An action that is being invoked if the
- /// fires.
- ///
- private Action delayedTimerAction;
-
- ///
- /// A timer that is used to differentiate between single
- /// and double clicks.
- ///
- private readonly Timer singleClickTimer;
-
- ///
- /// A timer that is used to close open balloon tooltips.
- ///
- private readonly Timer balloonCloseTimer;
-
- ///
- /// Indicates whether the taskbar icon has been created or not.
- ///
- public bool IsTaskbarIconCreated { get; private set; }
-
- ///
- /// Indicates whether custom tooltips are supported, which depends
- /// on the OS. Windows Vista or higher is required in order to
- /// support this feature.
- ///
- public bool SupportsCustomToolTips
+ public partial class TaskbarIcon : FrameworkElement, IDisposable
{
- get { return messageSink.Version == NotifyIconVersion.Vista; }
- }
+ #region Members
+ ///
+ /// Represents the current icon data.
+ ///
+ private NotifyIconData iconData;
+ ///
+ /// Receives messages from the taskbar icon.
+ ///
+ private readonly WindowMessageSink messageSink;
- ///
- /// Checks whether a non-tooltip popup is currently opened.
- ///
- private bool IsPopupOpen
- {
- get
- {
- var popup = TrayPopupResolved;
- var menu = ContextMenu;
- var balloon = CustomBalloon;
+ ///
+ /// An action that is being invoked if the
+ /// fires.
+ ///
+ private Action delayedTimerAction;
- return popup != null && popup.IsOpen ||
- menu != null && menu.IsOpen ||
- balloon != null && balloon.IsOpen;
+ ///
+ /// A timer that is used to differentiate between single
+ /// and double clicks.
+ ///
+ private readonly Timer singleClickTimer;
- }
- }
+ ///
+ /// A timer that is used to close open balloon tooltips.
+ ///
+ private readonly Timer balloonCloseTimer;
- #endregion
+ ///
+ /// Indicates whether the taskbar icon has been created or not.
+ ///
+ public bool IsTaskbarIconCreated { get; private set; }
-
- #region Construction
-
- ///
- /// Inits the taskbar icon and registers a message listener
- /// in order to receive events from the taskbar area.
- ///
- public TaskbarIcon()
- {
- //using dummy sink in design mode
- messageSink = Util.IsDesignMode
- ? WindowMessageSink.CreateEmpty()
- : new WindowMessageSink(NotifyIconVersion.Win95);
-
- //init icon data structure
- iconData = NotifyIconData.CreateDefault(messageSink.MessageWindowHandle);
-
- //create the taskbar icon
- CreateTaskbarIcon();
-
- //register event listeners
- messageSink.MouseEventReceived += OnMouseEvent;
- messageSink.TaskbarCreated += OnTaskbarCreated;
- messageSink.ChangeToolTipStateRequest += OnToolTipChange;
- messageSink.BalloonToolTipChanged += OnBalloonToolTipChanged;
-
- //init single click / balloon timers
- singleClickTimer = new Timer(DoSingleClickAction);
- balloonCloseTimer = new Timer(CloseBalloonCallback);
-
- //register listener in order to get notified when the application closes
- if (Application.Current != null) Application.Current.Exit += OnExit;
- }
-
- #endregion
-
-
- #region Custom Balloons
-
- ///
- /// Shows a custom control as a tooltip in the tray location.
- ///
- ///
- /// An optional animation for the popup.
- /// The time after which the popup is being closed.
- /// Submit null in order to keep the balloon open inde
- ///
- /// If
- /// is a null reference.
- public void ShowCustomBalloon(UIElement balloon, PopupAnimation animation, int? timeout)
- {
- Dispatcher dispatcher = this.GetDispatcher();
- if (!dispatcher.CheckAccess())
- {
- var action = new Action(() => ShowCustomBalloon(balloon, animation, timeout));
- dispatcher.Invoke(DispatcherPriority.Normal, action);
- return;
- }
-
- if (balloon == null) throw new ArgumentNullException("balloon");
- if (timeout.HasValue && timeout < 500)
- {
- string msg = "Invalid timeout of {0} milliseconds. Timeout must be at least 500 ms";
- msg = String.Format(msg, timeout);
- throw new ArgumentOutOfRangeException("timeout", msg);
- }
-
- EnsureNotDisposed();
-
- //make sure we don't have an open balloon
- lock (this)
- {
- CloseBalloon();
- }
-
- //create an invisible popup that hosts the UIElement
- Popup popup = new Popup();
- popup.AllowsTransparency = true;
-
- //provide the popup with the taskbar icon's data context
- UpdateDataContext(popup, null, DataContext);
-
- //don't animate by default - devs can use attached
- //events or override
- popup.PopupAnimation = animation;
-
- //in case the balloon is cleaned up through routed events, the
- //control didn't removed the balloon from its parent popup when
- //if was closed the last time - just make sure it doesn't have
- //a parent that is a popup
- var parent = LogicalTreeHelper.GetParent(balloon) as Popup;
- if (parent != null) parent.Child = null;
-
- if (parent != null)
- {
- string msg =
- "Cannot display control [{0}] in a new balloon popup - that control already has a parent. You may consider creating new balloons every time you want to show one.";
- msg = String.Format(msg, balloon);
- throw new InvalidOperationException(msg);
- }
-
- popup.Child = balloon;
-
- //don't set the PlacementTarget as it causes the popup to become hidden if the
- //TaskbarIcon's parent is hidden, too...
- //popup.PlacementTarget = this;
-
- popup.Placement = PlacementMode.AbsolutePoint;
- popup.StaysOpen = true;
-
- Point position = TrayInfo.GetTrayLocation();
- popup.HorizontalOffset = position.X -1;
- popup.VerticalOffset = position.Y -1;
-
- //store reference
- lock (this)
- {
- SetCustomBalloon(popup);
- }
-
- //assign this instance as an attached property
- SetParentTaskbarIcon(balloon, this);
-
- //fire attached event
- RaiseBalloonShowingEvent(balloon, this);
-
- //display item
- popup.IsOpen = true;
-
- if (timeout.HasValue)
- {
- //register timer to close the popup
- balloonCloseTimer.Change(timeout.Value, Timeout.Infinite);
- }
- }
-
-
- ///
- /// Resets the closing timeout, which effectively
- /// keeps a displayed balloon message open until
- /// it is either closed programmatically through
- /// or due to a new
- /// message being displayed.
- ///
- public void ResetBalloonCloseTimer()
- {
- if (IsDisposed) return;
-
- lock (this)
- {
- //reset timer in any case
- balloonCloseTimer.Change(Timeout.Infinite, Timeout.Infinite);
- }
- }
-
-
- ///
- /// Closes the current , if the
- /// property is set.
- ///
- public void CloseBalloon()
- {
- if (IsDisposed) return;
-
- Dispatcher dispatcher = this.GetDispatcher();
- if (!dispatcher.CheckAccess())
- {
- Action action = CloseBalloon;
- dispatcher.Invoke(DispatcherPriority.Normal, action);
- return;
- }
-
- lock (this)
- {
- //reset timer in any case
- balloonCloseTimer.Change(Timeout.Infinite, Timeout.Infinite);
-
- //reset old popup, if we still have one
- Popup popup = CustomBalloon;
- if (popup != null)
+ ///
+ /// Indicates whether custom tooltips are supported, which depends
+ /// on the OS. Windows Vista or higher is required in order to
+ /// support this feature.
+ ///
+ public bool SupportsCustomToolTips
{
- UIElement element = popup.Child;
-
- //announce closing
- RoutedEventArgs eventArgs = RaiseBalloonClosingEvent(element, this);
- if (!eventArgs.Handled)
- {
- //if the event was handled, clear the reference to the popup,
- //but don't close it - the handling code has to manage this stuff now
-
- //close the popup
- popup.IsOpen = false;
-
- //remove the reference of the popup to the balloon in case we want to reuse
- //the balloon (then added to a new popup)
- popup.Child = null;
-
- //reset attached property
- if (element != null) SetParentTaskbarIcon(element, null);
- }
-
- //remove custom balloon anyway
- SetCustomBalloon(null);
- }
- }
- }
-
-
- ///
- /// Timer-invoke event which closes the currently open balloon and
- /// resets the dependency property.
- ///
- private void CloseBalloonCallback(object state)
- {
- if (IsDisposed) return;
-
- //switch to UI thread
- Action action = CloseBalloon;
- this.GetDispatcher().Invoke(action);
- }
-
- #endregion
-
- #region Process Incoming Mouse Events
-
- ///
- /// Processes mouse events, which are bubbled
- /// through the class' routed events, trigger
- /// certain actions (e.g. show a popup), or
- /// both.
- ///
- /// Event flag.
- private void OnMouseEvent(MouseEvent me)
- {
- if (IsDisposed) return;
-
- switch (me)
- {
- case MouseEvent.MouseMove:
- RaiseTrayMouseMoveEvent();
- //immediately return - there's nothing left to evaluate
- return;
- case MouseEvent.IconRightMouseDown:
- RaiseTrayRightMouseDownEvent();
- break;
- case MouseEvent.IconLeftMouseDown:
- RaiseTrayLeftMouseDownEvent();
- break;
- case MouseEvent.IconRightMouseUp:
- RaiseTrayRightMouseUpEvent();
- break;
- case MouseEvent.IconLeftMouseUp:
- RaiseTrayLeftMouseUpEvent();
- break;
- case MouseEvent.IconMiddleMouseDown:
- RaiseTrayMiddleMouseDownEvent();
- break;
- case MouseEvent.IconMiddleMouseUp:
- RaiseTrayMiddleMouseUpEvent();
- break;
- case MouseEvent.IconDoubleClick:
- //cancel single click timer
- singleClickTimer.Change(Timeout.Infinite, Timeout.Infinite);
- //bubble event
- RaiseTrayMouseDoubleClickEvent();
- break;
- case MouseEvent.BalloonToolTipClicked:
- RaiseTrayBalloonTipClickedEvent();
- break;
- default:
- throw new ArgumentOutOfRangeException("me", "Missing handler for mouse event flag: " + me);
- }
-
-
- //get mouse coordinates
- Point cursorPosition = new Point();
- WinApi.GetCursorPos(ref cursorPosition);
-
- bool isLeftClickCommandInvoked = false;
-
- //show popup, if requested
- if (me.IsMatch(PopupActivation))
- {
- if (me == MouseEvent.IconLeftMouseUp)
- {
- //show popup once we are sure it's not a double click
- delayedTimerAction = () =>
- {
- LeftClickCommand.ExecuteIfEnabled(LeftClickCommandParameter, LeftClickCommandTarget ?? this);
- ShowTrayPopup(cursorPosition);
- };
- singleClickTimer.Change(WinApi.GetDoubleClickTime(), Timeout.Infinite);
- isLeftClickCommandInvoked = true;
- }
- else
- {
- //show popup immediately
- ShowTrayPopup(cursorPosition);
- }
- }
-
-
- //show context menu, if requested
- if (me.IsMatch(MenuActivation))
- {
- if (me == MouseEvent.IconLeftMouseUp)
- {
- //show context menu once we are sure it's not a double click
- delayedTimerAction = () =>
- {
- LeftClickCommand.ExecuteIfEnabled(LeftClickCommandParameter, LeftClickCommandTarget ?? this);
- ShowContextMenu(cursorPosition);
- };
- singleClickTimer.Change(WinApi.GetDoubleClickTime(), Timeout.Infinite);
- isLeftClickCommandInvoked = true;
- }
- else
- {
- //show context menu immediately
- ShowContextMenu(cursorPosition);
- }
- }
-
- //make sure the left click command is invoked on mouse clicks
- if (me == MouseEvent.IconLeftMouseUp && !isLeftClickCommandInvoked)
- {
- //show context menu once we are sure it's not a double click
- delayedTimerAction = () => LeftClickCommand.ExecuteIfEnabled(LeftClickCommandParameter, LeftClickCommandTarget ?? this);
- singleClickTimer.Change(WinApi.GetDoubleClickTime(), Timeout.Infinite);
- }
-
- }
-
- #endregion
-
- #region ToolTips
-
- ///
- /// Displays a custom tooltip, if available. This method is only
- /// invoked for Windows Vista and above.
- ///
- /// Whether to show or hide the tooltip.
- private void OnToolTipChange(bool visible)
- {
- //if we don't have a tooltip, there's nothing to do here...
- if (TrayToolTipResolved == null) return;
-
- if (visible)
- {
- if (IsPopupOpen)
- {
- //ignore if we are already displaying something down there
- return;
+ get { return messageSink.Version == NotifyIconVersion.Vista; }
}
- var args = RaisePreviewTrayToolTipOpenEvent();
- if (args.Handled) return;
- TrayToolTipResolved.IsOpen = true;
-
- //raise attached event first
- if (TrayToolTip != null) RaiseToolTipOpenedEvent(TrayToolTip);
-
- //bubble routed event
- RaiseTrayToolTipOpenEvent();
- }
- else
- {
- var args = RaisePreviewTrayToolTipCloseEvent();
- if (args.Handled) return;
-
- //raise attached event first
- if (TrayToolTip != null) RaiseToolTipCloseEvent(TrayToolTip);
-
- TrayToolTipResolved.IsOpen = false;
-
- //bubble event
- RaiseTrayToolTipCloseEvent();
- }
- }
-
-
- ///
- /// Creates a control that either
- /// wraps the currently set
- /// control or the string.
- /// If itself is already
- /// a instance, it will be used directly.
- ///
- /// We use a rather than
- /// because there was no way to prevent a
- /// popup from causing cyclic open/close commands if it was
- /// placed under the mouse. ToolTip internally uses a Popup of
- /// its own, but takes advance of Popup's internal
- /// property which prevents this issue.
- private void CreateCustomToolTip()
- {
- //check if the item itself is a tooltip
- ToolTip tt = TrayToolTip as ToolTip;
-
- if (tt == null && TrayToolTip != null)
- {
- //create an invisible tooltip that hosts the UIElement
- tt = new ToolTip();
- tt.Placement = PlacementMode.Mouse;
-
- //do *not* set the placement target, as it causes the popup to become hidden if the
- //TaskbarIcon's parent is hidden, too. At runtime, the parent can be resolved through
- //the ParentTaskbarIcon attached dependency property:
- //tt.PlacementTarget = this;
-
- //make sure the tooltip is invisible
- tt.HasDropShadow = false;
- tt.BorderThickness = new Thickness(0);
- tt.Background = System.Windows.Media.Brushes.Transparent;
-
- //setting the
- tt.StaysOpen = true;
- tt.Content = TrayToolTip;
- }
- else if (tt == null && !String.IsNullOrEmpty(ToolTipText))
- {
- //create a simple tooltip for the string
- tt = new ToolTip();
- tt.Content = ToolTipText;
- }
-
- //the tooltip explicitly gets the DataContext of this instance.
- //If there is no DataContext, the TaskbarIcon assigns itself
- if (tt != null)
- {
- UpdateDataContext(tt, null, DataContext);
- }
-
- //store a reference to the used tooltip
- SetTrayToolTipResolved(tt);
- }
-
-
- ///
- /// Sets tooltip settings for the class depending on defined
- /// dependency properties and OS support.
- ///
- private void WriteToolTipSettings()
- {
- const IconDataMembers flags = IconDataMembers.Tip;
- iconData.ToolTipText = ToolTipText;
-
- if (messageSink.Version == NotifyIconVersion.Vista)
- {
- //we need to set a tooltip text to get tooltip events from the
- //taskbar icon
- if (String.IsNullOrEmpty(iconData.ToolTipText) && TrayToolTipResolved != null)
+ ///
+ /// Checks whether a non-tooltip popup is currently opened.
+ ///
+ private bool IsPopupOpen
{
- //if we have not tooltip text but a custom tooltip, we
- //need to set a dummy value (we're displaying the ToolTip control, not the string)
- iconData.ToolTipText = "ToolTip";
- }
- }
+ get
+ {
+ var popup = TrayPopupResolved;
+ var menu = ContextMenu;
+ var balloon = CustomBalloon;
- //update the tooltip text
- Util.WriteIconData(ref iconData, NotifyCommand.Modify, flags);
- }
-
- #endregion
-
- #region Custom Popup
-
- ///
- /// Creates a control that either
- /// wraps the currently set
- /// control or the string.
- /// If itself is already
- /// a instance, it will be used directly.
- ///
- /// We use a rather than
- /// because there was no way to prevent a
- /// popup from causing cyclic open/close commands if it was
- /// placed under the mouse. ToolTip internally uses a Popup of
- /// its own, but takes advance of Popup's internal
- /// property which prevents this issue.
- private void CreatePopup()
- {
- //check if the item itself is a popup
- Popup popup = TrayPopup as Popup;
-
- if (popup == null && TrayPopup != null)
- {
- //create an invisible popup that hosts the UIElement
- popup = new Popup();
- popup.AllowsTransparency = true;
-
- //don't animate by default - devs can use attached
- //events or override
- popup.PopupAnimation = PopupAnimation.None;
-
- //the CreateRootPopup method outputs binding errors in the debug window because
- //it tries to bind to "Popup-specific" properties in case they are provided by the child.
- //We don't need that so just assign the control as the child.
- popup.Child = TrayPopup;
-
- //do *not* set the placement target, as it causes the popup to become hidden if the
- //TaskbarIcon's parent is hidden, too. At runtime, the parent can be resolved through
- //the ParentTaskbarIcon attached dependency property:
- //popup.PlacementTarget = this;
-
- popup.Placement = PlacementMode.AbsolutePoint;
- popup.StaysOpen = false;
- }
-
- //the popup explicitly gets the DataContext of this instance.
- //If there is no DataContext, the TaskbarIcon assigns itself
- if (popup != null)
- {
- UpdateDataContext(popup, null, DataContext);
- }
-
- //store a reference to the used tooltip
- SetTrayPopupResolved(popup);
- }
-
-
- ///
- /// Displays the control if
- /// it was set.
- ///
- private void ShowTrayPopup(Point cursorPosition)
- {
- if (IsDisposed) return;
-
- //raise preview event no matter whether popup is currently set
- //or not (enables client to set it on demand)
- var args = RaisePreviewTrayPopupOpenEvent();
- if (args.Handled) return;
-
- if (TrayPopup != null)
- {
- //use absolute position, but place the popup centered above the icon
- TrayPopupResolved.Placement = PlacementMode.AbsolutePoint;
- TrayPopupResolved.HorizontalOffset = cursorPosition.X;
- TrayPopupResolved.VerticalOffset = cursorPosition.Y;
-
- //open popup
- TrayPopupResolved.IsOpen = true;
-
-
- IntPtr handle = IntPtr.Zero;
- if (TrayPopupResolved.Child != null)
- {
- //try to get a handle on the popup itself (via its child)
- HwndSource source = (HwndSource)PresentationSource.FromVisual(TrayPopupResolved.Child);
- if (source != null) handle = source.Handle;
+ return popup != null && popup.IsOpen ||
+ menu != null && menu.IsOpen ||
+ balloon != null && balloon.IsOpen;
+ }
}
- //if we don't have a handle for the popup, fall back to the message sink
- if (handle == IntPtr.Zero) handle = messageSink.MessageWindowHandle;
+ #endregion
- //activate either popup or message sink to track deactivation.
- //otherwise, the popup does not close if the user clicks somewhere else
- WinApi.SetForegroundWindow(handle);
+ #region Construction
- //raise attached event - item should never be null unless developers
- //changed the CustomPopup directly...
- if (TrayPopup != null) RaisePopupOpenedEvent(TrayPopup);
-
- //bubble routed event
- RaiseTrayPopupOpenEvent();
- }
- }
-
- #endregion
-
- #region Context Menu
-
- ///
- /// Displays the if
- /// it was set.
- ///
- private void ShowContextMenu(Point cursorPosition)
- {
- if (IsDisposed) return;
-
- //raise preview event no matter whether context menu is currently set
- //or not (enables client to set it on demand)
- var args = RaisePreviewTrayContextMenuOpenEvent();
- if (args.Handled) return;
-
- if (ContextMenu != null)
- {
- //use absolute positioning. We need to set the coordinates, or a delayed opening
- //(e.g. when left-clicked) opens the context menu at the wrong place if the mouse
- //is moved!
- ContextMenu.Placement = PlacementMode.AbsolutePoint;
- ContextMenu.HorizontalOffset = cursorPosition.X;
- ContextMenu.VerticalOffset = cursorPosition.Y;
- ContextMenu.IsOpen = true;
-
- //activate the message window to track deactivation - otherwise, the context menu
- //does not close if the user clicks somewhere else
- WinApi.SetForegroundWindow(messageSink.MessageWindowHandle);
-
- //bubble event
- RaiseTrayContextMenuOpenEvent();
- }
- }
-
- #endregion
-
- #region Balloon Tips
-
- ///
- /// Bubbles events if a balloon ToolTip was displayed
- /// or removed.
- ///
- /// Whether the ToolTip was just displayed
- /// or removed.
- private void OnBalloonToolTipChanged(bool visible)
- {
- if (visible)
- {
- RaiseTrayBalloonTipShownEvent();
- }
- else
- {
- RaiseTrayBalloonTipClosedEvent();
- }
- }
-
- ///
- /// Displays a balloon tip with the specified title,
- /// text, and icon in the taskbar for the specified time period.
- ///
- /// The title to display on the balloon tip.
- /// The text to display on the balloon tip.
- /// A symbol that indicates the severity.
- public void ShowBalloonTip(string title, string message, BalloonIcon symbol)
- {
- lock (this)
- {
- ShowBalloonTip(title, message, symbol.GetBalloonFlag(), IntPtr.Zero);
- }
- }
-
-
- ///
- /// Displays a balloon tip with the specified title,
- /// text, and a custom icon in the taskbar for the specified time period.
- ///
- /// The title to display on the balloon tip.
- /// The text to display on the balloon tip.
- /// A custom icon.
- /// If
- /// is a null reference.
- public void ShowBalloonTip(string title, string message, Icon customIcon)
- {
- if (customIcon == null) throw new ArgumentNullException("customIcon");
-
- lock (this)
- {
- ShowBalloonTip(title, message, BalloonFlags.User, customIcon.Handle);
- }
- }
-
-
- ///
- /// Invokes in order to display
- /// a given balloon ToolTip.
- ///
- /// The title to display on the balloon tip.
- /// The text to display on the balloon tip.
- /// Indicates what icon to use.
- /// A handle to a custom icon, if any, or
- /// .
- private void ShowBalloonTip(string title, string message, BalloonFlags flags, IntPtr balloonIconHandle)
- {
- EnsureNotDisposed();
-
- iconData.BalloonText = message ?? String.Empty;
- iconData.BalloonTitle = title ?? String.Empty;
-
- iconData.BalloonFlags = flags;
- iconData.CustomBalloonIconHandle = balloonIconHandle;
- Util.WriteIconData(ref iconData, NotifyCommand.Modify, IconDataMembers.Info | IconDataMembers.Icon);
- }
-
-
- ///
- /// Hides a balloon ToolTip, if any is displayed.
- ///
- public void HideBalloonTip()
- {
- EnsureNotDisposed();
-
- //reset balloon by just setting the info to an empty string
- iconData.BalloonText = iconData.BalloonTitle = String.Empty;
- Util.WriteIconData(ref iconData, NotifyCommand.Modify, IconDataMembers.Info);
- }
-
- #endregion
-
- #region Single Click Timer event
-
- ///
- /// Performs a delayed action if the user requested an action
- /// based on a single click of the left mouse.
- /// This method is invoked by the .
- ///
- private void DoSingleClickAction(object state)
- {
- if (IsDisposed) return;
-
- //run action
- Action action = delayedTimerAction;
- if (action != null)
- {
- //cleanup action
- delayedTimerAction = null;
-
- //switch to UI thread
- this.GetDispatcher().Invoke(action);
- }
- }
-
- #endregion
-
- #region Set Version (API)
-
- ///
- /// Sets the version flag for the .
- ///
- private void SetVersion()
- {
- iconData.VersionOrTimeout = (uint) NotifyIconVersion.Vista;
- bool status = WinApi.Shell_NotifyIcon(NotifyCommand.SetVersion, ref iconData);
-
- if (!status)
- {
- iconData.VersionOrTimeout = (uint) NotifyIconVersion.Win2000;
- status = Util.WriteIconData(ref iconData, NotifyCommand.SetVersion);
- }
-
- if (!status)
- {
- iconData.VersionOrTimeout = (uint) NotifyIconVersion.Win95;
- status = Util.WriteIconData(ref iconData, NotifyCommand.SetVersion);
- }
-
- if (!status)
- {
- Debug.Fail("Could not set version");
- }
- }
-
- #endregion
-
- #region Create / Remove Taskbar Icon
-
- ///
- /// Recreates the taskbar icon if the whole taskbar was
- /// recreated (e.g. because Explorer was shut down).
- ///
- private void OnTaskbarCreated()
- {
- IsTaskbarIconCreated = false;
- CreateTaskbarIcon();
- }
-
-
- ///
- /// Creates the taskbar icon. This message is invoked during initialization,
- /// if the taskbar is restarted, and whenever the icon is displayed.
- ///
- private void CreateTaskbarIcon()
- {
- lock (this)
- {
- if (!IsTaskbarIconCreated)
+ ///
+ /// Inits the taskbar icon and registers a message listener
+ /// in order to receive events from the taskbar area.
+ ///
+ public TaskbarIcon()
{
- const IconDataMembers members = IconDataMembers.Message
- | IconDataMembers.Icon
- | IconDataMembers.Tip;
+ //using dummy sink in design mode
+ messageSink = Util.IsDesignMode
+ ? WindowMessageSink.CreateEmpty()
+ : new WindowMessageSink(NotifyIconVersion.Win95);
- //write initial configuration
- var status = Util.WriteIconData(ref iconData, NotifyCommand.Add, members);
- if (!status)
- {
- //couldn't create the icon - we can assume this is because explorer is not running (yet!)
- //-> try a bit later again rather than throwing an exception. Typically, if the windows
- // shell is being loaded later, this method is being reinvoked from OnTaskbarCreated
- // (we could also retry after a delay, but that's currently YAGNI)
- return;
- }
+ //init icon data structure
+ iconData = NotifyIconData.CreateDefault(messageSink.MessageWindowHandle);
- //set to most recent version
- SetVersion();
- messageSink.Version = (NotifyIconVersion) iconData.VersionOrTimeout;
+ //create the taskbar icon
+ CreateTaskbarIcon();
- IsTaskbarIconCreated = true;
- }
- }
- }
+ //register event listeners
+ messageSink.MouseEventReceived += OnMouseEvent;
+ messageSink.TaskbarCreated += OnTaskbarCreated;
+ messageSink.ChangeToolTipStateRequest += OnToolTipChange;
+ messageSink.BalloonToolTipChanged += OnBalloonToolTipChanged;
- ///
- /// Closes the taskbar icon if required.
- ///
- private void RemoveTaskbarIcon()
- {
- lock (this)
- {
- //make sure we didn't schedule a creation
+ //init single click / balloon timers
+ singleClickTimer = new Timer(DoSingleClickAction);
+ balloonCloseTimer = new Timer(CloseBalloonCallback);
- if (IsTaskbarIconCreated)
- {
- Util.WriteIconData(ref iconData, NotifyCommand.Delete, IconDataMembers.Message);
- IsTaskbarIconCreated = false;
- }
- }
- }
-
- #endregion
-
- #region Dispose / Exit
-
- ///
- /// Set to true as soon as
- /// has been invoked.
- ///
- public bool IsDisposed { get; private set; }
-
-
- ///
- /// Checks if the object has been disposed and
- /// raises a in case
- /// the flag is true.
- ///
- private void EnsureNotDisposed()
- {
- if (IsDisposed) throw new ObjectDisposedException(Name ?? GetType().FullName);
- }
-
-
- ///
- /// Disposes the class if the application exits.
- ///
- private void OnExit(object sender, EventArgs e)
- {
- Dispose();
- }
-
-
- ///
- /// This destructor will run only if the
- /// method does not get called. This gives this base class the
- /// opportunity to finalize.
- ///
- /// Important: Do not provide destructors in types derived from
- /// this class.
- ///
- ///
- ~TaskbarIcon()
- {
- Dispose(false);
- }
-
-
- ///
- /// Disposes the object.
- ///
- /// This method is not virtual by design. Derived classes
- /// should override .
- ///
- public void Dispose()
- {
- Dispose(true);
-
- // This object will be cleaned up by the Dispose method.
- // Therefore, you should call GC.SupressFinalize to
- // take this object off the finalization queue
- // and prevent finalization code for this object
- // from executing a second time.
- GC.SuppressFinalize(this);
- }
-
-
- ///
- /// Closes the tray and releases all resources.
- ///
- ///
- /// Dispose(bool disposing) executes in two distinct scenarios.
- /// If disposing equals true, the method has been called directly
- /// or indirectly by a user's code. Managed and unmanaged resources
- /// can be disposed.
- ///
- /// If disposing equals false, the method
- /// has been called by the runtime from inside the finalizer and you
- /// should not reference other objects. Only unmanaged resources can
- /// be disposed.
- /// Check the property to determine whether
- /// the method has already been called.
- private void Dispose(bool disposing)
- {
- //don't do anything if the component is already disposed
- if (IsDisposed || !disposing) return;
-
- lock (this)
- {
- IsDisposed = true;
-
- //deregister application event listener
- if (Application.Current != null)
- {
- Application.Current.Exit -= OnExit;
+ //register listener in order to get notified when the application closes
+ if (Application.Current != null) Application.Current.Exit += OnExit;
}
- //stop timers
- singleClickTimer.Dispose();
- balloonCloseTimer.Dispose();
+ #endregion
- //dispose message sink
- messageSink.Dispose();
+ #region Custom Balloons
- //remove icon
- RemoveTaskbarIcon();
- }
+ ///
+ /// Shows a custom control as a tooltip in the tray location.
+ ///
+ ///
+ /// An optional animation for the popup.
+ /// The time after which the popup is being closed.
+ /// Submit null in order to keep the balloon open inde
+ ///
+ /// If
+ /// is a null reference.
+ public void ShowCustomBalloon(UIElement balloon, PopupAnimation animation, int? timeout)
+ {
+ Dispatcher dispatcher = this.GetDispatcher();
+ if (!dispatcher.CheckAccess())
+ {
+ var action = new Action(() => ShowCustomBalloon(balloon, animation, timeout));
+ dispatcher.Invoke(DispatcherPriority.Normal, action);
+ return;
+ }
+
+ if (balloon == null) throw new ArgumentNullException("balloon");
+ if (timeout.HasValue && timeout < 500)
+ {
+ string msg = "Invalid timeout of {0} milliseconds. Timeout must be at least 500 ms";
+ msg = String.Format(msg, timeout);
+ throw new ArgumentOutOfRangeException("timeout", msg);
+ }
+
+ EnsureNotDisposed();
+
+ //make sure we don't have an open balloon
+ lock (this)
+ {
+ CloseBalloon();
+ }
+
+ //create an invisible popup that hosts the UIElement
+ Popup popup = new Popup();
+ popup.AllowsTransparency = true;
+
+ //provide the popup with the taskbar icon's data context
+ UpdateDataContext(popup, null, DataContext);
+
+ //don't animate by default - devs can use attached
+ //events or override
+ popup.PopupAnimation = animation;
+
+ //in case the balloon is cleaned up through routed events, the
+ //control didn't removed the balloon from its parent popup when
+ //if was closed the last time - just make sure it doesn't have
+ //a parent that is a popup
+ var parent = LogicalTreeHelper.GetParent(balloon) as Popup;
+ if (parent != null) parent.Child = null;
+
+ if (parent != null)
+ {
+ string msg =
+ "Cannot display control [{0}] in a new balloon popup - that control already has a parent. You may consider creating new balloons every time you want to show one.";
+ msg = String.Format(msg, balloon);
+ throw new InvalidOperationException(msg);
+ }
+
+ popup.Child = balloon;
+
+ //don't set the PlacementTarget as it causes the popup to become hidden if the
+ //TaskbarIcon's parent is hidden, too...
+ //popup.PlacementTarget = this;
+
+ popup.Placement = PlacementMode.AbsolutePoint;
+ popup.StaysOpen = true;
+
+ Point position = TrayInfo.GetTrayLocation();
+ popup.HorizontalOffset = position.X - 1;
+ popup.VerticalOffset = position.Y - 1;
+
+ //store reference
+ lock (this)
+ {
+ SetCustomBalloon(popup);
+ }
+
+ //assign this instance as an attached property
+ SetParentTaskbarIcon(balloon, this);
+
+ //fire attached event
+ RaiseBalloonShowingEvent(balloon, this);
+
+ //display item
+ popup.IsOpen = true;
+
+ if (timeout.HasValue)
+ {
+ //register timer to close the popup
+ balloonCloseTimer.Change(timeout.Value, Timeout.Infinite);
+ }
+ }
+
+
+ ///
+ /// Resets the closing timeout, which effectively
+ /// keeps a displayed balloon message open until
+ /// it is either closed programmatically through
+ /// or due to a new
+ /// message being displayed.
+ ///
+ public void ResetBalloonCloseTimer()
+ {
+ if (IsDisposed) return;
+
+ lock (this)
+ {
+ //reset timer in any case
+ balloonCloseTimer.Change(Timeout.Infinite, Timeout.Infinite);
+ }
+ }
+
+
+ ///
+ /// Closes the current , if the
+ /// property is set.
+ ///
+ public void CloseBalloon()
+ {
+ if (IsDisposed) return;
+
+ Dispatcher dispatcher = this.GetDispatcher();
+ if (!dispatcher.CheckAccess())
+ {
+ Action action = CloseBalloon;
+ dispatcher.Invoke(DispatcherPriority.Normal, action);
+ return;
+ }
+
+ lock (this)
+ {
+ //reset timer in any case
+ balloonCloseTimer.Change(Timeout.Infinite, Timeout.Infinite);
+
+ //reset old popup, if we still have one
+ Popup popup = CustomBalloon;
+ if (popup != null)
+ {
+ UIElement element = popup.Child;
+
+ //announce closing
+ RoutedEventArgs eventArgs = RaiseBalloonClosingEvent(element, this);
+ if (!eventArgs.Handled)
+ {
+ //if the event was handled, clear the reference to the popup,
+ //but don't close it - the handling code has to manage this stuff now
+
+ //close the popup
+ popup.IsOpen = false;
+
+ //remove the reference of the popup to the balloon in case we want to reuse
+ //the balloon (then added to a new popup)
+ popup.Child = null;
+
+ //reset attached property
+ if (element != null) SetParentTaskbarIcon(element, null);
+ }
+
+ //remove custom balloon anyway
+ SetCustomBalloon(null);
+ }
+ }
+ }
+
+
+ ///
+ /// Timer-invoke event which closes the currently open balloon and
+ /// resets the dependency property.
+ ///
+ private void CloseBalloonCallback(object state)
+ {
+ if (IsDisposed) return;
+
+ //switch to UI thread
+ Action action = CloseBalloon;
+ this.GetDispatcher().Invoke(action);
+ }
+
+ #endregion
+
+ #region Process Incoming Mouse Events
+
+ ///
+ /// Processes mouse events, which are bubbled
+ /// through the class' routed events, trigger
+ /// certain actions (e.g. show a popup), or
+ /// both.
+ ///
+ /// Event flag.
+ private void OnMouseEvent(MouseEvent me)
+ {
+ if (IsDisposed) return;
+
+ switch (me)
+ {
+ case MouseEvent.MouseMove:
+ RaiseTrayMouseMoveEvent();
+ //immediately return - there's nothing left to evaluate
+ return;
+ case MouseEvent.IconRightMouseDown:
+ RaiseTrayRightMouseDownEvent();
+ break;
+ case MouseEvent.IconLeftMouseDown:
+ RaiseTrayLeftMouseDownEvent();
+ break;
+ case MouseEvent.IconRightMouseUp:
+ RaiseTrayRightMouseUpEvent();
+ break;
+ case MouseEvent.IconLeftMouseUp:
+ RaiseTrayLeftMouseUpEvent();
+ break;
+ case MouseEvent.IconMiddleMouseDown:
+ RaiseTrayMiddleMouseDownEvent();
+ break;
+ case MouseEvent.IconMiddleMouseUp:
+ RaiseTrayMiddleMouseUpEvent();
+ break;
+ case MouseEvent.IconDoubleClick:
+ //cancel single click timer
+ singleClickTimer.Change(Timeout.Infinite, Timeout.Infinite);
+ //bubble event
+ RaiseTrayMouseDoubleClickEvent();
+ break;
+ case MouseEvent.BalloonToolTipClicked:
+ RaiseTrayBalloonTipClickedEvent();
+ break;
+ default:
+ throw new ArgumentOutOfRangeException("me", "Missing handler for mouse event flag: " + me);
+ }
+
+
+ //get mouse coordinates
+ Point cursorPosition = new Point();
+ WinApi.GetCursorPos(ref cursorPosition);
+
+ bool isLeftClickCommandInvoked = false;
+
+ //show popup, if requested
+ if (me.IsMatch(PopupActivation))
+ {
+ if (me == MouseEvent.IconLeftMouseUp)
+ {
+ //show popup once we are sure it's not a double click
+ delayedTimerAction = () =>
+ {
+ LeftClickCommand.ExecuteIfEnabled(LeftClickCommandParameter, LeftClickCommandTarget ?? this);
+ ShowTrayPopup(cursorPosition);
+ };
+ singleClickTimer.Change(WinApi.GetDoubleClickTime(), Timeout.Infinite);
+ isLeftClickCommandInvoked = true;
+ }
+ else
+ {
+ //show popup immediately
+ ShowTrayPopup(cursorPosition);
+ }
+ }
+
+
+ //show context menu, if requested
+ if (me.IsMatch(MenuActivation))
+ {
+ if (me == MouseEvent.IconLeftMouseUp)
+ {
+ //show context menu once we are sure it's not a double click
+ delayedTimerAction = () =>
+ {
+ LeftClickCommand.ExecuteIfEnabled(LeftClickCommandParameter, LeftClickCommandTarget ?? this);
+ ShowContextMenu(cursorPosition);
+ };
+ singleClickTimer.Change(WinApi.GetDoubleClickTime(), Timeout.Infinite);
+ isLeftClickCommandInvoked = true;
+ }
+ else
+ {
+ //show context menu immediately
+ ShowContextMenu(cursorPosition);
+ }
+ }
+
+ //make sure the left click command is invoked on mouse clicks
+ if (me == MouseEvent.IconLeftMouseUp && !isLeftClickCommandInvoked)
+ {
+ //show context menu once we are sure it's not a double click
+ delayedTimerAction =
+ () => LeftClickCommand.ExecuteIfEnabled(LeftClickCommandParameter, LeftClickCommandTarget ?? this);
+ singleClickTimer.Change(WinApi.GetDoubleClickTime(), Timeout.Infinite);
+ }
+ }
+
+ #endregion
+
+ #region ToolTips
+
+ ///
+ /// Displays a custom tooltip, if available. This method is only
+ /// invoked for Windows Vista and above.
+ ///
+ /// Whether to show or hide the tooltip.
+ private void OnToolTipChange(bool visible)
+ {
+ //if we don't have a tooltip, there's nothing to do here...
+ if (TrayToolTipResolved == null) return;
+
+ if (visible)
+ {
+ if (IsPopupOpen)
+ {
+ //ignore if we are already displaying something down there
+ return;
+ }
+
+ var args = RaisePreviewTrayToolTipOpenEvent();
+ if (args.Handled) return;
+
+ TrayToolTipResolved.IsOpen = true;
+
+ //raise attached event first
+ if (TrayToolTip != null) RaiseToolTipOpenedEvent(TrayToolTip);
+
+ //bubble routed event
+ RaiseTrayToolTipOpenEvent();
+ }
+ else
+ {
+ var args = RaisePreviewTrayToolTipCloseEvent();
+ if (args.Handled) return;
+
+ //raise attached event first
+ if (TrayToolTip != null) RaiseToolTipCloseEvent(TrayToolTip);
+
+ TrayToolTipResolved.IsOpen = false;
+
+ //bubble event
+ RaiseTrayToolTipCloseEvent();
+ }
+ }
+
+
+ ///
+ /// Creates a control that either
+ /// wraps the currently set
+ /// control or the string.
+ /// If itself is already
+ /// a instance, it will be used directly.
+ ///
+ /// We use a rather than
+ /// because there was no way to prevent a
+ /// popup from causing cyclic open/close commands if it was
+ /// placed under the mouse. ToolTip internally uses a Popup of
+ /// its own, but takes advance of Popup's internal
+ /// property which prevents this issue.
+ private void CreateCustomToolTip()
+ {
+ //check if the item itself is a tooltip
+ ToolTip tt = TrayToolTip as ToolTip;
+
+ if (tt == null && TrayToolTip != null)
+ {
+ //create an invisible tooltip that hosts the UIElement
+ tt = new ToolTip();
+ tt.Placement = PlacementMode.Mouse;
+
+ //do *not* set the placement target, as it causes the popup to become hidden if the
+ //TaskbarIcon's parent is hidden, too. At runtime, the parent can be resolved through
+ //the ParentTaskbarIcon attached dependency property:
+ //tt.PlacementTarget = this;
+
+ //make sure the tooltip is invisible
+ tt.HasDropShadow = false;
+ tt.BorderThickness = new Thickness(0);
+ tt.Background = System.Windows.Media.Brushes.Transparent;
+
+ //setting the
+ tt.StaysOpen = true;
+ tt.Content = TrayToolTip;
+ }
+ else if (tt == null && !String.IsNullOrEmpty(ToolTipText))
+ {
+ //create a simple tooltip for the string
+ tt = new ToolTip();
+ tt.Content = ToolTipText;
+ }
+
+ //the tooltip explicitly gets the DataContext of this instance.
+ //If there is no DataContext, the TaskbarIcon assigns itself
+ if (tt != null)
+ {
+ UpdateDataContext(tt, null, DataContext);
+ }
+
+ //store a reference to the used tooltip
+ SetTrayToolTipResolved(tt);
+ }
+
+
+ ///
+ /// Sets tooltip settings for the class depending on defined
+ /// dependency properties and OS support.
+ ///
+ private void WriteToolTipSettings()
+ {
+ const IconDataMembers flags = IconDataMembers.Tip;
+ iconData.ToolTipText = ToolTipText;
+
+ if (messageSink.Version == NotifyIconVersion.Vista)
+ {
+ //we need to set a tooltip text to get tooltip events from the
+ //taskbar icon
+ if (String.IsNullOrEmpty(iconData.ToolTipText) && TrayToolTipResolved != null)
+ {
+ //if we have not tooltip text but a custom tooltip, we
+ //need to set a dummy value (we're displaying the ToolTip control, not the string)
+ iconData.ToolTipText = "ToolTip";
+ }
+ }
+
+ //update the tooltip text
+ Util.WriteIconData(ref iconData, NotifyCommand.Modify, flags);
+ }
+
+ #endregion
+
+ #region Custom Popup
+
+ ///
+ /// Creates a control that either
+ /// wraps the currently set
+ /// control or the string.
+ /// If itself is already
+ /// a instance, it will be used directly.
+ ///
+ /// We use a rather than
+ /// because there was no way to prevent a
+ /// popup from causing cyclic open/close commands if it was
+ /// placed under the mouse. ToolTip internally uses a Popup of
+ /// its own, but takes advance of Popup's internal
+ /// property which prevents this issue.
+ private void CreatePopup()
+ {
+ //check if the item itself is a popup
+ Popup popup = TrayPopup as Popup;
+
+ if (popup == null && TrayPopup != null)
+ {
+ //create an invisible popup that hosts the UIElement
+ popup = new Popup();
+ popup.AllowsTransparency = true;
+
+ //don't animate by default - devs can use attached
+ //events or override
+ popup.PopupAnimation = PopupAnimation.None;
+
+ //the CreateRootPopup method outputs binding errors in the debug window because
+ //it tries to bind to "Popup-specific" properties in case they are provided by the child.
+ //We don't need that so just assign the control as the child.
+ popup.Child = TrayPopup;
+
+ //do *not* set the placement target, as it causes the popup to become hidden if the
+ //TaskbarIcon's parent is hidden, too. At runtime, the parent can be resolved through
+ //the ParentTaskbarIcon attached dependency property:
+ //popup.PlacementTarget = this;
+
+ popup.Placement = PlacementMode.AbsolutePoint;
+ popup.StaysOpen = false;
+ }
+
+ //the popup explicitly gets the DataContext of this instance.
+ //If there is no DataContext, the TaskbarIcon assigns itself
+ if (popup != null)
+ {
+ UpdateDataContext(popup, null, DataContext);
+ }
+
+ //store a reference to the used tooltip
+ SetTrayPopupResolved(popup);
+ }
+
+
+ ///
+ /// Displays the control if
+ /// it was set.
+ ///
+ private void ShowTrayPopup(Point cursorPosition)
+ {
+ if (IsDisposed) return;
+
+ //raise preview event no matter whether popup is currently set
+ //or not (enables client to set it on demand)
+ var args = RaisePreviewTrayPopupOpenEvent();
+ if (args.Handled) return;
+
+ if (TrayPopup != null)
+ {
+ //use absolute position, but place the popup centered above the icon
+ TrayPopupResolved.Placement = PlacementMode.AbsolutePoint;
+ TrayPopupResolved.HorizontalOffset = cursorPosition.X;
+ TrayPopupResolved.VerticalOffset = cursorPosition.Y;
+
+ //open popup
+ TrayPopupResolved.IsOpen = true;
+
+
+ IntPtr handle = IntPtr.Zero;
+ if (TrayPopupResolved.Child != null)
+ {
+ //try to get a handle on the popup itself (via its child)
+ HwndSource source = (HwndSource) PresentationSource.FromVisual(TrayPopupResolved.Child);
+ if (source != null) handle = source.Handle;
+ }
+
+ //if we don't have a handle for the popup, fall back to the message sink
+ if (handle == IntPtr.Zero) handle = messageSink.MessageWindowHandle;
+
+ //activate either popup or message sink to track deactivation.
+ //otherwise, the popup does not close if the user clicks somewhere else
+ WinApi.SetForegroundWindow(handle);
+
+ //raise attached event - item should never be null unless developers
+ //changed the CustomPopup directly...
+ if (TrayPopup != null) RaisePopupOpenedEvent(TrayPopup);
+
+ //bubble routed event
+ RaiseTrayPopupOpenEvent();
+ }
+ }
+
+ #endregion
+
+ #region Context Menu
+
+ ///
+ /// Displays the if
+ /// it was set.
+ ///
+ private void ShowContextMenu(Point cursorPosition)
+ {
+ if (IsDisposed) return;
+
+ //raise preview event no matter whether context menu is currently set
+ //or not (enables client to set it on demand)
+ var args = RaisePreviewTrayContextMenuOpenEvent();
+ if (args.Handled) return;
+
+ if (ContextMenu != null)
+ {
+ //use absolute positioning. We need to set the coordinates, or a delayed opening
+ //(e.g. when left-clicked) opens the context menu at the wrong place if the mouse
+ //is moved!
+ ContextMenu.Placement = PlacementMode.AbsolutePoint;
+ ContextMenu.HorizontalOffset = cursorPosition.X;
+ ContextMenu.VerticalOffset = cursorPosition.Y;
+
+ ContextMenu.IsOpen = true;
+
+ //activate the message window to track deactivation - otherwise, the context menu
+ //does not close if the user clicks somewhere else
+ WinApi.SetForegroundWindow(messageSink.MessageWindowHandle);
+
+ //bubble event
+ RaiseTrayContextMenuOpenEvent();
+ }
+ }
+
+ #endregion
+
+ #region Balloon Tips
+
+ ///
+ /// Bubbles events if a balloon ToolTip was displayed
+ /// or removed.
+ ///
+ /// Whether the ToolTip was just displayed
+ /// or removed.
+ private void OnBalloonToolTipChanged(bool visible)
+ {
+ if (visible)
+ {
+ RaiseTrayBalloonTipShownEvent();
+ }
+ else
+ {
+ RaiseTrayBalloonTipClosedEvent();
+ }
+ }
+
+ ///
+ /// Displays a balloon tip with the specified title,
+ /// text, and icon in the taskbar for the specified time period.
+ ///
+ /// The title to display on the balloon tip.
+ /// The text to display on the balloon tip.
+ /// A symbol that indicates the severity.
+ public void ShowBalloonTip(string title, string message, BalloonIcon symbol)
+ {
+ lock (this)
+ {
+ ShowBalloonTip(title, message, symbol.GetBalloonFlag(), IntPtr.Zero);
+ }
+ }
+
+
+ ///
+ /// Displays a balloon tip with the specified title,
+ /// text, and a custom icon in the taskbar for the specified time period.
+ ///
+ /// The title to display on the balloon tip.
+ /// The text to display on the balloon tip.
+ /// A custom icon.
+ /// If
+ /// is a null reference.
+ public void ShowBalloonTip(string title, string message, Icon customIcon)
+ {
+ if (customIcon == null) throw new ArgumentNullException("customIcon");
+
+ lock (this)
+ {
+ ShowBalloonTip(title, message, BalloonFlags.User, customIcon.Handle);
+ }
+ }
+
+
+ ///
+ /// Invokes in order to display
+ /// a given balloon ToolTip.
+ ///
+ /// The title to display on the balloon tip.
+ /// The text to display on the balloon tip.
+ /// Indicates what icon to use.
+ /// A handle to a custom icon, if any, or
+ /// .
+ private void ShowBalloonTip(string title, string message, BalloonFlags flags, IntPtr balloonIconHandle)
+ {
+ EnsureNotDisposed();
+
+ iconData.BalloonText = message ?? String.Empty;
+ iconData.BalloonTitle = title ?? String.Empty;
+
+ iconData.BalloonFlags = flags;
+ iconData.CustomBalloonIconHandle = balloonIconHandle;
+ Util.WriteIconData(ref iconData, NotifyCommand.Modify, IconDataMembers.Info | IconDataMembers.Icon);
+ }
+
+
+ ///
+ /// Hides a balloon ToolTip, if any is displayed.
+ ///
+ public void HideBalloonTip()
+ {
+ EnsureNotDisposed();
+
+ //reset balloon by just setting the info to an empty string
+ iconData.BalloonText = iconData.BalloonTitle = String.Empty;
+ Util.WriteIconData(ref iconData, NotifyCommand.Modify, IconDataMembers.Info);
+ }
+
+ #endregion
+
+ #region Single Click Timer event
+
+ ///
+ /// Performs a delayed action if the user requested an action
+ /// based on a single click of the left mouse.
+ /// This method is invoked by the .
+ ///
+ private void DoSingleClickAction(object state)
+ {
+ if (IsDisposed) return;
+
+ //run action
+ Action action = delayedTimerAction;
+ if (action != null)
+ {
+ //cleanup action
+ delayedTimerAction = null;
+
+ //switch to UI thread
+ this.GetDispatcher().Invoke(action);
+ }
+ }
+
+ #endregion
+
+ #region Set Version (API)
+
+ ///
+ /// Sets the version flag for the .
+ ///
+ private void SetVersion()
+ {
+ iconData.VersionOrTimeout = (uint) NotifyIconVersion.Vista;
+ bool status = WinApi.Shell_NotifyIcon(NotifyCommand.SetVersion, ref iconData);
+
+ if (!status)
+ {
+ iconData.VersionOrTimeout = (uint) NotifyIconVersion.Win2000;
+ status = Util.WriteIconData(ref iconData, NotifyCommand.SetVersion);
+ }
+
+ if (!status)
+ {
+ iconData.VersionOrTimeout = (uint) NotifyIconVersion.Win95;
+ status = Util.WriteIconData(ref iconData, NotifyCommand.SetVersion);
+ }
+
+ if (!status)
+ {
+ Debug.Fail("Could not set version");
+ }
+ }
+
+ #endregion
+
+ #region Create / Remove Taskbar Icon
+
+ ///
+ /// Recreates the taskbar icon if the whole taskbar was
+ /// recreated (e.g. because Explorer was shut down).
+ ///
+ private void OnTaskbarCreated()
+ {
+ IsTaskbarIconCreated = false;
+ CreateTaskbarIcon();
+ }
+
+
+ ///
+ /// Creates the taskbar icon. This message is invoked during initialization,
+ /// if the taskbar is restarted, and whenever the icon is displayed.
+ ///
+ private void CreateTaskbarIcon()
+ {
+ lock (this)
+ {
+ if (!IsTaskbarIconCreated)
+ {
+ const IconDataMembers members = IconDataMembers.Message
+ | IconDataMembers.Icon
+ | IconDataMembers.Tip;
+
+ //write initial configuration
+ var status = Util.WriteIconData(ref iconData, NotifyCommand.Add, members);
+ if (!status)
+ {
+ //couldn't create the icon - we can assume this is because explorer is not running (yet!)
+ //-> try a bit later again rather than throwing an exception. Typically, if the windows
+ // shell is being loaded later, this method is being reinvoked from OnTaskbarCreated
+ // (we could also retry after a delay, but that's currently YAGNI)
+ return;
+ }
+
+ //set to most recent version
+ SetVersion();
+ messageSink.Version = (NotifyIconVersion) iconData.VersionOrTimeout;
+
+ IsTaskbarIconCreated = true;
+ }
+ }
+ }
+
+ ///
+ /// Closes the taskbar icon if required.
+ ///
+ private void RemoveTaskbarIcon()
+ {
+ lock (this)
+ {
+ //make sure we didn't schedule a creation
+
+ if (IsTaskbarIconCreated)
+ {
+ Util.WriteIconData(ref iconData, NotifyCommand.Delete, IconDataMembers.Message);
+ IsTaskbarIconCreated = false;
+ }
+ }
+ }
+
+ #endregion
+
+ #region Dispose / Exit
+
+ ///
+ /// Set to true as soon as
+ /// has been invoked.
+ ///
+ public bool IsDisposed { get; private set; }
+
+
+ ///
+ /// Checks if the object has been disposed and
+ /// raises a in case
+ /// the flag is true.
+ ///
+ private void EnsureNotDisposed()
+ {
+ if (IsDisposed) throw new ObjectDisposedException(Name ?? GetType().FullName);
+ }
+
+
+ ///
+ /// Disposes the class if the application exits.
+ ///
+ private void OnExit(object sender, EventArgs e)
+ {
+ Dispose();
+ }
+
+
+ ///
+ /// This destructor will run only if the
+ /// method does not get called. This gives this base class the
+ /// opportunity to finalize.
+ ///
+ /// Important: Do not provide destructors in types derived from
+ /// this class.
+ ///
+ ///
+ ~TaskbarIcon()
+ {
+ Dispose(false);
+ }
+
+
+ ///
+ /// Disposes the object.
+ ///
+ /// This method is not virtual by design. Derived classes
+ /// should override .
+ ///
+ public void Dispose()
+ {
+ Dispose(true);
+
+ // This object will be cleaned up by the Dispose method.
+ // Therefore, you should call GC.SupressFinalize to
+ // take this object off the finalization queue
+ // and prevent finalization code for this object
+ // from executing a second time.
+ GC.SuppressFinalize(this);
+ }
+
+
+ ///
+ /// Closes the tray and releases all resources.
+ ///
+ ///
+ /// Dispose(bool disposing) executes in two distinct scenarios.
+ /// If disposing equals true, the method has been called directly
+ /// or indirectly by a user's code. Managed and unmanaged resources
+ /// can be disposed.
+ ///
+ /// If disposing equals false, the method
+ /// has been called by the runtime from inside the finalizer and you
+ /// should not reference other objects. Only unmanaged resources can
+ /// be disposed.
+ /// Check the property to determine whether
+ /// the method has already been called.
+ private void Dispose(bool disposing)
+ {
+ //don't do anything if the component is already disposed
+ if (IsDisposed || !disposing) return;
+
+ lock (this)
+ {
+ IsDisposed = true;
+
+ //deregister application event listener
+ if (Application.Current != null)
+ {
+ Application.Current.Exit -= OnExit;
+ }
+
+ //stop timers
+ singleClickTimer.Dispose();
+ balloonCloseTimer.Dispose();
+
+ //dispose message sink
+ messageSink.Dispose();
+
+ //remove icon
+ RemoveTaskbarIcon();
+ }
+ }
+
+ #endregion
}
-
- #endregion
- }
}
\ No newline at end of file