开发者

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

开发者 https://www.devze.com 2022-12-02 11:31 出处:网络 作者: 驚鏵
wpF 基础控件之托盘 框架使用大于等于.NET40。 Visual Studio 2022。 项目使用 MIT 开源许可协议。

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

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

示例代码

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实现基础控件之托盘的示例代码的详细内容,更多关于WPF托盘的资料请关注我们其它相关文章!

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号