WPF实现基础控件之托盘的示例代码

Bea ·
更新时间:2024-09-20
· 938 次阅读

WPF 基础控件之托盘

框架使用大于等于.NET40

Visual Studio 2022

项目使用 MIT 开源许可协议。

新建NotifyIcon自定义控件继承自FrameworkElement

创建托盘程序主要借助与 Win32API:

注册窗体对象RegisterClassEx

注册消息获取对应消息标识Id RegisterWindowMessage

创建窗体(本质上托盘在创建时需要一个窗口句柄,完全可以将主窗体的句柄给进去,但是为了更好的管理消息以及托盘的生命周期,通常会创建一个独立不可见的窗口)CreateWindowEx

以下2点需要注意:

托盘控件的ContextMenu菜单MenuItem 在使用binding时无效,是因为DataContext没有带过去,需要重新赋值一次。

托盘控件发送ShowBalloonTip消息通知时候需新建Shell_NotifyIcon

Nuget 最新 Install-Package WPFDevelopers 1.0.9.1-preview

示例代码

1) NotifyIcon.cs 代码如下:

using System; using System.IO; using System.Runtime.InteropServices; using System.Threading; using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Data; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using WPFDevelopers.Controls.Runtimes; using WPFDevelopers.Controls.Runtimes.Interop; using WPFDevelopers.Controls.Runtimes.Shell32; using WPFDevelopers.Controls.Runtimes.User32; namespace WPFDevelopers.Controls {     public class NotifyIcon : FrameworkElement, IDisposable     {         private static NotifyIcon NotifyIconCache;         public static readonly DependencyProperty ContextContentProperty = DependencyProperty.Register(             "ContextContent", typeof(object), typeof(NotifyIcon), new PropertyMetadata(default));         public static readonly DependencyProperty IconProperty =             DependencyProperty.Register("Icon", typeof(ImageSource), typeof(NotifyIcon),                 new PropertyMetadata(default, OnIconPropertyChanged));         public static readonly DependencyProperty TitleProperty =             DependencyProperty.Register("Title", typeof(string), typeof(NotifyIcon),                 new PropertyMetadata(default, OnTitlePropertyChanged));         public static readonly RoutedEvent ClickEvent =             EventManager.RegisterRoutedEvent("Click", RoutingStrategy.Bubble,                 typeof(RoutedEventHandler), typeof(NotifyIcon));         public static readonly RoutedEvent MouseDoubleClickEvent =             EventManager.RegisterRoutedEvent("MouseDoubleClick", RoutingStrategy.Bubble,                 typeof(RoutedEventHandler), typeof(NotifyIcon));         private static bool s_Loaded = false;         private static NotifyIcon s_NotifyIcon;         //这是窗口名称         private readonly string _TrayWndClassName;         //这个是窗口消息名称         private readonly string _TrayWndMessage;         //这个是窗口消息回调(窗口消息都需要在此捕获)         private readonly WndProc _TrayWndProc;         private Popup _contextContent;         private bool _doubleClick;         //图标句柄         private IntPtr _hIcon = IntPtr.Zero;         private ImageSource _icon;         private IntPtr _iconHandle;         private int _IsShowIn;         //托盘对象         private NOTIFYICONDATA _NOTIFYICONDATA;         //这个是传递给托盘的鼠标消息id         private int _TrayMouseMessage;         //窗口句柄         private IntPtr _TrayWindowHandle = IntPtr.Zero;         //通过注册窗口消息可以获取唯一标识Id         private int _WmTrayWindowMessage;         private bool disposedValue;         public NotifyIcon()         {             _TrayWndClassName = $"WPFDevelopers_{Guid.NewGuid()}";             _TrayWndProc = WndProc_CallBack;             _TrayWndMessage = "TrayWndMessageName";             _TrayMouseMessage = (int)WM.USER + 1024;             Start();             if (Application.Current != null)             {                 //Application.Current.MainWindow.Closed += (s, e) => Dispose();                 Application.Current.Exit += (s, e) => Dispose();             }             NotifyIconCache = this;         }         static NotifyIcon()         {             DataContextProperty.OverrideMetadata(typeof(NotifyIcon), new FrameworkPropertyMetadata(DataContextPropertyChanged));             ContextMenuProperty.OverrideMetadata(typeof(NotifyIcon), new FrameworkPropertyMetadata(ContextMenuPropertyChanged));         }         private static void DataContextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) =>             ((NotifyIcon)d).OnDataContextPropertyChanged(e);         private void OnDataContextPropertyChanged(DependencyPropertyChangedEventArgs e)         {             UpdateDataContext(_contextContent, e.OldValue, e.NewValue);             UpdateDataContext(ContextMenu, e.OldValue, e.NewValue);         }         private void UpdateDataContext(FrameworkElement target, object oldValue, object newValue)         {             if (target == null || BindingOperations.GetBindingExpression(target, DataContextProperty) != null) return;             if (ReferenceEquals(this, target.DataContext) || Equals(oldValue, target.DataContext))             {                 target.DataContext = newValue ?? this;             }         }         private static void ContextMenuPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)         {             var ctl = (NotifyIcon)d;             ctl.OnContextMenuPropertyChanged(e);         }         private void OnContextMenuPropertyChanged(DependencyPropertyChangedEventArgs e) =>             UpdateDataContext((ContextMenu)e.NewValue, null, DataContext);         public object ContextContent         {             get => GetValue(ContextContentProperty);             set => SetValue(ContextContentProperty, value);         }         public ImageSource Icon         {             get => (ImageSource)GetValue(IconProperty);             set => SetValue(IconProperty, value);         }         public string Title         {             get => (string)GetValue(TitleProperty);             set => SetValue(TitleProperty, value);         }         public void Dispose()         {             Dispose(true);             GC.SuppressFinalize(this);         }         private static void OnTitlePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)         {             if (d is NotifyIcon trayService)                 trayService.ChangeTitle(e.NewValue?.ToString());         }         private static void OnIconPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)         {             if (d is NotifyIcon trayService)             {                 var notifyIcon = (NotifyIcon)d;                 notifyIcon._icon = (ImageSource)e.NewValue;                 trayService.ChangeIcon();             }         }         public event RoutedEventHandler Click         {             add => AddHandler(ClickEvent, value);             remove => RemoveHandler(ClickEvent, value);         }         public event RoutedEventHandler MouseDoubleClick         {             add => AddHandler(MouseDoubleClickEvent, value);             remove => RemoveHandler(MouseDoubleClickEvent, value);         }         private static void Current_Exit(object sender, ExitEventArgs e)         {             s_NotifyIcon?.Dispose();             s_NotifyIcon = default;         }         public bool Start()         {             RegisterClass(_TrayWndClassName, _TrayWndProc, _TrayWndMessage);             LoadNotifyIconData(string.Empty);             Show();             return true;         }         public bool Stop()         {             //销毁窗体             if (_TrayWindowHandle != IntPtr.Zero)                 if (User32Interop.IsWindow(_TrayWindowHandle))                     User32Interop.DestroyWindow(_TrayWindowHandle);             //反注册窗口类             if (!string.IsNullOrWhiteSpace(_TrayWndClassName))                 User32Interop.UnregisterClassName(_TrayWndClassName, Kernel32Interop.GetModuleHandle(default));             //销毁Icon             if (_hIcon != IntPtr.Zero)                 User32Interop.DestroyIcon(_hIcon);             Hide();             return true;         }         /// <summary>         ///     注册并创建窗口对象         /// </summary>         /// <param name="className">窗口名称</param>         /// <param name="messageName">窗口消息名称</param>         /// <returns></returns>         private bool RegisterClass(string className, WndProc wndproccallback, string messageName)         {             var wndClass = new WNDCLASSEX             {                 cbSize = Marshal.SizeOf(typeof(WNDCLASSEX)),                 style = 0,                 lpfnWndProc = wndproccallback,                 cbClsExtra = 0,                 cbWndExtra = 0,                 hInstance = IntPtr.Zero,                 hCursor = IntPtr.Zero,                 hbrBackground = IntPtr.Zero,                 lpszMenuName = string.Empty,                 lpszClassName = className             };             //注册窗体对象             User32Interop.RegisterClassEx(ref wndClass);             //注册消息获取对应消息标识id             _WmTrayWindowMessage = User32Interop.RegisterWindowMessage(messageName);             //创建窗体(本质上托盘在创建时需要一个窗口句柄,完全可以将主窗体的句柄给进去,但是为了更好的管理消息以及托盘的生命周期,通常会创建一个独立不可见的窗口)             _TrayWindowHandle = User32Interop.CreateWindowEx(0, className, "", 0, 0, 0, 1, 1, IntPtr.Zero, IntPtr.Zero,                 IntPtr.Zero, IntPtr.Zero);             return true;         }         /// <summary>         ///     创建托盘对象         /// </summary>         /// <param name="icon">图标路径,可以修改托盘图标(本质上是可以接受用户传入一个图片对象,然后将图片转成Icon,但是算了这个有点复杂)</param>         /// <param name="title">托盘的tooltip</param>         /// <returns></returns>         private bool LoadNotifyIconData(string title)         {             lock (this)             {                 _NOTIFYICONDATA = NOTIFYICONDATA.GetDefaultNotifyData(_TrayWindowHandle);                 if (_TrayMouseMessage != 0)                     _NOTIFYICONDATA.uCallbackMessage = (uint)_TrayMouseMessage;                 else                     _TrayMouseMessage = (int)_NOTIFYICONDATA.uCallbackMessage;                 if (_iconHandle == IntPtr.Zero)                 {                     var processPath = Kernel32Interop.GetModuleFileName(new HandleRef());                     if (!string.IsNullOrWhiteSpace(processPath))                     {                         var index = IntPtr.Zero;                         var hIcon = Shell32Interop.ExtractAssociatedIcon(IntPtr.Zero, processPath, ref index);                         _NOTIFYICONDATA.hIcon = hIcon;                         _hIcon = hIcon;                     }                 }                 if (!string.IsNullOrWhiteSpace(title))                     _NOTIFYICONDATA.szTip = title;             }             return true;         }         private bool Show()         {             var command = NotifyCommand.NIM_Add;             if (Thread.VolatileRead(ref _IsShowIn) == 1)                 command = NotifyCommand.NIM_Modify;             else                 Thread.VolatileWrite(ref _IsShowIn, 1);             lock (this)             {                 return Shell32Interop.Shell_NotifyIcon(command, ref _NOTIFYICONDATA);             }         }         internal static int AlignToBytes(double original, int nBytesCount)         {             var nBitsCount = 8 << (nBytesCount - 1);             return ((int)Math.Ceiling(original) + (nBitsCount - 1)) / nBitsCount * nBitsCount;         }         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;         }         private byte[] BitmapImageToByteArray(BitmapImage bmp)         {             byte[] bytearray = null;             try             {                 var smarket = bmp.StreamSource;                 if (smarket != null && smarket.Length > 0)                 {                     //设置当前位置                     smarket.Position = 0;                     using (var br = new BinaryReader(smarket))                     {                         bytearray = br.ReadBytes((int)smarket.Length);                     }                 }             }             catch (Exception ex)             {             }             return bytearray;         }         private byte[] ConvertBitmapSourceToBitmapImage(             BitmapSource bitmapSource)         {             byte[] imgByte = default;             if (!(bitmapSource is BitmapImage bitmapImage))             {                 bitmapImage = new BitmapImage();                 var encoder = new BmpBitmapEncoder();                 encoder.Frames.Add(BitmapFrame.Create(bitmapSource));                 using (var memoryStream = new MemoryStream())                 {                     encoder.Save(memoryStream);                     memoryStream.Position = 0;                     bitmapImage.BeginInit();                     bitmapImage.CacheOption = BitmapCacheOption.OnLoad;                     bitmapImage.StreamSource = memoryStream;                     bitmapImage.EndInit();                     imgByte = BitmapImageToByteArray(bitmapImage);                 }             }             return imgByte;         }         internal static IconHandle CreateIconCursor(byte[] xor, int width, int height, int xHotspot,             int yHotspot, bool isIcon)         {             var bits = IntPtr.Zero;             BitmapHandle colorBitmap = null;             var bi = new BITMAPINFO(width, -height, 32)             {                 bmiHeader_biCompression = 0             };             colorBitmap = Gdi32Interop.CreateDIBSection(new HandleRef(null, IntPtr.Zero), ref bi, 0, ref bits, null, 0);             if (colorBitmap.IsInvalid || bits == IntPtr.Zero) return IconHandle.GetInvalidIcon();             Marshal.Copy(xor, 0, bits, xor.Length);             var maskArray = GenerateMaskArray(width, height, xor);             var maskBitmap = Gdi32Interop.CreateBitmap(width, height, 1, 1, maskArray);             if (maskBitmap.IsInvalid) return IconHandle.GetInvalidIcon();             var iconInfo = new Gdi32Interop.ICONINFO             {                 fIcon = isIcon,                 xHotspot = xHotspot,                 yHotspot = yHotspot,                 hbmMask = maskBitmap,                 hbmColor = colorBitmap             };             return User32Interop.CreateIconIndirect(iconInfo);         }         private bool ChangeIcon()         {             var bitmapFrame = _icon as BitmapFrame;             if (bitmapFrame != null && bitmapFrame.Decoder != null)                 if (bitmapFrame.Decoder is IconBitmapDecoder)                 {                     //var iconBitmapDecoder = new Rect(0, 0, _icon.Width, _icon.Height);                     //var dv = new DrawingVisual();                     //var dc = dv.RenderOpen();                     //dc.DrawImage(_icon, iconBitmapDecoder);                     //dc.Close();                     //var bmp = new RenderTargetBitmap((int)_icon.Width, (int)_icon.Height, 96, 96,                     //    PixelFormats.Pbgra32);                     //bmp.Render(dv);                     //BitmapSource bitmapSource = bmp;                     //if (bitmapSource.Format != PixelFormats.Bgra32 && bitmapSource.Format != PixelFormats.Pbgra32)                     //    bitmapSource = new FormatConvertedBitmap(bitmapSource, PixelFormats.Bgra32, null, 0.0);                     var w = bitmapFrame.PixelWidth;                     var h = bitmapFrame.PixelHeight;                     var bpp = bitmapFrame.Format.BitsPerPixel;                     var stride = (bpp * w + 31) / 32 * 4;                     var sizeCopyPixels = stride * h;                     var xor = new byte[sizeCopyPixels];                     bitmapFrame.CopyPixels(xor, stride, 0);                     var iconHandle = CreateIconCursor(xor, w, h, 0, 0, true);                     _iconHandle = iconHandle.CriticalGetHandle();                 }             if (Thread.VolatileRead(ref _IsShowIn) != 1)                 return false;             if (_hIcon != IntPtr.Zero)             {                 User32Interop.DestroyIcon(_hIcon);                 _hIcon = IntPtr.Zero;             }             lock (this)             {                 if (_iconHandle != IntPtr.Zero)                 {                     var hIcon = _iconHandle;                     _NOTIFYICONDATA.hIcon = hIcon;                     _hIcon = hIcon;                 }                 else                 {                     _NOTIFYICONDATA.hIcon = IntPtr.Zero;                 }                 return Shell32Interop.Shell_NotifyIcon(NotifyCommand.NIM_Modify, ref _NOTIFYICONDATA);             }         }         private bool ChangeTitle(string title)         {             if (Thread.VolatileRead(ref _IsShowIn) != 1)                 return false;             lock (this)             {                 _NOTIFYICONDATA.szTip = title;                 return Shell32Interop.Shell_NotifyIcon(NotifyCommand.NIM_Modify, ref _NOTIFYICONDATA);             }         }         public static void ShowBalloonTip(string title, string content, NotifyIconInfoType infoType)         {             if (NotifyIconCache != null)                 NotifyIconCache.ShowBalloonTips(title, content, infoType);         }         public void ShowBalloonTips(string title, string content, NotifyIconInfoType infoType)         {             if (Thread.VolatileRead(ref _IsShowIn) != 1)                 return;             var _ShowNOTIFYICONDATA = NOTIFYICONDATA.GetDefaultNotifyData(_TrayWindowHandle);             _ShowNOTIFYICONDATA.uFlags = NIFFlags.NIF_INFO;             _ShowNOTIFYICONDATA.szInfoTitle = title ?? string.Empty;             _ShowNOTIFYICONDATA.szInfo = content ?? string.Empty;             switch (infoType)             {                 case NotifyIconInfoType.Info:                     _ShowNOTIFYICONDATA.dwInfoFlags = NIIFFlags.NIIF_INFO;                     break;                 case NotifyIconInfoType.Warning:                     _ShowNOTIFYICONDATA.dwInfoFlags = NIIFFlags.NIIF_WARNING;                     break;                 case NotifyIconInfoType.Error:                     _ShowNOTIFYICONDATA.dwInfoFlags = NIIFFlags.NIIF_ERROR;                     break;                 case NotifyIconInfoType.None:                     _ShowNOTIFYICONDATA.dwInfoFlags = NIIFFlags.NIIF_NONE;                     break;             }             Shell32Interop.Shell_NotifyIcon(NotifyCommand.NIM_Modify, ref _ShowNOTIFYICONDATA);         }         private bool Hide()         {             var isShow = Thread.VolatileRead(ref _IsShowIn);             if (isShow != 1)                 return true;             Thread.VolatileWrite(ref _IsShowIn, 0);             lock (this)             {                 return Shell32Interop.Shell_NotifyIcon(NotifyCommand.NIM_Delete, ref _NOTIFYICONDATA);             }         }         private IntPtr WndProc_CallBack(IntPtr hwnd, WM msg, IntPtr wParam, IntPtr lParam)         {             //这是窗口相关的消息             if ((int)msg == _WmTrayWindowMessage)             {             }             else if ((int)msg == _TrayMouseMessage) //这是托盘上鼠标相关的消息             {                 switch ((WM)(long)lParam)                 {                     case WM.LBUTTONDOWN:                         break;                     case WM.LBUTTONUP:                         WMMouseUp(MouseButton.Left);                         break;                     case WM.LBUTTONDBLCLK:                         WMMouseDown(MouseButton.Left, 2);                         break;                     case WM.RBUTTONDOWN:                         break;                     case WM.RBUTTONUP:                         OpenMenu();                         break;                     case WM.MOUSEMOVE:                         break;                     case WM.MOUSEWHEEL:                         break;                 }             }             else if (msg == WM.COMMAND)             {             }             return User32Interop.DefWindowProc(hwnd, msg, wParam, lParam);         }         private void WMMouseUp(MouseButton button)         {             if (!_doubleClick && button == MouseButton.Left)                 RaiseEvent(new MouseButtonEventArgs(                     Mouse.PrimaryDevice,                     Environment.TickCount, button)                 {                     RoutedEvent = ClickEvent                 });             _doubleClick = false;         }         private void WMMouseDown(MouseButton button, int clicks)         {             if (clicks == 2)             {                 RaiseEvent(new MouseButtonEventArgs(                     Mouse.PrimaryDevice,                     Environment.TickCount, button)                 {                     RoutedEvent = MouseDoubleClickEvent                 });                 _doubleClick = true;             }         }         private void OpenMenu()         {             if (ContextContent != null)             {                 _contextContent = new Popup                 {                     Placement = PlacementMode.Mouse,                     AllowsTransparency = true,                     StaysOpen = false,                     UseLayoutRounding = true,                     SnapsToDevicePixels = true                 };                 _contextContent.Child = new ContentControl                 {                     Content = ContextContent                 };                 UpdateDataContext(_contextContent, null, DataContext);                 _contextContent.IsOpen = true;                 User32Interop.SetForegroundWindow(_contextContent.Child.GetHandle());             }             else if (ContextMenu != null)             {                 if (ContextMenu.Items.Count == 0) return;                 ContextMenu.InvalidateProperty(StyleProperty);                 foreach (var item in ContextMenu.Items)                     if (item is MenuItem menuItem)                     {                         menuItem.InvalidateProperty(StyleProperty);                     }                     else                     {                         var container = ContextMenu.ItemContainerGenerator.ContainerFromItem(item) as MenuItem;                         container?.InvalidateProperty(StyleProperty);                     }                 ContextMenu.Placement = PlacementMode.Mouse;                 ContextMenu.IsOpen = true;                 User32Interop.SetForegroundWindow(ContextMenu.GetHandle());             }         }         protected virtual void Dispose(bool disposing)         {             if (!disposedValue)             {                 if (disposing)                     Stop();                 disposedValue = true;             }         }     }     public enum NotifyIconInfoType     {         /// <summary>         ///     No Icon.         /// </summary>         None,         /// <summary>         ///     A Information Icon.         /// </summary>         Info,         /// <summary>         ///     A Warning Icon.         /// </summary>         Warning,         /// <summary>         ///     A Error Icon.         /// </summary>         Error     } }

2) NotifyIconExample.xaml 代码如下:

ContextMenu 使用如下:

<wpfdev:NotifyIcon Title="WPF开发者">             <wpfdev:NotifyIcon.ContextMenu>                 <ContextMenu>                     <MenuItem Header="托盘消息" Click="SendMessage_Click"/>                     <MenuItem Header="退出" Click="Quit_Click"/>                 </ContextMenu>             </wpfdev:NotifyIcon.ContextMenu>         </wpfdev:NotifyIcon>

ContextContent 使用如下:

<wpfdev:NotifyIcon Title="WPF开发者">     <wpfdev:NotifyIcon.ContextContent>           <Border CornerRadius="3" Margin="10"                    Background="{DynamicResource BackgroundSolidColorBrush}"            Effect="{StaticResource NormalShadowDepth}">           <StackPanel VerticalAlignment="Center" Margin="16">             <Rectangle Width="100" Height="100">               <Rectangle.Fill>                   <ImageBrush ImageSource="pack://application:,,,/Logo.ico"/>               </Rectangle.Fill>             </Rectangle>                 <StackPanel Margin="0,16,0,0" HorizontalAlignment="Center" Orientation="Horizontal">                   <Button MinWidth="100" Content="关于"                            Style="{DynamicResource PrimaryButton}"                            Command="{Binding GithubCommand}" />                   <Button Margin="16,0,0,0" MinWidth="100" Content="退出" Click="Quit_Click"/>                 </StackPanel>               </StackPanel>           </Border>      </wpfdev:NotifyIcon.ContextContent>  </wpfdev:NotifyIcon>

3) NotifyIconExample.cs 代码如下:

ContextMenu 使用如下:

 private void Quit_Click(object sender, RoutedEventArgs e)         {             Application.Current.Shutdown();         }         private void SendMessage_Click(object sender, RoutedEventArgs e)         {             NotifyIcon.ShowBalloonTip("Message", " Welcome to WPFDevelopers.Minimal ", NotifyIconInfoType.None);         }

ContextContent 使用如下:

 private void Quit_Click(object sender, RoutedEventArgs e)         {             Application.Current.Shutdown();         }         private void SendMessage_Click(object sender, RoutedEventArgs e)         {             NotifyIcon.ShowBalloonTip("Message", " Welcome to WPFDevelopers.Minimal ", NotifyIconInfoType.None);         }

实现效果

以上就是WPF实现基础控件之托盘的示例代码的详细内容,更多关于WPF托盘的资料请关注软件开发网其它相关文章!



示例 wpf

需要 登录 后方可回复, 如果你还没有账号请 注册新账号