Reputation: 81
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
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
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