WPF中系统托盘图标的暗/亮模式



我在WPF应用程序中使用通知图标。当我单击此图标时,上下文菜单的背景总是白色的,但我想将其更改为黑暗模式。但是如何应用Dark &通知图标的轻模式?

_notifyIcon = new Forms.NotifyIcon();
_notifyIcon.Icon = MySystray.Resources.Systray_icon;
_notifyIcon.Text = APP_NAME;

如果你想使用WPF上下文菜单和windows窗体的NotifyIcon,那么你可能需要调用鼠标钩子来跟踪鼠标指针,以便在菜单区域之外单击时隐藏上下文菜单。否则,此上下文菜单将永远不会隐藏。这是另一个上下文。

我也遇到过同样的问题。经过长时间的研究,我发现解决这个问题的唯一方法是覆盖上下文菜单的Renderer。有不同类型的渲染器存在。我使用了ToolStripProfessionalRenderer。为了获得充分的好处,我还继承了ProfessionalColorTable。最后,我使用这个自定义渲染作为我的上下文菜单渲染面板。以下是步骤。

首先,通过继承ProfessionalColorTable创建MenuColorTable。

public class MenuColorTable : ProfessionalColorTable
{
//Fields
private Color backColor;
private Color leftColumnColor;
private Color borderColor;
private Color menuItemBorderColor;
private Color menuItemSelectedColor;
private WindowsTheme systrayTheme;
[Browsable(false)]
public WindowsTheme SystrayTheme
{
get { return systrayTheme; }
set { systrayTheme = value; }
}
//Constructor
public MenuColorTable(bool isMainMenu, Color primaryColor, Color menuItemSelectedColor, Color menuItemBorderColor, WindowsTheme theme) : base()
{
this.UseSystemColors = false;
this.systrayTheme = theme;

if(menuItemSelectedColor == Color.Empty)
{
menuItemSelectedColor = Color.FromArgb(51, 102, 255);
}
if (menuItemBorderColor == Color.Empty)
{
menuItemBorderColor = Color.FromArgb(25, 51, 127);
}
if (isMainMenu)
{
switch (SystrayTheme)
{
case WindowsTheme.Light:
{
backColor = Color.FromArgb(255, 255, 255);
leftColumnColor = Color.FromArgb(242, 242, 242);
borderColor = Color.FromArgb(193, 193, 193);
this.menuItemBorderColor = menuItemBorderColor;
this.menuItemSelectedColor = menuItemSelectedColor;
}
break;
case WindowsTheme.Dark:
{
backColor = Color.FromArgb(37, 39, 60);
leftColumnColor = Color.FromArgb(32, 33, 51);
borderColor = Color.FromArgb(32, 33, 51);
this.menuItemBorderColor = menuItemBorderColor;
this.menuItemSelectedColor = menuItemSelectedColor;
}
break;
case WindowsTheme.HighContrast:
{
backColor = Color.FromArgb(37, 39, 60);
leftColumnColor = Color.FromArgb(32, 33, 51);
borderColor = Color.FromArgb(32, 33, 51);
this.menuItemBorderColor = menuItemBorderColor;
this.menuItemSelectedColor = menuItemSelectedColor;
}
break;
}
}
else
{
backColor = Color.White;
leftColumnColor = Color.LightGray;
borderColor = Color.LightGray;
this.menuItemBorderColor = menuItemBorderColor;
this.menuItemSelectedColor = menuItemSelectedColor;
}
}
//Overrides
public override Color ToolStripDropDownBackground { get { return backColor; } }
public override Color MenuBorder { get { return borderColor; } }
public override Color MenuItemBorder { get { return menuItemBorderColor; } }
public override Color MenuItemSelected { get { return menuItemSelectedColor; } }

public override Color ImageMarginGradientBegin { get { return leftColumnColor; } }
public override Color ImageMarginGradientMiddle { get { return leftColumnColor; } }
public override Color ImageMarginGradientEnd { get { return leftColumnColor; } }

public override Color ButtonSelectedHighlight { get { return menuItemSelectedColor; } }
public override Color ButtonSelectedHighlightBorder { get { return menuItemBorderColor; } }
}

创建必要的实用程序类:

public enum WindowsTheme
{
Default = 0,
Light = 1,
Dark = 2,
HighContrast = 3
}
public static class Utility
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static System.Drawing.Color ToDrawingColor(this System.Windows.Media.Color mediaColor)
{
return System.Drawing.Color.FromArgb(mediaColor.A, mediaColor.R, mediaColor.G, mediaColor.B);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static System.Windows.Media.Color ToMediaColor(this System.Drawing.Color drawingColor)
{
return System.Windows.Media.Color.FromArgb(drawingColor.A, drawingColor.R, drawingColor.G, drawingColor.B);
}
}

现在用自定义的MenuColorTable覆盖ToolStripProfessionalRenderer。

public class MenuRenderer : ToolStripProfessionalRenderer
{
//Fields
private Color primaryColor;
private Color textColor;
private int arrowThickness;
private WindowsTheme systrayTheme;
[Browsable(false)]
public WindowsTheme SystrayTheme
{
get { return systrayTheme; }
set { systrayTheme = value; }
}
//Constructor
public MenuRenderer(bool isMainMenu, Color primaryColor, Color textColor, Color menuItemMouseOverColor, Color menuItemMouseOverBorderColor, WindowsTheme theme)
: base(new MenuColorTable(isMainMenu, primaryColor, menuItemMouseOverColor, menuItemMouseOverBorderColor, theme))
{
RoundedEdges = true;

this.primaryColor = primaryColor;
this.systrayTheme = theme;
if (isMainMenu)
{
arrowThickness = 2;
if (textColor == Color.Empty) //Set Default Color
this.textColor = Color.Gainsboro;
else//Set custom text color 
this.textColor = textColor;
}
else
{
arrowThickness = 1;
if (textColor == Color.Empty) //Set Default Color
this.textColor = Color.DimGray;
else//Set custom text color
this.textColor = textColor;
}
}
//Overrides
protected override void OnRenderItemText(ToolStripItemTextRenderEventArgs e)
{
base.OnRenderItemText(e);
e.Item.ForeColor = e.Item.Selected ? Color.White : textColor;
}
protected override void OnRenderArrow(ToolStripArrowRenderEventArgs e)
{
//Fields
var graph = e.Graphics;
var arrowSize = new Size(5, 10);
var arrowColor = e.Item.Selected ? Color.White : primaryColor;
var rect = new Rectangle(e.ArrowRectangle.Location.X, (e.ArrowRectangle.Height - arrowSize.Height) / 2,
arrowSize.Width, arrowSize.Height);
using (GraphicsPath path = new GraphicsPath())
using (Pen pen = new Pen(arrowColor, arrowThickness))
{
//Drawing
graph.SmoothingMode = SmoothingMode.AntiAlias;
path.AddLine(rect.Left, rect.Top, rect.Right, rect.Top + rect.Height / 2);
path.AddLine(rect.Right, rect.Top + rect.Height / 2, rect.Left, rect.Top + rect.Height);
graph.DrawPath(pen, path);
}
}        

public GraphicsPath RoundedRect(Rectangle bounds, int radius)
{
int diameter = radius * 2;
Size size = new Size(diameter, diameter);
Rectangle arc = new Rectangle(bounds.Location, size);
GraphicsPath path = new GraphicsPath();
if (radius == 0)
{
path.AddRectangle(bounds);
return path;
}
// top left arc  
path.AddArc(arc, 180, 90);
// top right arc  
arc.X = bounds.Right - diameter;
path.AddArc(arc, 270, 90);
// bottom right arc  
arc.Y = bounds.Bottom - diameter;
path.AddArc(arc, 0, 90);
// bottom left arc 
arc.X = bounds.Left;
path.AddArc(arc, 90, 90);
path.CloseFigure();
return path;
}
}

现在是时候通过继承ContextMenuStrip来创建自定义的StripMenu了。通过重写OnHandleCreated,将您的自定义MenuRenderer指定为ContextMenus的Render

public class CustomContextMenu : ContextMenuStrip
{
//Fields
private bool isMainMenu;
private int menuItemHeight = 20;
private int menuItemWidth = 20;
private Color menuItemTextColor = Color.Empty;
private Color primaryColor = Color.Empty;
private Color MouseOverColor = Color.Empty;
private Color MouseOverBorderColor = Color.Empty;
private WindowsTheme systrayTheme = WindowsTheme.Light;
private Bitmap menuItemHeaderSize;
//Constructor
public CustomContextMenu() 
{

}
//Properties
[Browsable(false)]
public bool IsMainMenu
{
get { return isMainMenu; }
set { isMainMenu = value; }
}
[Browsable(false)]
public int MenuItemHeight
{
get { return menuItemHeight; }
set { menuItemHeight = value; }
}

[Browsable(false)]
public int MenuItemWidth
{
get { return menuItemWidth; }
set { menuItemWidth = value; }
}
[Browsable(false)]
public Color MenuItemTextColor
{
get { return menuItemTextColor; }
set { menuItemTextColor = value; }
}
[Browsable(false)]
public Color PrimaryColor
{
get { return primaryColor; }
set { primaryColor = value; }
}
[Browsable(false)]
public Color MenuItemMouseOverColor
{
get { return MouseOverColor; }
set { MouseOverColor = value; }
}

[Browsable(false)]
public Color MenuItemMouseOverBorderColor
{
get { return MouseOverBorderColor; }
set { MouseOverBorderColor = value; }
}
[Browsable(false)]
public WindowsTheme SystrayTheme
{ 
get { return systrayTheme; }
set { systrayTheme = value; }
}
//Private methods
private void LoadMenuItemHeight()
{
if (isMainMenu)
menuItemHeaderSize = new Bitmap(menuItemWidth, menuItemHeight);
else menuItemHeaderSize = new Bitmap(menuItemWidth-5, menuItemHeight);
foreach (Forms.ToolStripMenuItem menuItemL1 in this.Items)
{
menuItemL1.ImageScaling = ToolStripItemImageScaling.None;
if (menuItemL1.Image == null) menuItemL1.Image = menuItemHeaderSize;
foreach (Forms.ToolStripMenuItem menuItemL2 in menuItemL1.DropDownItems)
{
menuItemL2.ImageScaling = ToolStripItemImageScaling.None;
if (menuItemL2.Image == null) menuItemL2.Image = menuItemHeaderSize;
foreach (Forms.ToolStripMenuItem menuItemL3 in menuItemL2.DropDownItems)
{
menuItemL3.ImageScaling = ToolStripItemImageScaling.None;
if (menuItemL3.Image == null) menuItemL3.Image = menuItemHeaderSize;
foreach (Forms.ToolStripMenuItem menuItemL4 in menuItemL3.DropDownItems)
{
menuItemL4.ImageScaling = ToolStripItemImageScaling.None;
if (menuItemL4.Image == null) menuItemL4.Image = menuItemHeaderSize;
///Level 5++
}
}
}
}
}
//Overrides
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
if (this.DesignMode == false)
{
switch (SystrayTheme)
{
case WindowsTheme.Light:
{
menuItemTextColor = Color.Black;
}
break;
case WindowsTheme.Dark:
{
menuItemTextColor = Color.White;
}
break;
case WindowsTheme.HighContrast:
{
menuItemTextColor = Utility.ToDrawingColor(System.Windows.SystemColors.MenuTextColor);
}
break;
}
this.Renderer = new MenuRenderer(isMainMenu, primaryColor, menuItemTextColor, MouseOverColor, MouseOverBorderColor, SystrayTheme);
LoadMenuItemHeight();
}
}
}

