From cbb0bd0fc1e01067ab2e69fe8733d179b27a7300 Mon Sep 17 00:00:00 2001 From: NaBian <836904362@qq.com> Date: Sat, 2 Mar 2019 14:38:13 +0800 Subject: [PATCH 1/9] init pr add-notifyicon --- HandyControl/Controls/Other/NotifyIcon.cs | 31 ++++++++++++++++++ HandyControl/HandyControl.csproj | 1 + HandyControl/Tools/Helper/ExternDllHelper.cs | 29 +++++++++++++++- HandyControlDemo/Data/MessageToken.cs | 2 ++ HandyControlDemo/Data/MessageToken.tt | 3 +- HandyControlDemo/HandyControlDemo.csproj | 8 +++++ .../Properties/Langs/Lang.Designer.cs | 11 ++++++- .../Properties/Langs/Lang.en.resx | 3 ++ HandyControlDemo/Properties/Langs/Lang.resx | 3 ++ .../LeftMainContent/NotificationHub_16x.png | Bin 0 -> 335 bytes .../Controls/ChatBubbleDemoCtl.xaml | 5 +-- .../Controls/NotifyIconDemoCtl.xaml | 8 +++++ .../Controls/NotifyIconDemoCtl.xaml.cs | 10 ++++++ .../UserControl/Main/LeftMainContent.xaml | 9 +++-- 14 files changed, 116 insertions(+), 7 deletions(-) create mode 100644 HandyControl/Controls/Other/NotifyIcon.cs create mode 100644 HandyControlDemo/Resources/Img/LeftMainContent/NotificationHub_16x.png create mode 100644 HandyControlDemo/UserControl/Controls/NotifyIconDemoCtl.xaml create mode 100644 HandyControlDemo/UserControl/Controls/NotifyIconDemoCtl.xaml.cs diff --git a/HandyControl/Controls/Other/NotifyIcon.cs b/HandyControl/Controls/Other/NotifyIcon.cs new file mode 100644 index 00000000..c792ac4f --- /dev/null +++ b/HandyControl/Controls/Other/NotifyIcon.cs @@ -0,0 +1,31 @@ +using System.Windows; +using System.Windows.Controls.Primitives; +using System.Windows.Media; +using HandyControl.Tools; + +namespace HandyControl.Controls +{ + public class NotifyIcon : FrameworkElement + { + private Popup _trayPopup; + + private readonly object _syncObj = new object(); + + public static readonly DependencyProperty IconProperty = DependencyProperty.Register( + "Icon", typeof(ImageSource), typeof(NotifyIcon), new PropertyMetadata(default(ImageSource))); + + public ImageSource Icon + { + get => (ImageSource) GetValue(IconProperty); + set => SetValue(IconProperty, value); + } + + private void UpdateIcon(bool showIconInTray) + { + lock (_syncObj) + { + if (DesignerHelper.IsInDesignMode) return; + } + } + } +} \ No newline at end of file diff --git a/HandyControl/HandyControl.csproj b/HandyControl/HandyControl.csproj index efbf174e..9168fd2f 100644 --- a/HandyControl/HandyControl.csproj +++ b/HandyControl/HandyControl.csproj @@ -112,6 +112,7 @@ + diff --git a/HandyControl/Tools/Helper/ExternDllHelper.cs b/HandyControl/Tools/Helper/ExternDllHelper.cs index d79faab7..345abe7a 100644 --- a/HandyControl/Tools/Helper/ExternDllHelper.cs +++ b/HandyControl/Tools/Helper/ExternDllHelper.cs @@ -10,13 +10,40 @@ using System.Threading; namespace HandyControl.Tools { + [SuppressMessage("ReSharper", "InconsistentNaming")] internal class ExternDllHelper { private const string Gdi32 = "gdi32.dll"; private const string User32 = "user32.dll"; - // ReSharper disable once InconsistentNaming + private const string Shell32 = "shell32.dll"; + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + public class NOTIFYICONDATA + { + public int cbSize = Marshal.SizeOf(typeof(NOTIFYICONDATA)); + public IntPtr hWnd; + public int uID; + public int uFlags; + public int uCallbackMessage; + public IntPtr hIcon; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] + public string szTip; + public int dwState = 0; + public int dwStateMask = 0; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)] + public string szInfo; + public int uTimeoutOrVersion; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)] + public string szInfoTitle; + public int dwInfoFlags; + } + + [DllImport(Shell32, CharSet = CharSet.Auto)] + [ResourceExposure(ResourceScope.None)] + public static extern int Shell_NotifyIcon(int message, NOTIFYICONDATA pnid); + public const int E_FAIL = unchecked((int) 0x80004005); [StructLayout(LayoutKind.Sequential)] diff --git a/HandyControlDemo/Data/MessageToken.cs b/HandyControlDemo/Data/MessageToken.cs index e63c9770..85aa8546 100644 --- a/HandyControlDemo/Data/MessageToken.cs +++ b/HandyControlDemo/Data/MessageToken.cs @@ -84,6 +84,8 @@ public static readonly string GotoTopDemoCtl = nameof(GotoTopDemoCtl); + public static readonly string NotifyIconDemoCtl = nameof(NotifyIconDemoCtl); + public static readonly string ButtonDemoCtl = nameof(ButtonDemoCtl); public static readonly string ToggleButtonDemoCtl = nameof(ToggleButtonDemoCtl); diff --git a/HandyControlDemo/Data/MessageToken.tt b/HandyControlDemo/Data/MessageToken.tt index 377d9b64..ffdba442 100644 --- a/HandyControlDemo/Data/MessageToken.tt +++ b/HandyControlDemo/Data/MessageToken.tt @@ -44,7 +44,8 @@ var controlList = new List "ProgressButtonDemoCtl", "TransferDemoCtl", "ChatBubbleDemoCtl", - "GotoTopDemoCtl" + "GotoTopDemoCtl", + "NotifyIconDemoCtl" }; var styleList = new List { diff --git a/HandyControlDemo/HandyControlDemo.csproj b/HandyControlDemo/HandyControlDemo.csproj index fb8411db..998e6cf8 100644 --- a/HandyControlDemo/HandyControlDemo.csproj +++ b/HandyControlDemo/HandyControlDemo.csproj @@ -178,6 +178,9 @@ GotoTopDemoCtl.xaml + + NotifyIconDemoCtl.xaml + OutlineTextDemoCtl.xaml @@ -495,6 +498,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + Designer MSBuild:Compile @@ -753,6 +760,7 @@ TextTemplatingFileGenerator MessageToken.cs + diff --git a/HandyControlDemo/Properties/Langs/Lang.Designer.cs b/HandyControlDemo/Properties/Langs/Lang.Designer.cs index 44d750f8..a031bfb0 100644 --- a/HandyControlDemo/Properties/Langs/Lang.Designer.cs +++ b/HandyControlDemo/Properties/Langs/Lang.Designer.cs @@ -47,7 +47,7 @@ namespace HandyControlDemo.Properties.Langs { } /// - /// 重写当前线程的 CurrentUICulture 属性 + /// 使用此强类型资源类,为所有资源查找 /// 重写当前线程的 CurrentUICulture 属性。 /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] @@ -645,6 +645,15 @@ namespace HandyControlDemo.Properties.Langs { } } + /// + /// 查找类似 托盘图标 的本地化字符串。 + /// + public static string NotifyIcon { + get { + return ResourceManager.GetString("NotifyIcon", resourceCulture); + } + } + /// /// 查找类似 数值选择控件 的本地化字符串。 /// diff --git a/HandyControlDemo/Properties/Langs/Lang.en.resx b/HandyControlDemo/Properties/Langs/Lang.en.resx index 3f57a795..ff6aa4cf 100644 --- a/HandyControlDemo/Properties/Langs/Lang.en.resx +++ b/HandyControlDemo/Properties/Langs/Lang.en.resx @@ -501,4 +501,7 @@ GotoTop + + NotifyIcon + \ No newline at end of file diff --git a/HandyControlDemo/Properties/Langs/Lang.resx b/HandyControlDemo/Properties/Langs/Lang.resx index 32ab263a..00755530 100644 --- a/HandyControlDemo/Properties/Langs/Lang.resx +++ b/HandyControlDemo/Properties/Langs/Lang.resx @@ -501,4 +501,7 @@ 回到顶部 + + 托盘图标 + \ No newline at end of file diff --git a/HandyControlDemo/Resources/Img/LeftMainContent/NotificationHub_16x.png b/HandyControlDemo/Resources/Img/LeftMainContent/NotificationHub_16x.png new file mode 100644 index 0000000000000000000000000000000000000000..49c9764453d18d9b84562dbe254b7a6f356ca531 GIT binary patch literal 335 zcmV-V0kHmwP)u z_|zmqW=D6y_kFA$SIF`VX`1fc1?)ny*7LlU-LRH(-U|5z_F!Ow`N#oHq>q7@g hE(rvGmSruV;2%Mrbk^x1KJNek002ovPDHLkV1j%{jmiK3 literal 0 HcmV?d00001 diff --git a/HandyControlDemo/UserControl/Controls/ChatBubbleDemoCtl.xaml b/HandyControlDemo/UserControl/Controls/ChatBubbleDemoCtl.xaml index b9b99343..285330c6 100644 --- a/HandyControlDemo/UserControl/Controls/ChatBubbleDemoCtl.xaml +++ b/HandyControlDemo/UserControl/Controls/ChatBubbleDemoCtl.xaml @@ -2,8 +2,9 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:userControl="clr-namespace:HandyControlDemo.UserControl" + xmlns:controls="clr-namespace:HandyControl.Controls;assembly=HandyControl" Background="{DynamicResource RegionBrush}"> - + @@ -16,5 +17,5 @@ - + diff --git a/HandyControlDemo/UserControl/Controls/NotifyIconDemoCtl.xaml b/HandyControlDemo/UserControl/Controls/NotifyIconDemoCtl.xaml new file mode 100644 index 00000000..dc1085aa --- /dev/null +++ b/HandyControlDemo/UserControl/Controls/NotifyIconDemoCtl.xaml @@ -0,0 +1,8 @@ + + + + + diff --git a/HandyControlDemo/UserControl/Controls/NotifyIconDemoCtl.xaml.cs b/HandyControlDemo/UserControl/Controls/NotifyIconDemoCtl.xaml.cs new file mode 100644 index 00000000..93c2bd58 --- /dev/null +++ b/HandyControlDemo/UserControl/Controls/NotifyIconDemoCtl.xaml.cs @@ -0,0 +1,10 @@ +namespace HandyControlDemo.UserControl +{ + public partial class NotifyIconDemoCtl + { + public NotifyIconDemoCtl() + { + InitializeComponent(); + } + } +} diff --git a/HandyControlDemo/UserControl/Main/LeftMainContent.xaml b/HandyControlDemo/UserControl/Main/LeftMainContent.xaml index 9828b9ef..07b35890 100644 --- a/HandyControlDemo/UserControl/Main/LeftMainContent.xaml +++ b/HandyControlDemo/UserControl/Main/LeftMainContent.xaml @@ -203,6 +203,11 @@ + + + + + @@ -303,7 +308,7 @@ - + @@ -378,7 +383,7 @@ - + From ae28d7d603bbdf72ef536f5fce3c545d5f39a96a Mon Sep 17 00:00:00 2001 From: NaBian <836904362@qq.com> Date: Wed, 13 Mar 2019 19:53:18 +0800 Subject: [PATCH 2/9] update ExternDllHelper --- HandyControl/Tools/Helper/ExternDllHelper.cs | 17 +++++++++++++++++ HandyControlDemo/HandyControlDemo.csproj | 1 + 2 files changed, 18 insertions(+) diff --git a/HandyControl/Tools/Helper/ExternDllHelper.cs b/HandyControl/Tools/Helper/ExternDllHelper.cs index 345abe7a..77a55d5f 100644 --- a/HandyControl/Tools/Helper/ExternDllHelper.cs +++ b/HandyControl/Tools/Helper/ExternDllHelper.cs @@ -19,6 +19,8 @@ namespace HandyControl.Tools private const string Shell32 = "shell32.dll"; + public delegate IntPtr WndProc(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam); + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] public class NOTIFYICONDATA { @@ -40,6 +42,21 @@ namespace HandyControl.Tools public int dwInfoFlags; } + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + public class WNDCLASS_D + { + public int style; + public WndProc lpfnWndProc; + public int cbClsExtra = 0; + public int cbWndExtra = 0; + public IntPtr hInstance = IntPtr.Zero; + public IntPtr hIcon = IntPtr.Zero; + public IntPtr hCursor = IntPtr.Zero; + public IntPtr hbrBackground = IntPtr.Zero; + public string lpszMenuName = null; + public string lpszClassName = null; + } + [DllImport(Shell32, CharSet = CharSet.Auto)] [ResourceExposure(ResourceScope.None)] public static extern int Shell_NotifyIcon(int message, NOTIFYICONDATA pnid); diff --git a/HandyControlDemo/HandyControlDemo.csproj b/HandyControlDemo/HandyControlDemo.csproj index 9c659dd1..4231f0f6 100644 --- a/HandyControlDemo/HandyControlDemo.csproj +++ b/HandyControlDemo/HandyControlDemo.csproj @@ -189,6 +189,7 @@ NotifyIconDemoCtl.xaml + GravatarDemoCtl.xaml From ce18931d5d1fd8bdfbd9f7d103bfdad2a0bfba9f Mon Sep 17 00:00:00 2001 From: NaBian <836904362@qq.com> Date: Mon, 25 Mar 2019 23:50:57 +0800 Subject: [PATCH 3/9] dev 19-3-25 --- .../Controls/NotifyIcon/NotifyIcon.cs | 152 +++++++++ .../NotifyIcon/NotifyIconNativeWindow.cs | 29 ++ HandyControl/Controls/Other/NotifyIcon.cs | 31 -- HandyControl/Data/Enum/ToolTipIcon.cs | 10 + HandyControl/Data/Icon.cs | 41 +++ HandyControl/HandyControl.csproj | 24 +- HandyControl/Interactivity/EventToCommand.cs | 2 +- HandyControl/Tools/Helper/CreateParams.cs | 54 ++++ HandyControl/Tools/Helper/ExternDllHelper.cs | 51 +-- HandyControl/Tools/Helper/IconHelper.cs | 292 ++++++++++++++++++ HandyControl/Tools/Helper/SecurityHelper.cs | 20 ++ HandyControl/Tools/Interop/BITMAPINFO.cs | 52 ++++ HandyControl/Tools/Interop/BitmapHandle.cs | 37 +++ HandyControl/Tools/Interop/CommonHandles.cs | 13 + HandyControl/Tools/Interop/ExternDll.cs | 11 + HandyControl/Tools/Interop/HandleCollector.cs | 132 ++++++++ HandyControl/Tools/Interop/ICONINFO.cs | 14 + HandyControl/Tools/Interop/IconHandle.cs | 33 ++ HandyControl/Tools/Interop/IntSecurity.cs | 21 ++ HandyControl/Tools/Interop/NOTIFYICONDATA.cs | 28 ++ HandyControl/Tools/Interop/NativeMethods.cs | 37 +++ HandyControl/Tools/Interop/NativeValues.cs | 98 ++++++ .../Tools/Interop/SafeFileMappingHandle.cs | 50 +++ .../Tools/Interop/UnsafeNativeMethods.cs | 173 +++++++++++ HandyControl/Tools/Interop/WNDCLASS.cs | 23 ++ HandyControl/Tools/Interop/WindowClass.cs | 103 ++++++ HandyControl/Tools/Interop/WpfSafeHandle.cs | 24 ++ .../Properties/Langs/Lang.en.resx | 2 +- HandyControlDemo/Properties/Langs/Lang.resx | 2 +- 29 files changed, 1474 insertions(+), 85 deletions(-) create mode 100644 HandyControl/Controls/NotifyIcon/NotifyIcon.cs create mode 100644 HandyControl/Controls/NotifyIcon/NotifyIconNativeWindow.cs delete mode 100644 HandyControl/Controls/Other/NotifyIcon.cs create mode 100644 HandyControl/Data/Enum/ToolTipIcon.cs create mode 100644 HandyControl/Data/Icon.cs create mode 100644 HandyControl/Tools/Helper/CreateParams.cs create mode 100644 HandyControl/Tools/Helper/IconHelper.cs create mode 100644 HandyControl/Tools/Helper/SecurityHelper.cs create mode 100644 HandyControl/Tools/Interop/BITMAPINFO.cs create mode 100644 HandyControl/Tools/Interop/BitmapHandle.cs create mode 100644 HandyControl/Tools/Interop/CommonHandles.cs create mode 100644 HandyControl/Tools/Interop/ExternDll.cs create mode 100644 HandyControl/Tools/Interop/HandleCollector.cs create mode 100644 HandyControl/Tools/Interop/ICONINFO.cs create mode 100644 HandyControl/Tools/Interop/IconHandle.cs create mode 100644 HandyControl/Tools/Interop/IntSecurity.cs create mode 100644 HandyControl/Tools/Interop/NOTIFYICONDATA.cs create mode 100644 HandyControl/Tools/Interop/NativeMethods.cs create mode 100644 HandyControl/Tools/Interop/NativeValues.cs create mode 100644 HandyControl/Tools/Interop/SafeFileMappingHandle.cs create mode 100644 HandyControl/Tools/Interop/UnsafeNativeMethods.cs create mode 100644 HandyControl/Tools/Interop/WNDCLASS.cs create mode 100644 HandyControl/Tools/Interop/WindowClass.cs create mode 100644 HandyControl/Tools/Interop/WpfSafeHandle.cs diff --git a/HandyControl/Controls/NotifyIcon/NotifyIcon.cs b/HandyControl/Controls/NotifyIcon/NotifyIcon.cs new file mode 100644 index 00000000..a2717693 --- /dev/null +++ b/HandyControl/Controls/NotifyIcon/NotifyIcon.cs @@ -0,0 +1,152 @@ +using System.Windows; +using System.Windows.Media; +using HandyControl.Data; +using HandyControl.Tools; +using HandyControl.Tools.Interop; + +namespace HandyControl.Controls +{ + public class NotifyIcon : FrameworkElement + { + private bool _added; + + private readonly object _syncObj = new object(); + + private WindowClass _windowClass; + + private readonly int _id; + + private static int NextId; + + private ImageSource _icon; + + private IconHandle _defaultLargeIconHandle; + + private IconHandle _defaultSmallIconHandle; + + private IconHandle _currentLargeIconHandle; + + private IconHandle _currentSmallIconHandle; + + private const int WmTrayMouseMessage = NativeMethods.WM_USER + 1024; + + public NotifyIcon() + { + _id = ++NextId; + _windowClass = new WindowClass(); + } + + public static readonly DependencyProperty IconProperty = DependencyProperty.Register( + "Icon", typeof(ImageSource), typeof(NotifyIcon), new PropertyMetadata(default(ImageSource), OnIconChanged)); + + private static void OnIconChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var ctl = (NotifyIcon) d; + ctl._icon = (ImageSource) e.NewValue; + ctl.UpdateIcon(); + } + + public ImageSource Icon + { + get => (ImageSource) GetValue(IconProperty); + set => SetValue(IconProperty, value); + } + + private void UpdateIcon() + { + IconHandle largeIconHandle; + IconHandle smallIconHandle; + + if (_icon != null) + { + IconHelper.GetIconHandlesFromImageSource(_icon, out largeIconHandle, out smallIconHandle); + } + else + { + if (_defaultLargeIconHandle == null && _defaultSmallIconHandle == null) + { + IconHelper.GetDefaultIconHandles(out largeIconHandle, out smallIconHandle); + _defaultLargeIconHandle = largeIconHandle; + _defaultSmallIconHandle = smallIconHandle; + } + else + { + largeIconHandle = _defaultLargeIconHandle; + smallIconHandle = _defaultSmallIconHandle; + } + } + + if (_currentLargeIconHandle != null && _currentLargeIconHandle != _defaultLargeIconHandle) + { + _currentLargeIconHandle.Dispose(); + } + + if (_currentSmallIconHandle != null && _currentSmallIconHandle != _defaultSmallIconHandle) + { + _currentSmallIconHandle.Dispose(); + } + + _currentLargeIconHandle = largeIconHandle; + _currentSmallIconHandle = smallIconHandle; + } + + public static readonly DependencyProperty BalloonTipIconProperty = DependencyProperty.Register( + "BalloonTipIcon", typeof(ToolTipIcon), typeof(NotifyIcon), new PropertyMetadata(default(ToolTipIcon))); + + public ToolTipIcon BalloonTipIcon + { + get => (ToolTipIcon) GetValue(BalloonTipIconProperty); + set => SetValue(BalloonTipIconProperty, value); + } + + public static readonly DependencyProperty BalloonTipTitleProperty = DependencyProperty.Register( + "BalloonTipTitle", typeof(string), typeof(NotifyIcon), new PropertyMetadata(default(string))); + + public string BalloonTipTitle + { + get => (string) GetValue(BalloonTipTitleProperty); + set => SetValue(BalloonTipTitleProperty, value); + } + + public static readonly DependencyProperty TextProperty = DependencyProperty.Register( + "Text", typeof(string), typeof(NotifyIcon), new PropertyMetadata(default(string))); + + public string Text + { + get => (string) GetValue(TextProperty); + set => SetValue(TextProperty, value); + } + + public void ShowBalloonTip(int timeout, string tipTitle, string tipText, ToolTipIcon tipIcon) + { + if (timeout < 0) return; + if (string.IsNullOrEmpty(tipTitle)) return; + + if (_added) + { + if (DesignerHelper.IsInDesignMode) return; + + IntSecurity.UnrestrictedWindows.Demand(); + + var data = new NOTIFYICONDATA + { + hWnd = _windowClass.MessageWindowHandle, + uID = _id, + uFlags = NativeMethods.NIF_INFO, + uTimeoutOrVersion = timeout, + szInfoTitle = tipTitle, + szInfo = tipText + }; + + switch (tipIcon) + { + case ToolTipIcon.Info: data.dwInfoFlags = NativeMethods.NIIF_INFO; break; + case ToolTipIcon.Warning: data.dwInfoFlags = NativeMethods.NIIF_WARNING; break; + case ToolTipIcon.Error: data.dwInfoFlags = NativeMethods.NIIF_ERROR; break; + case ToolTipIcon.None: data.dwInfoFlags = NativeMethods.NIIF_NONE; break; + } + UnsafeNativeMethods.Shell_NotifyIcon(NativeMethods.NIM_MODIFY, data); + } + } + } +} \ No newline at end of file diff --git a/HandyControl/Controls/NotifyIcon/NotifyIconNativeWindow.cs b/HandyControl/Controls/NotifyIcon/NotifyIconNativeWindow.cs new file mode 100644 index 00000000..1bcf1a11 --- /dev/null +++ b/HandyControl/Controls/NotifyIcon/NotifyIconNativeWindow.cs @@ -0,0 +1,29 @@ +using System.Runtime.InteropServices; + +namespace HandyControl.Controls +{ + internal class NotifyIconNativeWindow + { + internal NotifyIcon Reference; + + private GCHandle _rootRef; + + public void LockReference(bool locked) + { + if (locked) + { + if (!_rootRef.IsAllocated) + { + _rootRef = GCHandle.Alloc(Reference, GCHandleType.Normal); + } + } + else + { + if (_rootRef.IsAllocated) + { + _rootRef.Free(); + } + } + } + } +} \ No newline at end of file diff --git a/HandyControl/Controls/Other/NotifyIcon.cs b/HandyControl/Controls/Other/NotifyIcon.cs deleted file mode 100644 index c792ac4f..00000000 --- a/HandyControl/Controls/Other/NotifyIcon.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.Windows; -using System.Windows.Controls.Primitives; -using System.Windows.Media; -using HandyControl.Tools; - -namespace HandyControl.Controls -{ - public class NotifyIcon : FrameworkElement - { - private Popup _trayPopup; - - private readonly object _syncObj = new object(); - - public static readonly DependencyProperty IconProperty = DependencyProperty.Register( - "Icon", typeof(ImageSource), typeof(NotifyIcon), new PropertyMetadata(default(ImageSource))); - - public ImageSource Icon - { - get => (ImageSource) GetValue(IconProperty); - set => SetValue(IconProperty, value); - } - - private void UpdateIcon(bool showIconInTray) - { - lock (_syncObj) - { - if (DesignerHelper.IsInDesignMode) return; - } - } - } -} \ No newline at end of file diff --git a/HandyControl/Data/Enum/ToolTipIcon.cs b/HandyControl/Data/Enum/ToolTipIcon.cs new file mode 100644 index 00000000..d14beaed --- /dev/null +++ b/HandyControl/Data/Enum/ToolTipIcon.cs @@ -0,0 +1,10 @@ +namespace HandyControl.Data +{ + public enum ToolTipIcon + { + None, + Info, + Warning, + Error + } +} \ No newline at end of file diff --git a/HandyControl/Data/Icon.cs b/HandyControl/Data/Icon.cs new file mode 100644 index 00000000..d962da56 --- /dev/null +++ b/HandyControl/Data/Icon.cs @@ -0,0 +1,41 @@ +using HandyControl.Tools.Interop; +using System; +using System.Diagnostics.CodeAnalysis; + +namespace HandyControl.Data +{ + public sealed class Icon : IDisposable + { + private IntPtr _handle; + + ~Icon() + { + Dispose(false); + } + + internal Icon(IntPtr handle) + { + if (handle == IntPtr.Zero) + { + throw new ArgumentException("InvalidGDIHandle", typeof(Icon).Name); + } + _handle = handle; + } + + [SuppressMessage("ReSharper", "UnusedParameter.Local")] + private void Dispose(bool disposing) + { + if (_handle != IntPtr.Zero) + { + UnsafeNativeMethods.DestroyIcon(_handle); + _handle = IntPtr.Zero; + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + } +} \ No newline at end of file diff --git a/HandyControl/HandyControl.csproj b/HandyControl/HandyControl.csproj index 3cb91255..b4fcfd7b 100644 --- a/HandyControl/HandyControl.csproj +++ b/HandyControl/HandyControl.csproj @@ -115,8 +115,9 @@ - + + @@ -172,11 +173,13 @@ + + @@ -224,8 +227,13 @@ + + + + + @@ -305,6 +313,20 @@ + + + + + + + + + + + + + + diff --git a/HandyControl/Interactivity/EventToCommand.cs b/HandyControl/Interactivity/EventToCommand.cs index de449aa5..dca2bfae 100644 --- a/HandyControl/Interactivity/EventToCommand.cs +++ b/HandyControl/Interactivity/EventToCommand.cs @@ -1,4 +1,4 @@ -/*refrence from https://github.com/lbugnion/mvvmlight*/ +// reference from https://github.com/lbugnion/mvvmlight //The MIT License(MIT) diff --git a/HandyControl/Tools/Helper/CreateParams.cs b/HandyControl/Tools/Helper/CreateParams.cs new file mode 100644 index 00000000..03f4d550 --- /dev/null +++ b/HandyControl/Tools/Helper/CreateParams.cs @@ -0,0 +1,54 @@ +using System; +using System.Text; + +namespace HandyControl.Tools +{ + internal class CreateParams + { + public string ClassName { get; set; } + + public string Caption { get; set; } + + public int Style { get; set; } + + public int ExStyle{ get; set; } + + public int ClassStyle { get; set; } + + public int X { get; set; } + + public int Y { get; set; } + + public int Width { get; set; } + + public int Height { get; set; } + + public IntPtr Parent { get; set; } + + public object Param { get; set; } + + public override string ToString() + { + var sb = new StringBuilder(64); + sb.Append("CreateParams {'"); + sb.Append(ClassName); + sb.Append("', '"); + sb.Append(Caption); + sb.Append("', 0x"); + sb.Append(Convert.ToString(Style, 16)); + sb.Append(", 0x"); + sb.Append(Convert.ToString(ExStyle, 16)); + sb.Append(", {"); + sb.Append(X); + sb.Append(", "); + sb.Append(Y); + sb.Append(", "); + sb.Append(Width); + sb.Append(", "); + sb.Append(Height); + sb.Append("}"); + sb.Append("}"); + return sb.ToString(); + } + } +} \ No newline at end of file diff --git a/HandyControl/Tools/Helper/ExternDllHelper.cs b/HandyControl/Tools/Helper/ExternDllHelper.cs index 77a55d5f..858885dc 100644 --- a/HandyControl/Tools/Helper/ExternDllHelper.cs +++ b/HandyControl/Tools/Helper/ExternDllHelper.cs @@ -17,49 +17,7 @@ namespace HandyControl.Tools private const string User32 = "user32.dll"; - private const string Shell32 = "shell32.dll"; - - public delegate IntPtr WndProc(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam); - - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] - public class NOTIFYICONDATA - { - public int cbSize = Marshal.SizeOf(typeof(NOTIFYICONDATA)); - public IntPtr hWnd; - public int uID; - public int uFlags; - public int uCallbackMessage; - public IntPtr hIcon; - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] - public string szTip; - public int dwState = 0; - public int dwStateMask = 0; - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)] - public string szInfo; - public int uTimeoutOrVersion; - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)] - public string szInfoTitle; - public int dwInfoFlags; - } - - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] - public class WNDCLASS_D - { - public int style; - public WndProc lpfnWndProc; - public int cbClsExtra = 0; - public int cbWndExtra = 0; - public IntPtr hInstance = IntPtr.Zero; - public IntPtr hIcon = IntPtr.Zero; - public IntPtr hCursor = IntPtr.Zero; - public IntPtr hbrBackground = IntPtr.Zero; - public string lpszMenuName = null; - public string lpszClassName = null; - } - - [DllImport(Shell32, CharSet = CharSet.Auto)] - [ResourceExposure(ResourceScope.None)] - public static extern int Shell_NotifyIcon(int message, NOTIFYICONDATA pnid); + public delegate IntPtr WndProc(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam); public const int E_FAIL = unchecked((int) 0x80004005); @@ -114,7 +72,6 @@ namespace HandyControl.Tools [In] int len); - int Write( [In] IntPtr buf, @@ -128,7 +85,6 @@ namespace HandyControl.Tools [In] int dwOrigin); - void SetSize( [In, MarshalAs(UnmanagedType.I8)] long libNewSize); @@ -142,15 +98,12 @@ namespace HandyControl.Tools [Out, MarshalAs(UnmanagedType.LPArray)] long[] pcbRead); - void Commit( [In] int grfCommitFlags); - void Revert(); - void LockRegion( [In, MarshalAs(UnmanagedType.I8)] long libOffset, @@ -159,7 +112,6 @@ namespace HandyControl.Tools [In] int dwLockType); - void UnlockRegion( [In, MarshalAs(UnmanagedType.I8)] long libOffset, @@ -168,7 +120,6 @@ namespace HandyControl.Tools [In] int dwLockType); - void Stat( [In] IntPtr pStatstg, diff --git a/HandyControl/Tools/Helper/IconHelper.cs b/HandyControl/Tools/Helper/IconHelper.cs new file mode 100644 index 00000000..a45b146c --- /dev/null +++ b/HandyControl/Tools/Helper/IconHelper.cs @@ -0,0 +1,292 @@ +using System; +using System.Collections.ObjectModel; +using System.Runtime.InteropServices; +using System.Security; +using System.Windows; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using HandyControl.Tools.Interop; + +namespace HandyControl.Tools +{ + internal static class IconHelper + { + private static Size _smallIconSize; + private static Size _iconSize; + private static int _systemBitDepth; + + [SecurityCritical, SecuritySafeCritical] + public static void GetIconHandlesFromImageSource(ImageSource image, out IconHandle largeIconHandle, out IconHandle smallIconHandle) + { + EnsureSystemMetrics(); + largeIconHandle = CreateIconHandleFromImageSource(image, _iconSize); + smallIconHandle = CreateIconHandleFromImageSource(image, _smallIconSize); + } + + [SecurityCritical] + public static IconHandle CreateIconHandleFromImageSource(ImageSource image, Size size) + { + EnsureSystemMetrics(); + + var asGoodAsItGets = false; + + var bf = image as BitmapFrame; + if (bf?.Decoder?.Frames != null) + { + bf = GetBestMatch(bf.Decoder.Frames, size); + + asGoodAsItGets = bf.Decoder is IconBitmapDecoder || bf.PixelWidth == (int)size.Width && bf.PixelHeight == (int)size.Height; + + image = bf; + } + + if (!asGoodAsItGets) + { + bf = BitmapFrame.Create(GenerateBitmapSource(image, size)); + } + + return CreateIconHandleFromBitmapFrame(bf); + } + + [SecurityCritical] + private static IconHandle CreateIconHandleFromBitmapFrame(BitmapFrame sourceBitmapFrame) + { + BitmapSource bitmapSource = sourceBitmapFrame; + + if (bitmapSource.Format != PixelFormats.Bgra32 && bitmapSource.Format != PixelFormats.Pbgra32) + { + bitmapSource = new FormatConvertedBitmap(bitmapSource, PixelFormats.Bgra32, null, 0.0); + } + + var w = bitmapSource.PixelWidth; + var h = bitmapSource.PixelHeight; + var bpp = bitmapSource.Format.BitsPerPixel; + var stride = (bpp * w + 31) / 32 * 4; + var sizeCopyPixels = stride * h; + var xor = new byte[sizeCopyPixels]; + bitmapSource.CopyPixels(xor, stride, 0); + + return CreateIconCursor(xor, w, h, 0, 0, true); + } + + [SecurityCritical] + internal static IconHandle CreateIconCursor(byte[] colorArray, int width, int height, int xHotspot, int yHotspot, bool isIcon) + { + BitmapHandle colorBitmap = null; + BitmapHandle maskBitmap = null; + + try + { + var bi = new BITMAPINFO(width, -height, 32) + { + bmiHeader_biCompression = NativeMethods.BI_RGB + }; + + var bits = IntPtr.Zero; + colorBitmap = UnsafeNativeMethods.CreateDIBSection(new HandleRef(null, IntPtr.Zero), ref bi, NativeMethods.DIB_RGB_COLORS, ref bits, null, 0); + + if (colorBitmap.IsInvalid || bits == IntPtr.Zero) + { + return IconHandle.GetInvalidIcon(); + } + + Marshal.Copy(colorArray, 0, bits, colorArray.Length); + var maskArray = GenerateMaskArray(width, height, colorArray); + + maskBitmap = UnsafeNativeMethods.CreateBitmap(width, height, 1, 1, maskArray); + if (maskBitmap.IsInvalid) + { + return IconHandle.GetInvalidIcon(); + } + + var iconInfo = new ICONINFO + { + fIcon = isIcon, + xHotspot = xHotspot, + yHotspot = yHotspot, + hbmMask = maskBitmap, + hbmColor = colorBitmap + }; + + return UnsafeNativeMethods.CreateIconIndirect(iconInfo); + } + finally + { + colorBitmap?.Dispose(); + maskBitmap?.Dispose(); + } + } + + private static byte[] GenerateMaskArray(int width, int height, byte[] colorArray) + { + var nCount = width * height; + var bytesPerScanLine = AlignToBytes(width, 2) / 8; + var bitsMask = new byte[bytesPerScanLine * height]; + + for (var i = 0; i < nCount; i++) + { + var hPos = i % width; + var vPos = i / width; + var byteIndex = hPos / 8; + var offsetBit = (byte)(0x80 >> (hPos % 8)); + + if (colorArray[i * 4 + 3] == 0x00) + { + bitsMask[byteIndex + bytesPerScanLine * vPos] |= offsetBit; + } + else + { + bitsMask[byteIndex + bytesPerScanLine * vPos] &= (byte)~offsetBit; + } + + if (hPos == width - 1 && width == 8) + { + bitsMask[1 + bytesPerScanLine * vPos] = 0xff; + } + } + + return bitsMask; + } + + internal static int AlignToBytes(double original, int nBytesCount) + { + var nBitsCount = 8 << (nBytesCount - 1); + return ((int)Math.Ceiling(original) + (nBitsCount - 1)) / nBitsCount * nBitsCount; + } + + private static BitmapSource GenerateBitmapSource(ImageSource img, Size renderSize) + { + var drawingDimensions = new Rect(0, 0, renderSize.Width, renderSize.Height); + + var renderRatio = renderSize.Width / renderSize.Height; + var aspectRatio = img.Width / img.Height; + + if (img.Width <= renderSize.Width && img.Height <= renderSize.Height) + { + drawingDimensions = new Rect((renderSize.Width - img.Width) / 2, (renderSize.Height - img.Height) / 2, img.Width, img.Height); + } + else if (renderRatio > aspectRatio) + { + var scaledRenderWidth = (img.Width / img.Height) * renderSize.Width; + drawingDimensions = new Rect((renderSize.Width - scaledRenderWidth) / 2, 0, scaledRenderWidth, renderSize.Height); + } + else if (renderRatio < aspectRatio) + { + var scaledRenderHeight = img.Height / img.Width * renderSize.Height; + drawingDimensions = new Rect(0, (renderSize.Height - scaledRenderHeight) / 2, renderSize.Width, scaledRenderHeight); + } + + var dv = new DrawingVisual(); + var dc = dv.RenderOpen(); + dc.DrawImage(img, drawingDimensions); + dc.Close(); + + var bmp = new RenderTargetBitmap((int)renderSize.Width, (int)renderSize.Height, 96, 96, PixelFormats.Pbgra32); + bmp.Render(dv); + + return bmp; + } + + private static BitmapFrame GetBestMatch(ReadOnlyCollection frames, Size size) + { + var bestScore = int.MaxValue; + var bestBpp = 0; + var bestIndex = 0; + + var isBitmapIconDecoder = frames[0].Decoder is IconBitmapDecoder; + + for (int i = 0; i < frames.Count && bestScore != 0; ++i) + { + var currentIconBitDepth = isBitmapIconDecoder ? frames[i].Thumbnail.Format.BitsPerPixel : frames[i].Format.BitsPerPixel; + + if (currentIconBitDepth == 0) + { + currentIconBitDepth = 8; + } + + var score = MatchImage(frames[i], size, currentIconBitDepth); + if (score < bestScore) + { + bestIndex = i; + bestBpp = currentIconBitDepth; + bestScore = score; + } + else if (score == bestScore) + { + if (bestBpp < currentIconBitDepth) + { + bestIndex = i; + bestBpp = currentIconBitDepth; + } + } + } + + return frames[bestIndex]; + } + + private static int MatchImage(BitmapFrame frame, Size size, int bpp) + { + var score = 2 * MyAbs(bpp, _systemBitDepth, false) + + MyAbs(frame.PixelWidth, (int)size.Width, true) + + MyAbs(frame.PixelHeight, (int)size.Height, true); + + return score; + } + + private static int MyAbs(int valueHave, int valueWant, bool fPunish) + { + var diff = (valueHave - valueWant); + + if (diff < 0) + { + diff = (fPunish ? -2 : -1) * diff; + } + + return diff; + } + + [SecurityCritical, SecuritySafeCritical] + private static void EnsureSystemMetrics() + { + if (_systemBitDepth == 0) + { + var hdcDesktop = new HandleRef(null, UnsafeNativeMethods.GetDC(new HandleRef())); + try + { + var sysBitDepth = UnsafeNativeMethods.GetDeviceCaps(hdcDesktop, NativeMethods.BITSPIXEL); + sysBitDepth *= UnsafeNativeMethods.GetDeviceCaps(hdcDesktop, NativeMethods.PLANES); + + if (sysBitDepth == 8) + { + sysBitDepth = 4; + } + + var cxSmallIcon = UnsafeNativeMethods.GetSystemMetrics(SM.CXSMICON); + var cySmallIcon = UnsafeNativeMethods.GetSystemMetrics(SM.CYSMICON); + var cxIcon = UnsafeNativeMethods.GetSystemMetrics(SM.CXICON); + var cyIcon = UnsafeNativeMethods.GetSystemMetrics(SM.CYICON); + + _smallIconSize = new Size(cxSmallIcon, cySmallIcon); + _iconSize = new Size(cxIcon, cyIcon); + _systemBitDepth = sysBitDepth; + } + finally + { + UnsafeNativeMethods.ReleaseDC(new HandleRef(), hdcDesktop); + } + } + } + + [SecurityCritical, SecuritySafeCritical] + public static void GetDefaultIconHandles(out IconHandle largeIconHandle, out IconHandle smallIconHandle) + { + largeIconHandle = null; + smallIconHandle = null; + + SecurityHelper.DemandUIWindowPermission(); + + var iconModuleFile = UnsafeNativeMethods.GetModuleFileName(new HandleRef()); + UnsafeNativeMethods.ExtractIconEx(iconModuleFile, 0, out largeIconHandle, out smallIconHandle, 1); + } + } +} \ No newline at end of file diff --git a/HandyControl/Tools/Helper/SecurityHelper.cs b/HandyControl/Tools/Helper/SecurityHelper.cs new file mode 100644 index 00000000..af582881 --- /dev/null +++ b/HandyControl/Tools/Helper/SecurityHelper.cs @@ -0,0 +1,20 @@ +using System.Security; +using System.Security.Permissions; + +namespace HandyControl.Tools +{ + internal class SecurityHelper + { + private static UIPermission _allWindowsUIPermission; + + [SecurityCritical] + internal static void DemandUIWindowPermission() + { + if (_allWindowsUIPermission == null) + { + _allWindowsUIPermission = new UIPermission(UIPermissionWindow.AllWindows); + } + _allWindowsUIPermission.Demand(); + } + } +} \ No newline at end of file diff --git a/HandyControl/Tools/Interop/BITMAPINFO.cs b/HandyControl/Tools/Interop/BITMAPINFO.cs new file mode 100644 index 00000000..4a803564 --- /dev/null +++ b/HandyControl/Tools/Interop/BITMAPINFO.cs @@ -0,0 +1,52 @@ +using System.Runtime.InteropServices; +using System.Security; + +namespace HandyControl.Tools.Interop +{ + [StructLayout(LayoutKind.Sequential, Pack = 2)] + internal struct BITMAPINFO + { + public int bmiHeader_biSize; + + public int bmiHeader_biWidth; + + public int bmiHeader_biHeight; + + public short bmiHeader_biPlanes; + + public short bmiHeader_biBitCount; + + public int bmiHeader_biCompression; + + public int bmiHeader_biSizeImage; + + public int bmiHeader_biXPelsPerMeter; + + public int bmiHeader_biYPelsPerMeter; + + public int bmiHeader_biClrUsed; + + public int bmiHeader_biClrImportant; + + public BITMAPINFO(int width, int height, short bpp) + { + bmiHeader_biSize = SizeOf(); + bmiHeader_biWidth = width; + bmiHeader_biHeight = height; + bmiHeader_biPlanes = 1; + bmiHeader_biBitCount = bpp; + bmiHeader_biCompression = 0; + bmiHeader_biSizeImage = 0; + bmiHeader_biXPelsPerMeter = 0; + bmiHeader_biYPelsPerMeter = 0; + bmiHeader_biClrUsed = 0; + bmiHeader_biClrImportant = 0; + } + + [SecuritySafeCritical] + private static int SizeOf() + { + return Marshal.SizeOf(typeof(BITMAPINFO)); + } + } +} \ No newline at end of file diff --git a/HandyControl/Tools/Interop/BitmapHandle.cs b/HandyControl/Tools/Interop/BitmapHandle.cs new file mode 100644 index 00000000..2970e514 --- /dev/null +++ b/HandyControl/Tools/Interop/BitmapHandle.cs @@ -0,0 +1,37 @@ +using System; +using System.Runtime.ConstrainedExecution; +using System.Runtime.InteropServices; +using System.Security; + +namespace HandyControl.Tools.Interop +{ + internal sealed class BitmapHandle : WpfSafeHandle + { + [SecurityCritical] + private BitmapHandle(bool ownsHandle) : base(ownsHandle, CommonHandles.GDI) + { + } + + [SecurityCritical] + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] + protected override bool ReleaseHandle() + { + return UnsafeNativeMethods.DeleteObject(handle); + } + + [SecurityCritical] + internal HandleRef MakeHandleRef(object obj) + { + return new HandleRef(obj, handle); + } + + [SecurityCritical] + internal static BitmapHandle CreateFromHandle(IntPtr hbitmap, bool ownsHandle = true) + { + return new BitmapHandle(ownsHandle) + { + handle = hbitmap, + }; + } + } +} \ No newline at end of file diff --git a/HandyControl/Tools/Interop/CommonHandles.cs b/HandyControl/Tools/Interop/CommonHandles.cs new file mode 100644 index 00000000..deb2a7f5 --- /dev/null +++ b/HandyControl/Tools/Interop/CommonHandles.cs @@ -0,0 +1,13 @@ +namespace HandyControl.Tools.Interop +{ + internal static class CommonHandles + { + public static readonly int Icon = HandleCollector.RegisterType(nameof(Icon), 20, 500); + + public static readonly int HDC = HandleCollector.RegisterType(nameof(HDC), 100, 2); + + public static readonly int GDI = HandleCollector.RegisterType(nameof(GDI), 50, 500); + + public static readonly int Kernel = HandleCollector.RegisterType(nameof(Kernel), 0, 1000); + } +} \ No newline at end of file diff --git a/HandyControl/Tools/Interop/ExternDll.cs b/HandyControl/Tools/Interop/ExternDll.cs new file mode 100644 index 00000000..81fa0c71 --- /dev/null +++ b/HandyControl/Tools/Interop/ExternDll.cs @@ -0,0 +1,11 @@ +namespace HandyControl.Tools.Interop +{ + internal static class ExternDll + { + public const string + User32 = "user32.dll", + Gdi32 = "gdi32.dll", + Kernel32 = "kernel32.dll", + Shell32 = "shell32.dll"; + } +} \ No newline at end of file diff --git a/HandyControl/Tools/Interop/HandleCollector.cs b/HandyControl/Tools/Interop/HandleCollector.cs new file mode 100644 index 00000000..b53f3c96 --- /dev/null +++ b/HandyControl/Tools/Interop/HandleCollector.cs @@ -0,0 +1,132 @@ +// reference from https://referencesource.microsoft.com/#WindowsBase/Shared/MS/Win32/HandleCollector.cs,d0f99220d8e1b708 + +using System; +using System.Runtime.InteropServices; + +namespace HandyControl.Tools.Interop +{ + internal static class HandleCollector + { + private static HandleType[] HandleTypes; + private static int HandleTypeCount; + + private static readonly object HandleMutex = new object(); + + internal static IntPtr Add(IntPtr handle, int type) + { + HandleTypes[type - 1].Add(); + return handle; + } + + [System.Security.SecuritySafeCritical] + internal static SafeHandle Add(SafeHandle handle, int type) + { + HandleTypes[type - 1].Add(); + return handle; + } + + internal static void Add(int type) + { + HandleTypes[type - 1].Add(); + } + + internal static int RegisterType(string typeName, int expense, int initialThreshold) + { + lock (HandleMutex) + { + if (HandleTypeCount == 0 || HandleTypeCount == HandleTypes.Length) + { + HandleType[] newTypes = new HandleType[HandleTypeCount + 10]; + if (HandleTypes != null) + { + Array.Copy(HandleTypes, 0, newTypes, 0, HandleTypeCount); + } + HandleTypes = newTypes; + } + + HandleTypes[HandleTypeCount++] = new HandleType(expense, initialThreshold); + return HandleTypeCount; + } + } + + internal static IntPtr Remove(IntPtr handle, int type) + { + HandleTypes[type - 1].Remove(); + return handle; + } + + [System.Security.SecuritySafeCritical] + internal static SafeHandle Remove(SafeHandle handle, int type) + { + HandleTypes[type - 1].Remove(); + return handle; + } + + internal static void Remove(int type) + { + HandleTypes[type - 1].Remove(); + } + + private class HandleType + { + private readonly int _initialThreshHold; + private int _threshHold; + private int _handleCount; + private readonly int _deltaPercent; + + internal HandleType(int expense, int initialThreshHold) + { + _initialThreshHold = initialThreshHold; + _threshHold = initialThreshHold; + _deltaPercent = 100 - expense; + } + + internal void Add() + { + lock (this) + { + _handleCount++; + var performCollect = NeedCollection(); + + if (!performCollect) + { + return; + } + } + + GC.Collect(); + + var sleep = (100 - _deltaPercent) / 4; + System.Threading.Thread.Sleep(sleep); + } + + private bool NeedCollection() + { + + if (_handleCount > _threshHold) + { + _threshHold = _handleCount + ((_handleCount * _deltaPercent) / 100); + return true; + } + + var oldThreshHold = (100 * _threshHold) / (100 + _deltaPercent); + if (oldThreshHold >= _initialThreshHold && _handleCount < (int)(oldThreshHold * .9F)) + { + _threshHold = oldThreshHold; + } + + return false; + } + + internal void Remove() + { + lock (this) + { + _handleCount--; + + _handleCount = Math.Max(0, _handleCount); + } + } + } + } +} \ No newline at end of file diff --git a/HandyControl/Tools/Interop/ICONINFO.cs b/HandyControl/Tools/Interop/ICONINFO.cs new file mode 100644 index 00000000..564e8df9 --- /dev/null +++ b/HandyControl/Tools/Interop/ICONINFO.cs @@ -0,0 +1,14 @@ +using System.Runtime.InteropServices; + +namespace HandyControl.Tools.Interop +{ + [StructLayout(LayoutKind.Sequential)] + internal class ICONINFO + { + public bool fIcon = false; + public int xHotspot = 0; + public int yHotspot = 0; + public BitmapHandle hbmMask = null; + public BitmapHandle hbmColor = null; + } +} \ No newline at end of file diff --git a/HandyControl/Tools/Interop/IconHandle.cs b/HandyControl/Tools/Interop/IconHandle.cs new file mode 100644 index 00000000..cbaec541 --- /dev/null +++ b/HandyControl/Tools/Interop/IconHandle.cs @@ -0,0 +1,33 @@ +using System; +using System.Runtime.ConstrainedExecution; +using System.Security; + +namespace HandyControl.Tools.Interop +{ + internal sealed class IconHandle : WpfSafeHandle + { + [SecurityCritical] + private IconHandle() : base(true, CommonHandles.Icon) + { + } + + [SecurityCritical] + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] + protected override bool ReleaseHandle() + { + return UnsafeNativeMethods.DestroyIcon(handle); + } + + [SecurityCritical, SecuritySafeCritical] + internal static IconHandle GetInvalidIcon() + { + return new IconHandle(); + } + + [SecurityCritical] + internal IntPtr CriticalGetHandle() + { + return handle; + } + } +} \ No newline at end of file diff --git a/HandyControl/Tools/Interop/IntSecurity.cs b/HandyControl/Tools/Interop/IntSecurity.cs new file mode 100644 index 00000000..679ddaa4 --- /dev/null +++ b/HandyControl/Tools/Interop/IntSecurity.cs @@ -0,0 +1,21 @@ +using System; +using System.Security; +using System.Security.Permissions; + +namespace HandyControl.Tools.Interop +{ + internal class IntSecurity + { + public static CodeAccessPermission UnrestrictedWindows => + new Lazy(() => AllWindows).Value; + + public static CodeAccessPermission AllWindows => + new Lazy(() => new UIPermission(UIPermissionWindow.AllWindows)).Value; + + public static CodeAccessPermission CreateAnyWindow => + new Lazy(() => SafeSubWindows).Value; + + public static CodeAccessPermission SafeSubWindows => + new Lazy(() => new UIPermission(UIPermissionWindow.SafeSubWindows)).Value; + } +} \ No newline at end of file diff --git a/HandyControl/Tools/Interop/NOTIFYICONDATA.cs b/HandyControl/Tools/Interop/NOTIFYICONDATA.cs new file mode 100644 index 00000000..6794cbb0 --- /dev/null +++ b/HandyControl/Tools/Interop/NOTIFYICONDATA.cs @@ -0,0 +1,28 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; + +namespace HandyControl.Tools.Interop +{ + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + [SuppressMessage("ReSharper", "InconsistentNaming")] + public class NOTIFYICONDATA + { + public int cbSize = Marshal.SizeOf(typeof(NOTIFYICONDATA)); + public IntPtr hWnd; + public int uID; + public int uFlags; + public int uCallbackMessage; + public IntPtr hIcon; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] + public string szTip; + public int dwState = 0; + public int dwStateMask = 0; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)] + public string szInfo; + public int uTimeoutOrVersion; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)] + public string szInfoTitle; + public int dwInfoFlags; + } +} \ No newline at end of file diff --git a/HandyControl/Tools/Interop/NativeMethods.cs b/HandyControl/Tools/Interop/NativeMethods.cs new file mode 100644 index 00000000..c670c683 --- /dev/null +++ b/HandyControl/Tools/Interop/NativeMethods.cs @@ -0,0 +1,37 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; + +namespace HandyControl.Tools.Interop +{ + public delegate IntPtr WndProc(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam); + + [SuppressMessage("ReSharper", "InconsistentNaming")] + [SuppressMessage("ReSharper", "IdentifierTypo")] + internal class NativeMethods + { + public const int + WS_CHILD = 0x40000000, + WM_USER = 0x0400, + BITSPIXEL = 12, + PLANES = 14, + BI_RGB = 0, + DIB_RGB_COLORS = 0, + NIF_MESSAGE = 0x00000001, + NIF_INFO = 0x00000010, + NIF_ICON = 0x00000002, + NIF_TIP = 0x00000004, + NIM_ADD = 0x00000000, + NIM_DELETE = 0x00000002, + NIM_MODIFY = 0x00000001, + NIIF_NONE = 0x00000000, + NIIF_INFO = 0x00000001, + NIIF_WARNING = 0x00000002, + NIIF_ERROR = 0x00000003; + + [DllImport(ExternDll.User32, CharSet = CharSet.Auto)] + [ResourceExposure(ResourceScope.None)] + public static extern int RegisterWindowMessage(string msg); + } +} \ No newline at end of file diff --git a/HandyControl/Tools/Interop/NativeValues.cs b/HandyControl/Tools/Interop/NativeValues.cs new file mode 100644 index 00000000..628123f7 --- /dev/null +++ b/HandyControl/Tools/Interop/NativeValues.cs @@ -0,0 +1,98 @@ +namespace HandyControl.Tools.Interop +{ + internal enum SM + { + CXSCREEN = 0, + CYSCREEN = 1, + CXVSCROLL = 2, + CYHSCROLL = 3, + CYCAPTION = 4, + CXBORDER = 5, + CYBORDER = 6, + CXFIXEDFRAME = 7, + CYFIXEDFRAME = 8, + CYVTHUMB = 9, + CXHTHUMB = 10, + CXICON = 11, + CYICON = 12, + CXCURSOR = 13, + CYCURSOR = 14, + CYMENU = 15, + CXFULLSCREEN = 16, + CYFULLSCREEN = 17, + CYKANJIWINDOW = 18, + MOUSEPRESENT = 19, + CYVSCROLL = 20, + CXHSCROLL = 21, + DEBUG = 22, + SWAPBUTTON = 23, + CXMIN = 28, + CYMIN = 29, + CXSIZE = 30, + CYSIZE = 31, + CXFRAME = 32, + CXSIZEFRAME = CXFRAME, + CYFRAME = 33, + CYSIZEFRAME = CYFRAME, + CXMINTRACK = 34, + CYMINTRACK = 35, + CXDOUBLECLK = 36, + CYDOUBLECLK = 37, + CXICONSPACING = 38, + CYICONSPACING = 39, + MENUDROPALIGNMENT = 40, + PENWINDOWS = 41, + DBCSENABLED = 42, + CMOUSEBUTTONS = 43, + SECURE = 44, + CXEDGE = 45, + CYEDGE = 46, + CXMINSPACING = 47, + CYMINSPACING = 48, + CXSMICON = 49, + CYSMICON = 50, + CYSMCAPTION = 51, + CXSMSIZE = 52, + CYSMSIZE = 53, + CXMENUSIZE = 54, + CYMENUSIZE = 55, + ARRANGE = 56, + CXMINIMIZED = 57, + CYMINIMIZED = 58, + CXMAXTRACK = 59, + CYMAXTRACK = 60, + CXMAXIMIZED = 61, + CYMAXIMIZED = 62, + NETWORK = 63, + CLEANBOOT = 67, + CXDRAG = 68, + CYDRAG = 69, + SHOWSOUNDS = 70, + CXMENUCHECK = 71, + CYMENUCHECK = 72, + SLOWMACHINE = 73, + MIDEASTENABLED = 74, + MOUSEWHEELPRESENT = 75, + XVIRTUALSCREEN = 76, + YVIRTUALSCREEN = 77, + CXVIRTUALSCREEN = 78, + CYVIRTUALSCREEN = 79, + CMONITORS = 80, + SAMEDISPLAYFORMAT = 81, + IMMENABLED = 82, + CXFOCUSBORDER = 83, + CYFOCUSBORDER = 84, + TABLETPC = 86, + MEDIACENTER = 87, + REMOTESESSION = 0x1000, + REMOTECONTROL = 0x2001, + } + + internal static class Win32Constant + { + internal const int MAX_PATH = 260; + internal const int INFOTIPSIZE = 1024; + internal const int TRUE = 1; + internal const int FALSE = 0; + } +} \ No newline at end of file diff --git a/HandyControl/Tools/Interop/SafeFileMappingHandle.cs b/HandyControl/Tools/Interop/SafeFileMappingHandle.cs new file mode 100644 index 00000000..ad26db90 --- /dev/null +++ b/HandyControl/Tools/Interop/SafeFileMappingHandle.cs @@ -0,0 +1,50 @@ +using System; +using System.Runtime.InteropServices; +using System.Security; +using System.Security.Permissions; +using Microsoft.Win32.SafeHandles; + +namespace HandyControl.Tools.Interop +{ + internal sealed class SafeFileMappingHandle : SafeHandleZeroOrMinusOneIsInvalid + { + [SecurityCritical] + internal SafeFileMappingHandle(IntPtr handle) : base(false) + { + SetHandle(handle); + } + + [SecurityCritical, SecuritySafeCritical] + internal SafeFileMappingHandle() : base(true) + { + } + + public override bool IsInvalid + { + [SecurityCritical, SecuritySafeCritical] + get => handle == IntPtr.Zero; + } + + [SecurityCritical, SecuritySafeCritical] + protected override bool ReleaseHandle() + { + new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Assert(); + try + { + return CloseHandleNoThrow(new HandleRef(null, handle)); + } + finally + { + CodeAccessPermission.RevertAssert(); + } + } + + [SecurityCritical] + public static bool CloseHandleNoThrow(HandleRef handle) + { + HandleCollector.Remove((IntPtr)handle, CommonHandles.Kernel); + var result = UnsafeNativeMethods.IntCloseHandle(handle); + return result; + } + } +} \ No newline at end of file diff --git a/HandyControl/Tools/Interop/UnsafeNativeMethods.cs b/HandyControl/Tools/Interop/UnsafeNativeMethods.cs new file mode 100644 index 00000000..ed6eb2a4 --- /dev/null +++ b/HandyControl/Tools/Interop/UnsafeNativeMethods.cs @@ -0,0 +1,173 @@ +using System; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; +using System.Security; +using System.Text; + +namespace HandyControl.Tools.Interop +{ + [SuppressMessage("ReSharper", "InconsistentNaming")] + internal class UnsafeNativeMethods + { + [SecurityCritical] + [SuppressUnmanagedCodeSecurity] + [DllImport(ExternDll.User32, SetLastError = true, ExactSpelling = true, EntryPoint = nameof(GetDC), CharSet = CharSet.Auto)] + public static extern IntPtr IntGetDC(HandleRef hWnd); + + [SecurityCritical] + public static IntPtr GetDC(HandleRef hWnd) + { + var hDc = IntGetDC(hWnd); + if (hDc == IntPtr.Zero) + { + throw new Win32Exception(); + } + + return HandleCollector.Add(hDc, CommonHandles.HDC); + } + + [SecurityCritical] + [SuppressUnmanagedCodeSecurity] + [DllImport(ExternDll.User32, ExactSpelling = true, EntryPoint = nameof(ReleaseDC), CharSet = CharSet.Auto)] + public static extern int IntReleaseDC(HandleRef hWnd, HandleRef hDC); + + [SecurityCritical] + public static int ReleaseDC(HandleRef hWnd, HandleRef hDC) + { + HandleCollector.Remove((IntPtr)hDC, CommonHandles.HDC); + return IntReleaseDC(hWnd, hDC); + } + + [SecurityCritical] + [SuppressUnmanagedCodeSecurity] + [DllImport(ExternDll.Gdi32, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Auto)] + public static extern int GetDeviceCaps(HandleRef hDC, int nIndex); + + [SecurityCritical] + [SuppressUnmanagedCodeSecurity] + [DllImport(ExternDll.User32)] + public static extern int GetSystemMetrics(SM nIndex); + + [SecurityCritical] + [SuppressUnmanagedCodeSecurity] + [DllImport(ExternDll.User32, EntryPoint = nameof(DestroyIcon), CharSet = CharSet.Auto, SetLastError = true)] + private static extern bool IntDestroyIcon(IntPtr hIcon); + + [SecurityCritical] + public static bool DestroyIcon(IntPtr hIcon) + { + var result = IntDestroyIcon(hIcon); + return result; + } + + [SecurityCritical] + [SuppressUnmanagedCodeSecurity] + [DllImport(ExternDll.Gdi32, EntryPoint = nameof(DeleteObject), CharSet = CharSet.Auto, SetLastError = true)] + private static extern bool IntDeleteObject(IntPtr hObject); + + [SecurityCritical] + public static bool DeleteObject(IntPtr hObject) + { + var result = IntDeleteObject(hObject); + return result; + } + + [SecurityCritical] + public static BitmapHandle CreateDIBSection(HandleRef hdc, ref BITMAPINFO bitmapInfo, int iUsage, ref IntPtr ppvBits, SafeFileMappingHandle hSection, int dwOffset) + { + if (hSection == null) + { + hSection = new SafeFileMappingHandle(IntPtr.Zero); + } + + var hBitmap = PrivateCreateDIBSection(hdc, ref bitmapInfo, iUsage, ref ppvBits, hSection, dwOffset); + return hBitmap; + } + + [DllImport(ExternDll.Kernel32, EntryPoint = "CloseHandle", CharSet = CharSet.Auto, SetLastError = true)] + public static extern bool IntCloseHandle(HandleRef handle); + + [SecurityCritical, SuppressUnmanagedCodeSecurity] + [DllImport(ExternDll.Gdi32, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Auto, EntryPoint = nameof(CreateDIBSection))] + private static extern BitmapHandle PrivateCreateDIBSection(HandleRef hdc, ref BITMAPINFO bitmapInfo, int iUsage, ref IntPtr ppvBits, SafeFileMappingHandle hSection, int dwOffset); + + [SecurityCritical, SuppressUnmanagedCodeSecurity] + [DllImport(ExternDll.User32, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Auto, EntryPoint = nameof(CreateIconIndirect))] + private static extern IconHandle PrivateCreateIconIndirect([In, MarshalAs(UnmanagedType.LPStruct)]ICONINFO iconInfo); + + [SecurityCritical] + public static IconHandle CreateIconIndirect([In, MarshalAs(UnmanagedType.LPStruct)]ICONINFO iconInfo) + { + var hIcon = PrivateCreateIconIndirect(iconInfo); + return hIcon; + } + + [SecurityCritical, SuppressUnmanagedCodeSecurity] + [DllImport(ExternDll.Gdi32, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Auto, EntryPoint = nameof(CreateBitmap))] + private static extern BitmapHandle PrivateCreateBitmap(int width, int height, int planes, int bitsPerPixel, byte[] lpvBits); + + [SecurityCritical] + internal static BitmapHandle CreateBitmap(int width, int height, int planes, int bitsPerPixel, byte[] lpvBits) + { + var hBitmap = PrivateCreateBitmap(width, height, planes, bitsPerPixel, lpvBits); + return hBitmap; + } + + [SecurityCritical, SuppressUnmanagedCodeSecurity] + [DllImport(ExternDll.Kernel32, EntryPoint = "GetModuleFileName", CharSet = CharSet.Unicode, SetLastError = true)] + private static extern int IntGetModuleFileName(HandleRef hModule, StringBuilder buffer, int length); + + [SecurityCritical] + internal static string GetModuleFileName(HandleRef hModule) + { + var buffer = new StringBuilder(Win32Constant.MAX_PATH); + while (true) + { + var size = IntGetModuleFileName(hModule, buffer, buffer.Capacity); + if (size == 0) + { + throw new Win32Exception(); + } + + if (size == buffer.Capacity) + { + buffer.EnsureCapacity(buffer.Capacity * 2); + continue; + } + + return buffer.ToString(); + } + } + + [SecurityCritical, SuppressUnmanagedCodeSecurity] + [DllImport(ExternDll.Shell32, CharSet = CharSet.Auto, BestFitMapping = false, ThrowOnUnmappableChar = true)] + internal static extern int ExtractIconEx(string szExeFileName, int nIconIndex, out IconHandle phiconLarge, out IconHandle phiconSmall, int nIcons); + + [DllImport(ExternDll.Shell32, CharSet = CharSet.Auto)] + [ResourceExposure(ResourceScope.None)] + public static extern int Shell_NotifyIcon(int message, NOTIFYICONDATA pnid); + + [SecurityCritical] + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + [DllImport(ExternDll.User32, SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "CreateWindowExW")] + public static extern IntPtr CreateWindowEx( + int dwExStyle, + [MarshalAs(UnmanagedType.LPWStr)] string lpClassName, + [MarshalAs(UnmanagedType.LPWStr)] string lpWindowName, + int dwStyle, + int x, + int y, + int nWidth, + int nHeight, + IntPtr hWndParent, + IntPtr hMenu, + IntPtr hInstance, + IntPtr lpParam); + + [DllImport(ExternDll.User32, CharSet = CharSet.Auto, SetLastError = true)] + [ResourceExposure(ResourceScope.None)] + public static extern short RegisterClass(WNDCLASS wc); + } +} \ No newline at end of file diff --git a/HandyControl/Tools/Interop/WNDCLASS.cs b/HandyControl/Tools/Interop/WNDCLASS.cs new file mode 100644 index 00000000..363d083f --- /dev/null +++ b/HandyControl/Tools/Interop/WNDCLASS.cs @@ -0,0 +1,23 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; + +namespace HandyControl.Tools.Interop +{ + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + [SuppressMessage("ReSharper", "InconsistentNaming")] + [SuppressMessage("ReSharper", "IdentifierTypo")] + internal class WNDCLASS + { + public int style; + public ExternDllHelper.WndProc lpfnWndProc; + public int cbClsExtra = 0; + public int cbWndExtra = 0; + public IntPtr hInstance = IntPtr.Zero; + public IntPtr hIcon = IntPtr.Zero; + public IntPtr hCursor = IntPtr.Zero; + public IntPtr hbrBackground = IntPtr.Zero; + public string lpszMenuName = null; + public string lpszClassName = null; + } +} \ No newline at end of file diff --git a/HandyControl/Tools/Interop/WindowClass.cs b/HandyControl/Tools/Interop/WindowClass.cs new file mode 100644 index 00000000..db49dc77 --- /dev/null +++ b/HandyControl/Tools/Interop/WindowClass.cs @@ -0,0 +1,103 @@ +using System; +using System.Security.Permissions; + +namespace HandyControl.Tools.Interop +{ + [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)] + internal class WindowClass + { + internal string WindowClassName; + + private static readonly int WmTaskbarcreated = NativeMethods.RegisterWindowMessage("TaskbarCreated"); + + internal IntPtr MessageWindowHandle { get; private set; } + + private readonly object _syncObj = new object(); + + private bool _added; + + public WindowClass() + { + RegisterClass(); + } + + private void RegisterClass() + { + WindowClassName = $"HandyControl.Controls.NotifyIcon{Guid.NewGuid()}"; + var wndclass = new WNDCLASS + { + style = 0, + lpfnWndProc = Callback, + cbClsExtra = 0, + cbWndExtra = 0, + hInstance = IntPtr.Zero, + hIcon = IntPtr.Zero, + hCursor = IntPtr.Zero, + hbrBackground = IntPtr.Zero, + lpszMenuName = string.Empty, + lpszClassName = WindowClassName + }; + + UnsafeNativeMethods.RegisterClass(wndclass); + MessageWindowHandle = UnsafeNativeMethods.CreateWindowEx(0, WindowClassName, "", 0, 0, 0, 1, 1, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); + } + + public IntPtr Callback(IntPtr hWnd, int msg, IntPtr wparam, IntPtr lparam) + { + if (msg == WmTaskbarcreated) + { + _added = false; + AddIcon(); + } + return IntPtr.Zero; + } + + private void AddIcon() + { + lock (_syncObj) + { + if (DesignerHelper.IsInDesignMode) return; + + //NativeMethods.NOTIFYICONDATA data = new NativeMethods.NOTIFYICONDATA(); + //data.uCallbackMessage = WM_TRAYMOUSEMESSAGE; + //data.uFlags = NativeMethods.NIF_MESSAGE; + //if (showIconInTray) + //{ + // if (window.Handle == IntPtr.Zero) + // { + // window.CreateHandle(new CreateParams()); + // } + //} + //data.hWnd = window.Handle; + //data.uID = id; + //data.hIcon = IntPtr.Zero; + //data.szTip = null; + //if (icon != null) + //{ + // data.uFlags |= NativeMethods.NIF_ICON; + // data.hIcon = icon.Handle; + //} + //data.uFlags |= NativeMethods.NIF_TIP; + //data.szTip = text; + + //if (showIconInTray && icon != null) + //{ + // if (!added) + // { + // UnsafeNativeMethods.Shell_NotifyIcon(NativeMethods.NIM_ADD, data); + // added = true; + // } + // else + // { + // UnsafeNativeMethods.Shell_NotifyIcon(NativeMethods.NIM_MODIFY, data); + // } + //} + //else if (added) + //{ + // UnsafeNativeMethods.Shell_NotifyIcon(NativeMethods.NIM_DELETE, data); + // added = false; + //} + } + } + } +} \ No newline at end of file diff --git a/HandyControl/Tools/Interop/WpfSafeHandle.cs b/HandyControl/Tools/Interop/WpfSafeHandle.cs new file mode 100644 index 00000000..8141a2cf --- /dev/null +++ b/HandyControl/Tools/Interop/WpfSafeHandle.cs @@ -0,0 +1,24 @@ +using System.Security; +using Microsoft.Win32.SafeHandles; + +namespace HandyControl.Tools.Interop +{ + internal abstract class WpfSafeHandle : SafeHandleZeroOrMinusOneIsInvalid + { + private readonly int _collectorId; + + [SecurityCritical] + protected WpfSafeHandle(bool ownsHandle, int collectorId) : base(ownsHandle) + { + HandleCollector.Add(collectorId); + _collectorId = collectorId; + } + + [SecurityCritical, SecuritySafeCritical] + protected override void Dispose(bool disposing) + { + HandleCollector.Remove(_collectorId); + base.Dispose(disposing); + } + } +} \ No newline at end of file diff --git a/HandyControlDemo/Properties/Langs/Lang.en.resx b/HandyControlDemo/Properties/Langs/Lang.en.resx index 58dc6dc0..27a87236 100644 --- a/HandyControlDemo/Properties/Langs/Lang.en.resx +++ b/HandyControlDemo/Properties/Langs/Lang.en.resx @@ -500,7 +500,7 @@ NotifyIcon - + Badge diff --git a/HandyControlDemo/Properties/Langs/Lang.resx b/HandyControlDemo/Properties/Langs/Lang.resx index 3687f4be..dc7baedd 100644 --- a/HandyControlDemo/Properties/Langs/Lang.resx +++ b/HandyControlDemo/Properties/Langs/Lang.resx @@ -500,7 +500,7 @@ 托盘图标 - + 标记 From 93c351a308f2552311ca8601c7e5a63912c16f97 Mon Sep 17 00:00:00 2001 From: NaBian <836904362@qq.com> Date: Tue, 26 Mar 2019 23:09:16 +0800 Subject: [PATCH 4/9] dev 19-3-26 --- .../Controls/NotifyIcon/NotifyIcon.cs | 141 +++++++++++------- .../NotifyIcon/NotifyIconNativeWindow.cs | 29 ---- HandyControl/HandyControl.csproj | 3 - HandyControl/Tools/Helper/ExternDllHelper.cs | 2 - HandyControl/Tools/Helper/IconHelper.cs | 22 +-- HandyControl/Tools/Interop/BITMAPINFO.cs | 4 +- HandyControl/Tools/Interop/CommonHandles.cs | 5 +- HandyControl/Tools/Interop/IntSecurity.cs | 21 --- HandyControl/Tools/Interop/NativeValues.cs | 23 ++- .../Tools/Interop/UnsafeNativeMethods.cs | 8 + HandyControl/Tools/Interop/WNDCLASS.cs | 2 +- HandyControl/Tools/Interop/WindowClass.cs | 103 ------------- .../Controls/NotifyIconDemoCtl.xaml | 3 +- 13 files changed, 141 insertions(+), 225 deletions(-) delete mode 100644 HandyControl/Controls/NotifyIcon/NotifyIconNativeWindow.cs delete mode 100644 HandyControl/Tools/Interop/IntSecurity.cs delete mode 100644 HandyControl/Tools/Interop/WindowClass.cs diff --git a/HandyControl/Controls/NotifyIcon/NotifyIcon.cs b/HandyControl/Controls/NotifyIcon/NotifyIcon.cs index a2717693..c2a6713d 100644 --- a/HandyControl/Controls/NotifyIcon/NotifyIcon.cs +++ b/HandyControl/Controls/NotifyIcon/NotifyIcon.cs @@ -1,6 +1,9 @@ -using System.Windows; +using System; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Data; using System.Windows.Media; -using HandyControl.Data; using HandyControl.Tools; using HandyControl.Tools.Interop; @@ -12,8 +15,6 @@ namespace HandyControl.Controls private readonly object _syncObj = new object(); - private WindowClass _windowClass; - private readonly int _id; private static int NextId; @@ -30,10 +31,24 @@ namespace HandyControl.Controls private const int WmTrayMouseMessage = NativeMethods.WM_USER + 1024; + private string _windowClassName; + + private static readonly int WmTaskbarcreated = NativeMethods.RegisterWindowMessage("TaskbarCreated"); + + private IntPtr _messageWindowHandle; + + private readonly WndProc _callback; + + private ToolTip _toolTip; + public NotifyIcon() { _id = ++NextId; - _windowClass = new WindowClass(); + _callback = Callback; + CreateToolTip(); + RegisterClass(); + OnIconChanged(); + UpdateIcon(true); } public static readonly DependencyProperty IconProperty = DependencyProperty.Register( @@ -43,7 +58,7 @@ namespace HandyControl.Controls { var ctl = (NotifyIcon) d; ctl._icon = (ImageSource) e.NewValue; - ctl.UpdateIcon(); + ctl.OnIconChanged(); } public ImageSource Icon @@ -52,7 +67,7 @@ namespace HandyControl.Controls set => SetValue(IconProperty, value); } - private void UpdateIcon() + private void OnIconChanged() { IconHandle largeIconHandle; IconHandle smallIconHandle; @@ -90,63 +105,85 @@ namespace HandyControl.Controls _currentSmallIconHandle = smallIconHandle; } - public static readonly DependencyProperty BalloonTipIconProperty = DependencyProperty.Register( - "BalloonTipIcon", typeof(ToolTipIcon), typeof(NotifyIcon), new PropertyMetadata(default(ToolTipIcon))); - - public ToolTipIcon BalloonTipIcon + private void UpdateIcon(bool showIconInTray) { - get => (ToolTipIcon) GetValue(BalloonTipIconProperty); - set => SetValue(BalloonTipIconProperty, value); - } - - public static readonly DependencyProperty BalloonTipTitleProperty = DependencyProperty.Register( - "BalloonTipTitle", typeof(string), typeof(NotifyIcon), new PropertyMetadata(default(string))); - - public string BalloonTipTitle - { - get => (string) GetValue(BalloonTipTitleProperty); - set => SetValue(BalloonTipTitleProperty, value); - } - - public static readonly DependencyProperty TextProperty = DependencyProperty.Register( - "Text", typeof(string), typeof(NotifyIcon), new PropertyMetadata(default(string))); - - public string Text - { - get => (string) GetValue(TextProperty); - set => SetValue(TextProperty, value); - } - - public void ShowBalloonTip(int timeout, string tipTitle, string tipText, ToolTipIcon tipIcon) - { - if (timeout < 0) return; - if (string.IsNullOrEmpty(tipTitle)) return; - - if (_added) + lock (_syncObj) { if (DesignerHelper.IsInDesignMode) return; - IntSecurity.UnrestrictedWindows.Demand(); - var data = new NOTIFYICONDATA { - hWnd = _windowClass.MessageWindowHandle, + uCallbackMessage = WmTrayMouseMessage, + uFlags = NativeMethods.NIF_MESSAGE | NativeMethods.NIF_ICON | NativeMethods.NIF_TIP, + hWnd = _messageWindowHandle, uID = _id, - uFlags = NativeMethods.NIF_INFO, - uTimeoutOrVersion = timeout, - szInfoTitle = tipTitle, - szInfo = tipText + dwInfoFlags = NativeMethods.NIIF_INFO, + hIcon = _currentSmallIconHandle.CriticalGetHandle() }; - switch (tipIcon) + if (showIconInTray) { - case ToolTipIcon.Info: data.dwInfoFlags = NativeMethods.NIIF_INFO; break; - case ToolTipIcon.Warning: data.dwInfoFlags = NativeMethods.NIIF_WARNING; break; - case ToolTipIcon.Error: data.dwInfoFlags = NativeMethods.NIIF_ERROR; break; - case ToolTipIcon.None: data.dwInfoFlags = NativeMethods.NIIF_NONE; break; + if (!_added) + { + UnsafeNativeMethods.Shell_NotifyIcon(NativeMethods.NIM_ADD, data); + _added = true; + } + else + { + UnsafeNativeMethods.Shell_NotifyIcon(NativeMethods.NIM_MODIFY, data); + } + } + else if (_added) + { + UnsafeNativeMethods.Shell_NotifyIcon(NativeMethods.NIM_DELETE, data); + _added = false; } - UnsafeNativeMethods.Shell_NotifyIcon(NativeMethods.NIM_MODIFY, data); } } + + private void RegisterClass() + { + _windowClassName = $"HandyControl.Controls.NotifyIcon{Guid.NewGuid()}"; + var wndclass = new WNDCLASS + { + style = 0, + lpfnWndProc = _callback, + cbClsExtra = 0, + cbWndExtra = 0, + hInstance = IntPtr.Zero, + hIcon = IntPtr.Zero, + hCursor = IntPtr.Zero, + hbrBackground = IntPtr.Zero, + lpszMenuName = string.Empty, + lpszClassName = _windowClassName + }; + + UnsafeNativeMethods.RegisterClass(wndclass); + _messageWindowHandle = UnsafeNativeMethods.CreateWindowEx(0, _windowClassName, "", 0, 0, 0, 1, 1, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); + } + + private IntPtr Callback(IntPtr hWnd, int msg, IntPtr wparam, IntPtr lparam) + { + if (msg == WmTaskbarcreated) + { + UpdateIcon(true); + } + + return UnsafeNativeMethods.DefWindowProc(hWnd, msg, wparam, lparam); + } + + private void CreateToolTip() + { + _toolTip = new ToolTip + { + Placement = PlacementMode.Mouse, + HasDropShadow = false, + BorderThickness = new Thickness(0), + Background = Brushes.Transparent + }; + + _toolTip.SetBinding(ContentControl.ContentProperty, new Binding(ToolTipProperty.Name) {Source = this}); + _toolTip.SetBinding(DataContextProperty, new Binding(DataContextProperty.Name) { Source = this }); + } } } \ No newline at end of file diff --git a/HandyControl/Controls/NotifyIcon/NotifyIconNativeWindow.cs b/HandyControl/Controls/NotifyIcon/NotifyIconNativeWindow.cs deleted file mode 100644 index 1bcf1a11..00000000 --- a/HandyControl/Controls/NotifyIcon/NotifyIconNativeWindow.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.Runtime.InteropServices; - -namespace HandyControl.Controls -{ - internal class NotifyIconNativeWindow - { - internal NotifyIcon Reference; - - private GCHandle _rootRef; - - public void LockReference(bool locked) - { - if (locked) - { - if (!_rootRef.IsAllocated) - { - _rootRef = GCHandle.Alloc(Reference, GCHandleType.Normal); - } - } - else - { - if (_rootRef.IsAllocated) - { - _rootRef.Free(); - } - } - } - } -} \ No newline at end of file diff --git a/HandyControl/HandyControl.csproj b/HandyControl/HandyControl.csproj index b4fcfd7b..31a32fbf 100644 --- a/HandyControl/HandyControl.csproj +++ b/HandyControl/HandyControl.csproj @@ -117,7 +117,6 @@ - @@ -231,7 +230,6 @@ - @@ -324,7 +322,6 @@ - diff --git a/HandyControl/Tools/Helper/ExternDllHelper.cs b/HandyControl/Tools/Helper/ExternDllHelper.cs index 858885dc..58264b24 100644 --- a/HandyControl/Tools/Helper/ExternDllHelper.cs +++ b/HandyControl/Tools/Helper/ExternDllHelper.cs @@ -17,8 +17,6 @@ namespace HandyControl.Tools private const string User32 = "user32.dll"; - public delegate IntPtr WndProc(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam); - public const int E_FAIL = unchecked((int) 0x80004005); [StructLayout(LayoutKind.Sequential)] diff --git a/HandyControl/Tools/Helper/IconHelper.cs b/HandyControl/Tools/Helper/IconHelper.cs index a45b146c..afecc130 100644 --- a/HandyControl/Tools/Helper/IconHelper.cs +++ b/HandyControl/Tools/Helper/IconHelper.cs @@ -11,16 +11,18 @@ namespace HandyControl.Tools { internal static class IconHelper { - private static Size _smallIconSize; - private static Size _iconSize; - private static int _systemBitDepth; + private static Size SmallIconSize; + + private static Size IconSize; + + private static int SystemBitDepth; [SecurityCritical, SecuritySafeCritical] public static void GetIconHandlesFromImageSource(ImageSource image, out IconHandle largeIconHandle, out IconHandle smallIconHandle) { EnsureSystemMetrics(); - largeIconHandle = CreateIconHandleFromImageSource(image, _iconSize); - smallIconHandle = CreateIconHandleFromImageSource(image, _smallIconSize); + largeIconHandle = CreateIconHandleFromImageSource(image, IconSize); + smallIconHandle = CreateIconHandleFromImageSource(image, SmallIconSize); } [SecurityCritical] @@ -226,7 +228,7 @@ namespace HandyControl.Tools private static int MatchImage(BitmapFrame frame, Size size, int bpp) { - var score = 2 * MyAbs(bpp, _systemBitDepth, false) + + var score = 2 * MyAbs(bpp, SystemBitDepth, false) + MyAbs(frame.PixelWidth, (int)size.Width, true) + MyAbs(frame.PixelHeight, (int)size.Height, true); @@ -248,7 +250,7 @@ namespace HandyControl.Tools [SecurityCritical, SecuritySafeCritical] private static void EnsureSystemMetrics() { - if (_systemBitDepth == 0) + if (SystemBitDepth == 0) { var hdcDesktop = new HandleRef(null, UnsafeNativeMethods.GetDC(new HandleRef())); try @@ -266,9 +268,9 @@ namespace HandyControl.Tools var cxIcon = UnsafeNativeMethods.GetSystemMetrics(SM.CXICON); var cyIcon = UnsafeNativeMethods.GetSystemMetrics(SM.CYICON); - _smallIconSize = new Size(cxSmallIcon, cySmallIcon); - _iconSize = new Size(cxIcon, cyIcon); - _systemBitDepth = sysBitDepth; + SmallIconSize = new Size(cxSmallIcon, cySmallIcon); + IconSize = new Size(cxIcon, cyIcon); + SystemBitDepth = sysBitDepth; } finally { diff --git a/HandyControl/Tools/Interop/BITMAPINFO.cs b/HandyControl/Tools/Interop/BITMAPINFO.cs index 4a803564..4938fbdd 100644 --- a/HandyControl/Tools/Interop/BITMAPINFO.cs +++ b/HandyControl/Tools/Interop/BITMAPINFO.cs @@ -1,9 +1,11 @@ -using System.Runtime.InteropServices; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; using System.Security; namespace HandyControl.Tools.Interop { [StructLayout(LayoutKind.Sequential, Pack = 2)] + [SuppressMessage("ReSharper", "InconsistentNaming")] internal struct BITMAPINFO { public int bmiHeader_biSize; diff --git a/HandyControl/Tools/Interop/CommonHandles.cs b/HandyControl/Tools/Interop/CommonHandles.cs index deb2a7f5..9a61a221 100644 --- a/HandyControl/Tools/Interop/CommonHandles.cs +++ b/HandyControl/Tools/Interop/CommonHandles.cs @@ -1,5 +1,8 @@ -namespace HandyControl.Tools.Interop +using System.Diagnostics.CodeAnalysis; + +namespace HandyControl.Tools.Interop { + [SuppressMessage("ReSharper", "InconsistentNaming")] internal static class CommonHandles { public static readonly int Icon = HandleCollector.RegisterType(nameof(Icon), 20, 500); diff --git a/HandyControl/Tools/Interop/IntSecurity.cs b/HandyControl/Tools/Interop/IntSecurity.cs deleted file mode 100644 index 679ddaa4..00000000 --- a/HandyControl/Tools/Interop/IntSecurity.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.Security; -using System.Security.Permissions; - -namespace HandyControl.Tools.Interop -{ - internal class IntSecurity - { - public static CodeAccessPermission UnrestrictedWindows => - new Lazy(() => AllWindows).Value; - - public static CodeAccessPermission AllWindows => - new Lazy(() => new UIPermission(UIPermissionWindow.AllWindows)).Value; - - public static CodeAccessPermission CreateAnyWindow => - new Lazy(() => SafeSubWindows).Value; - - public static CodeAccessPermission SafeSubWindows => - new Lazy(() => new UIPermission(UIPermissionWindow.SafeSubWindows)).Value; - } -} \ No newline at end of file diff --git a/HandyControl/Tools/Interop/NativeValues.cs b/HandyControl/Tools/Interop/NativeValues.cs index 628123f7..67796c7a 100644 --- a/HandyControl/Tools/Interop/NativeValues.cs +++ b/HandyControl/Tools/Interop/NativeValues.cs @@ -1,4 +1,8 @@ -namespace HandyControl.Tools.Interop +// ReSharper disable InconsistentNaming + +using System.Runtime.InteropServices; + +namespace HandyControl.Tools.Interop { internal enum SM { @@ -95,4 +99,21 @@ internal const int TRUE = 1; internal const int FALSE = 0; } + + [StructLayout(LayoutKind.Sequential)] + public class POINT + { + public int x; + public int y; + + public POINT() + { + } + + public POINT(int x, int y) + { + this.x = x; + this.y = y; + } + } } \ No newline at end of file diff --git a/HandyControl/Tools/Interop/UnsafeNativeMethods.cs b/HandyControl/Tools/Interop/UnsafeNativeMethods.cs index ed6eb2a4..6d472e5a 100644 --- a/HandyControl/Tools/Interop/UnsafeNativeMethods.cs +++ b/HandyControl/Tools/Interop/UnsafeNativeMethods.cs @@ -169,5 +169,13 @@ namespace HandyControl.Tools.Interop [DllImport(ExternDll.User32, CharSet = CharSet.Auto, SetLastError = true)] [ResourceExposure(ResourceScope.None)] public static extern short RegisterClass(WNDCLASS wc); + + [DllImport(ExternDll.User32, CharSet = CharSet.Auto)] + [ResourceExposure(ResourceScope.None)] + public static extern IntPtr DefWindowProc(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam); + + [DllImport(ExternDll.User32, ExactSpelling = true, CharSet = CharSet.Auto)] + [ResourceExposure(ResourceScope.None)] + public static extern bool GetCursorPos([In, Out] POINT pt); } } \ No newline at end of file diff --git a/HandyControl/Tools/Interop/WNDCLASS.cs b/HandyControl/Tools/Interop/WNDCLASS.cs index 363d083f..1fef93ed 100644 --- a/HandyControl/Tools/Interop/WNDCLASS.cs +++ b/HandyControl/Tools/Interop/WNDCLASS.cs @@ -10,7 +10,7 @@ namespace HandyControl.Tools.Interop internal class WNDCLASS { public int style; - public ExternDllHelper.WndProc lpfnWndProc; + public WndProc lpfnWndProc; public int cbClsExtra = 0; public int cbWndExtra = 0; public IntPtr hInstance = IntPtr.Zero; diff --git a/HandyControl/Tools/Interop/WindowClass.cs b/HandyControl/Tools/Interop/WindowClass.cs deleted file mode 100644 index db49dc77..00000000 --- a/HandyControl/Tools/Interop/WindowClass.cs +++ /dev/null @@ -1,103 +0,0 @@ -using System; -using System.Security.Permissions; - -namespace HandyControl.Tools.Interop -{ - [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)] - internal class WindowClass - { - internal string WindowClassName; - - private static readonly int WmTaskbarcreated = NativeMethods.RegisterWindowMessage("TaskbarCreated"); - - internal IntPtr MessageWindowHandle { get; private set; } - - private readonly object _syncObj = new object(); - - private bool _added; - - public WindowClass() - { - RegisterClass(); - } - - private void RegisterClass() - { - WindowClassName = $"HandyControl.Controls.NotifyIcon{Guid.NewGuid()}"; - var wndclass = new WNDCLASS - { - style = 0, - lpfnWndProc = Callback, - cbClsExtra = 0, - cbWndExtra = 0, - hInstance = IntPtr.Zero, - hIcon = IntPtr.Zero, - hCursor = IntPtr.Zero, - hbrBackground = IntPtr.Zero, - lpszMenuName = string.Empty, - lpszClassName = WindowClassName - }; - - UnsafeNativeMethods.RegisterClass(wndclass); - MessageWindowHandle = UnsafeNativeMethods.CreateWindowEx(0, WindowClassName, "", 0, 0, 0, 1, 1, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); - } - - public IntPtr Callback(IntPtr hWnd, int msg, IntPtr wparam, IntPtr lparam) - { - if (msg == WmTaskbarcreated) - { - _added = false; - AddIcon(); - } - return IntPtr.Zero; - } - - private void AddIcon() - { - lock (_syncObj) - { - if (DesignerHelper.IsInDesignMode) return; - - //NativeMethods.NOTIFYICONDATA data = new NativeMethods.NOTIFYICONDATA(); - //data.uCallbackMessage = WM_TRAYMOUSEMESSAGE; - //data.uFlags = NativeMethods.NIF_MESSAGE; - //if (showIconInTray) - //{ - // if (window.Handle == IntPtr.Zero) - // { - // window.CreateHandle(new CreateParams()); - // } - //} - //data.hWnd = window.Handle; - //data.uID = id; - //data.hIcon = IntPtr.Zero; - //data.szTip = null; - //if (icon != null) - //{ - // data.uFlags |= NativeMethods.NIF_ICON; - // data.hIcon = icon.Handle; - //} - //data.uFlags |= NativeMethods.NIF_TIP; - //data.szTip = text; - - //if (showIconInTray && icon != null) - //{ - // if (!added) - // { - // UnsafeNativeMethods.Shell_NotifyIcon(NativeMethods.NIM_ADD, data); - // added = true; - // } - // else - // { - // UnsafeNativeMethods.Shell_NotifyIcon(NativeMethods.NIM_MODIFY, data); - // } - //} - //else if (added) - //{ - // UnsafeNativeMethods.Shell_NotifyIcon(NativeMethods.NIM_DELETE, data); - // added = false; - //} - } - } - } -} \ No newline at end of file diff --git a/HandyControlDemo/UserControl/Controls/NotifyIconDemoCtl.xaml b/HandyControlDemo/UserControl/Controls/NotifyIconDemoCtl.xaml index dc1085aa..6f055fe8 100644 --- a/HandyControlDemo/UserControl/Controls/NotifyIconDemoCtl.xaml +++ b/HandyControlDemo/UserControl/Controls/NotifyIconDemoCtl.xaml @@ -1,8 +1,9 @@  - + From 2cd021d34cfa19d217e66ca2162eb65fa586ad4f Mon Sep 17 00:00:00 2001 From: NaBian <836904362@qq.com> Date: Wed, 27 Mar 2019 23:37:40 +0800 Subject: [PATCH 5/9] dev 19-3-27 --- .../Controls/NotifyIcon/NotifyIcon.cs | 188 +++++++++++++++++- HandyControl/Tools/Interop/NativeMethods.cs | 18 +- .../Controls/NotifyIconDemoCtl.xaml | 2 +- 3 files changed, 196 insertions(+), 12 deletions(-) diff --git a/HandyControl/Controls/NotifyIcon/NotifyIcon.cs b/HandyControl/Controls/NotifyIcon/NotifyIcon.cs index c2a6713d..2073315e 100644 --- a/HandyControl/Controls/NotifyIcon/NotifyIcon.cs +++ b/HandyControl/Controls/NotifyIcon/NotifyIcon.cs @@ -3,6 +3,7 @@ using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Data; +using System.Windows.Input; using System.Windows.Media; using HandyControl.Tools; using HandyControl.Tools.Interop; @@ -41,6 +42,10 @@ namespace HandyControl.Controls private ToolTip _toolTip; + private Popup _contextMenu; + + private bool _doubleClick; + public NotifyIcon() { _id = ++NextId; @@ -56,14 +61,14 @@ namespace HandyControl.Controls private static void OnIconChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { - var ctl = (NotifyIcon) d; - ctl._icon = (ImageSource) e.NewValue; + var ctl = (NotifyIcon)d; + ctl._icon = (ImageSource)e.NewValue; ctl.OnIconChanged(); } public ImageSource Icon { - get => (ImageSource) GetValue(IconProperty); + get => (ImageSource)GetValue(IconProperty); set => SetValue(IconProperty, value); } @@ -164,26 +169,195 @@ namespace HandyControl.Controls private IntPtr Callback(IntPtr hWnd, int msg, IntPtr wparam, IntPtr lparam) { - if (msg == WmTaskbarcreated) + if (IsLoaded) { - UpdateIcon(true); + if (msg == WmTaskbarcreated) + { + UpdateIcon(true); + } + else + { + //switch (lparam.ToInt32()) + //{ + // case NativeMethods.WM_LBUTTONDBLCLK: + // WmMouseDown(MouseButton.Left, 2); + // break; + // case NativeMethods.WM_LBUTTONDOWN: + // WmMouseDown(MouseButton.Left, 1); + // break; + // case NativeMethods.WM_LBUTTONUP: + // WmMouseUp(MouseButton.Left); + // break; + // case NativeMethods.WM_MBUTTONDBLCLK: + // WmMouseDown(MouseButton.Middle, 2); + // break; + // case NativeMethods.WM_MBUTTONDOWN: + // WmMouseDown(MouseButton.Middle, 1); + // break; + // case NativeMethods.WM_MBUTTONUP: + // WmMouseUp(MouseButton.Middle); + // break; + // case NativeMethods.WM_MOUSEMOVE: + // WmMouseMove(); + // break; + // case NativeMethods.WM_RBUTTONDBLCLK: + // WmMouseDown(MouseButton.Right, 2); + // break; + // case NativeMethods.WM_RBUTTONDOWN: + // WmMouseDown(MouseButton.Right, 1); + // break; + // case NativeMethods.WM_RBUTTONUP: + // ShowContextMenu(); + // WmMouseUp(MouseButton.Right); + // break; + //} + } + + var point = new POINT(); + UnsafeNativeMethods.GetCursorPos(point); + ShowToolTip(point); } return UnsafeNativeMethods.DefWindowProc(hWnd, msg, wparam, lparam); } + private void WmMouseDown(MouseButton button, int clicks) + { + if (clicks == 2) + { + OnMouseDoubleClick(new MouseButtonEventArgs(Mouse.PrimaryDevice, Environment.TickCount, button) + { + Source = this, + RoutedEvent = MouseDoubleClickEvent + }); + _doubleClick = true; + } + OnMouseDown(new MouseButtonEventArgs(Mouse.PrimaryDevice, Environment.TickCount, button) + { + Source = this, + RoutedEvent = MouseDownEvent + }); + } + + private void WmMouseUp(MouseButton button) + { + OnMouseUp(new MouseButtonEventArgs(Mouse.PrimaryDevice, Environment.TickCount, button) + { + Source = this, + RoutedEvent = MouseUpEvent + }); + + if (!_doubleClick) + { + OnClick(new MouseButtonEventArgs(Mouse.PrimaryDevice, Environment.TickCount, button) + { + Source = this, + RoutedEvent = ClickEvent + }); + } + _doubleClick = false; + } + + private void WmMouseMove() + { + OnMouseMove(new MouseEventArgs(Mouse.PrimaryDevice, Environment.TickCount) + { + RoutedEvent = MouseMoveEvent, + Source = this, + }); + } + + private void ShowContextMenu() + { + if (ContextMenu != null) ContextMenu.IsOpen = true; + } + + private void ShowToolTip(POINT point) + { + if (_toolTip != null) + { + _toolTip.HorizontalOffset = point.x; + _toolTip.VerticalOffset = point.x; + _toolTip.IsOpen = true; + } + } + private void CreateToolTip() { _toolTip = new ToolTip { - Placement = PlacementMode.Mouse, + Placement = PlacementMode.AbsolutePoint, HasDropShadow = false, BorderThickness = new Thickness(0), Background = Brushes.Transparent }; - _toolTip.SetBinding(ContentControl.ContentProperty, new Binding(ToolTipProperty.Name) {Source = this}); + _toolTip.SetBinding(ContentControl.ContentProperty, new Binding(ToolTipProperty.Name) { Source = this }); _toolTip.SetBinding(DataContextProperty, new Binding(DataContextProperty.Name) { Source = this }); } + + public static readonly RoutedEvent ClickEvent = + EventManager.RegisterRoutedEvent("Click", RoutingStrategy.Bubble, + typeof(RoutedEventHandler), typeof(NotifyIcon)); + + public event RoutedEventHandler Click + { + add => AddHandler(ClickEvent, value); + remove => RemoveHandler(ClickEvent, value); + } + + public static readonly RoutedEvent MouseDoubleClickEvent = + EventManager.RegisterRoutedEvent("MouseDoubleClick", RoutingStrategy.Bubble, + typeof(RoutedEventHandler), typeof(NotifyIcon)); + + public event RoutedEventHandler MouseDoubleClick + { + add => AddHandler(MouseDoubleClickEvent, value); + remove => RemoveHandler(MouseDoubleClickEvent, value); + } + + public static readonly RoutedEvent BalloonTipShownEvent = + EventManager.RegisterRoutedEvent("BalloonTipShown", RoutingStrategy.Bubble, + typeof(RoutedEventHandler), typeof(NotifyIcon)); + + public event RoutedEventHandler BalloonTipShown + { + add => AddHandler(BalloonTipShownEvent, value); + remove => RemoveHandler(BalloonTipShownEvent, value); + } + + public static readonly RoutedEvent BalloonTipClosedEvent = + EventManager.RegisterRoutedEvent("BalloonTipClosed", RoutingStrategy.Bubble, + typeof(RoutedEventHandler), typeof(NotifyIcon)); + + public event RoutedEventHandler BalloonTipClosed + { + add => AddHandler(BalloonTipClosedEvent, value); + remove => RemoveHandler(BalloonTipClosedEvent, value); + } + + protected virtual void OnClick(MouseButtonEventArgs e) => RaiseEvent(e); + + protected virtual void OnDoubleClick(MouseButtonEventArgs e) => RaiseEvent(e); + + protected virtual void OnMouseDoubleClick(MouseButtonEventArgs e) => RaiseEvent(e); + + protected override void OnMouseDown(MouseButtonEventArgs e) + { + base.OnMouseDown(e); + RaiseEvent(e); + } + + protected override void OnMouseUp(MouseButtonEventArgs e) + { + base.OnMouseUp(e); + RaiseEvent(e); + } + + protected override void OnMouseMove(MouseEventArgs e) + { + base.OnMouseMove(e); + RaiseEvent(e); + } } } \ No newline at end of file diff --git a/HandyControl/Tools/Interop/NativeMethods.cs b/HandyControl/Tools/Interop/NativeMethods.cs index c670c683..b8cfcd43 100644 --- a/HandyControl/Tools/Interop/NativeMethods.cs +++ b/HandyControl/Tools/Interop/NativeMethods.cs @@ -13,22 +13,32 @@ namespace HandyControl.Tools.Interop { public const int WS_CHILD = 0x40000000, - WM_USER = 0x0400, BITSPIXEL = 12, PLANES = 14, BI_RGB = 0, DIB_RGB_COLORS = 0, NIF_MESSAGE = 0x00000001, - NIF_INFO = 0x00000010, NIF_ICON = 0x00000002, NIF_TIP = 0x00000004, + NIF_INFO = 0x00000010, NIM_ADD = 0x00000000, - NIM_DELETE = 0x00000002, NIM_MODIFY = 0x00000001, + NIM_DELETE = 0x00000002, NIIF_NONE = 0x00000000, NIIF_INFO = 0x00000001, NIIF_WARNING = 0x00000002, - NIIF_ERROR = 0x00000003; + NIIF_ERROR = 0x00000003, + WM_MOUSEMOVE = 0x0200, + WM_LBUTTONDOWN = 0x0201, + WM_LBUTTONUP = 0x0202, + WM_LBUTTONDBLCLK = 0x0203, + WM_RBUTTONDOWN = 0x0204, + WM_RBUTTONUP = 0x0205, + WM_RBUTTONDBLCLK = 0x0206, + WM_MBUTTONDOWN = 0x0207, + WM_MBUTTONUP = 0x0208, + WM_MBUTTONDBLCLK = 0x0209, + WM_USER = 0x0400; [DllImport(ExternDll.User32, CharSet = CharSet.Auto)] [ResourceExposure(ResourceScope.None)] diff --git a/HandyControlDemo/UserControl/Controls/NotifyIconDemoCtl.xaml b/HandyControlDemo/UserControl/Controls/NotifyIconDemoCtl.xaml index 6f055fe8..06e00392 100644 --- a/HandyControlDemo/UserControl/Controls/NotifyIconDemoCtl.xaml +++ b/HandyControlDemo/UserControl/Controls/NotifyIconDemoCtl.xaml @@ -4,6 +4,6 @@ xmlns:controls="clr-namespace:HandyControl.Controls;assembly=HandyControl" Background="{DynamicResource RegionBrush}"> - + From 67dc02ae830e3705ab376a5b4d2d2ea9fed22ae7 Mon Sep 17 00:00:00 2001 From: NaBian <836904362@qq.com> Date: Thu, 28 Mar 2019 23:44:47 +0800 Subject: [PATCH 6/9] dev 19-3-28 --- .../Controls/NotifyIcon/NotifyIcon.cs | 236 +++++++++++------- HandyControl/Tools/Interop/NativeMethods.cs | 3 + .../Tools/Interop/UnsafeNativeMethods.cs | 7 +- .../Controls/NotifyIconDemoCtl.xaml | 10 +- 4 files changed, 154 insertions(+), 102 deletions(-) diff --git a/HandyControl/Controls/NotifyIcon/NotifyIcon.cs b/HandyControl/Controls/NotifyIcon/NotifyIcon.cs index 2073315e..b0493987 100644 --- a/HandyControl/Controls/NotifyIcon/NotifyIcon.cs +++ b/HandyControl/Controls/NotifyIcon/NotifyIcon.cs @@ -4,13 +4,14 @@ using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Data; using System.Windows.Input; +using System.Windows.Interop; using System.Windows.Media; using HandyControl.Tools; using HandyControl.Tools.Interop; namespace HandyControl.Controls { - public class NotifyIcon : FrameworkElement + public class NotifyIcon : FrameworkElement, IDisposable { private bool _added; @@ -50,10 +51,30 @@ namespace HandyControl.Controls { _id = ++NextId; _callback = Callback; - CreateToolTip(); - RegisterClass(); - OnIconChanged(); - UpdateIcon(true); + + Loaded += (s, e) => + { + CreateToolTip(); + RegisterClass(); + OnIconChanged(); + UpdateIcon(true); + }; + + if (Application.Current != null) Application.Current.Exit += (s, e) => Dispose(); + } + + ~NotifyIcon() + { + Dispose(false); + } + + public static readonly DependencyProperty TextProperty = DependencyProperty.Register( + "Text", typeof(string), typeof(NotifyIcon), new PropertyMetadata(default(string))); + + public string Text + { + get => (string) GetValue(TextProperty); + set => SetValue(TextProperty, value); } public static readonly DependencyProperty IconProperty = DependencyProperty.Register( @@ -121,9 +142,11 @@ namespace HandyControl.Controls uCallbackMessage = WmTrayMouseMessage, uFlags = NativeMethods.NIF_MESSAGE | NativeMethods.NIF_ICON | NativeMethods.NIF_TIP, hWnd = _messageWindowHandle, + uTimeoutOrVersion = 10, uID = _id, - dwInfoFlags = NativeMethods.NIIF_INFO, - hIcon = _currentSmallIconHandle.CriticalGetHandle() + dwInfoFlags = NativeMethods.NIF_TIP, + hIcon = _currentSmallIconHandle.DangerousGetHandle(), + szTip = Text }; if (showIconInTray) @@ -177,81 +200,92 @@ namespace HandyControl.Controls } else { - //switch (lparam.ToInt32()) - //{ - // case NativeMethods.WM_LBUTTONDBLCLK: - // WmMouseDown(MouseButton.Left, 2); - // break; - // case NativeMethods.WM_LBUTTONDOWN: - // WmMouseDown(MouseButton.Left, 1); - // break; - // case NativeMethods.WM_LBUTTONUP: - // WmMouseUp(MouseButton.Left); - // break; - // case NativeMethods.WM_MBUTTONDBLCLK: - // WmMouseDown(MouseButton.Middle, 2); - // break; - // case NativeMethods.WM_MBUTTONDOWN: - // WmMouseDown(MouseButton.Middle, 1); - // break; - // case NativeMethods.WM_MBUTTONUP: - // WmMouseUp(MouseButton.Middle); - // break; - // case NativeMethods.WM_MOUSEMOVE: - // WmMouseMove(); - // break; - // case NativeMethods.WM_RBUTTONDBLCLK: - // WmMouseDown(MouseButton.Right, 2); - // break; - // case NativeMethods.WM_RBUTTONDOWN: - // WmMouseDown(MouseButton.Right, 1); - // break; - // case NativeMethods.WM_RBUTTONUP: - // ShowContextMenu(); - // WmMouseUp(MouseButton.Right); - // break; - //} + Console.WriteLine(lparam); + switch (lparam.ToInt32()) + { + case NativeMethods.WM_LBUTTONDBLCLK: + WmMouseDown(MouseButton.Left, 2); + break; + case NativeMethods.WM_LBUTTONDOWN: + WmMouseDown(MouseButton.Left, 1); + break; + case NativeMethods.WM_LBUTTONUP: + WmMouseUp(MouseButton.Left); + break; + case NativeMethods.WM_MBUTTONDBLCLK: + WmMouseDown(MouseButton.Middle, 2); + break; + case NativeMethods.WM_MBUTTONDOWN: + WmMouseDown(MouseButton.Middle, 1); + break; + case NativeMethods.WM_MBUTTONUP: + WmMouseUp(MouseButton.Middle); + break; + case NativeMethods.WM_MOUSEMOVE: + WmMouseMove(); + break; + case NativeMethods.WM_RBUTTONDBLCLK: + WmMouseDown(MouseButton.Right, 2); + break; + case NativeMethods.WM_RBUTTONDOWN: + WmMouseDown(MouseButton.Right, 1); + break; + case NativeMethods.WM_RBUTTONUP: + ShowContextMenu(); + WmMouseUp(MouseButton.Right); + break; + case 0x406: + OnBalloonTipShown(); + break; + case 0x407: + OnBalloonTipClosed(); + break; + case NativeMethods.NIN_BALLOONTIMEOUT: + OnBalloonTipClosed(); + break; + } } - - var point = new POINT(); - UnsafeNativeMethods.GetCursorPos(point); - ShowToolTip(point); } return UnsafeNativeMethods.DefWindowProc(hWnd, msg, wparam, lparam); } + private void OnBalloonTipShown() + { + var point = new POINT(); + UnsafeNativeMethods.GetCursorPos(point); + ShowToolTip(point); + } + + private void OnBalloonTipClosed() => _toolTip.IsOpen = false; + private void WmMouseDown(MouseButton button, int clicks) { if (clicks == 2) { - OnMouseDoubleClick(new MouseButtonEventArgs(Mouse.PrimaryDevice, Environment.TickCount, button) + RaiseEvent(new MouseButtonEventArgs(Mouse.PrimaryDevice, Environment.TickCount, button) { - Source = this, RoutedEvent = MouseDoubleClickEvent }); _doubleClick = true; } - OnMouseDown(new MouseButtonEventArgs(Mouse.PrimaryDevice, Environment.TickCount, button) + RaiseEvent(new MouseButtonEventArgs(Mouse.PrimaryDevice, Environment.TickCount, button) { - Source = this, RoutedEvent = MouseDownEvent }); } private void WmMouseUp(MouseButton button) { - OnMouseUp(new MouseButtonEventArgs(Mouse.PrimaryDevice, Environment.TickCount, button) + RaiseEvent(new MouseButtonEventArgs(Mouse.PrimaryDevice, Environment.TickCount, button) { - Source = this, RoutedEvent = MouseUpEvent }); if (!_doubleClick) { - OnClick(new MouseButtonEventArgs(Mouse.PrimaryDevice, Environment.TickCount, button) + RaiseEvent(new MouseButtonEventArgs(Mouse.PrimaryDevice, Environment.TickCount, button) { - Source = this, RoutedEvent = ClickEvent }); } @@ -260,16 +294,32 @@ namespace HandyControl.Controls private void WmMouseMove() { - OnMouseMove(new MouseEventArgs(Mouse.PrimaryDevice, Environment.TickCount) + RaiseEvent(new MouseEventArgs(Mouse.PrimaryDevice, Environment.TickCount) { - RoutedEvent = MouseMoveEvent, - Source = this, + RoutedEvent = MouseMoveEvent }); } private void ShowContextMenu() { - if (ContextMenu != null) ContextMenu.IsOpen = true; + if (ContextMenu != null) + { + var point = new POINT(); + UnsafeNativeMethods.GetCursorPos(point); + + ContextMenu.Placement = PlacementMode.AbsolutePoint; + ContextMenu.HorizontalOffset = point.x; + ContextMenu.VerticalOffset = point.y; + ContextMenu.IsOpen = true; + + var handle = IntPtr.Zero; + var hwndSource = (HwndSource)PresentationSource.FromVisual(ContextMenu); + if (hwndSource != null) + { + handle = hwndSource.Handle; + } + UnsafeNativeMethods.SetForegroundWindow(handle); + } } private void ShowToolTip(POINT point) @@ -287,9 +337,7 @@ namespace HandyControl.Controls _toolTip = new ToolTip { Placement = PlacementMode.AbsolutePoint, - HasDropShadow = false, - BorderThickness = new Thickness(0), - Background = Brushes.Transparent + Style = null }; _toolTip.SetBinding(ContentControl.ContentProperty, new Binding(ToolTipProperty.Name) { Source = this }); @@ -316,48 +364,42 @@ namespace HandyControl.Controls remove => RemoveHandler(MouseDoubleClickEvent, value); } - public static readonly RoutedEvent BalloonTipShownEvent = - EventManager.RegisterRoutedEvent("BalloonTipShown", RoutingStrategy.Bubble, - typeof(RoutedEventHandler), typeof(NotifyIcon)); + //public static readonly RoutedEvent BalloonTipShownEvent = + // EventManager.RegisterRoutedEvent("BalloonTipShown", RoutingStrategy.Bubble, + // typeof(RoutedEventHandler), typeof(NotifyIcon)); - public event RoutedEventHandler BalloonTipShown + //public event RoutedEventHandler BalloonTipShown + //{ + // add => AddHandler(BalloonTipShownEvent, value); + // remove => RemoveHandler(BalloonTipShownEvent, value); + //} + + //public static readonly RoutedEvent BalloonTipClosedEvent = + // EventManager.RegisterRoutedEvent("BalloonTipClosed", RoutingStrategy.Bubble, + // typeof(RoutedEventHandler), typeof(NotifyIcon)); + + //public event RoutedEventHandler BalloonTipClosed + //{ + // add => AddHandler(BalloonTipClosedEvent, value); + // remove => RemoveHandler(BalloonTipClosedEvent, value); + //} + + private void Dispose(bool disposing) { - add => AddHandler(BalloonTipShownEvent, value); - remove => RemoveHandler(BalloonTipShownEvent, value); + if (disposing) + { + UpdateIcon(false); + _defaultLargeIconHandle?.Dispose(); + _defaultSmallIconHandle?.Dispose(); + _currentLargeIconHandle?.Dispose(); + _currentSmallIconHandle?.Dispose(); + } } - public static readonly RoutedEvent BalloonTipClosedEvent = - EventManager.RegisterRoutedEvent("BalloonTipClosed", RoutingStrategy.Bubble, - typeof(RoutedEventHandler), typeof(NotifyIcon)); - - public event RoutedEventHandler BalloonTipClosed + public void Dispose() { - add => AddHandler(BalloonTipClosedEvent, value); - remove => RemoveHandler(BalloonTipClosedEvent, value); - } - - protected virtual void OnClick(MouseButtonEventArgs e) => RaiseEvent(e); - - protected virtual void OnDoubleClick(MouseButtonEventArgs e) => RaiseEvent(e); - - protected virtual void OnMouseDoubleClick(MouseButtonEventArgs e) => RaiseEvent(e); - - protected override void OnMouseDown(MouseButtonEventArgs e) - { - base.OnMouseDown(e); - RaiseEvent(e); - } - - protected override void OnMouseUp(MouseButtonEventArgs e) - { - base.OnMouseUp(e); - RaiseEvent(e); - } - - protected override void OnMouseMove(MouseEventArgs e) - { - base.OnMouseMove(e); - RaiseEvent(e); + Dispose(true); + GC.SuppressFinalize(this); } } } \ No newline at end of file diff --git a/HandyControl/Tools/Interop/NativeMethods.cs b/HandyControl/Tools/Interop/NativeMethods.cs index b8cfcd43..b5a01f22 100644 --- a/HandyControl/Tools/Interop/NativeMethods.cs +++ b/HandyControl/Tools/Interop/NativeMethods.cs @@ -21,6 +21,9 @@ namespace HandyControl.Tools.Interop NIF_ICON = 0x00000002, NIF_TIP = 0x00000004, NIF_INFO = 0x00000010, + NIN_BALLOONSHOW = WM_USER + 2, + NIN_BALLOONHIDE = WM_USER + 3, + NIN_BALLOONTIMEOUT = WM_USER + 4, NIM_ADD = 0x00000000, NIM_MODIFY = 0x00000001, NIM_DELETE = 0x00000002, diff --git a/HandyControl/Tools/Interop/UnsafeNativeMethods.cs b/HandyControl/Tools/Interop/UnsafeNativeMethods.cs index 6d472e5a..76ac1fe6 100644 --- a/HandyControl/Tools/Interop/UnsafeNativeMethods.cs +++ b/HandyControl/Tools/Interop/UnsafeNativeMethods.cs @@ -146,7 +146,6 @@ namespace HandyControl.Tools.Interop internal static extern int ExtractIconEx(string szExeFileName, int nIconIndex, out IconHandle phiconLarge, out IconHandle phiconSmall, int nIcons); [DllImport(ExternDll.Shell32, CharSet = CharSet.Auto)] - [ResourceExposure(ResourceScope.None)] public static extern int Shell_NotifyIcon(int message, NOTIFYICONDATA pnid); [SecurityCritical] @@ -167,15 +166,15 @@ namespace HandyControl.Tools.Interop IntPtr lpParam); [DllImport(ExternDll.User32, CharSet = CharSet.Auto, SetLastError = true)] - [ResourceExposure(ResourceScope.None)] public static extern short RegisterClass(WNDCLASS wc); [DllImport(ExternDll.User32, CharSet = CharSet.Auto)] - [ResourceExposure(ResourceScope.None)] public static extern IntPtr DefWindowProc(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam); [DllImport(ExternDll.User32, ExactSpelling = true, CharSet = CharSet.Auto)] - [ResourceExposure(ResourceScope.None)] public static extern bool GetCursorPos([In, Out] POINT pt); + + [DllImport(ExternDll.User32, ExactSpelling = true, CharSet = CharSet.Auto)] + public static extern bool SetForegroundWindow(IntPtr hWnd); } } \ No newline at end of file diff --git a/HandyControlDemo/UserControl/Controls/NotifyIconDemoCtl.xaml b/HandyControlDemo/UserControl/Controls/NotifyIconDemoCtl.xaml index 06e00392..daa6433f 100644 --- a/HandyControlDemo/UserControl/Controls/NotifyIconDemoCtl.xaml +++ b/HandyControlDemo/UserControl/Controls/NotifyIconDemoCtl.xaml @@ -4,6 +4,14 @@ xmlns:controls="clr-namespace:HandyControl.Controls;assembly=HandyControl" Background="{DynamicResource RegionBrush}"> - + + + + + + + + + From 5ee2bdd3f76421a54ab5d205a2b7b49ed76f3504 Mon Sep 17 00:00:00 2001 From: NaBian <836904362@qq.com> Date: Fri, 29 Mar 2019 23:45:16 +0800 Subject: [PATCH 7/9] dev 19-3-29 --- .../{NotifyIcon => Other}/NotifyIcon.cs | 163 +++++++++--------- HandyControl/HandyControl.csproj | 4 +- HandyControl/Interactivity/ControlCommands.cs | 10 ++ .../PushMainWindow2TopCommand.cs | 28 +++ .../Interactivity/ShutdownAppCommand.cs | 15 ++ .../Styles/Base/ToggleButtonBaseStyle.xaml | 8 +- HandyControl/Tools/Interop/WNDCLASS.cs | 16 +- HandyControlDemo/Data/GlobalData.cs | 2 + HandyControlDemo/HandyControlDemo.csproj | 1 + HandyControlDemo/MainWindow.xaml.cs | 16 ++ .../Properties/Langs/Lang.Designer.cs | 63 +++++++ .../Properties/Langs/Lang.en.resx | 21 +++ HandyControlDemo/Properties/Langs/Lang.resx | 21 +++ .../Controls/NotifyIconDemoCtl.xaml | 22 ++- .../UserControl/Main/NoUserContent.xaml.cs | 5 +- .../Controls/NotifyIconDemoViewModel.cs | 20 +++ .../ViewModel/ViewModelLocator.cs | 3 + 17 files changed, 321 insertions(+), 97 deletions(-) rename HandyControl/Controls/{NotifyIcon => Other}/NotifyIcon.cs (79%) create mode 100644 HandyControl/Interactivity/PushMainWindow2TopCommand.cs create mode 100644 HandyControl/Interactivity/ShutdownAppCommand.cs create mode 100644 HandyControlDemo/ViewModel/Controls/NotifyIconDemoViewModel.cs diff --git a/HandyControl/Controls/NotifyIcon/NotifyIcon.cs b/HandyControl/Controls/Other/NotifyIcon.cs similarity index 79% rename from HandyControl/Controls/NotifyIcon/NotifyIcon.cs rename to HandyControl/Controls/Other/NotifyIcon.cs index b0493987..eb263959 100644 --- a/HandyControl/Controls/NotifyIcon/NotifyIcon.cs +++ b/HandyControl/Controls/Other/NotifyIcon.cs @@ -2,10 +2,10 @@ using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; -using System.Windows.Data; using System.Windows.Input; using System.Windows.Interop; using System.Windows.Media; +using HandyControl.Data; using HandyControl.Tools; using HandyControl.Tools.Interop; @@ -35,18 +35,40 @@ namespace HandyControl.Controls private string _windowClassName; - private static readonly int WmTaskbarcreated = NativeMethods.RegisterWindowMessage("TaskbarCreated"); + private int _wmTaskbarCreated; private IntPtr _messageWindowHandle; private readonly WndProc _callback; - private ToolTip _toolTip; - - private Popup _contextMenu; + private Popup _contextContent; private bool _doubleClick; + static NotifyIcon() + { + VisibilityProperty.OverrideMetadata(typeof(NotifyIcon), new PropertyMetadata(Visibility.Visible, OnVisibilityChanged)); + } + + private static void OnVisibilityChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var ctl = (NotifyIcon) d; + var v = (Visibility)e.NewValue; + + if (v == Visibility.Visible) + { + if (ctl._currentSmallIconHandle == null) + { + ctl.OnIconChanged(); + } + ctl.UpdateIcon(true); + } + else if(ctl._currentSmallIconHandle !=null) + { + ctl.UpdateIcon(false); + } + } + public NotifyIcon() { _id = ++NextId; @@ -54,10 +76,12 @@ namespace HandyControl.Controls Loaded += (s, e) => { - CreateToolTip(); RegisterClass(); - OnIconChanged(); - UpdateIcon(true); + if (Visibility == Visibility.Visible) + { + OnIconChanged(); + UpdateIcon(true); + } }; if (Application.Current != null) Application.Current.Exit += (s, e) => Dispose(); @@ -87,6 +111,24 @@ namespace HandyControl.Controls ctl.OnIconChanged(); } + public static readonly DependencyProperty ContextContentProperty = DependencyProperty.Register( + "ContextContent", typeof(object), typeof(NotifyIcon), new PropertyMetadata(default(object))); + + public object ContextContent + { + get => GetValue(ContextContentProperty); + set => SetValue(ContextContentProperty, value); + } + + public static readonly DependencyProperty IsBlinkProperty = DependencyProperty.Register( + "IsBlink", typeof(bool), typeof(NotifyIcon), new PropertyMetadata(ValueBoxes.FalseBox)); + + public bool IsBlink + { + get => (bool) GetValue(IsBlinkProperty); + set => SetValue(IsBlinkProperty, value); + } + public ImageSource Icon { get => (ImageSource)GetValue(IconProperty); @@ -145,7 +187,7 @@ namespace HandyControl.Controls uTimeoutOrVersion = 10, uID = _id, dwInfoFlags = NativeMethods.NIF_TIP, - hIcon = _currentSmallIconHandle.DangerousGetHandle(), + hIcon = _currentSmallIconHandle.CriticalGetHandle(), szTip = Text }; @@ -182,11 +224,12 @@ namespace HandyControl.Controls hIcon = IntPtr.Zero, hCursor = IntPtr.Zero, hbrBackground = IntPtr.Zero, - lpszMenuName = string.Empty, + lpszMenuName = "", lpszClassName = _windowClassName }; UnsafeNativeMethods.RegisterClass(wndclass); + _wmTaskbarCreated = NativeMethods.RegisterWindowMessage("TaskbarCreated"); _messageWindowHandle = UnsafeNativeMethods.CreateWindowEx(0, _windowClassName, "", 0, 0, 0, 1, 1, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); } @@ -194,13 +237,12 @@ namespace HandyControl.Controls { if (IsLoaded) { - if (msg == WmTaskbarcreated) + if (msg == _wmTaskbarCreated) { UpdateIcon(true); } else { - Console.WriteLine(lparam); switch (lparam.ToInt32()) { case NativeMethods.WM_LBUTTONDBLCLK: @@ -234,15 +276,6 @@ namespace HandyControl.Controls ShowContextMenu(); WmMouseUp(MouseButton.Right); break; - case 0x406: - OnBalloonTipShown(); - break; - case 0x407: - OnBalloonTipClosed(); - break; - case NativeMethods.NIN_BALLOONTIMEOUT: - OnBalloonTipClosed(); - break; } } } @@ -250,15 +283,6 @@ namespace HandyControl.Controls return UnsafeNativeMethods.DefWindowProc(hWnd, msg, wparam, lparam); } - private void OnBalloonTipShown() - { - var point = new POINT(); - UnsafeNativeMethods.GetCursorPos(point); - ShowToolTip(point); - } - - private void OnBalloonTipClosed() => _toolTip.IsOpen = false; - private void WmMouseDown(MouseButton button, int clicks) { if (clicks == 2) @@ -302,11 +326,38 @@ namespace HandyControl.Controls private void ShowContextMenu() { - if (ContextMenu != null) - { - var point = new POINT(); - UnsafeNativeMethods.GetCursorPos(point); + var point = new POINT(); + UnsafeNativeMethods.GetCursorPos(point); + if (ContextContent != null) + { + if (_contextContent == null) + { + _contextContent = new Popup + { + Placement = PlacementMode.AbsolutePoint, + AllowsTransparency = true, + StaysOpen = false + }; + } + + _contextContent.HorizontalOffset = point.x; + _contextContent.VerticalOffset = point.y; + _contextContent.Child = new ContentControl + { + Content = ContextContent + }; + _contextContent.IsOpen = true; + var handle = IntPtr.Zero; + var hwndSource = (HwndSource)PresentationSource.FromVisual(_contextContent.Child); + if (hwndSource != null) + { + handle = hwndSource.Handle; + } + UnsafeNativeMethods.SetForegroundWindow(handle); + } + else if (ContextMenu != null) + { ContextMenu.Placement = PlacementMode.AbsolutePoint; ContextMenu.HorizontalOffset = point.x; ContextMenu.VerticalOffset = point.y; @@ -322,28 +373,6 @@ namespace HandyControl.Controls } } - private void ShowToolTip(POINT point) - { - if (_toolTip != null) - { - _toolTip.HorizontalOffset = point.x; - _toolTip.VerticalOffset = point.x; - _toolTip.IsOpen = true; - } - } - - private void CreateToolTip() - { - _toolTip = new ToolTip - { - Placement = PlacementMode.AbsolutePoint, - Style = null - }; - - _toolTip.SetBinding(ContentControl.ContentProperty, new Binding(ToolTipProperty.Name) { Source = this }); - _toolTip.SetBinding(DataContextProperty, new Binding(DataContextProperty.Name) { Source = this }); - } - public static readonly RoutedEvent ClickEvent = EventManager.RegisterRoutedEvent("Click", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(NotifyIcon)); @@ -362,27 +391,7 @@ namespace HandyControl.Controls { add => AddHandler(MouseDoubleClickEvent, value); remove => RemoveHandler(MouseDoubleClickEvent, value); - } - - //public static readonly RoutedEvent BalloonTipShownEvent = - // EventManager.RegisterRoutedEvent("BalloonTipShown", RoutingStrategy.Bubble, - // typeof(RoutedEventHandler), typeof(NotifyIcon)); - - //public event RoutedEventHandler BalloonTipShown - //{ - // add => AddHandler(BalloonTipShownEvent, value); - // remove => RemoveHandler(BalloonTipShownEvent, value); - //} - - //public static readonly RoutedEvent BalloonTipClosedEvent = - // EventManager.RegisterRoutedEvent("BalloonTipClosed", RoutingStrategy.Bubble, - // typeof(RoutedEventHandler), typeof(NotifyIcon)); - - //public event RoutedEventHandler BalloonTipClosed - //{ - // add => AddHandler(BalloonTipClosedEvent, value); - // remove => RemoveHandler(BalloonTipClosedEvent, value); - //} + } private void Dispose(bool disposing) { diff --git a/HandyControl/HandyControl.csproj b/HandyControl/HandyControl.csproj index 31a32fbf..8a67d368 100644 --- a/HandyControl/HandyControl.csproj +++ b/HandyControl/HandyControl.csproj @@ -115,7 +115,7 @@ - + @@ -182,6 +182,8 @@ + + diff --git a/HandyControl/Interactivity/ControlCommands.cs b/HandyControl/Interactivity/ControlCommands.cs index 5a1471f8..efee5e51 100644 --- a/HandyControl/Interactivity/ControlCommands.cs +++ b/HandyControl/Interactivity/ControlCommands.cs @@ -146,5 +146,15 @@ namespace HandyControl.Interactivity /// 打开链接 /// public static RoutedCommand OpenLink { get; } = new RoutedCommand(nameof(OpenLink), typeof(ControlCommands)); + + /// + /// 关闭程序 + /// + public static ShutdownAppCommand ShutdownApp { get; } = new ShutdownAppCommand(); + + /// + /// 前置主窗口 + /// + public static PushMainWindow2TopCommand PushMainWindow2Top { get; } = new PushMainWindow2TopCommand(); } } \ No newline at end of file diff --git a/HandyControl/Interactivity/PushMainWindow2TopCommand.cs b/HandyControl/Interactivity/PushMainWindow2TopCommand.cs new file mode 100644 index 00000000..3bc1ca18 --- /dev/null +++ b/HandyControl/Interactivity/PushMainWindow2TopCommand.cs @@ -0,0 +1,28 @@ +using System; +using System.Windows; +using System.Windows.Input; +using System.Windows.Interop; +using HandyControl.Tools.Interop; + +namespace HandyControl.Interactivity +{ + public class PushMainWindow2TopCommand : ICommand + { + public bool CanExecute(object parameter) => true; + + public void Execute(object parameter) + { + if (Application.Current.MainWindow != null) + { + Application.Current.MainWindow.Show(); + var hwndSource = (HwndSource)PresentationSource.FromDependencyObject(Application.Current.MainWindow); + if (hwndSource != null) + { + UnsafeNativeMethods.SetForegroundWindow(hwndSource.Handle); + } + } + } + + public event EventHandler CanExecuteChanged; + } +} \ No newline at end of file diff --git a/HandyControl/Interactivity/ShutdownAppCommand.cs b/HandyControl/Interactivity/ShutdownAppCommand.cs new file mode 100644 index 00000000..85bc706c --- /dev/null +++ b/HandyControl/Interactivity/ShutdownAppCommand.cs @@ -0,0 +1,15 @@ +using System; +using System.Windows; +using System.Windows.Input; + +namespace HandyControl.Interactivity +{ + public class ShutdownAppCommand : ICommand + { + public bool CanExecute(object parameter) => true; + + public void Execute(object parameter) => Application.Current.Shutdown(); + + public event EventHandler CanExecuteChanged; + } +} \ No newline at end of file diff --git a/HandyControl/Themes/Styles/Base/ToggleButtonBaseStyle.xaml b/HandyControl/Themes/Styles/Base/ToggleButtonBaseStyle.xaml index 86a687e0..dc041caa 100644 --- a/HandyControl/Themes/Styles/Base/ToggleButtonBaseStyle.xaml +++ b/HandyControl/Themes/Styles/Base/ToggleButtonBaseStyle.xaml @@ -123,22 +123,22 @@ - + - - + + - + diff --git a/HandyControl/Tools/Interop/WNDCLASS.cs b/HandyControl/Tools/Interop/WNDCLASS.cs index 1fef93ed..33dd3903 100644 --- a/HandyControl/Tools/Interop/WNDCLASS.cs +++ b/HandyControl/Tools/Interop/WNDCLASS.cs @@ -11,13 +11,13 @@ namespace HandyControl.Tools.Interop { public int style; public WndProc lpfnWndProc; - public int cbClsExtra = 0; - public int cbWndExtra = 0; - public IntPtr hInstance = IntPtr.Zero; - public IntPtr hIcon = IntPtr.Zero; - public IntPtr hCursor = IntPtr.Zero; - public IntPtr hbrBackground = IntPtr.Zero; - public string lpszMenuName = null; - public string lpszClassName = null; + public int cbClsExtra; + public int cbWndExtra; + public IntPtr hInstance; + public IntPtr hIcon; + public IntPtr hCursor; + public IntPtr hbrBackground; + public string lpszMenuName; + public string lpszClassName; } } \ No newline at end of file diff --git a/HandyControlDemo/Data/GlobalData.cs b/HandyControlDemo/Data/GlobalData.cs index 9c0f9f69..92e46551 100644 --- a/HandyControlDemo/Data/GlobalData.cs +++ b/HandyControlDemo/Data/GlobalData.cs @@ -32,5 +32,7 @@ namespace HandyControlDemo.Data } public static AppConfig Config { get; set; } + + public static bool NotifyIconIsShow { get; set; } } } \ No newline at end of file diff --git a/HandyControlDemo/HandyControlDemo.csproj b/HandyControlDemo/HandyControlDemo.csproj index 6a8273dd..53deb9f4 100644 --- a/HandyControlDemo/HandyControlDemo.csproj +++ b/HandyControlDemo/HandyControlDemo.csproj @@ -362,6 +362,7 @@ + diff --git a/HandyControlDemo/MainWindow.xaml.cs b/HandyControlDemo/MainWindow.xaml.cs index bbd4e185..f4ff06dc 100644 --- a/HandyControlDemo/MainWindow.xaml.cs +++ b/HandyControlDemo/MainWindow.xaml.cs @@ -1,5 +1,7 @@ using System; +using System.ComponentModel; using HandyControl.Controls; +using HandyControlDemo.Data; namespace HandyControlDemo { @@ -23,5 +25,19 @@ namespace HandyControlDemo Growl.SetGrowlParent(GrowlPanel, false); } + + protected override void OnClosing(CancelEventArgs e) + { + if (GlobalData.NotifyIconIsShow) + { + MessageBox.Info(Properties.Langs.Lang.AppClosingTip, Properties.Langs.Lang.Tip); + Hide(); + e.Cancel = true; + } + else + { + base.OnClosing(e); + } + } } } \ No newline at end of file diff --git a/HandyControlDemo/Properties/Langs/Lang.Designer.cs b/HandyControlDemo/Properties/Langs/Lang.Designer.cs index 4c80a9c8..57bd671d 100644 --- a/HandyControlDemo/Properties/Langs/Lang.Designer.cs +++ b/HandyControlDemo/Properties/Langs/Lang.Designer.cs @@ -78,6 +78,15 @@ namespace HandyControlDemo.Properties.Langs { } } + /// + /// 查找类似 托盘图标已打开,将隐藏窗口而不是关闭程序 的本地化字符串。 + /// + public static string AppClosingTip { + get { + return ResourceManager.GetString("AppClosingTip", resourceCulture); + } + } + /// /// 查找类似 询问 的本地化字符串。 /// @@ -105,6 +114,15 @@ namespace HandyControlDemo.Properties.Langs { } } + /// + /// 查找类似 闪烁 的本地化字符串。 + /// + public static string Blink { + get { + return ResourceManager.GetString("Blink", resourceCulture); + } + } + /// /// 查找类似 博客 的本地化字符串。 /// @@ -429,6 +447,15 @@ namespace HandyControlDemo.Properties.Langs { } } + /// + /// 查找类似 退出 的本地化字符串。 + /// + public static string Exit { + get { + return ResourceManager.GetString("Exit", resourceCulture); + } + } + /// /// 查找类似 展开框 的本地化字符串。 /// @@ -798,6 +825,15 @@ namespace HandyControlDemo.Properties.Langs { } } + /// + /// 查找类似 打开面板 的本地化字符串。 + /// + public static string OpenPanel { + get { + return ResourceManager.GetString("OpenPanel", resourceCulture); + } + } + /// /// 查找类似 轮廓文本 的本地化字符串。 /// @@ -1176,6 +1212,15 @@ namespace HandyControlDemo.Properties.Langs { } } + /// + /// 查找类似 提示 的本地化字符串。 + /// + public static string Tip { + get { + return ResourceManager.GetString("Tip", resourceCulture); + } + } + /// /// 查找类似 标题 的本地化字符串。 /// @@ -1248,6 +1293,15 @@ namespace HandyControlDemo.Properties.Langs { } } + /// + /// 查找类似 试试关闭程序吧? 的本地化字符串。 + /// + public static string Try2CloseApp { + get { + return ResourceManager.GetString("Try2CloseApp", resourceCulture); + } + } + /// /// 查找类似 类型 的本地化字符串。 /// @@ -1275,6 +1329,15 @@ namespace HandyControlDemo.Properties.Langs { } } + /// + /// 查找类似 可见性 的本地化字符串。 + /// + public static string Visibility { + get { + return ResourceManager.GetString("Visibility", resourceCulture); + } + } + /// /// 查找类似 警告 的本地化字符串。 /// diff --git a/HandyControlDemo/Properties/Langs/Lang.en.resx b/HandyControlDemo/Properties/Langs/Lang.en.resx index 27a87236..ea35ef98 100644 --- a/HandyControlDemo/Properties/Langs/Lang.en.resx +++ b/HandyControlDemo/Properties/Langs/Lang.en.resx @@ -531,4 +531,25 @@ New window + + Visibility + + + Blink + + + Exit + + + Open Panel + + + The tray icon is open and will hide the window instead of closing the program + + + Prompt + + + Try to close app? + \ No newline at end of file diff --git a/HandyControlDemo/Properties/Langs/Lang.resx b/HandyControlDemo/Properties/Langs/Lang.resx index dc7baedd..f6cf4a2e 100644 --- a/HandyControlDemo/Properties/Langs/Lang.resx +++ b/HandyControlDemo/Properties/Langs/Lang.resx @@ -531,4 +531,25 @@ 新建窗口 + + 可见性 + + + 闪烁 + + + 退出 + + + 打开面板 + + + 托盘图标已打开,将隐藏窗口而不是关闭程序 + + + 提示 + + + 试试关闭程序吧? + \ No newline at end of file diff --git a/HandyControlDemo/UserControl/Controls/NotifyIconDemoCtl.xaml b/HandyControlDemo/UserControl/Controls/NotifyIconDemoCtl.xaml index daa6433f..3b701d5d 100644 --- a/HandyControlDemo/UserControl/Controls/NotifyIconDemoCtl.xaml +++ b/HandyControlDemo/UserControl/Controls/NotifyIconDemoCtl.xaml @@ -1,15 +1,29 @@  - + + + + + + + + + + + - - - + + diff --git a/HandyControlDemo/UserControl/Main/NoUserContent.xaml.cs b/HandyControlDemo/UserControl/Main/NoUserContent.xaml.cs index f765a014..acb4e8b4 100644 --- a/HandyControlDemo/UserControl/Main/NoUserContent.xaml.cs +++ b/HandyControlDemo/UserControl/Main/NoUserContent.xaml.cs @@ -1,5 +1,4 @@ -using System; -using System.Diagnostics; +using System.Diagnostics; using System.Reflection; using System.Windows; using System.Windows.Controls; @@ -32,7 +31,7 @@ namespace HandyControlDemo.UserControl GlobalData.Config.Lang = tag; GlobalData.Save(); Process.Start(Assembly.GetExecutingAssembly().Location); - Environment.Exit(0); + Application.Current.Shutdown(); return true; }); } diff --git a/HandyControlDemo/ViewModel/Controls/NotifyIconDemoViewModel.cs b/HandyControlDemo/ViewModel/Controls/NotifyIconDemoViewModel.cs new file mode 100644 index 00000000..d2e48bc5 --- /dev/null +++ b/HandyControlDemo/ViewModel/Controls/NotifyIconDemoViewModel.cs @@ -0,0 +1,20 @@ +using GalaSoft.MvvmLight; +using HandyControlDemo.Data; + +namespace HandyControlDemo.ViewModel +{ + public class NotifyIconDemoViewModel : ViewModelBase + { + private bool _isShow; + + public bool IsShow + { + get => _isShow; + set + { + Set(ref _isShow, value); + GlobalData.NotifyIconIsShow = value; + } + } + } +} \ No newline at end of file diff --git a/HandyControlDemo/ViewModel/ViewModelLocator.cs b/HandyControlDemo/ViewModel/ViewModelLocator.cs index 800ce4fe..1627d565 100644 --- a/HandyControlDemo/ViewModel/ViewModelLocator.cs +++ b/HandyControlDemo/ViewModel/ViewModelLocator.cs @@ -26,6 +26,7 @@ namespace HandyControlDemo.ViewModel SimpleIoc.Default.Register(); SimpleIoc.Default.Register(); SimpleIoc.Default.Register(); + SimpleIoc.Default.Register(); } public static ViewModelLocator Instance => new Lazy(() => @@ -57,6 +58,8 @@ namespace HandyControlDemo.ViewModel public SearchBarDemoViewModel SearchBarDemo => ServiceLocator.Current.GetInstance(); + public NotifyIconDemoViewModel NotifyIconDemo => ServiceLocator.Current.GetInstance(); + #endregion } } \ No newline at end of file From 6e56c967ceca9ee6c8d96e367f27825c5d8d48fd Mon Sep 17 00:00:00 2001 From: NaBian <836904362@qq.com> Date: Sat, 30 Mar 2019 23:42:21 +0800 Subject: [PATCH 8/9] dev 19-3-30 --- HandyControl/Controls/Other/NotifyIcon.cs | 78 +++++++++++++++--- HandyControl/Tools/Interop/BitmapHandle.cs | 8 ++ HandyControlDemo/HandyControlDemo.csproj | 1 + HandyControlDemo/Resources/Img/icon-white.ico | Bin 0 -> 99678 bytes HandyControlDemo/Resources/Img/icon-white.png | Bin 16561 -> 0 bytes .../Controls/NotifyIconDemoCtl.xaml | 49 ++++++++--- .../Controls/NotifyIconDemoViewModel.cs | 38 +++++++-- 7 files changed, 147 insertions(+), 27 deletions(-) create mode 100644 HandyControlDemo/Resources/Img/icon-white.ico delete mode 100644 HandyControlDemo/Resources/Img/icon-white.png diff --git a/HandyControl/Controls/Other/NotifyIcon.cs b/HandyControl/Controls/Other/NotifyIcon.cs index eb263959..64c2e802 100644 --- a/HandyControl/Controls/Other/NotifyIcon.cs +++ b/HandyControl/Controls/Other/NotifyIcon.cs @@ -5,6 +5,7 @@ using System.Windows.Controls.Primitives; using System.Windows.Input; using System.Windows.Interop; using System.Windows.Media; +using System.Windows.Threading; using HandyControl.Data; using HandyControl.Tools; using HandyControl.Tools.Interop; @@ -45,6 +46,10 @@ namespace HandyControl.Controls private bool _doubleClick; + private DispatcherTimer _dispatcherTimer; + + private bool _isTransparent; + static NotifyIcon() { VisibilityProperty.OverrideMetadata(typeof(NotifyIcon), new PropertyMetadata(Visibility.Visible, OnVisibilityChanged)); @@ -111,6 +116,12 @@ namespace HandyControl.Controls ctl.OnIconChanged(); } + public ImageSource Icon + { + get => (ImageSource)GetValue(IconProperty); + set => SetValue(IconProperty, value); + } + public static readonly DependencyProperty ContextContentProperty = DependencyProperty.Register( "ContextContent", typeof(object), typeof(NotifyIcon), new PropertyMetadata(default(object))); @@ -120,8 +131,56 @@ namespace HandyControl.Controls set => SetValue(ContextContentProperty, value); } + public static readonly DependencyProperty BlinkIntervalProperty = DependencyProperty.Register( + "BlinkInterval", typeof(TimeSpan), typeof(NotifyIcon), new PropertyMetadata(TimeSpan.FromMilliseconds(500), OnBlinkIntervalChanged)); + + private static void OnBlinkIntervalChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var ctl = (NotifyIcon)d; + if (ctl._dispatcherTimer != null) + { + ctl._dispatcherTimer.Interval = (TimeSpan) e.NewValue; + } + } + + public TimeSpan BlinkInterval + { + get => (TimeSpan) GetValue(BlinkIntervalProperty); + set => SetValue(BlinkIntervalProperty, value); + } + public static readonly DependencyProperty IsBlinkProperty = DependencyProperty.Register( - "IsBlink", typeof(bool), typeof(NotifyIcon), new PropertyMetadata(ValueBoxes.FalseBox)); + "IsBlink", typeof(bool), typeof(NotifyIcon), new PropertyMetadata(ValueBoxes.FalseBox, OnIsBlinkChanged)); + + private static void OnIsBlinkChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var ctl = (NotifyIcon)d; + if (ctl.Visibility != Visibility.Visible) return; + if ((bool) e.NewValue) + { + if (ctl._dispatcherTimer == null) + { + ctl._dispatcherTimer = new DispatcherTimer + { + Interval = ctl.BlinkInterval + }; + ctl._dispatcherTimer.Tick += ctl.DispatcherTimer_Tick; + } + ctl._dispatcherTimer.Start(); + } + else + { + ctl._dispatcherTimer?.Stop(); + ctl._dispatcherTimer = null; + ctl.UpdateIcon(true); + } + } + + private void DispatcherTimer_Tick(object sender, EventArgs e) + { + if (Visibility != Visibility.Visible || _currentSmallIconHandle == null) return; + UpdateIcon(true, !_isTransparent); + } public bool IsBlink { @@ -129,12 +188,6 @@ namespace HandyControl.Controls set => SetValue(IsBlinkProperty, value); } - public ImageSource Icon - { - get => (ImageSource)GetValue(IconProperty); - set => SetValue(IconProperty, value); - } - private void OnIconChanged() { IconHandle largeIconHandle; @@ -173,12 +226,13 @@ namespace HandyControl.Controls _currentSmallIconHandle = smallIconHandle; } - private void UpdateIcon(bool showIconInTray) + private void UpdateIcon(bool showIconInTray, bool isTransparent = false) { lock (_syncObj) { - if (DesignerHelper.IsInDesignMode) return; + if (_currentSmallIconHandle == null || DesignerHelper.IsInDesignMode) return; + _isTransparent = isTransparent; var data = new NOTIFYICONDATA { uCallbackMessage = WmTrayMouseMessage, @@ -187,7 +241,7 @@ namespace HandyControl.Controls uTimeoutOrVersion = 10, uID = _id, dwInfoFlags = NativeMethods.NIF_TIP, - hIcon = _currentSmallIconHandle.CriticalGetHandle(), + hIcon = isTransparent ? IntPtr.Zero : _currentSmallIconHandle.CriticalGetHandle(), szTip = Text }; @@ -397,6 +451,10 @@ namespace HandyControl.Controls { if (disposing) { + if (_dispatcherTimer != null && IsBlink) + { + _dispatcherTimer.Stop(); + } UpdateIcon(false); _defaultLargeIconHandle?.Dispose(); _defaultSmallIconHandle?.Dispose(); diff --git a/HandyControl/Tools/Interop/BitmapHandle.cs b/HandyControl/Tools/Interop/BitmapHandle.cs index 2970e514..1e893fa9 100644 --- a/HandyControl/Tools/Interop/BitmapHandle.cs +++ b/HandyControl/Tools/Interop/BitmapHandle.cs @@ -1,12 +1,20 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Runtime.ConstrainedExecution; using System.Runtime.InteropServices; using System.Security; namespace HandyControl.Tools.Interop { + [SuppressMessage("ReSharper", "UnusedMember.Local")] internal sealed class BitmapHandle : WpfSafeHandle { + [SecurityCritical] + private BitmapHandle() : this(true) + { + //请不要删除此构造函数,否则当使用自定义ico文件时会报错 + } + [SecurityCritical] private BitmapHandle(bool ownsHandle) : base(ownsHandle, CommonHandles.GDI) { diff --git a/HandyControlDemo/HandyControlDemo.csproj b/HandyControlDemo/HandyControlDemo.csproj index 53deb9f4..cbbb9f37 100644 --- a/HandyControlDemo/HandyControlDemo.csproj +++ b/HandyControlDemo/HandyControlDemo.csproj @@ -805,6 +805,7 @@ TextTemplatingFileGenerator MessageToken.cs + diff --git a/HandyControlDemo/Resources/Img/icon-white.ico b/HandyControlDemo/Resources/Img/icon-white.ico new file mode 100644 index 0000000000000000000000000000000000000000..a90a5b46828516d6856396b4a348664538ecf932 GIT binary patch literal 99678 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 literal 0 HcmV?d00001 diff --git a/HandyControlDemo/Resources/Img/icon-white.png b/HandyControlDemo/Resources/Img/icon-white.png deleted file mode 100644 index d0bb47760c2597efdc857303fafce90c2151d651..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16561 zcmeHPJ!lkB5Z;Y{N#KHF7cGL|4@AV^wZa)9Nt6W9G=jgSotj3FG+v=%p%#LTSP5dG zp!idnMg&1Df`y!@AQBrvVj30V`Axi)$KKo9*_rpa-+{?{_ulM$GvAy2z1v(qFgcp* z?e7hOAU8fXau98Sw0kxF-aB>XBbuJmV~3}Mpzj4~cw?|{Ll6uE<0E^f&V^y2FgqK< zC@7c9IW$G+KImp>E6aN%_XIimi^$iW~RF^+}+&gzH*i!#S~je`@JXKpnFaKO|Aa6n)q#?cVK zSs8I)M&e#^T7>|EuWVfn0URV372p*|Ljb2VIl-sdc*XHefQt&?5Fp0U5RkTVG%pT} z2T8m#hI0-9Vw{x*P}VLdu-|Z+_}qXA$iZQ{sy6dV z0PfW2nc>=MSmz_+3h=!PIL|b}VO_lKqC=QT>%^O&nKea0x)UQkimp# zY%>i3ja>~)-a7;^iL#kBQU%!JV~u&eCcvXD`bEYuRwT(rMO<6_N}gVxsXid4Y!FRr z2#9>tYByn0^(6+jzXbJ0fbU~~cx4P$IlGz|$2S2+9^!6O6Chq0FF|HwCJMQ*;iW+m=sR>{lN--AWXb7N~c0nH_KzaksCN%Ft?f_%b<_P2TP=|h1E)zI#As^u6 z2=xC-pg#zmhq{s_IJyNLh%!aL3wjMI7T50&Eh4zL&>rXxsJ6o3qDp9grxzqYLkVL& zR4lFu5C>AP4SlRoy>b!Zb_f7%^j>YdN{K2!_4Y-RGxwAZf^-gV-xsOzwG*AX=#ut2 vl-? - - - - + + + + + + + + + + + + - - - + + + + + + + + + + - - + @@ -27,5 +39,18 @@ - + + + + + + +