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 代码如下:
usingSystem; usingSystem.IO; usingSystem.Runtime.InteropServices; usingSystem.Threading; usingSystem.Windows; usingSystem.Windows.Controls; usingSystem.Windows.Controls.Primitives; usingSystem.Windows.Data; usingSystem.Windows.Input; usingSystem.Windows.Media; usingSystem.Windows.Media.Imaging; usingWPFDevelopers.Controls.Runtimes; usingWPFDevelopers.Controls.Runtimes.Interop; usingWPFDevelopers.Controls.Runtimes.Shell32; usingWPFDevelopers.Controls.Runtimes.User32; namespaceWPFDevelopers.Controls { publicclassNotifyIcon:FrameworkElement,IDisposable { privatestaticNotifyIconNotifyIconCache; publicstaticreadonlyDependencyPropertyContextContentProperty=DependencyProperty.Register( "ContextContent",typeof(object),typeof(NotifyIcon),newPropertyMetadata(default)); publicstaticreadonlyDependencyPropertyIconProperty= DependencyProperty.Register("Icon",typeof(ImageSource),typeof(NotifyIcon), newPropertyMetadata(default,OnIconPropertyChanged)); publicstaticreadonlyDependencyPropertyTitleProperty= DependencyProperty.Register("Title",typeof(string),typeof(NotifyIcon), newPropertyMetadata(default,OnTitlePropertyChanged)); publicstaticreadonlyRoutedEventClickEvent= EventManager.RegisterRoutedEvent("Click",RoutingStrategy.Bubble, typeof(RoutedEventHandler),typeof(NotifyIcon)); publicstaticreadonlyRoutedEventMouseDoubleClickEvent= EventManager.RegisterRoutedEvent("MouseDoubleClick",RoutingStrategy.Bubble, typeof(RoutedEventHandler),typeof(NotifyIcon)); privatestaticbools_Loaded=false; privatestaticNotifyIcons_NotifyIcon; //这是窗口名称 privatereadonlystring_TrayWndClassName; //这个是窗口消息名称 privatereadonlystring_TrayWndMessage; //这个是窗口消息回调(窗口消息都需要在此捕获) privatereadonlyWndProc_TrayWndProc; privatePopup_contextContent; privatebool_doubleClick; //图标句柄 privateIntPtr_hIcon=IntPtr.Zero; privateImageSource_icon; privateIntPtr_iconHandle; privateint_IsShowIn; //托盘对象 privateNOTIFYICONDATA_NOTIFYICONDATA; //这个是传递给托盘的鼠标消息id privateint_TrayMouseMessage; //窗口句柄 privateIntPtr_TrayWindowHandle=IntPtr.Zero; //通过注册窗口消息可以获取唯一标识Id privateint_WmTrayWindowMessage; privatebooldisposedValue; publicNotifyIcon() { _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; } staticNotifyIcon() { DataContextProperty.OverrideMetadata(typeof(NotifyIcon),newFrameworkPropertyMetadata(DataContextPropertyChanged)); ContextMenuProperty.OverrideMetadata(typeof(NotifyIcon编程客栈),newFrameworkPropertyMetadata(ContextMenuPropertyChanged)); } privatestaticvoidDataContextPropertyChanged(DependencyObjectd,DependencyPropertyChangedEventArgse)=> ((NotifyIcon)d).OnDataContextPropertyChanged(e); privatevoidOnDataContextPropertyChanged(DependencyPropertyChangedEventArgse) { UpdateDataContext(_contextContent,e.OldValue,e.NewValue); UpdateDataContext(ContextMenu,e.OldValue,e.NewValue); } privatevoidUpdateDataContext(FrameworkElementtarget,objectoldValue,objectnewValue) { if(target==null||BindingOperations.GetBindingExpression(target,DataContextProperty)!=null)return; if(ReferenceEquals(this,target.DataContext)||Equals(oldValue,target.DataContext)) { target.DataContext=newValue??this; } } privatestaticvoidContextMenuPropertyChanged(DependencyObjectd,DependencyPropertyChangedEventArgse) { varctl=(NotifyIcon)d; ctl.OnContextMenuPropertyChanged(e); } privatevoidOnContextMenuPropertyChanged(DependencyPropertyChangedEventArgse)=> UpdateDataContext((ContextMenu)e.NewValue,null,DataContext); publicobjectContextContent { get=>GetValue(ContextContentProperty); set=>SetValue(ContextContentProperty,value); } publicImageSourceIcon { get=>(ImageSource)GetValue(IconProperty); set=>SetValue(IconProperty,value); } publicstringTitle { get=>(string)GetValue(TitleProperty); set=>SetValue(TitleProperty,value); } publicvoidDispose() { Dispose(true); GC.SuppressFinalize(this); } privatestaticvoidOnTitlePropertyChanged(DependencyObjectd,DependencyPropertyChangedEventArgse) { if(disNotifyIcontrayService) trayService.ChangeTitle(e.NewValue?.ToString()); } privatestaticvoidOnIjavascriptconPropertyChanged(DependencyObjectd,DependencyPropertyChangedEventArgse) { if(disNotifyIcontrayService) { varnotifyIcon=(NotifyIcon)d; notifyIcon._icon=(ImageSource)e.NewValue; trayService.ChangeIcon(); } } publiceventRoutedEventHandlerClick { add=>AddHandler(ClickEvent,value); remove=>RemoveHandler(ClickEvent,value); } publiceventRoutedEventHandlerMouseDoubleClick { add=>AddHandler(MouseDoubleClickEvent,value); remove=>RemoveHandler(MouseDoubleClickEvent,value); } privatestaticvoidCurrent_Exit(objectsender,ExitEventArgse) { s_NotifyIcon?.Dispose(); s_NotifyIcon=default; } publicboolStart() { RegisterClass(_TrayWndClassName,_TrayWndProc,_TrayWndMessage); LoadNotifyIconData(string.Empty); Show(); returntrue; } publicboolStop() { //销毁窗体 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(); returntrue; } ///<summary> ///注册并创建窗口对象 ///</summary> ///<paramname="className">窗口名称</param> ///<paramname="messageName">窗口消息名称</param> ///<returns></returns> privateboolRegisterClass(stringclassName,WndProcwndproccallback,stringmessageName) { varwndClass=newWNDCLASSEX { 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(refwndClass); //注册消息获取对应消息标识id _WmTrayWindowMessage=User32Interop.RegisterWindowMessage(messageName); //创建窗体(本质上托盘在创建时需要一个窗口句柄,完全可以将主窗体的句柄给进去,但是为了更好的管理消息以及托盘的生命周期,通常会创建一个独立不可见的窗口) _TrayWindowHandle=User32Interop.CreateWindowEx(0,className,"",0,0,0,1,1,IntPtr.Zero,IntPtr.Zero, IntPtr.Zero,IntPtr.Zero); returntrue; } ///<js;summary> ///创建托盘对象 ///</summary> ///<paramname="icon">图标路径,可以修改托盘图标(本质上是可以接受用户传入一个图片对象,然后将图片转成Icon,但是算了这个有点复杂)</param> ///<paramname="title">托盘的tooltip</param> ///<returns></returns> privateboolLoadNotifyIconData(stringtitle) { lock(this) { _NOTIFYICONDATA=NOTIFYICONDATA.GetDefaultNotifyData(_TrayWindowHandle); if(_TrayMouseMessage!=0) _NOTIFYICONDATA.uCallbackMessage=(uint)_TrayMouseMessage; else _TrayMouseMessage=(int)_NOTIFYICONDATA.uCallbackMessage; if(_iconHandle==IntPtr.Zero) { varprocessPath=Kernel32Interop.GetModuleFileName(newHandleRef()); if(!string.IsNullOrWhiteSpace(processPath)) { varindex=IntPtr.Zero; varhIcon=Shell32Interop.ExtractAssociatedIcon(IntPtr.Zero,processPath,refindex); _NOTIFYICONDATA.hIcon=hIcon; _hIcon=hIcon; } } if(!string.IsNullOrWhiteSpace(title)) _NOTIFYICONDATA.szTip=title; } returntrue; } privateboolShow() { varcommand=NotifyCommand.NIM_Add; if(Thread.VolatileRead(ref_IsShowIn)==1) command=NotifyCommand.NIM_Modify; else Thread.VolatileWrite(ref_IsShowIn,1); lock(this) { returnShell32Interop.Shell_NotifyIcon(command,ref_NOTIFYICONDATA); } } internalstaticintAlignToBytes(doubleoriginal,intnBytesCount) { varnBitsCount=8<<(nBytesCount-1); return((int)Math.Ceiling(original)+(nBitsCount-1))/nBitsCount*nBitsCount; } privatestaticbyte[]GenerateMaskArray(intwidth,intheight,byte[]colorArray) { varnCount=width*height; varbytesPerScanLine=AlignToBytes(width,2)/8; varbitsMask=newbyte[bytesPerScanLine*height]; for(vari=0;i<nCount;i++) { varhPos=i%width; varvPos=i/width; varbyteIndex=hPos/8; varoffsetBit=(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; } returnbitsMask; } privatebyte[]BitmapImageToByteArray(BitmapImagebmp) { byte[]bytearray=null; try { varsmarket=bmp.StreamSource; if(smarket!=null&&smarket.Length>0) { //设置当前位置 smarket.Position=0; using(varbr=newBinaryReader(smarket)) { bytearray=br.ReadBytes((int)smarket.Length); } } } catch(Exceptionex) { } returnbytearray; } privatebyte[]ConvertBitmapSourceToBitmapImage( BitmapSourcebitmapSource) { byte[]imgByte=default; if(!(bitmapSourceisBitmapImagebitmapImage)) { bitmapImage=newBitmapImage(); varencoder=newBmpBitmapEncoder(); encoder.Frames.Add(BitmapFrame.Create(bitmapSource)); using(varmemoryStream=newMemoryStream()) { encoder.Save(memoryStream); memoryStream.Position=0; bitmapImage.BeginInit(); bitmapImage.CacheOption=BitmapCach编程客栈eOption.OnLoad; bitmapImage.StreamSource=memoryStream; bitmapImage.EndInit(); imgByte=BitmapImageToByteArray(bitmapImage); } } returnimgByte; } internalstaticIconHandleCreateIconCursor(byte[]xor,intwidth,intheight,intxHotspot, intyHotspot,boolisIcon) { varbits=IntPtr.Zero; BitmapHandlecolorBitmap=null; varbi=newBITMAPINFO(width,-height,32) { bmiHeader_biCompression=0 }; colorBitmap=Gdi32Interop.CreateDIBSection(newHandleRef(null,IntPtr.Zero),refbi,0,refbits,null,0); if(colorBitmap.IsInvalid||bits==IntPtr.Zero)returnIconHandle.GetInvalidIcon(); Marshal.Copy(xor,0,bits,xor.Length); varmaskArray=GenerateMaskArray(width,height,xor); varmaskBitmap=Gdi32Interop.CreateBitmap(width,height,1,1,maskArray); if(maskBitmap.IsInvalid)returnIconHandle.GetInvalidIcon(); variconInfo=newGdi32Interop.ICONINFO { fIcon=isIcon, xHotspot=xHotspot, yHotspot=yHotspot, hbmMask=maskBitmap, hbmColor=colorBitmap }; returnUser32Interop.CreateIconIndirect(iconInfo); } privateboolChangeIcon() { varbitmapFrame=_iconasBitmapFrame; if(bitmapFrame!=null&&bitmapFrame.Decoder!=null) if(bitmapFrame.DecoderisIconBitmapDecoder) { //variconBitmapDecoder=newRect(0,0,_icon.Width,_icon.Height); //vardv=newDrawingVisual(); //vardc=dv.RenderOpen(); //dc.DrawImage(_icon,iconBitmapDecoder); //dc.Close(); //varbmp=newRenderTargetBitmap((int)_icon.Width,(int)_icon.Height,96,96, //PixelFormats.Pbgra32); //bmp.Render(dv); //BitmapSourcebitmapSource=bmp; //if(bitmapSource.Format!=PixelFormats.Bgra32&&bitmapSource.Format!=PixelFormats.Pbgra32) //bitmapSource=newFormatConvertedBitmap(bitmapSource,PixelFormats.Bgra32,null,0.0); varw=bitmapFrame.PixelWidth; varh=bitmapFrame.PixelHeight; varbpp=bitmapFrame.Format.BitsPerPixel; varstride=(bpp*w+31)/32*4; varsizeCopyPixels=stride*h; varxor=newbyte[sizeCopyPixels]; bitmapFrame.CopyPixels(xor,stride,0); variconHandle=CreateIconCursor(xor,w,h,0,0,true); _iconHandle=iconHandle.CriticalGetHandle(); } if(Thread.VolatileRead(ref_IsShowIn)!=1) returnfalse; if(_hIcon!=IntPtr.Zero) { User32Interop.DestroyIcon(_hIcon); _hIcon=IntPtr.Zero; } lock(this) { if(_iconHandle!=IntPtr.Zero) { varhIcon=_iconHandle; _NOTIFYICONDATA.hIcon=hIcon; _hIcon=hIcon; } else { _NOTIFYICONDATA.hIcon=IntPtr.Zero; } returnShell32Interop.Shell_NotifyIcon(NotifyCommand.NIM_Modify,ref_NOTIFYICONDATA); } } privateboolChangeTitle(stringtitle) { if(Thread.VolatileRead(ref_IsShowIn)!=1) returnfalse; lock(this) { _NOTIFYICONDATA.szTip=title; returnShell32Interop.Shell_NotifyIcon(NotifyCommand.NIM_Modify,ref_NOTIFYICONDATA); } } publicstaticvoidShowBalloonTip(stringtitle,stringcontent,NotifyIconInfoTypeinfoType) { if(NotifyIconCache!=null) NotifyIconCache.ShowBalloonTips(title,content,infoType); } publicvoidShowBalloonTips(stringtitle,stringcontent,NotifyIconInfoTypeinfoType) { 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) { caseNotifyIconInfoType.Info: _ShowNOTIFYICONDATA.dwInfoFlags=NIIFFlags.NIIF_INFO; break; caseNotifyIconInfoType.Warning: _ShowNOTIFYICONDATA.dwInfoFlags=NIIFFlags.NIIF_WARNING; break; caseNotifyIconInfoType.Error: _ShowNOTIFYICONDATA.dwInfoFlags=NIIFFlags.NIIF_ERROR; break; caseNotifyIconInfoType.None: _ShowNOTIFYICONDATA.dwInfoFlags=NIIFFlags.NIIF_NONE; break; } Shell32Interop.Shell_NotifyIcon(NotifyCommand.NIM_Modify,ref_ShowNOTIFYICONDATA); } privateboolHide() { varisShow=Thread.VolatileRead(ref_IsShowIn); if(isShow!=1) returntrue; Thread.VolatileWrite(ref_IsShowIn,0); lock(this) { returnShell32Interop.Shell_NotifyIcon(NotifyCommand.NIM_Delete,ref_NOTIFYICONDATA); } } privateIntPtrWndProc_CallBack(IntPt开发者_开发学习rhwnd,WMmsg,IntPtrwParam,IntPtrlParam) { //这是窗口相关的消息 if((int)msg==_WmTrayWindowMessage) { } elseif((int)msg==_TrayMouseMessage)//这是托盘上鼠标相关的消息 { switch((WM)(long)lParam) { caseWM.LBUTTONDOWN: break; caseWM.LBUTTONUP: WMMouseUp(MouseButton.Left); break; caseWM.LBUTTONDBLCLK: WMMouseDown(MouseButton.Left,2); break; caseWM.RBUTTONDOWN: break; caseWM.RBUTTONUP: OpenMenu(); break; caseWM.MOUSEMOVE: break; caseWM.MOUSEWHEEL: break; } } elseif(msg==WM.COMMAND) { } returnUser32Interop.DefWindowProc(hwnd,msg,wParam,lParam); } privatevoidWMMouseUp(MouseButtonbutton) { if(!_doubleClick&&button==MouseButton.Left) RaiseEvent(newMouseButtonEventArgs( Mouse.PrimaryDevice, Environment.TickCount,button) { RoutedEvent=ClickEvent }); _doubleClick=false; } privatevoidWMMouseDown(MouseButtonbutton,intclicks) { if(clicks==2) { RaiseEvent(newMouseButtonEventArgs( Mouse.PrimaryDevice, Environment.TickCount,button) { RoutedEvent=MouseDoubleClickEvent }); _doubleClick=true; } } privatevoidOpenMenu() { if(ContextContent!=null) { _contextContent=newPopup { Placement=PlacementMode.Mouse, AllowsTransparency=true, StaysOpen=false, UseLayoutRounding=true, SnapsToDevicePixels=true }; _contextContent.Child=newContentControl { Content=ContextContent }; UpdateDataContext(_contextContent,null,DataContext); _contextContent.IsOpen=true; User32Interop.SetForegroundWindow(_contextContent.Child.GetHandle()); } elseif(ContextMenu!=null) { if(ContextMenu.Items.Count==0)return; ContextMenu.InvalidateProperty(StyleProperty); foreach(variteminContextMenu.Items) if(itemisMenuItemmenuItem) { menuItem.InvalidateProperty(StyleProperty); } else { varcontainer=ContextMenu.ItemContainerGenerator.ContainerFromItem(item)asMenuItem; container?.InvalidateProperty(StyleProperty); } ContextMenu.Placement=PlacementMode.Mouse; ContextMenu.IsOpen=true; User32Interop.SetForegroundWindow(ContextMenu.GetHandle()); } } protectedvirtualvoidDispose(booldisposing) { if(!disposedValue) { if(disposing) Stop(); disposedValue=true; } } } publicenumNotifyIconInfoType { ///<summary> ///NoIcon. ///</summary> None, ///<summary> ///AInformationIcon. ///</summar编程y> Info, ///<summary> ///AWarningIcon. ///</summary> Warning, ///<summary> ///AErrorIcon. ///</summary> Error } }
2) NotifyIconExample.xaml 代码如下:
ContextMenu
使用如下:
<wpfdev:NotifyIconTitle="WPF开发者"> <wpfdev:NotifyIcon.ContextMenu> <ContextMenu> <MenuItemHeader="托盘消息"Click="SendMessage_Click"/> <MenuItemHeader="退出"Click="Quit_Click"/> </ContextMenu> </wpfdev:NotifyIcon.ContextMenu> </wpfdev:NotifyIcon>
ContextContent
使用如下:
<wpfdev:NotifyIconTitle="WPF开发者"> <wpfdev:NotifyIcon.ContextContent> <BorderCornerRadius="3"Margin="10" Background="{DynamicResourceBackgroundSolidColorBrush}" Effect="{StaticResourceNormalShadowDepth}"> <StackPanelVerticalAlignment="Center"Margin="16"> <RectangleWidth="100"Height="100"> <Rectangle.Fill> <ImageBrushImageSource="pack://application:,,,/Logo.ico"/> </Rectangle.Fill> </Rectangle> <StackPanelMargin="0,16,0,0"HorizontalAlignment="Center"Orientation="Horizontal"> <ButtonMinWidth="100"Content="关于" Style="{DynamicResourcePrimaryButton}" Command="{BindinggithubCommand}"/> <ButtonMargin="16,0,0,0"MinWidth="100"Content="退出"Click="Quit_Click"/> </StackPanel> </StackPanel> </Border> </wpfdev:NotifyIcon.ContextContent> </wpfdev:NotifyIcon>
3) NotifyIconExample.cs 代码如下:
ContextMenu
使用如下:
privatevoidQuit_Click(objectsender,RoutedEventArgse) { Application.Current.Shutdown(); } privatevoidSendMessage_Click(objectsender,RoutedEventArgse) { NotifyIcon.ShowBalloonTip("Message","WelcometoWPFDevelopers.Minimal",NotifyIconInfoType.None); }
ContextContent
使用如下:
privatevoidQuit_Click(objectsender,RoutedEventArgse) { Application.Current.Shutdown(); } privatevoidSendMessage_Click(objectsender,RoutedEventArgse) { NotifyIcon.ShowBalloonTip("Message","WelcometoWPFDevelopers.Minimal",NotifyIconInfoType.None); }
实现效果
以上就是WPF实现基础控件之托盘的示例代码的详细内容,更多关于WPF托盘的资料请关注我们其它相关文章!
精彩评论