现在是时候通过继承ContextMenuStrip来创建自定义的StripMenu了。通过重写OnHandleCreated

,将您的自定义MenuRenderer指定为ContextMenus的Render最后,为不同的主题创建不同类型的菜单,并根据windows的当前主题将它们分配为NotiIcon的ContextMenuStrip。

顺便说一下,您可以通过WMI查询监视器从wpf检测系统主题更改事件。在这里你可以找到我的答案从wpf运行时主题更改检测。

//Create Different menu's for different theme
private CustomContextMenu contextMenuLight;
private CustomContextMenu contextMenuDark;
private CustomContextMenu contextMenuHiContrast;

//Udpate notifyIcon.ContextMenuStrip based on Theme changed event.
private void UpdateContextMenuOnWindowsThemeChange(WindowsTheme windowsTheme)
{
switch (windowsTheme)
{
case WindowsTheme.Default:
case WindowsTheme.Light:
{
notifyIcon.ContextMenuStrip = contextMenuLight;
}
break;
case WindowsTheme.Dark:
{
notifyIcon.ContextMenuStrip = contextMenuDark;
}
break;
case WindowsTheme.HighContrast:
{
notifyIcon.ContextMenuStrip = contextMenuHiContrast;
}
break;
}
InvalidateNotiIcon();
}
//Don't forget to Invalidate the notifyIcon after re-assigning new context menu
private void InvalidateNotiIcon()
{
try
{
notifyIcon.ContextMenuStrip.Invalidate(true);
notifyIcon.ContextMenuStrip.Update();
notifyIcon.ContextMenuStrip.Refresh();
}
catch (Exception ex)
{

}
}

在根据windows当前主题重新分配新的上下文菜单后,不要忘记使NotifyIcon无效。

由于WPF使用旧的运行时,您无法像使用简单的API一样访问Windows10环境。您可以查询注册表以获取Windows是否处于暗模式。

要自定义上下文菜单,只需定义一个在System.Windows.Forms.NotifyIcon鼠标交互时引用的XAML资源。

App.xaml
配置快捷菜单的外观出现黑色背景,白色的前景。

<ContextMenu x:Key="NotifierContextMenu" 
Placement="MousePoint" 
Background="#1e1e1e" 
Foreground="WhiteSmoke">
<MenuItem Header="Close" Click="Menu_Close" />
</ContextMenu>

App.xaml.cs
配置System.Windows.Forms.NotifyIcon,以便在应用程序启动时使用资源中的ContxtMenu。

private async Task InitializeSystemTrayIconAsync()
{
StreamResourceInfo streamResourceInfo = Application.GetResourceStream(new Uri("pack://application:,,,/Main.Resources;component/Icons/applicationIcon.ico", UriKind.Absolute));
await using var iconFileStream = streamResourceInfo.Stream;
this.SystemTrayIcon.Icon = new System.Drawing.Icon(iconFileStream);
this.SystemTrayIcon.Visible = true;
this.SystemTrayIcon.MouseClick += (sender, args) =>
{
switch (args.Button)
{
case System.Windows.Forms.MouseButtons.Right:
ContextMenu menu = (ContextMenu)this.FindResource("NotifierContextMenu");
menu.IsOpen = true;
break;
}
};
}

最新更新