简体   繁体   English

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

[英]Dark/Light mode of system tray icon in WPF

I am using notify icon in my WPF app.我在我的 WPF 应用程序中使用通知图标。 When I click this icon, always there is a white background of context menu but I want to change this for Dark mode.当我单击此图标时,上下文菜单的背景始终为白色,但我想将其更改为暗模式。 But how to apply Dark & Light mode in notify icon?但是如何在通知图标中应用深色和浅色模式?

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

Since WPF uses the old runtime you can't access the Windows10 environment like you can when targeting UWP using a simple API.由于 WPF 使用旧运行时,因此您无法像使用简单 API 定位 UWP 时那样访问 Windows10 环境。 You can query registry to get whether Windows is in dark mode or not.您可以查询注册表以了解 Windows 是否处于暗模式。

To customize the context menu simply define a XAML resource that you refernce on System.Windows.Forms.NotifyIcon mouse interaction.要自定义上下文菜单,只需定义您在 System.Windows.Forms.NotifyIcon 鼠标交互中引用的 XAML 资源。

App.xaml应用程序.xaml
Configure the appearance of the ContextMenu to appear with a black background and white foreground.将 ContextMenu 的外观配置为以黑色背景和白色前景显示。

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

App.xaml.cs应用程序.xaml.cs
Configure the System.Windows.Forms.NotifyIcon to use the ContxtMenu from the resource at some point during the application startup.将 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;
    }
  };
}

If you want to use WPF context menu with windows form's NotifyIcon then you may need to invoke Mouse Hook to track down the mouse pointer for hiding the Context menu if you click outside of the menu area.如果您想将 WPF 上下文菜单与 Windows 窗体的 NotifyIcon 一起使用,那么如果您在菜单区域外单击,您可能需要调用鼠标钩子来跟踪鼠标指针以隐藏上下文菜单。 Otherwise, this context menu will never hide.否则,此上下文菜单将永远不会隐藏。 This is another context.这是另一个背景。

I have also faced the same problem.我也遇到过同样的问题。 By doing, long R&D I have found that there is a only way to resolve this problem to override the Renderer of the context menu.通过这样做,长期的研发我发现有一个唯一的方法可以解决这个问题来覆盖上下文菜单的渲染器。 There are different types of Renderer exist out there.存在不同类型的渲染器。 I have used ToolStripProfessionalRenderer.我使用了 ToolStripProfessionalRenderer。 For getting the full benefit, I have also inherited ProfessionalColorTable.为了获得全部好处,我还继承了 ProfessionalColorTable。 Finally, I have used this customized render as My Context menus render panel.最后,我将此自定义渲染用作“我的上下文菜单”渲染面板。 Here below is Step.下面是步骤。

Firstly, create MenuColorTable by Inheriting 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; } }
}

Create necessary utility class:创建必要的实用程序类:

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);
    }
}

Now override ToolStripProfessionalRenderer with customized MenuColorTable.现在使用自定义的 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;
    }
}

Now it's time to create your custom own StripMenu by inheriting ContextMenuStrip.现在是时候通过继承 ContextMenuStrip 创建您自己的自定义 StripMenu。 Assign your custom MenuRenderer as ContextMenus's Render by overriding OnHandleCreated通过覆盖 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();
        }
    }

 }

Now it's time to create your custom own StripMenu by inheriting ContextMenuStrip.现在是时候通过继承 ContextMenuStrip 创建您自己的自定义 StripMenu。 Assign your custom MenuRenderer as ContextMenus's Render by overriding OnHandleCreated通过覆盖 OnHandleCreated 将您的自定义 MenuRenderer 分配为 ContextMenus 的 Render

Finally, create different types of the menu's for a different theme and assign them as NotiIcon's ContextMenuStrip based on windows' current theme.最后,为不同的主题创建不同类型的菜单,并根据 Windows 的当前主题将它们分配为 NotiIcon 的 ContextMenuStrip。

By the way, you can detect system theme changed event from wpf through WMI query watcher.顺便说一下,您可以通过 WMI 查询观察器从 wpf 检测系统主题更改事件。 Here you find my answers for runtime theme change detection from 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)
        {
        
        }
    }

Do not forget to invalid the NotifyIcon after reassigning the new context menu based on windows' current theme.在根据 Windows 的当前主题重新分配新的上下文菜单后,不要忘记使 NotifyIcon 无效。

Thank you for reading.感谢您的阅读。 Happy coding.快乐编码。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM