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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +