diff --git a/Source/NotifyIconWpf/NotifyIconWpf.csproj b/Source/NotifyIconWpf/NotifyIconWpf.csproj index 255b6c1..b78f889 100644 --- a/Source/NotifyIconWpf/NotifyIconWpf.csproj +++ b/Source/NotifyIconWpf/NotifyIconWpf.csproj @@ -58,7 +58,6 @@ - diff --git a/Source/NotifyIconWpf/TaskbarIcon.Declarations.cs b/Source/NotifyIconWpf/TaskbarIcon.Declarations.cs index 937bbc7..cc115d0 100644 --- a/Source/NotifyIconWpf/TaskbarIcon.Declarations.cs +++ b/Source/NotifyIconWpf/TaskbarIcon.Declarations.cs @@ -25,14 +25,98 @@ namespace Hardcodet.Wpf.TaskbarNotification /// in order to display either /// or . /// - internal ToolTip CustomToolTip { get; private set; } + internal ToolTip CustomToolTip + { + get { return IconToolTipResolved; } + private set { SetIconToolTipResolved(value); } + } /// /// A which is either the /// control itself or a /// that wraps it. /// - internal Popup CustomPopup { get; private set; } + internal Popup CustomPopup + { + get { return IconPopupResolved; } + private set { SetIconPopupResolved(value); } + } + + + + //RESOLVED POPUPS CONTROLS + #region IconPopupResolved + + /// + /// IconPopupResolved Read-Only Dependency Property + /// + private static readonly DependencyPropertyKey IconPopupResolvedPropertyKey + = DependencyProperty.RegisterReadOnly("IconPopupResolved", typeof(Popup), typeof(TaskbarIcon), + new FrameworkPropertyMetadata(null)); + + public static readonly DependencyProperty IconPopupResolvedProperty + = IconPopupResolvedPropertyKey.DependencyProperty; + + /// + /// Gets the IconPopupResolved property. This dependency property + /// indicates .... + /// + [Category(CategoryName)] + public Popup IconPopupResolved + { + get { return (Popup)GetValue(IconPopupResolvedProperty); } + } + + /// + /// Provides a secure method for setting the IconPopupResolved property. + /// This dependency property indicates .... + /// + /// The new value for the property. + protected void SetIconPopupResolved(Popup value) + { + SetValue(IconPopupResolvedPropertyKey, value); + } + + #endregion + + #region IconToolTipResolved + + /// + /// IconToolTipResolved Read-Only Dependency Property + /// + private static readonly DependencyPropertyKey IconToolTipResolvedPropertyKey + = DependencyProperty.RegisterReadOnly("IconToolTipResolved", typeof(ToolTip), typeof(TaskbarIcon), + new FrameworkPropertyMetadata(null )); + + public static readonly DependencyProperty IconToolTipResolvedProperty + = IconToolTipResolvedPropertyKey.DependencyProperty; + + /// + /// Gets the IconToolTipResolved property. This dependency property + /// indicates .... + /// + [Category(CategoryName)] + [Browsable(true)] + [Bindable(true)] + public ToolTip IconToolTipResolved + { + get { return (ToolTip)GetValue(IconToolTipResolvedProperty); } + } + + /// + /// Provides a secure method for setting the IconToolTipResolved property. + /// This dependency property indicates .... + /// + /// The new value for the property. + protected void SetIconToolTipResolved(ToolTip value) + { + SetValue(IconToolTipResolvedPropertyKey, value); + } + + #endregion + + + //DEPENDENCY PROPERTIES @@ -292,7 +376,7 @@ namespace Hardcodet.Wpf.TaskbarNotification /// Provides information about the updated property. private void OnTaskbarIconPopupPropertyChanged(DependencyPropertyChangedEventArgs e) { - //currently not needed + //create a pop CreatePopup(); } @@ -453,38 +537,6 @@ namespace Hardcodet.Wpf.TaskbarNotification #endregion - #region ContextMenu dependency property override - - /// - /// A static callback listener which is being invoked if the - /// dependency property has - /// been changed. Invokes the - /// instance method of the changed instance. - /// - /// The currently processed owner of the property. - /// Provides information about the updated property. - private static void ContextMenuPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - TaskbarIcon owner = (TaskbarIcon) d; - owner.OnContextMenuPropertyChanged(e); - } - - - /// - /// Handles changes of the dependency property. As - /// WPF internally uses the dependency property system and bypasses the - /// property wrapper, updates of the property's value - /// should be handled here. - /// Provides information about the updated property. - private void OnContextMenuPropertyChanged(DependencyPropertyChangedEventArgs e) - { - //currently not needed - } - - #endregion - - //EVENTS @@ -1258,6 +1310,148 @@ namespace Hardcodet.Wpf.TaskbarNotification #endregion + //ATTACHED EVENTS + + #region PopupOpened + + /// + /// PopupOpened Attached Routed Event + /// + public static readonly RoutedEvent PopupOpenedEvent = EventManager.RegisterRoutedEvent("PopupOpened", + RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(TaskbarIcon)); + + /// + /// Adds a handler for the PopupOpened attached event + /// + /// UIElement or ContentElement that listens to the event + /// Event handler to be added + public static void AddPopupOpenedHandler(DependencyObject element, RoutedEventHandler handler) + { + RoutedEventHelper.AddHandler(element, PopupOpenedEvent, handler); + } + + /// + /// Removes a handler for the PopupOpened attached event + /// + /// UIElement or ContentElement that listens to the event + /// Event handler to be removed + public static void RemovePopupOpenedHandler(DependencyObject element, RoutedEventHandler handler) + { + RoutedEventHelper.RemoveHandler(element, PopupOpenedEvent, handler); + } + + /// + /// A static helper method to raise the PopupOpened event on a target element. + /// + /// UIElement or ContentElement on which to raise the event + internal static RoutedEventArgs RaisePopupOpenedEvent(DependencyObject target) + { + if (target == null) return null; + + RoutedEventArgs args = new RoutedEventArgs(); + args.RoutedEvent = PopupOpenedEvent; + RoutedEventHelper.RaiseEvent(target, args); + return args; + } + + #endregion + + + + #region ToolTipOpened + + /// + /// ToolTipOpened Attached Routed Event + /// + public static readonly RoutedEvent ToolTipOpenedEvent = EventManager.RegisterRoutedEvent("ToolTipOpened", + RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(TaskbarIcon)); + + /// + /// Adds a handler for the ToolTipOpened attached event + /// + /// UIElement or ContentElement that listens to the event + /// Event handler to be added + public static void AddToolTipOpenedHandler(DependencyObject element, RoutedEventHandler handler) + { + RoutedEventHelper.AddHandler(element, ToolTipOpenedEvent, handler); + } + + /// + /// Removes a handler for the ToolTipOpened attached event + /// + /// UIElement or ContentElement that listens to the event + /// Event handler to be removed + public static void RemoveToolTipOpenedHandler(DependencyObject element, RoutedEventHandler handler) + { + RoutedEventHelper.RemoveHandler(element, ToolTipOpenedEvent, handler); + } + + /// + /// A static helper method to raise the ToolTipOpened event on a target element. + /// + /// UIElement or ContentElement on which to raise the event + internal static RoutedEventArgs RaiseToolTipOpenedEvent(DependencyObject target) + { + if (target == null) return null; + + RoutedEventArgs args = new RoutedEventArgs(); + args.RoutedEvent = ToolTipOpenedEvent; + RoutedEventHelper.RaiseEvent(target, args); + return args; + } + + #endregion + + #region ToolTipClose + + /// + /// ToolTipClose Attached Routed Event + /// + public static readonly RoutedEvent ToolTipCloseEvent = EventManager.RegisterRoutedEvent("ToolTipClose", + RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(TaskbarIcon)); + + /// + /// Adds a handler for the ToolTipClose attached event + /// + /// UIElement or ContentElement that listens to the event + /// Event handler to be added + public static void AddToolTipCloseHandler(DependencyObject element, RoutedEventHandler handler) + { + RoutedEventHelper.AddHandler(element, ToolTipCloseEvent, handler); + } + + /// + /// Removes a handler for the ToolTipClose attached event + /// + /// UIElement or ContentElement that listens to the event + /// Event handler to be removed + public static void RemoveToolTipCloseHandler(DependencyObject element, RoutedEventHandler handler) + { + RoutedEventHelper.RemoveHandler(element, ToolTipCloseEvent, handler); + } + + /// + /// A static helper method to raise the ToolTipClose event on a target element. + /// + /// UIElement or ContentElement on which to raise the event + internal static RoutedEventArgs RaiseToolTipCloseEvent(DependencyObject target) + { + if (target == null) return null; + + RoutedEventArgs args = new RoutedEventArgs(); + args.RoutedEvent = ToolTipCloseEvent; + RoutedEventHelper.RaiseEvent(target, args); + return args; + } + + #endregion + + + + + + + //BASE CLASS PROPERTY OVERRIDES @@ -1269,10 +1463,6 @@ namespace Hardcodet.Wpf.TaskbarNotification //register change listener for the Visibility property PropertyMetadata md = new PropertyMetadata(Visibility.Visible, VisibilityPropertyChanged); VisibilityProperty.OverrideMetadata(typeof(TaskbarIcon), md); - - //register change listener for the ContextMenu property - md = new FrameworkPropertyMetadata(new PropertyChangedCallback(ContextMenuPropertyChanged)); - ContextMenuProperty.OverrideMetadata(typeof (TaskbarIcon), md); } } } \ No newline at end of file diff --git a/Source/NotifyIconWpf/TaskbarIcon.Interop.cs b/Source/NotifyIconWpf/TaskbarIcon.Interop.cs deleted file mode 100644 index 57c07dc..0000000 --- a/Source/NotifyIconWpf/TaskbarIcon.Interop.cs +++ /dev/null @@ -1,360 +0,0 @@ -using System; -using System.Drawing; -using System.Threading; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Controls.Primitives; -using Hardcodet.Wpf.TaskbarNotification.Interop; -using Point=Hardcodet.Wpf.TaskbarNotification.Interop.Point; - - -namespace Hardcodet.Wpf.TaskbarNotification -{ - partial class TaskbarIcon - { - /// - /// 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; - - - #region ToolTip - - /// - /// 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 (CustomToolTip == null) return; - - if (visible) - { - if (ContextMenu != null && ContextMenu.IsOpen || - CustomPopup != null && CustomPopup.IsOpen) - { - //ignore if we have an open context menu or popup - return; - } - - var args = RaisePreviewTaskbarIconToolTipOpenEvent(); - if (args.Handled) return; - - CustomToolTip.IsOpen = true; - RaiseTaskbarIconToolTipOpenEvent(); - } - else - { - var args = RaisePreviewTaskbarIconToolTipCloseEvent(); - if (args.Handled) return; - - CustomToolTip.IsOpen = false; - RaiseTaskbarIconToolTipCloseEvent(); - } - } - - /// - /// 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 = TaskbarIconToolTip as ToolTip; - - if (tt == null && TaskbarIconToolTip != null) - { - //create an invisible tooltip that hosts the UIElement - tt = new ToolTip(); - tt.Placement = PlacementMode.Mouse; - tt.PlacementTarget = this; - - //the tooltip (and implicitly its context) explicitly gets - //the DataContext of this instance. If there is no DataContext, - //the TaskbarIcon sets itself - tt.DataContext = DataContext ?? 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 = TaskbarIconToolTip; - } - else if (tt == null && !String.IsNullOrEmpty(ToolTipText)) - { - //create a simple tooltip for the string - tt = new ToolTip(); - tt.Content = ToolTipText; - } - - //store a reference to the used tooltip - CustomToolTip = 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) && CustomToolTip != 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 Show / Hide Balloon Tip - - /// - /// 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; - iconData.BalloonTitle = title; - - iconData.BalloonFlags = flags; - iconData.CustomBalloonIconHandle = balloonIconHandle; - Util.WriteIconData(ref iconData, NotifyCommand.Modify, IconDataMembers.Info); - } - - - /// - /// 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; - - Console.Out.WriteLine("TIMER EVENT"); - - //run action - Action action = delayedTimerAction; - if (action != null) - { - //cleanup action - delayedTimerAction = null; - - //switch to UI thread - Application.Current.Dispatcher.Invoke(action); - } - } - - #endregion - - #region Create 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() - { - //no popup is available - if (TaskbarIconPopup == null) return; - - //check if the item itself is a popup - Popup popup = TaskbarIconPopup as Popup; - - if (popup == null) - { - //create an invisible popup that hosts the UIElement - popup = new Popup(); - popup.AllowsTransparency = true; - popup.PopupAnimation = PopupAnimation.Fade; - - //the tooltip (and implicitly its context) explicitly gets - //the DataContext of this instance. If there is no DataContext, - //the TaskbarIcon assigns itself - popup.DataContext = DataContext ?? this; - - Popup.CreateRootPopup(popup, TaskbarIconPopup); - - popup.PlacementTarget = this; - popup.Placement = PlacementMode.AbsolutePoint; - popup.StaysOpen = false; - } - - //store a reference to the used tooltip - CustomPopup = popup; - } - - #endregion - - #region Show Tray Popup / Context Menu - - /// - /// 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 = RaisePreviewTaskbarIconPopupOpenEvent(); - if (args.Handled) return; - - if (TaskbarIconPopup != null) - { - //use absolute position, but place the popup centered above the icon - CustomPopup.Placement = PlacementMode.AbsolutePoint; - CustomPopup.HorizontalOffset = cursorPosition.X; //+ TaskbarIconPopup.ActualWidth/2; - CustomPopup.VerticalOffset = cursorPosition.Y; - - //open popup - CustomPopup.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 - RaiseTaskbarIconPopupOpenEvent(); - } - } - - - /// - /// 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 = RaisePreviewTaskbarIconContextMenuOpenEvent(); - if (args.Handled) return; - - if (ContextMenu != null) - { - //use absolute position - 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 - RaiseTaskbarIconContextMenuOpenEvent(); - } - } - - #endregion - } -} \ No newline at end of file diff --git a/Source/NotifyIconWpf/TaskbarIcon.cs b/Source/NotifyIconWpf/TaskbarIcon.cs index 1e6bb1d..4872628 100644 --- a/Source/NotifyIconWpf/TaskbarIcon.cs +++ b/Source/NotifyIconWpf/TaskbarIcon.cs @@ -1,17 +1,14 @@ using System; using System.ComponentModel; using System.Diagnostics; -using System.Reflection; +using System.Drawing; using System.Threading; using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; -using System.Windows.Input; -using System.Windows.Media; using Hardcodet.Wpf.TaskbarNotification.Interop; using Point=Hardcodet.Wpf.TaskbarNotification.Interop.Point; - namespace Hardcodet.Wpf.TaskbarNotification { /// @@ -30,11 +27,22 @@ namespace Hardcodet.Wpf.TaskbarNotification /// 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; + /// /// Indicates whether the taskbar icon has been created or not. /// - public bool IsTaskbarIconCreated { get; set; } - + public bool IsTaskbarIconCreated { get; private set; } /// /// Indicates whether custom tooltips are supported, which depends @@ -46,7 +54,6 @@ namespace Hardcodet.Wpf.TaskbarNotification get { return messageSink.Version == NotifyIconVersion.Vista; } } - #region Construction /// @@ -55,16 +62,10 @@ namespace Hardcodet.Wpf.TaskbarNotification /// public TaskbarIcon() { - //do nothing if in design mode - if (Util.IsDesignMode) - { - messageSink = WindowMessageSink.CreateEmpty(); - } - else - { - //create message sink that receives window messages - messageSink = new WindowMessageSink(NotifyIconVersion.Win95); - } + //using dummy sink in design mode + messageSink = Util.IsDesignMode + ? WindowMessageSink.CreateEmpty() + : new WindowMessageSink(NotifyIconVersion.Win95); //init icon data structure iconData = NotifyIconData.CreateDefault(messageSink.MessageWindowHandle); @@ -87,8 +88,7 @@ namespace Hardcodet.Wpf.TaskbarNotification #endregion - - #region Handle Mouse Events + #region Process Incoming Mouse Events /// /// Processes mouse events, which are bubbled @@ -101,7 +101,7 @@ namespace Hardcodet.Wpf.TaskbarNotification { if (IsDisposed) return; - switch(me) + switch (me) { case MouseEvent.MouseMove: RaiseTaskbarIconMouseMoveEvent(); @@ -136,7 +136,6 @@ namespace Hardcodet.Wpf.TaskbarNotification break; default: throw new ArgumentOutOfRangeException("me", "Missing handler for mouse event flag: " + me); - } @@ -178,6 +177,252 @@ namespace Hardcodet.Wpf.TaskbarNotification } } + #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 (CustomToolTip == null) return; + + if (visible) + { + if (ContextMenu != null && ContextMenu.IsOpen || + CustomPopup != null && CustomPopup.IsOpen) + { + //ignore if we have an open context menu or popup + return; + } + + var args = RaisePreviewTaskbarIconToolTipOpenEvent(); + if (args.Handled) return; + + CustomToolTip.IsOpen = true; + + //raise attached event first + if (TaskbarIconToolTip != null) RaiseToolTipOpenedEvent(TaskbarIconToolTip); + + //bubble routed event + RaiseTaskbarIconToolTipOpenEvent(); + } + else + { + var args = RaisePreviewTaskbarIconToolTipCloseEvent(); + if (args.Handled) return; + + //raise attached event first + if (TaskbarIconToolTip != null) RaiseToolTipCloseEvent(TaskbarIconToolTip); + + //CustomToolTip.IsOpen = false; + RaiseTaskbarIconToolTipCloseEvent(); + } + } + + + /// + /// 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 = TaskbarIconToolTip as ToolTip; + + if (tt == null && TaskbarIconToolTip != null) + { + //create an invisible tooltip that hosts the UIElement + tt = new ToolTip(); + tt.Placement = PlacementMode.Mouse; + tt.PlacementTarget = this; + + //the tooltip (and implicitly its context) explicitly gets + //the DataContext of this instance. If there is no DataContext, + //the TaskbarIcon sets itself + tt.DataContext = DataContext ?? 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 = TaskbarIconToolTip; + } + else if (tt == null && !String.IsNullOrEmpty(ToolTipText)) + { + //create a simple tooltip for the string + tt = new ToolTip(); + tt.Content = ToolTipText; + } + + //store a reference to the used tooltip + CustomToolTip = 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) && CustomToolTip != 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() + { + //no popup is available + if (TaskbarIconPopup == null) return; + + //check if the item itself is a popup + Popup popup = TaskbarIconPopup as Popup; + + if (popup == null) + { + //create an invisible popup that hosts the UIElement + popup = new Popup(); + popup.AllowsTransparency = true; + popup.PopupAnimation = PopupAnimation.Fade; + + //the tooltip (and implicitly its context) explicitly gets + //the DataContext of this instance. If there is no DataContext, + //the TaskbarIcon assigns itself + popup.DataContext = DataContext ?? this; + + Popup.CreateRootPopup(popup, TaskbarIconPopup); + + popup.PlacementTarget = this; + popup.Placement = PlacementMode.AbsolutePoint; + popup.StaysOpen = false; + } + + //store a reference to the used tooltip + CustomPopup = 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 = RaisePreviewTaskbarIconPopupOpenEvent(); + if (args.Handled) return; + + if (TaskbarIconPopup != null) + { + //use absolute position, but place the popup centered above the icon + CustomPopup.Placement = PlacementMode.AbsolutePoint; + CustomPopup.HorizontalOffset = cursorPosition.X; //+ TaskbarIconPopup.ActualWidth/2; + CustomPopup.VerticalOffset = cursorPosition.Y; + + //open popup + CustomPopup.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); + + //raise attached event - item should never be null unless developers + //changed the CustomPopup directly... + if (TaskbarIconPopup != null) RaisePopupOpenedEvent(TaskbarIconPopup); + + //bubble routed event + RaiseTaskbarIconPopupOpenEvent(); + } + } + + #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 = RaisePreviewTaskbarIconContextMenuOpenEvent(); + if (args.Handled) return; + + if (ContextMenu != null) + { + //use absolute position + 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 + RaiseTaskbarIconContextMenuOpenEvent(); + } + } + + #endregion + + #region Balloon Tips /// /// Bubbles events if a balloon ToolTip was displayed @@ -197,28 +442,122 @@ namespace Hardcodet.Wpf.TaskbarNotification } } + /// + /// 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; + iconData.BalloonTitle = title; + + iconData.BalloonFlags = flags; + iconData.CustomBalloonIconHandle = balloonIconHandle; + Util.WriteIconData(ref iconData, NotifyCommand.Modify, IconDataMembers.Info); + } + + + /// + /// 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 - #region SetVersion + /// + /// 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 + Application.Current.Dispatcher.Invoke(action); + } + } + + #endregion + + #region Set Version (API) /// /// Sets the version flag for the . /// private void SetVersion() { - iconData.VersionOrTimeout = (uint)NotifyIconVersion.Vista; + iconData.VersionOrTimeout = (uint) NotifyIconVersion.Vista; bool status = WinApi.Shell_NotifyIcon(NotifyCommand.SetVersion, ref iconData); if (!status) { - iconData.VersionOrTimeout = (uint)NotifyIconVersion.Win2000; + iconData.VersionOrTimeout = (uint) NotifyIconVersion.Win2000; status = Util.WriteIconData(ref iconData, NotifyCommand.SetVersion); } if (!status) { - iconData.VersionOrTimeout = (uint)NotifyIconVersion.Win95; + iconData.VersionOrTimeout = (uint) NotifyIconVersion.Win95; status = Util.WriteIconData(ref iconData, NotifyCommand.SetVersion); } @@ -230,7 +569,6 @@ namespace Hardcodet.Wpf.TaskbarNotification #endregion - #region Create / Remove Taskbar Icon /// @@ -292,7 +630,6 @@ namespace Hardcodet.Wpf.TaskbarNotification #endregion - #region Dispose / Exit /// @@ -311,7 +648,7 @@ namespace Hardcodet.Wpf.TaskbarNotification { if (IsDisposed) throw new ObjectDisposedException(Name ?? GetType().FullName); } - + /// /// Disposes the class if the application exits. @@ -320,7 +657,7 @@ namespace Hardcodet.Wpf.TaskbarNotification { Dispose(); } - + /// /// This destructor will run only if the @@ -372,7 +709,7 @@ namespace Hardcodet.Wpf.TaskbarNotification /// 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; @@ -389,10 +726,11 @@ namespace Hardcodet.Wpf.TaskbarNotification //dispose message sink messageSink.Dispose(); + //remove icon RemoveTaskbarIcon(); } } #endregion } -} +} \ No newline at end of file diff --git a/Source/Sample Project/FancyPopup.xaml b/Source/Sample Project/FancyPopup.xaml index cd1804c..c6cddc8 100644 --- a/Source/Sample Project/FancyPopup.xaml +++ b/Source/Sample Project/FancyPopup.xaml @@ -2,8 +2,22 @@ x:Class="Sample_Project.FancyPopup" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:tb="http://www.hardcodet.net/taskbar" Height="215" Width="300" x:Name="me"> + + + + + + + + + + + + + + Stretch="Fill" x:Name="image" RenderTransformOrigin="0.5,0.5" > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -19,7 +49,7 @@ Height="Auto" CornerRadius="6,6,6,6" BorderThickness="3,3,3,3" - Margin="0,0,5,5"> + Margin="0,0,5,5" x:Name="border"> @@ -44,7 +74,16 @@ Source="Images\Info.png" Stretch="Fill" VerticalAlignment="Top" - RenderTransformOrigin="0.792,0.486" /> + RenderTransformOrigin="0.792,0.486" x:Name="image" > + + + + + + + + + /// A property wrapper for the @@ -40,34 +41,6 @@ namespace Sample_Project set { SetValue(InfoTextProperty, value); } } - - /// - /// A static callback listener which is being invoked if the - /// dependency property has - /// been changed. Invokes the - /// instance method of the changed instance. - /// - /// The currently processed owner of the property. - /// Provides information about the updated property. - private static void InfoTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - FancyToolTip owner = (FancyToolTip) d; - owner.OnInfoTextPropertyChanged(e); - } - - - /// - /// Handles changes of the dependency property. As - /// WPF internally uses the dependency property system and bypasses the - /// property wrapper, updates of the property's value - /// should be handled here. - /// Provides information about the updated property. - private void OnInfoTextPropertyChanged(DependencyPropertyChangedEventArgs e) - { -// string newValue = (string) e.NewValue; - } - #endregion @@ -76,5 +49,6 @@ namespace Sample_Project { this.InitializeComponent(); } + } } \ No newline at end of file diff --git a/Source/Sample Project/Window1.xaml b/Source/Sample Project/Window1.xaml index f7b8f30..1938cd5 100644 --- a/Source/Sample Project/Window1.xaml +++ b/Source/Sample Project/Window1.xaml @@ -25,117 +25,25 @@ TypeName="tb:PopupActivationMode" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - + + + + + @@ -148,6 +56,13 @@ + + + + + + +