diff --git a/Source/NotifyIconWpf/BalloonIcon.cs b/Source/NotifyIconWpf/BalloonIcon.cs index 018aae9..5c182d7 100644 --- a/Source/NotifyIconWpf/BalloonIcon.cs +++ b/Source/NotifyIconWpf/BalloonIcon.cs @@ -1,7 +1,26 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +// hardcodet.net NotifyIcon for WPF +// Copyright (c) 2009 Philipp Sumi +// Contact and Information: http://www.hardcodet.net +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the Code Project Open License (CPOL); +// either version 1.0 of the License, or (at your option) any later +// version. +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// THIS COPYRIGHT NOTICE MAY NOT BE REMOVED FROM THIS FILE + namespace Hardcodet.Wpf.TaskbarNotification { @@ -27,4 +46,4 @@ namespace Hardcodet.Wpf.TaskbarNotification /// Error } -} +} \ No newline at end of file diff --git a/Source/NotifyIconWpf/Interop/MouseEvent.cs b/Source/NotifyIconWpf/Interop/MouseEvent.cs index c304289..6f57593 100644 --- a/Source/NotifyIconWpf/Interop/MouseEvent.cs +++ b/Source/NotifyIconWpf/Interop/MouseEvent.cs @@ -1,4 +1,6 @@ -namespace Hardcodet.Wpf.TaskbarNotification.Interop + + +namespace Hardcodet.Wpf.TaskbarNotification.Interop { /// /// Event flags for clicked events. diff --git a/Source/NotifyIconWpf/Interop/NotifyCommand.cs b/Source/NotifyIconWpf/Interop/NotifyCommand.cs index 7c3abde..de0a639 100644 --- a/Source/NotifyIconWpf/Interop/NotifyCommand.cs +++ b/Source/NotifyIconWpf/Interop/NotifyCommand.cs @@ -1,4 +1,4 @@ -using System; + namespace Hardcodet.Wpf.TaskbarNotification.Interop { diff --git a/Source/NotifyIconWpf/Interop/TrayInfo.cs b/Source/NotifyIconWpf/Interop/TrayInfo.cs new file mode 100644 index 0000000..ded285b --- /dev/null +++ b/Source/NotifyIconWpf/Interop/TrayInfo.cs @@ -0,0 +1,172 @@ +// Interop code taken from Mike Marshall's AnyForm + +using System; +using System.Drawing; +using System.Runtime.InteropServices; + + +namespace Hardcodet.Wpf.TaskbarNotification.Interop +{ + /// + /// Resolves the current tray position. + /// + public static class TrayInfo + { + + [DllImport("user32.dll")] + private static extern Int32 GetWindowLong(IntPtr hWnd, Int32 Offset); + + + public static Point GetTrayLocation() + { + var info = new AppBarInfo(); + info.GetSystemTaskBarPosition(); + + Rectangle rcWorkArea = info.WorkArea; + + int x = 0, y = 0; + if (info.Edge == AppBarInfo.ScreenEdge.Left) + { + x = rcWorkArea.Left + 2; + y = rcWorkArea.Bottom; + } + else if (info.Edge == AppBarInfo.ScreenEdge.Bottom) + { + x = rcWorkArea.Right; + y = rcWorkArea.Bottom; + } + else if (info.Edge == AppBarInfo.ScreenEdge.Top) + { + x = rcWorkArea.Right; + y = rcWorkArea.Top; + } + else if (info.Edge == AppBarInfo.ScreenEdge.Right) + { + x = rcWorkArea.Right; + y = rcWorkArea.Bottom; + } + + return new Point { X = x, Y = y}; + } + } + + + + + internal class AppBarInfo + { + + [DllImport("user32.dll")] + private static extern IntPtr FindWindow(String lpClassName, String lpWindowName); + + [DllImport("shell32.dll")] + private static extern UInt32 SHAppBarMessage(UInt32 dwMessage, ref APPBARDATA data); + + [DllImport("user32.dll")] + private static extern Int32 SystemParametersInfo(UInt32 uiAction, UInt32 uiParam, + IntPtr pvParam, UInt32 fWinIni); + + + private const int ABE_BOTTOM = 3; + private const int ABE_LEFT = 0; + private const int ABE_RIGHT = 2; + private const int ABE_TOP = 1; + + private const int ABM_GETTASKBARPOS = 0x00000005; + + // SystemParametersInfo constants + private const UInt32 SPI_GETWORKAREA = 0x0030; + + private APPBARDATA m_data; + + public ScreenEdge Edge + { + get { return (ScreenEdge) m_data.uEdge; } + } + + + public Rectangle WorkArea + { + get + { + Int32 bResult = 0; + var rc = new RECT(); + IntPtr rawRect = Marshal.AllocHGlobal(Marshal.SizeOf(rc)); + bResult = SystemParametersInfo(SPI_GETWORKAREA, 0, rawRect, 0); + rc = (RECT) Marshal.PtrToStructure(rawRect, rc.GetType()); + + if (bResult == 1) + { + Marshal.FreeHGlobal(rawRect); + return new Rectangle(rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top); + } + + return new Rectangle(0, 0, 0, 0); + } + } + + + + public void GetPosition(string strClassName, string strWindowName) + { + m_data = new APPBARDATA(); + m_data.cbSize = (UInt32) Marshal.SizeOf(m_data.GetType()); + + IntPtr hWnd = FindWindow(strClassName, strWindowName); + + if (hWnd != IntPtr.Zero) + { + UInt32 uResult = SHAppBarMessage(ABM_GETTASKBARPOS, ref m_data); + + if (uResult != 1) + { + throw new Exception("Failed to communicate with the given AppBar"); + } + } + else + { + throw new Exception("Failed to find an AppBar that matched the given criteria"); + } + } + + + public void GetSystemTaskBarPosition() + { + GetPosition("Shell_TrayWnd", null); + } + + + + + public enum ScreenEdge + { + Undefined = -1, + Left = ABE_LEFT, + Top = ABE_TOP, + Right = ABE_RIGHT, + Bottom = ABE_BOTTOM + } + + + [StructLayout(LayoutKind.Sequential)] + private struct APPBARDATA + { + public UInt32 cbSize; + public IntPtr hWnd; + public UInt32 uCallbackMessage; + public UInt32 uEdge; + public RECT rc; + public Int32 lParam; + } + + [StructLayout(LayoutKind.Sequential)] + private struct RECT + { + public Int32 left; + public Int32 top; + public Int32 right; + public Int32 bottom; + } + + } +} \ No newline at end of file diff --git a/Source/NotifyIconWpf/Interop/WindowMessageSink.cs b/Source/NotifyIconWpf/Interop/WindowMessageSink.cs index 9aa2a2f..710fa06 100644 --- a/Source/NotifyIconWpf/Interop/WindowMessageSink.cs +++ b/Source/NotifyIconWpf/Interop/WindowMessageSink.cs @@ -1,4 +1,29 @@ -using System; +// hardcodet.net NotifyIcon for WPF +// Copyright (c) 2009 Philipp Sumi +// Contact and Information: http://www.hardcodet.net +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the Code Project Open License (CPOL); +// either version 1.0 of the License, or (at your option) any later +// version. +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// THIS COPYRIGHT NOTICE MAY NOT BE REMOVED FROM THIS FILE + + + +using System; using System.ComponentModel; using System.Diagnostics; diff --git a/Source/NotifyIconWpf/NotifyIconWpf.csproj b/Source/NotifyIconWpf/NotifyIconWpf.csproj index 821a357..914b6b0 100644 --- a/Source/NotifyIconWpf/NotifyIconWpf.csproj +++ b/Source/NotifyIconWpf/NotifyIconWpf.csproj @@ -40,14 +40,18 @@ 3.5 + + - + + Code + diff --git a/Source/NotifyIconWpf/PopupActivationMode.cs b/Source/NotifyIconWpf/PopupActivationMode.cs index 81248b8..2020929 100644 --- a/Source/NotifyIconWpf/PopupActivationMode.cs +++ b/Source/NotifyIconWpf/PopupActivationMode.cs @@ -1,4 +1,28 @@ -namespace Hardcodet.Wpf.TaskbarNotification +// hardcodet.net NotifyIcon for WPF +// Copyright (c) 2009 Philipp Sumi +// Contact and Information: http://www.hardcodet.net +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the Code Project Open License (CPOL); +// either version 1.0 of the License, or (at your option) any later +// version. +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// THIS COPYRIGHT NOTICE MAY NOT BE REMOVED FROM THIS FILE + + +namespace Hardcodet.Wpf.TaskbarNotification { /// /// Defines flags that define when a popup diff --git a/Source/NotifyIconWpf/Properties/AssemblyInfo.cs b/Source/NotifyIconWpf/Properties/AssemblyInfo.cs index d869e15..df1277c 100644 --- a/Source/NotifyIconWpf/Properties/AssemblyInfo.cs +++ b/Source/NotifyIconWpf/Properties/AssemblyInfo.cs @@ -1,6 +1,4 @@ using System.Reflection; -using System.Resources; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Windows; using System.Windows.Markup; @@ -8,16 +6,16 @@ using System.Windows.Markup; // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. -[assembly: AssemblyTitle("NotifyIconWpf")] -[assembly: AssemblyDescription("NotifyIcon Implementation for WPF.")] +[assembly: AssemblyTitle("NotifyIcon for WPF")] +[assembly: AssemblyDescription("NotifyIcon Implementation for the WPF platform.")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("hardcodet.net")] -[assembly: AssemblyProduct("NotifyIconWpf")] +[assembly: AssemblyProduct("NotifyIcon WPF")] [assembly: AssemblyCopyright("Copyright © Philipp Sumi 2009")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] - +//provides simplified declaration [assembly: XmlnsDefinition("http://www.hardcodet.net/taskbar", "Hardcodet.Wpf.TaskbarNotification")] // Setting ComVisible to false makes the types in this assembly not visible diff --git a/Source/NotifyIconWpf/TaskbarIcon.Declarations.cs b/Source/NotifyIconWpf/TaskbarIcon.Declarations.cs index 2870a7c..22fe1d5 100644 --- a/Source/NotifyIconWpf/TaskbarIcon.Declarations.cs +++ b/Source/NotifyIconWpf/TaskbarIcon.Declarations.cs @@ -1,4 +1,29 @@ -using System; +// hardcodet.net NotifyIcon for WPF +// Copyright (c) 2009 Philipp Sumi +// Contact and Information: http://www.hardcodet.net +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the Code Project Open License (CPOL); +// either version 1.0 of the License, or (at your option) any later +// version. +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// THIS COPYRIGHT NOTICE MAY NOT BE REMOVED FROM THIS FILE + + + +using System; using System.ComponentModel; using System.Drawing; using System.Windows; @@ -33,6 +58,11 @@ namespace Hardcodet.Wpf.TaskbarNotification = DependencyProperty.RegisterReadOnly("TrayPopupResolved", typeof(Popup), typeof(TaskbarIcon), new FrameworkPropertyMetadata(null)); + + /// + /// A read-only dependency property that returns the + /// that is being displayed in the taskbar area based on a user action. + /// public static readonly DependencyProperty TrayPopupResolvedProperty = TrayPopupResolvedPropertyKey.DependencyProperty; @@ -70,6 +100,11 @@ namespace Hardcodet.Wpf.TaskbarNotification = DependencyProperty.RegisterReadOnly("TrayToolTipResolved", typeof(ToolTip), typeof(TaskbarIcon), new FrameworkPropertyMetadata(null )); + + /// + /// A read-only dependency property that returns the + /// that is being displayed. + /// public static readonly DependencyProperty TrayToolTipResolvedProperty = TrayToolTipResolvedPropertyKey.DependencyProperty; @@ -336,7 +371,7 @@ namespace Hardcodet.Wpf.TaskbarNotification if (e.OldValue != null) { //remove the taskbar icon reference from the previously used element - SetParentTaskbarIcon((DependencyObject) e.OldValue, this); + SetParentTaskbarIcon((DependencyObject) e.OldValue, null); } @@ -407,7 +442,7 @@ namespace Hardcodet.Wpf.TaskbarNotification if (e.OldValue != null) { //remove the taskbar icon reference from the previously used element - SetParentTaskbarIcon((DependencyObject)e.OldValue, this); + SetParentTaskbarIcon((DependencyObject)e.OldValue, null); } @@ -434,7 +469,7 @@ namespace Hardcodet.Wpf.TaskbarNotification DependencyProperty.Register("MenuActivation", typeof (PopupActivationMode), typeof (TaskbarIcon), - new FrameworkPropertyMetadata(PopupActivationMode.RightClick, MenuActivationPropertyChanged)); + new FrameworkPropertyMetadata(PopupActivationMode.RightClick)); /// /// A property wrapper for the @@ -450,34 +485,6 @@ namespace Hardcodet.Wpf.TaskbarNotification set { SetValue(MenuActivationProperty, 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 MenuActivationPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - TaskbarIcon owner = (TaskbarIcon) d; - owner.OnMenuActivationPropertyChanged(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 OnMenuActivationPropertyChanged(DependencyPropertyChangedEventArgs e) - { - //currently not needed - } - #endregion #region PopupActivation dependency property @@ -490,7 +497,7 @@ namespace Hardcodet.Wpf.TaskbarNotification DependencyProperty.Register("PopupActivation", typeof (PopupActivationMode), typeof (TaskbarIcon), - new FrameworkPropertyMetadata(PopupActivationMode.LeftClick, PopupActivationPropertyChanged)); + new FrameworkPropertyMetadata(PopupActivationMode.LeftClick)); /// /// A property wrapper for the @@ -506,34 +513,6 @@ namespace Hardcodet.Wpf.TaskbarNotification set { SetValue(PopupActivationProperty, 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 PopupActivationPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - TaskbarIcon owner = (TaskbarIcon) d; - owner.OnPopupActivationPropertyChanged(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 OnPopupActivationPropertyChanged(DependencyPropertyChangedEventArgs e) - { - //currently not needed - } - #endregion @@ -726,21 +705,6 @@ namespace Hardcodet.Wpf.TaskbarNotification #endregion - /// - /// Executes a given command. - /// - /// The command to be executed. - /// An optional parameter that was associated with - /// the command. - private static void RunCommand(ICommand command, object commandParameter) - { - if (command == null) return; - if (command.CanExecute(commandParameter)) - { - command.Execute(commandParameter); - } - } - //EVENTS @@ -769,7 +733,7 @@ namespace Hardcodet.Wpf.TaskbarNotification { //first raise event, then command RoutedEventArgs args = RaiseTrayLeftMouseDownEvent(this); - RunCommand(LeftClickCommand, LeftClickCommandParameter); + LeftClickCommand.ExecuteIfEnabled(LeftClickCommandParameter); return args; } @@ -1019,7 +983,7 @@ namespace Hardcodet.Wpf.TaskbarNotification protected RoutedEventArgs RaiseTrayMouseDoubleClickEvent() { RoutedEventArgs args = RaiseTrayMouseDoubleClickEvent(this); - RunCommand(DoubleClickCommand, DoubleClickCommandParameter); + DoubleClickCommand.ExecuteIfEnabled(DoubleClickCommandParameter); return args; } @@ -1685,21 +1649,66 @@ namespace Hardcodet.Wpf.TaskbarNotification /// A static helper method to raise the BalloonShowing event on a target element. /// /// UIElement or ContentElement on which to raise the event - internal static RoutedEventArgs RaiseBalloonShowingEvent(DependencyObject target) + /// The instance that manages the balloon. + internal static RoutedEventArgs RaiseBalloonShowingEvent(DependencyObject target, TaskbarIcon source) { if (target == null) return null; - RoutedEventArgs args = new RoutedEventArgs(); - args.RoutedEvent = BalloonShowingEvent; + RoutedEventArgs args = new RoutedEventArgs(BalloonShowingEvent, source); + RoutedEventHelper.RaiseEvent(target, args); + return args; + } + + #endregion + + #region BalloonClosing + + /// + /// BalloonClosing Attached Routed Event + /// + public static readonly RoutedEvent BalloonClosingEvent = EventManager.RegisterRoutedEvent("BalloonClosing", + RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(TaskbarIcon)); + + /// + /// Adds a handler for the BalloonClosing attached event + /// + /// UIElement or ContentElement that listens to the event + /// Event handler to be added + public static void AddBalloonClosingHandler(DependencyObject element, RoutedEventHandler handler) + { + RoutedEventHelper.AddHandler(element, BalloonClosingEvent, handler); + } + + /// + /// Removes a handler for the BalloonClosing attached event + /// + /// UIElement or ContentElement that listens to the event + /// Event handler to be removed + public static void RemoveBalloonClosingHandler(DependencyObject element, RoutedEventHandler handler) + { + RoutedEventHelper.RemoveHandler(element, BalloonClosingEvent, handler); + } + + /// + /// A static helper method to raise the BalloonClosing event on a target element. + /// + /// UIElement or ContentElement on which to raise the event + /// The instance that manages the balloon. + internal static RoutedEventArgs RaiseBalloonClosingEvent(DependencyObject target, TaskbarIcon source) + { + if (target == null) return null; + + RoutedEventArgs args = new RoutedEventArgs(BalloonClosingEvent, source); RoutedEventHelper.RaiseEvent(target, args); return args; } #endregion + + //ATTACHED PROPERTIES - //TODO put into use #region ParentTaskbarIcon /// @@ -1729,9 +1738,6 @@ namespace Hardcodet.Wpf.TaskbarNotification #endregion - - - //BASE CLASS PROPERTY OVERRIDES /// diff --git a/Source/NotifyIconWpf/TaskbarIcon.cs b/Source/NotifyIconWpf/TaskbarIcon.cs index 4021df0..dd8405a 100644 --- a/Source/NotifyIconWpf/TaskbarIcon.cs +++ b/Source/NotifyIconWpf/TaskbarIcon.cs @@ -1,4 +1,28 @@ -using System; +// hardcodet.net NotifyIcon for WPF +// Copyright (c) 2009 Philipp Sumi +// Contact and Information: http://www.hardcodet.net +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the Code Project Open License (CPOL); +// either version 1.0 of the License, or (at your option) any later +// version. +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// THIS COPYRIGHT NOTICE MAY NOT BE REMOVED FROM THIS FILE + + +using System; using System.ComponentModel; using System.Diagnostics; using System.Drawing; @@ -6,6 +30,7 @@ using System.Threading; using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; +using System.Windows.Threading; using Hardcodet.Wpf.TaskbarNotification.Interop; using Point=Hardcodet.Wpf.TaskbarNotification.Interop.Point; @@ -122,6 +147,8 @@ namespace Hardcodet.Wpf.TaskbarNotification #endregion + #region Custom Balloons + /// /// Shows a custom control as a tooltip in the tray location. /// @@ -133,6 +160,13 @@ namespace Hardcodet.Wpf.TaskbarNotification /// is a null reference. public void ShowCustomBalloon(UIElement balloon, PopupAnimation animation, int? timeout) { + if (!Application.Current.Dispatcher.CheckAccess()) + { + var action = new Action(() => ShowCustomBalloon(balloon, animation, timeout)); + Application.Current.Dispatcher.Invoke(DispatcherPriority.Normal, action); + return; + } + if (balloon == null) throw new ArgumentNullException("balloon"); if (timeout.HasValue && timeout < 500) { @@ -184,7 +218,7 @@ namespace Hardcodet.Wpf.TaskbarNotification SetParentTaskbarIcon(balloon, this); //fire attached event - RaiseBalloonShowingEvent(balloon); + RaiseBalloonShowingEvent(balloon, this); //display item popup.IsOpen = true; @@ -200,12 +234,39 @@ namespace Hardcodet.Wpf.TaskbarNotification /// - /// Closes the current , if it's set. + /// 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; + if (!Application.Current.Dispatcher.CheckAccess()) + { + Action action = CloseBalloon; + Application.Current.Dispatcher.Invoke(DispatcherPriority.Normal, action); + return; + } + lock (this) { //reset timer in any case @@ -215,13 +276,23 @@ namespace Hardcodet.Wpf.TaskbarNotification Popup popup = CustomBalloon; if (popup != null) { - //if a balloon message is already displayed, close it immediately - popup.IsOpen = false; - - //reset attached property UIElement element = popup.Child; - if (element != null) SetParentTaskbarIcon(element, null); + //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; + + //reset attached property + if (element != null) SetParentTaskbarIcon(element, null); + } + + //remove custom balloon anyway SetCustomBalloon(null); } } @@ -241,6 +312,9 @@ namespace Hardcodet.Wpf.TaskbarNotification Application.Current.Dispatcher.Invoke(action); } + #endregion + + #region Process Incoming Mouse Events @@ -405,7 +479,8 @@ namespace Hardcodet.Wpf.TaskbarNotification 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. + //TaskbarIcon's parent is hidden, too. At runtime, the parent can be resolved through + //the ParentTaskbarIcon attached dependency property: //tt.PlacementTarget = this; //the tooltip (and implicitly its context) explicitly gets @@ -478,13 +553,10 @@ namespace Hardcodet.Wpf.TaskbarNotification /// property which prevents this issue. private void CreatePopup() { - //no popup is available - if (TrayPopup == null) return; - //check if the item itself is a popup Popup popup = TrayPopup as Popup; - if (popup == null) + if (popup == null && TrayPopup != null) { //create an invisible popup that hosts the UIElement popup = new Popup(); @@ -501,8 +573,9 @@ namespace Hardcodet.Wpf.TaskbarNotification Popup.CreateRootPopup(popup, TrayPopup); - //TODO we don't really need this and it causes the popup to become hidden if the - //TaskbarIcon's parent is hidden, too. + //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; diff --git a/Source/NotifyIconWpf/Util.cs b/Source/NotifyIconWpf/Util.cs index 4b2acc4..8691996 100644 --- a/Source/NotifyIconWpf/Util.cs +++ b/Source/NotifyIconWpf/Util.cs @@ -1,7 +1,33 @@ -using System; +// hardcodet.net NotifyIcon for WPF +// Copyright (c) 2009 Philipp Sumi +// Contact and Information: http://www.hardcodet.net +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the Code Project Open License (CPOL); +// either version 1.0 of the License, or (at your option) any later +// version. +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// THIS COPYRIGHT NOTICE MAY NOT BE REMOVED FROM THIS FILE + + + +using System; using System.ComponentModel; using System.Drawing; using System.Windows; +using System.Windows.Input; using System.Windows.Media; using System.Windows.Resources; using Hardcodet.Wpf.TaskbarNotification.Interop; @@ -222,5 +248,27 @@ namespace Hardcodet.Wpf.TaskbarNotification } #endregion + + + #region execute command + + /// + /// Executes a given command if its method + /// indicates it can run. + /// + /// The command to be executed, or a null reference. + /// An optional parameter that is associated with + /// the command. + public static void ExecuteIfEnabled(this ICommand command, object commandParameter) + { + if (command == null) return; + if (command.CanExecute(commandParameter)) + { + command.Execute(commandParameter); + } + } + + #endregion + } } diff --git a/Source/Sample Project/FancyBalloon.xaml b/Source/Sample Project/FancyBalloon.xaml index da723bb..b7339d5 100644 --- a/Source/Sample Project/FancyBalloon.xaml +++ b/Source/Sample Project/FancyBalloon.xaml @@ -12,7 +12,7 @@ - + @@ -27,10 +27,22 @@ + + + + + + + + + + + + - + @@ -38,8 +50,15 @@ + + + + + + + - + - + diff --git a/Source/Sample Project/FancyBalloon.xaml.cs b/Source/Sample Project/FancyBalloon.xaml.cs index ccda58e..23c97cb 100644 --- a/Source/Sample Project/FancyBalloon.xaml.cs +++ b/Source/Sample Project/FancyBalloon.xaml.cs @@ -4,10 +4,12 @@ using System.Linq; using System.Text; using System.Windows; using System.Windows.Controls; +using System.Windows.Controls.Primitives; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; +using System.Windows.Media.Animation; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; @@ -20,6 +22,8 @@ namespace Sample_Project /// public partial class FancyBalloon : UserControl { + private bool isClosing = false; + #region BalloonText dependency property /// @@ -48,6 +52,19 @@ namespace Sample_Project public FancyBalloon() { InitializeComponent(); + TaskbarIcon.AddBalloonClosingHandler(this, OnBalloonClosing); + } + + + /// + /// By subscribing to the + /// and setting the "Handled" property to true, we suppress the popup + /// from being closed in order to display the fade-out animation. + /// + private void OnBalloonClosing(object sender, RoutedEventArgs e) + { + e.Handled = true; + isClosing = true; } @@ -61,5 +78,31 @@ namespace Sample_Project TaskbarIcon taskbarIcon = TaskbarIcon.GetParentTaskbarIcon(this); taskbarIcon.CloseBalloon(); } + + /// + /// If the users hovers over the balloon, we don't close it. + /// + private void grid_MouseEnter(object sender, MouseEventArgs e) + { + //if we're already running the fade-out animation, do not interrupt anymore + //(makes things too complicated for the sample) + if (isClosing) return; + + //the tray icon assigned this attached property to simplify access + TaskbarIcon taskbarIcon = TaskbarIcon.GetParentTaskbarIcon(this); + taskbarIcon.ResetBalloonCloseTimer(); + } + + + /// + /// Closes the popup once the fade-out animation completed. + /// The animation was triggered in XAML through the attached + /// BalloonClosing event. + /// + private void OnFadeOutCompleted(object sender, EventArgs e) + { + Popup pp = (Popup)Parent; + pp.IsOpen = false; + } } } diff --git a/Source/Sample Project/Images/Close.png b/Source/Sample Project/Images/Close.png new file mode 100644 index 0000000..7b03ffc Binary files /dev/null and b/Source/Sample Project/Images/Close.png differ diff --git a/Source/Sample Project/Window1.xaml b/Source/Sample Project/Window1.xaml index bdd10f5..66acba9 100644 --- a/Source/Sample Project/Window1.xaml +++ b/Source/Sample Project/Window1.xaml @@ -11,7 +11,7 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" xmlns:local="clr-namespace:Sample_Project" - xmlns:Commands="clr-namespace:Sample_Project.Commands" MinWidth="750" MinHeight="800"> + xmlns:Commands="clr-namespace:Sample_Project.Commands" MinWidth="750" MinHeight="800" ResizeMode="NoResize"> + x:Name="ToolTips" Height="233" VerticalAlignment="Top" d:LayoutOverrides="VerticalAlignment">