Rajib_119
Rajib_119

Reputation: 81

Dark/Light mode of system tray icon in WPF

I am using notify icon in my WPF app. 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;

Upvotes: 1

Views: 1612

Answers (2)

consequence007
consequence007

Reputation: 2160

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. 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. For getting the full benefit, I have also inherited ProfessionalColorTable. Finally, I have used this customized render as My Context menus render panel. Here below is Step.

Firstly, create MenuColorTable by Inheriting ProfessionalColorTable.

    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.

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. Assign your custom MenuRenderer as ContextMenus’s Render by overriding OnHandleCreated

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. Assign your custom MenuRenderer as ContextMenus’s Render by overriding OnHandleCreated

Finally, create different types of the menu's for a different theme and assign them as NotiIcon's ContextMenuStrip based on windows' current theme.

By the way, you can detect system theme changed event from wpf through WMI query watcher. Here you find my answers for runtime theme change detection from 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.

Upvotes: 1

BionicCode
BionicCode

Reputation: 28948

Since WPF uses the old runtime you can't access the Windows10 environment like you can when targeting UWP using a simple API. You can query registry to get whether Windows is in dark mode or not.

To customize the context menu simply define a XAML resource that you refernce on System.Windows.Forms.NotifyIcon mouse interaction.

App.xaml
Configure the appearance of the ContextMenu to appear with a black background and white foreground.

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

App.xaml.cs
Configure the System.Windows.Forms.NotifyIcon to use the ContxtMenu from the resource at some point during the application startup.

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

Upvotes: 1

Related Questions