[英]Dark/Light mode of system tray icon in WPF
我在我的 WPF 應用程序中使用通知圖標。 當我單擊此圖標時,上下文菜單的背景始終為白色,但我想將其更改為暗模式。 但是如何在通知圖標中應用深色和淺色模式?
_notifyIcon = new Forms.NotifyIcon();
_notifyIcon.Icon = MySystray.Resources.Systray_icon;
_notifyIcon.Text = APP_NAME;
由於 WPF 使用舊運行時,因此您無法像使用簡單 API 定位 UWP 時那樣訪問 Windows10 環境。 您可以查詢注冊表以了解 Windows 是否處於暗模式。
要自定義上下文菜單,只需定義您在 System.Windows.Forms.NotifyIcon 鼠標交互中引用的 XAML 資源。
應用程序.xaml
將 ContextMenu 的外觀配置為以黑色背景和白色前景顯示。
<ContextMenu x:Key="NotifierContextMenu"
Placement="MousePoint"
Background="#1e1e1e"
Foreground="WhiteSmoke">
<MenuItem Header="Close" Click="Menu_Close" />
</ContextMenu>
應用程序.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;
}
};
}
如果您想將 WPF 上下文菜單與 Windows 窗體的 NotifyIcon 一起使用,那么如果您在菜單區域外單擊,您可能需要調用鼠標鈎子來跟蹤鼠標指針以隱藏上下文菜單。 否則,此上下文菜單將永遠不會隱藏。 這是另一個背景。
我也遇到過同樣的問題。 通過這樣做,長期的研發我發現有一個唯一的方法可以解決這個問題來覆蓋上下文菜單的渲染器。 存在不同類型的渲染器。 我使用了 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 無效。
感謝您的閱讀。 快樂編碼。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.