diff --git a/HandyControl/Controls/Other/NotifyIcon.cs b/HandyControl/Controls/Other/NotifyIcon.cs
new file mode 100644
index 00000000..c916b913
--- /dev/null
+++ b/HandyControl/Controls/Other/NotifyIcon.cs
@@ -0,0 +1,451 @@
+using System;
+using System.Windows;
+using System.Windows.Controls;
+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;
+
+namespace HandyControl.Controls
+{
+ public class NotifyIcon : FrameworkElement, IDisposable
+ {
+ private bool _added;
+
+ private readonly object _syncObj = new object();
+
+ 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;
+
+ private string _windowClassName;
+
+ private int _wmTaskbarCreated;
+
+ private IntPtr _messageWindowHandle;
+
+ private readonly WndProc _callback;
+
+ private Popup _contextContent;
+
+ private bool _doubleClick;
+
+ private DispatcherTimer _dispatcherTimer;
+
+ private bool _isTransparent;
+
+ private bool _isDisposed;
+
+ 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;
+ _callback = Callback;
+
+ Loaded += (s, e) =>
+ {
+ RegisterClass();
+ if (Visibility == Visibility.Visible)
+ {
+ 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(
+ "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.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)));
+
+ public object ContextContent
+ {
+ get => GetValue(ContextContentProperty);
+ 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, 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
+ {
+ get => (bool) GetValue(IsBlinkProperty);
+ set => SetValue(IsBlinkProperty, value);
+ }
+
+ private void OnIconChanged()
+ {
+ 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;
+ }
+
+ private void UpdateIcon(bool showIconInTray, bool isTransparent = false)
+ {
+ lock (_syncObj)
+ {
+ if (_currentSmallIconHandle == null || DesignerHelper.IsInDesignMode) return;
+
+ _isTransparent = isTransparent;
+ var data = new NOTIFYICONDATA
+ {
+ uCallbackMessage = WmTrayMouseMessage,
+ uFlags = NativeMethods.NIF_MESSAGE | NativeMethods.NIF_ICON | NativeMethods.NIF_TIP,
+ hWnd = _messageWindowHandle,
+ uID = _id,
+ dwInfoFlags = NativeMethods.NIF_TIP,
+ hIcon = isTransparent ? IntPtr.Zero : _currentSmallIconHandle.CriticalGetHandle(),
+ szTip = Text
+ };
+
+ if (showIconInTray)
+ {
+ 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;
+ }
+ }
+ }
+
+ 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 = "",
+ 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);
+ }
+
+ private IntPtr Callback(IntPtr hWnd, int msg, IntPtr wparam, IntPtr lparam)
+ {
+ if (IsLoaded)
+ {
+ if (msg == _wmTaskbarCreated)
+ {
+ UpdateIcon(true);
+ }
+ else
+ {
+ switch (lparam.ToInt32())
+ {
+ case NativeMethods.WM_LBUTTONDBLCLK:
+ WmMouseDown(MouseButton.Left, 2);
+ break;
+ case NativeMethods.WM_LBUTTONUP:
+ WmMouseUp(MouseButton.Left);
+ break;
+ case NativeMethods.WM_RBUTTONUP:
+ ShowContextMenu();
+ WmMouseUp(MouseButton.Right);
+ break;
+ }
+ }
+ }
+
+ return UnsafeNativeMethods.DefWindowProc(hWnd, msg, wparam, lparam);
+ }
+
+ private void WmMouseDown(MouseButton button, int clicks)
+ {
+ if (clicks == 2)
+ {
+ RaiseEvent(new MouseButtonEventArgs(Mouse.PrimaryDevice, Environment.TickCount, button)
+ {
+ RoutedEvent = MouseDoubleClickEvent
+ });
+ _doubleClick = true;
+ }
+ }
+
+ private void WmMouseUp(MouseButton button)
+ {
+ if (!_doubleClick && button == MouseButton.Left)
+ {
+ RaiseEvent(new MouseButtonEventArgs(Mouse.PrimaryDevice, Environment.TickCount, button)
+ {
+ RoutedEvent = ClickEvent
+ });
+ }
+ _doubleClick = false;
+ }
+
+ private void ShowContextMenu()
+ {
+ 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;
+ ContextMenu.IsOpen = true;
+
+ var handle = IntPtr.Zero;
+ var hwndSource = (HwndSource)PresentationSource.FromVisual(ContextMenu);
+ if (hwndSource != null)
+ {
+ handle = hwndSource.Handle;
+ }
+ UnsafeNativeMethods.SetForegroundWindow(handle);
+ }
+ }
+
+ 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);
+ }
+
+ private void Dispose(bool disposing)
+ {
+ if (_isDisposed) return;
+ if (disposing)
+ {
+ if (_dispatcherTimer != null && IsBlink)
+ {
+ _dispatcherTimer.Stop();
+ }
+ UpdateIcon(false);
+ _defaultLargeIconHandle?.Dispose();
+ _defaultSmallIconHandle?.Dispose();
+ _currentLargeIconHandle?.Dispose();
+ _currentSmallIconHandle?.Dispose();
+ }
+
+ _isDisposed = true;
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ public void CloseContextControl()
+ {
+ if (_contextContent != null)
+ {
+ _contextContent.IsOpen = false;
+ }
+ else if (ContextMenu != null)
+ {
+ ContextMenu.IsOpen = false;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/HandyControl/HandyControl.csproj b/HandyControl/HandyControl.csproj
index a5a8e24d..8ec96797 100644
--- a/HandyControl/HandyControl.csproj
+++ b/HandyControl/HandyControl.csproj
@@ -115,6 +115,7 @@
+
@@ -179,6 +180,8 @@
+
+
@@ -223,8 +226,12 @@
+
+
+
+
@@ -304,6 +311,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
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/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/Interactivity/PushMainWindow2TopCommand.cs b/HandyControl/Interactivity/PushMainWindow2TopCommand.cs
new file mode 100644
index 00000000..65e96979
--- /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.Visibility != Visibility.Visible)
+ {
+ 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/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 d79faab7..58264b24 100644
--- a/HandyControl/Tools/Helper/ExternDllHelper.cs
+++ b/HandyControl/Tools/Helper/ExternDllHelper.cs
@@ -10,13 +10,13 @@ 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
public const int E_FAIL = unchecked((int) 0x80004005);
[StructLayout(LayoutKind.Sequential)]
@@ -70,7 +70,6 @@ namespace HandyControl.Tools
[In]
int len);
-
int Write(
[In]
IntPtr buf,
@@ -84,7 +83,6 @@ namespace HandyControl.Tools
[In]
int dwOrigin);
-
void SetSize(
[In, MarshalAs(UnmanagedType.I8)]
long libNewSize);
@@ -98,15 +96,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,
@@ -115,7 +110,6 @@ namespace HandyControl.Tools
[In]
int dwLockType);
-
void UnlockRegion(
[In, MarshalAs(UnmanagedType.I8)]
long libOffset,
@@ -124,7 +118,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..afecc130
--- /dev/null
+++ b/HandyControl/Tools/Helper/IconHelper.cs
@@ -0,0 +1,294 @@
+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..10c17ec4
--- /dev/null
+++ b/HandyControl/Tools/Helper/SecurityHelper.cs
@@ -0,0 +1,22 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Security;
+using System.Security.Permissions;
+
+namespace HandyControl.Tools
+{
+ [SuppressMessage("ReSharper", "InconsistentNaming")]
+ 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..4938fbdd
--- /dev/null
+++ b/HandyControl/Tools/Interop/BITMAPINFO.cs
@@ -0,0 +1,54 @@
+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;
+
+ 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..1e893fa9
--- /dev/null
+++ b/HandyControl/Tools/Interop/BitmapHandle.cs
@@ -0,0 +1,45 @@
+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)
+ {
+ }
+
+ [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..9a61a221
--- /dev/null
+++ b/HandyControl/Tools/Interop/CommonHandles.cs
@@ -0,0 +1,16 @@
+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);
+
+ 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..e0500e0f
--- /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..659c98e6
--- /dev/null
+++ b/HandyControl/Tools/Interop/ICONINFO.cs
@@ -0,0 +1,16 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
+
+namespace HandyControl.Tools.Interop
+{
+ [StructLayout(LayoutKind.Sequential)]
+ [SuppressMessage("ReSharper", "InconsistentNaming")]
+ 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/NOTIFYICONDATA.cs b/HandyControl/Tools/Interop/NOTIFYICONDATA.cs
new file mode 100644
index 00000000..c57d604a
--- /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 = string.Empty;
+ public int dwState = 0x01;
+ public int dwStateMask = 0x01;
+ [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
+ public string szInfo = string.Empty;
+ public int uTimeoutOrVersion;
+ [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
+ public string szInfoTitle = string.Empty;
+ 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..b5a01f22
--- /dev/null
+++ b/HandyControl/Tools/Interop/NativeMethods.cs
@@ -0,0 +1,50 @@
+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,
+ BITSPIXEL = 12,
+ PLANES = 14,
+ BI_RGB = 0,
+ DIB_RGB_COLORS = 0,
+ NIF_MESSAGE = 0x00000001,
+ 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,
+ NIIF_NONE = 0x00000000,
+ NIIF_INFO = 0x00000001,
+ NIIF_WARNING = 0x00000002,
+ 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)]
+ 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..67796c7a
--- /dev/null
+++ b/HandyControl/Tools/Interop/NativeValues.cs
@@ -0,0 +1,119 @@
+// ReSharper disable InconsistentNaming
+
+using System.Runtime.InteropServices;
+
+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;
+ }
+
+ [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/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..f4ff08dd
--- /dev/null
+++ b/HandyControl/Tools/Interop/UnsafeNativeMethods.cs
@@ -0,0 +1,182 @@
+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)]
+ 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);
+
+ [SecurityCritical]
+ [SuppressUnmanagedCodeSecurity]
+ [DllImport(ExternDll.User32, CharSet = CharSet.Unicode, SetLastError = true, BestFitMapping = false)]
+ public static extern short RegisterClass(WNDCLASS wc);
+
+ [DllImport(ExternDll.User32, CharSet = CharSet.Auto)]
+ public static extern IntPtr DefWindowProc(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
+
+ [DllImport(ExternDll.User32, ExactSpelling = true, CharSet = CharSet.Auto)]
+ 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/HandyControl/Tools/Interop/WNDCLASS.cs b/HandyControl/Tools/Interop/WNDCLASS.cs
new file mode 100644
index 00000000..33dd3903
--- /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 WndProc lpfnWndProc;
+ 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/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/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/Data/MessageToken.cs b/HandyControlDemo/Data/MessageToken.cs
index 31d0e3ba..6bf61c6e 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 BadgeDemoCtl = nameof(BadgeDemoCtl);
public static readonly string GravatarDemoCtl = nameof(GravatarDemoCtl);
diff --git a/HandyControlDemo/Data/MessageToken.tt b/HandyControlDemo/Data/MessageToken.tt
index c4801cee..149e30eb 100644
--- a/HandyControlDemo/Data/MessageToken.tt
+++ b/HandyControlDemo/Data/MessageToken.tt
@@ -45,6 +45,7 @@ var controlList = new List
"TransferDemoCtl",
"ChatBubbleDemoCtl",
"GotoTopDemoCtl",
+ "NotifyIconDemoCtl"
"BadgeDemoCtl",
"GravatarDemoCtl",
"DialogDemoCtl"
diff --git a/HandyControlDemo/HandyControlDemo.csproj b/HandyControlDemo/HandyControlDemo.csproj
index dee20316..cbbb9f37 100644
--- a/HandyControlDemo/HandyControlDemo.csproj
+++ b/HandyControlDemo/HandyControlDemo.csproj
@@ -187,6 +187,9 @@
GotoTopDemoCtl.xaml
+
+ NotifyIconDemoCtl.xaml
+
GravatarDemoCtl.xaml
@@ -359,6 +362,7 @@
+
@@ -527,6 +531,10 @@
Designer
MSBuild:Compile
+
+ Designer
+ MSBuild:Compile
+
Designer
MSBuild:Compile
@@ -797,6 +805,8 @@
TextTemplatingFileGenerator
MessageToken.cs
+
+
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 8808342d..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);
+ }
+ }
+
///
/// 查找类似 展开框 的本地化字符串。
///
@@ -699,6 +726,15 @@ namespace HandyControlDemo.Properties.Langs {
}
}
+ ///
+ /// 查找类似 托盘图标 的本地化字符串。
+ ///
+ public static string NotifyIcon {
+ get {
+ return ResourceManager.GetString("NotifyIcon", resourceCulture);
+ }
+ }
+
///
/// 查找类似 数值选择控件 的本地化字符串。
///
@@ -789,6 +825,15 @@ namespace HandyControlDemo.Properties.Langs {
}
}
+ ///
+ /// 查找类似 打开面板 的本地化字符串。
+ ///
+ public static string OpenPanel {
+ get {
+ return ResourceManager.GetString("OpenPanel", resourceCulture);
+ }
+ }
+
///
/// 查找类似 轮廓文本 的本地化字符串。
///
@@ -1167,6 +1212,15 @@ namespace HandyControlDemo.Properties.Langs {
}
}
+ ///
+ /// 查找类似 提示 的本地化字符串。
+ ///
+ public static string Tip {
+ get {
+ return ResourceManager.GetString("Tip", resourceCulture);
+ }
+ }
+
///
/// 查找类似 标题 的本地化字符串。
///
@@ -1239,6 +1293,15 @@ namespace HandyControlDemo.Properties.Langs {
}
}
+ ///
+ /// 查找类似 试试关闭程序吧? 的本地化字符串。
+ ///
+ public static string Try2CloseApp {
+ get {
+ return ResourceManager.GetString("Try2CloseApp", resourceCulture);
+ }
+ }
+
///
/// 查找类似 类型 的本地化字符串。
///
@@ -1266,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 da18fdc5..ea35ef98 100644
--- a/HandyControlDemo/Properties/Langs/Lang.en.resx
+++ b/HandyControlDemo/Properties/Langs/Lang.en.resx
@@ -498,6 +498,9 @@
GotoTop
+
+ NotifyIcon
+
Badge
@@ -528,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 1718b806..f6cf4a2e 100644
--- a/HandyControlDemo/Properties/Langs/Lang.resx
+++ b/HandyControlDemo/Properties/Langs/Lang.resx
@@ -498,6 +498,9 @@
回到顶部
+
+ 托盘图标
+
标记
@@ -528,4 +531,25 @@
新建窗口
+
+ 可见性
+
+
+ 闪烁
+
+
+ 退出
+
+
+ 打开面板
+
+
+ 托盘图标已打开,将隐藏窗口而不是关闭程序
+
+
+ 提示
+
+
+ 试试关闭程序吧?
+
\ 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 00000000..49c97644
Binary files /dev/null and b/HandyControlDemo/Resources/Img/LeftMainContent/NotificationHub_16x.png differ
diff --git a/HandyControlDemo/Resources/Img/icon-white.ico b/HandyControlDemo/Resources/Img/icon-white.ico
new file mode 100644
index 00000000..a90a5b46
Binary files /dev/null and b/HandyControlDemo/Resources/Img/icon-white.ico differ
diff --git a/HandyControlDemo/Resources/Img/icon-white.png b/HandyControlDemo/Resources/Img/icon-white.png
deleted file mode 100644
index d0bb4776..00000000
Binary files a/HandyControlDemo/Resources/Img/icon-white.png and /dev/null differ
diff --git a/HandyControlDemo/UserControl/Controls/NotifyIconDemoCtl.xaml b/HandyControlDemo/UserControl/Controls/NotifyIconDemoCtl.xaml
new file mode 100644
index 00000000..bcb6864b
--- /dev/null
+++ b/HandyControlDemo/UserControl/Controls/NotifyIconDemoCtl.xaml
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/HandyControlDemo/UserControl/Controls/NotifyIconDemoCtl.xaml.cs b/HandyControlDemo/UserControl/Controls/NotifyIconDemoCtl.xaml.cs
new file mode 100644
index 00000000..4efd472c
--- /dev/null
+++ b/HandyControlDemo/UserControl/Controls/NotifyIconDemoCtl.xaml.cs
@@ -0,0 +1,14 @@
+using System.Windows;
+
+namespace HandyControlDemo.UserControl
+{
+ public partial class NotifyIconDemoCtl
+ {
+ public NotifyIconDemoCtl()
+ {
+ InitializeComponent();
+ }
+
+ private void ButtonPush_OnClick(object sender, RoutedEventArgs e) => NotifyIconContextContent.CloseContextControl();
+ }
+}
diff --git a/HandyControlDemo/UserControl/Main/LeftMainContent.xaml b/HandyControlDemo/UserControl/Main/LeftMainContent.xaml
index 8b0a2bb3..1f1fb986 100644
--- a/HandyControlDemo/UserControl/Main/LeftMainContent.xaml
+++ b/HandyControlDemo/UserControl/Main/LeftMainContent.xaml
@@ -213,6 +213,11 @@
+
+
+
+
+
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..77456549
--- /dev/null
+++ b/HandyControlDemo/ViewModel/Controls/NotifyIconDemoViewModel.cs
@@ -0,0 +1,54 @@
+using System;
+using GalaSoft.MvvmLight;
+using GalaSoft.MvvmLight.CommandWpf;
+using HandyControl.Controls;
+using HandyControlDemo.Data;
+
+namespace HandyControlDemo.ViewModel
+{
+ public class NotifyIconDemoViewModel : ViewModelBase
+ {
+ private bool _contextMenuIsShow;
+
+ public bool ContextMenuIsShow
+ {
+ get => _contextMenuIsShow;
+ set
+ {
+ Set(ref _contextMenuIsShow, value);
+ GlobalData.NotifyIconIsShow = ContextMenuIsShow || ContextContentIsShow;
+ }
+ }
+
+ private bool _contextMenuIsBlink;
+
+ public bool ContextMenuIsBlink
+ {
+ get => _contextMenuIsBlink;
+ set => Set(ref _contextMenuIsBlink, value);
+ }
+
+ private bool _contextContentIsShow;
+
+ public bool ContextContentIsShow
+ {
+ get => _contextContentIsShow;
+ set
+ {
+ Set(ref _contextContentIsShow, value);
+ GlobalData.NotifyIconIsShow = ContextMenuIsShow || ContextContentIsShow;
+ }
+ }
+
+ private bool _contextContentIsBlink;
+
+ public bool ContextContentIsBlink
+ {
+ get => _contextContentIsBlink;
+ set => Set(ref _contextContentIsBlink, value);
+ }
+
+ public RelayCommand