wpfsystem-traynotifyicon

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;

Solution

  • 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.