Swing and Nimbus: Replace background of JPopupMenu (attached to JMenu)
Asked Answered
F

3

16

Nimbus often looks great, but for certain color combinations the result is non-optimal. In my case, the background of a JPopupMenu does not fit, which is why I want to set it manually.

I'm on Java 7 and, interestingly, Nimbus fully ignores the setting of some properties in the UIManager (like PopupMenu.background). So my only option was to create a subclass of JPopupMenu that overrides paintComponent(...). I know, that's nasty, but at least it worked.

However, if you add a JMenu to another menu, it embeds it's own instance of JPopupMenu and I could not figure out how to replace it with my own subclass.

Even assigning an own PopupMenuUI to the embedded instance didn't bring any results. If inherited directly from JPopupMenu the overriden paint(...) method was called, but, not matter what I did, nothing was drawn. If inherited from javax.swing.plaf.synth.SynthPopupMenuUI paint isn't even called and the result is if I hadn't set an own PopupMenuUI at all.

So the simple question is: How do I adjust the background color of one JPopupMenu or (if that's easier) all of them on Java 7 using Nimbus as L&F?

Edit: Code example

Take a look at the following code and the result:

public static void main(final String[] args) {
    try {
        UIManager.setLookAndFeel(NimbusLookAndFeel.class.getCanonicalName());
        UIManager.getLookAndFeelDefaults().put("PopupMenu.background", Color.GREEN);
        UIManager.getLookAndFeelDefaults().put("Panel.background", Color.RED);
        UIManager.getLookAndFeelDefaults().put("List.background", Color.BLUE);
    } catch (ClassNotFoundException | InstantiationException
            | IllegalAccessException | UnsupportedLookAndFeelException e) {
        e.printStackTrace();
    }
    JFrame frame = new JFrame();
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setSize(200,200);

    JPanel panel = new JPanel(new BorderLayout());
    panel.setBorder(BorderFactory.createEmptyBorder(10,10,10,10));
    JList list = new JList();
    panel.add(list);

    frame.getContentPane().add(panel);

    JPopupMenu menu = new JPopupMenu();
    menu.add(new JMenuItem("A"));
    menu.add(new JMenuItem("B"));
    menu.add(new JMenuItem("C"));

    frame.setVisible(true);
    menu.show(frame, 50, 50);
}

I know, some say that you should use UIManager.put(key, value) or UIManager.getLookAndFeelDefautls().put(key,value) before setting the L&F, but for me this does not bring any results (meaning: no changes to the default colors at all). The code above at least brings:

First screenshot

Same thing (meaning nothing) happens if you use JPopupMenu.setBackground(...). This is because Nimbus uses an internal painter, which computes the color from Nimbus' primary colors and ignores the components' property. In this example, you can use the following as workaround:

JPopupMenu menu = new JPopupMenu() {
    @Override
    public void paintComponent(final Graphics g) {
        g.setColor(Color.GREEN);
        g.fillRect(0,0,getWidth(), getHeight());
    }
};

Which brings

SecondScreen

However, this workaround does not work if you insert a JMenu which itself wraps a JPopupMenu you can't override:

JMenu jmenu = new JMenu("D");
jmenu.add(new JMenuItem("E"));
menu.add(jmenu);

gives, as expected:

Third screen

You can retrieve this JPopupMenu using JMenu.getPopupMenu() but you can't set it. Even overriding this method in an own subclass of JMenu does not bring any results, as JMenu seems to access it's enwrapped instance of JPopupMenu without using the getter.

Foehn answered 23/7, 2012 at 23:43 Comment(3)
You may get better answers if you outline your actual goal. The UI delegate, PopupMenuUI, is not trivial.Minard
@Foehn I think that better could be post an SSCCE, Nimbus not ignoring, its development ended somewhere in the middle, sure there are (needed) add some code lines moreover, another option could be or another combinationsDirkdirks
@Foehn You should follow mKorbel's advice: post a SSCCE that people can run as is on their machine and I'm sure you will get some answers.Shoshana
D
8
  • there are a few mistakes in both answers

  • and above mentioned way to required to override most UIDeafaults that have got impact to the another JComponents and its Color(s)

  • Nimbus has own Painter, one example for that ...

enter image description here

from code

import com.sun.java.swing.Painter;
import java.awt.*;
import javax.swing.*;

public class MyPopupWithNimbus {

    public MyPopupWithNimbus() {
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(200, 200);
        JPanel panel = new JPanel(new BorderLayout());
        panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
        JList list = new JList();
        panel.add(list);
        frame.getContentPane().add(panel);
        JPopupMenu menu = new JPopupMenu();
        menu.add(new JMenuItem("A"));
        menu.add(new JMenuItem("B"));
        menu.add(new JMenuItem("C"));
        JMenu jmenu = new JMenu("D");
        jmenu.add(new JMenuItem("E"));
        menu.add(jmenu);
        frame.setVisible(true);
        menu.show(frame, 50, 50);
    }

    public static void main(String[] args) {

        try {
            for (UIManager.LookAndFeelInfo laf : UIManager.getInstalledLookAndFeels()) {
                if ("Nimbus".equals(laf.getName())) {
                    UIManager.setLookAndFeel(laf.getClassName());
                    UIManager.getLookAndFeelDefaults().put("PopupMenu[Enabled].backgroundPainter",
                            new FillPainter(new Color(127, 255, 191)));
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                MyPopupWithNimbus aa = new MyPopupWithNimbus();
            }
        });
    }
}

class FillPainter implements Painter<JComponent> {

    private final Color color;

    FillPainter(Color c) {
        color = c;
    }

    @Override
    public void paint(Graphics2D g, JComponent object, int width, int height) {
        g.setColor(color);
        g.fillRect(0, 0, width - 1, height - 1);
    }
}
Dirkdirks answered 1/8, 2012 at 12:35 Comment(3)
+1 for the answer (there's a ever so slight disadvantage if client code in java6 is not allowed to access the com.sun packages). Just wondering why the default painter doesn't take up the background if set - it doesn't seem to paint anything fancy.Ballflower
The cleanest approach. A bit more complicated than the other two, but simpler than I thought (relating to the painters).Foehn
this is one of common and confortabe from the possible ways, Nimbus has a few another ZOODirkdirks
A
9

One way to do it is to color the background of the individual JMenuItems and make them opaque:

JMenuItem a = new JMenuItem("A");
a.setOpaque(true);
a.setBackground(Color.GREEN);

Then give the menu itself a green border to fill the rest:

menu.setBorder(BorderFactory.createLineBorder(Color.GREEN));

There may be an easy/more straightforward way out there, but this worked for me.

Angadresma answered 27/7, 2012 at 18:43 Comment(2)
Adding on to what @kleopatra wrote below (not enough rep to even comment on someone else's post =/ ): Setting ui.put("PopupMenu[Enabled].border", null); makes the border green. Now, I'm assuming this is because you're blowing away the LaF border painter, so I am hesitant to suggest this as a solution. What you probably want to do is set it to a custom Painter<JComponent> that just always paints it green. Unfortunately there seem to be access restrictions in Java 6 (what I'm running) that prevent me from testing this out. These seem to be changed in Java 7 though, so it may be worth a shot.Angadresma
you should be able to use the painter even in jdk6 by changing the IDE's access restriction rules - might not be feasable for production code, just for playing with itBallflower
D
8
  • there are a few mistakes in both answers

  • and above mentioned way to required to override most UIDeafaults that have got impact to the another JComponents and its Color(s)

  • Nimbus has own Painter, one example for that ...

enter image description here

from code

import com.sun.java.swing.Painter;
import java.awt.*;
import javax.swing.*;

public class MyPopupWithNimbus {

    public MyPopupWithNimbus() {
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(200, 200);
        JPanel panel = new JPanel(new BorderLayout());
        panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
        JList list = new JList();
        panel.add(list);
        frame.getContentPane().add(panel);
        JPopupMenu menu = new JPopupMenu();
        menu.add(new JMenuItem("A"));
        menu.add(new JMenuItem("B"));
        menu.add(new JMenuItem("C"));
        JMenu jmenu = new JMenu("D");
        jmenu.add(new JMenuItem("E"));
        menu.add(jmenu);
        frame.setVisible(true);
        menu.show(frame, 50, 50);
    }

    public static void main(String[] args) {

        try {
            for (UIManager.LookAndFeelInfo laf : UIManager.getInstalledLookAndFeels()) {
                if ("Nimbus".equals(laf.getName())) {
                    UIManager.setLookAndFeel(laf.getClassName());
                    UIManager.getLookAndFeelDefaults().put("PopupMenu[Enabled].backgroundPainter",
                            new FillPainter(new Color(127, 255, 191)));
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                MyPopupWithNimbus aa = new MyPopupWithNimbus();
            }
        });
    }
}

class FillPainter implements Painter<JComponent> {

    private final Color color;

    FillPainter(Color c) {
        color = c;
    }

    @Override
    public void paint(Graphics2D g, JComponent object, int width, int height) {
        g.setColor(color);
        g.fillRect(0, 0, width - 1, height - 1);
    }
}
Dirkdirks answered 1/8, 2012 at 12:35 Comment(3)
+1 for the answer (there's a ever so slight disadvantage if client code in java6 is not allowed to access the com.sun packages). Just wondering why the default painter doesn't take up the background if set - it doesn't seem to paint anything fancy.Ballflower
The cleanest approach. A bit more complicated than the other two, but simpler than I thought (relating to the painters).Foehn
this is one of common and confortabe from the possible ways, Nimbus has a few another ZOODirkdirks
B
7

not the whole story - but looks like setting the opacity of menu/items to true partly solves it (as @Derek Richard already did for a item created under full application control):

UIDefaults ui = UIManager.getLookAndFeelDefaults();
ui.put("PopupMenu.background", GREEN);
ui.put("Menu.background", GREEN);
ui.put("Menu.opaque", true);
ui.put("MenuItem.background", GREEN);
ui.put("MenuItem.opaque", true);

and so on, for all types of items like radioButton/checkBox.

This still leaves a upper/lower grey area - don't know which part is responsible for that drawing. Removing the margin helps at the price of looking squeezed

// this looks not so good, but without the margins above/below are still grey
ui.put("PopupMenu.contentMargins", null);

There's a list of property keys in the tutorial.

Ballflower answered 30/7, 2012 at 9:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.