I am trying to fashion a dark mode menu bar for my desktop GUI. Developing and testing on Windows with System L&F.
What has worked so far:
- Setting the background color of the actual
JMenuBar
component itself. - Setting the background and foreground colors of the
JMenu
buttons (e.g. File, Edit, etc). - Setting the background color of the Menu items (somewhat).
- Setting the foreground color of the Menu items (somewhat).
Following is an illustration of what has worked and what has not worked so far:
As you can see, I couldn't manage to set the desired background color for the Menu items' borders. I am not even sure if those are per-item borders or a single container border for the items' JMenu
parent. Either way, calling setBorder(new EmptyBorder(0,0,0,0))
and setBorder(new LineBorder(Color.red))
on both the parent JMenu
and the actual Menu items failed to make any changes to the UI.
Moreover, another two issues that need to be dealt with are:
- Setting the foreground color of the accelerator shortcut.
- Setting the background (and possibly foreground) color of
Separator
items (which are not actual Menu items per-se since the call toaddSeparator()
returnsvoid
.
I don't wanna give up on this endeavor! Help!
EDIT:
Setting the accelerators' color application wide using:
UIManager.getLookAndFeelDefaults().put("MenuItem.acceleratorForeground", Color.yellow);
before any components are created seems to work for System L&F on Windows.
CodePudding user response:
After digging a bit deeper into the Swing API, I was able to achieve what I set out to do when I posted this question. Here is an illustration of the outcome:
In order to help anyone who might come across a similar problem in the future, I will post my own answer to this question.
First, regarding the foreground (text) color of the accelerator
menu shortcuts, the only way I could get it to work was setting some application wide flags using UIManager
.
UIDefaults uiDefaults = UIManager.getLookAndFeelDefaults();
// For Windows L&F:
uiDefaults.put("MenuItem.acceleratorForeground", Color.lightGray);
uiDefaults.put("MenuItem.acceleratorSelectionForeground", Color.lightGray);
// For Nimbus L&F:
uiDefaults.put("MenuItem:MenuItemAccelerator.textForeground", Color.lightGray);
Next on the list was finding a way to apply the custom "skin" to the separator
items in the menu list. The way to do this was to call the menu's add(Component c)
method instead of the more common addSeparator()
, while passing to it a JSeparator
component. First, you would need to create the JSeparator
object and set its properties before adding it to the menu. To simplify things, I created a helper class:
public class XMenuSeparator extends JSeparator
{
public XMenuSeparator(Color bgColor, Color fgColor)
{
super.setOpaque(true);
super.setBackground(bgColor);
super.setForeground(fgColor);
}
}
Which can subsequently be used as such:
myMenu.add(new XMenuSeparator(Color.darkGray, Color.lightGray));
Where myMenu
is a JMenu
instance (or as we will see next, an instance of our other helper class, XMenu
).
Last on the list was figuring out how to customize the menu list (background color, foreground color, and most importantly, the surrounding borders around the menu list). The first two customizations were pretty straight forward, using setBackground(Color.darkGray)
and setForeground(Color.lightGray)
, with the only consideration needed was setting the opacity to true: setOpaque(true)
.
As for the surrounding border that had preserved the default background colors, and where the initial attempts to customize it using JMenu
's setBorder
had failed, the solution was to actually set the borders of the menu list and not the menu command button itself (obviously!) as such:
JMenu myMenu = new JMenu("File");
myMenu.getPopupMenu().setBorder(new LineBorder(Color.gray, borderThickness));
And so to encapsulate all those steps (and some additional functionality of switching back and forth between the default and "skinned" looks), here's a simple helper class:
public class XMenu extends JMenu
{
private Color skinBgColor = Color.darkGray;
private Color skinFgColor = Color.lightGray;
private Color skinBorderColor = Color.gray;
private int skinBorderThickness = 1;
private Color defaultBgColor;
private Color defaultFgColor;
private Border defaultBorder;
private boolean defaultOpacity;
private void saveDefaults()
{
defaultBgColor = super.getBackground();
defaultFgColor = super.getForeground();
defaultBorder = super.getPopupMenu().getBorder();
defaultOpacity = super.isOpaque();
}
public XMenu(String s)
{
super(s);
this.saveDefaults();
}
public XMenu(String s, Color bgColor, Color fgColor, Color borderColor)
{
super(s);
this.saveDefaults();
this.setSkinProperties(bgColor, fgColor, borderColor, 1);
this.applySkin();
}
public XMenu(String s, Color bgColor, Color fgColor, Color borderColor, int borderThickness)
{
super(s);
this.saveDefaults();
this.setSkinProperties(bgColor, fgColor, borderColor, borderThickness);
this.applySkin();
}
public void setSkinProperties(Color bgColor, Color fgColor, Color borderColor, int borderThickness)
{
this.skinBgColor = bgColor;
this.skinFgColor = fgColor;
this.skinBorderColor = borderColor;
this.skinBorderThickness = borderThickness;
}
public void applySkin()
{
super.setOpaque(true);
super.setBackground(skinBgColor);
super.setForeground(skinFgColor);
super.getPopupMenu().setBorder(new LineBorder(skinBorderColor, skinBorderThickness));
}
public void stripSkin()
{
super.setOpaque(defaultOpacity);
super.setBackground(defaultBgColor);
super.setForeground(defaultFgColor);
super.getPopupMenu().setBorder(defaultBorder);
}
}
And now, one could simply instantiate this class for the menu lists:
JMenu myMenu = new XMenu("File", Color.darkGray, Color.lightGray, Color.gray);
Hopefully this helps anyone who comes across this posting!