问题
Seems like I've found a bug in Java:
I need to create the JFrame
with a transparent background and now I need to show the JPopupMenu
for some user actions. It works fine when JPopupMenu
is housed fully inside a JFrame
. But when the JPopupMenu
is partly outside the JFrame
, no item is visible.
SSCCE:
public class PopupTest {
public static void main(String[] a) {
final JFrame frame = new JFrame();
frame.setSize(500, 500);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final JPanel panel = new JPanel(new BorderLayout());
panel.setBorder(BorderFactory.createLineBorder(Color.RED));
panel.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getButton() == MouseEvent.BUTTON3) {
JPopupMenu menu = new JPopupMenu();
for (int i = 0 ; i < 10; i++) {
menu.add(String.valueOf(i));
}
menu.show(panel, e.getX(), e.getY());
}
}
});
frame.setContentPane(panel);
frame.setUndecorated(true);
frame.setBackground(new Color(50, 50, 50, 200));
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
frame.setVisible(true);
}
});
}
}
Does anyone know how to solve this?
PS: JDK 7u40, Win x64
回答1:
This is bug in Oracle JDK 7 (it cannot be reproduced in Open JDK 7 by the way).
To fix this you can make a workaround (yes, this is just a workaround, there is no guarantees that it won't break with some Java update) so that the window created for popup-menu will become non-opaque as soon as it shows up, then it will be displayed properly. Atleast for now. Here is how this can be done for Java version 7 and later:
PropertyChangeListener propertyChangeListener = new PropertyChangeListener ()
{
@Override
public void propertyChange ( final PropertyChangeEvent evt )
{
if ( evt.getNewValue () == Boolean.TRUE )
{
// Retrieving popup menu window (we won't find it if it is inside of parent frame)
final Window ancestor = getWindowAncestor ( popupMenu );
if ( ancestor != null && ancestor.getClass ().getCanonicalName ().endsWith ( "HeavyWeightWindow" ) )
{
// Checking that parent window for our window is opaque, only then setting opacity
final Component parent = ancestor.getParent ();
if ( parent != null && parent instanceof Window && parent.getBackground ().getAlpha () == 0 )
{
// Making popup menu window non-opaque
ancestor.setBackground ( new Color ( 0, 0, 0, 0 ) );
}
}
}
}
private Window getWindowAncestor ( Component component )
{
if ( component == null )
{
return null;
}
if ( component instanceof Window )
{
return ( Window ) component;
}
for ( Container p = component.getParent (); p != null; p = p.getParent () )
{
if ( p instanceof Window )
{
return ( Window ) p;
}
}
return null;
}
};
popupMenu.addPropertyChangeListener ( "visible", propertyChangeListener );
You will have to put some more effort if you also want to support JDK 6- versions because that code won't even compile on earlier JDK versions (there are no "set/getBackground" methods in Window in earlier versions).
回答2:
remember about root pane! == your way is buggy, this is answer (too to your comment) to ---> What JButton do you mean?, funny that JButton is transparent only with UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsClassicLookAndFeel");
this is proper of ways, here is translucent everything, rest is some binnary deformations between Java6 and 7, because isn't possible to create decorated container with changed L&F (for diff as is getSystemL&F)
my testing code for Java6/7 _011, 025 and 040 too, no bug for any LaFs (distributed only implemented in Oracle APIs)
.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.Painter;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
public class PopupTest {
private JFrame frame = new JFrame();
private JPanel panel = new JPanel(new BorderLayout()) {
private static final long serialVersionUID = 1L;
@Override
public Dimension getPreferredSize() {
return new Dimension(500, 500);
}
};
private JButton button = new JButton("Close me");
public PopupTest() {
//panel.setOpaque(false);
panel.setBorder(BorderFactory.createLineBorder(Color.RED));
panel.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getButton() == MouseEvent.BUTTON3) {
JPopupMenu menu = new JPopupMenu();
for (int i = 0; i < 56; i++) {//only FHD display
menu.add(String.valueOf(i));
}
menu.show(panel, e.getX(), e.getY());
}
}
});
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.exit(0);
}
});
button.setOpaque(false);
panel.add(button, BorderLayout.SOUTH);
frame.setLocation(150, 150);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//frame.add(panel);
frame.setContentPane(panel);
frame.setUndecorated(true);
frame.pack();
frame.setBackground(new Color(150, 50, 50, 200));
frame.setVisible(true);
}
public static void main(String[] a) {
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)));
UIManager.getLookAndFeelDefaults().put("text", new Color(255, 0, 0));
//UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
//UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
//UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsClassicLookAndFeel");
}
}
} catch (Exception e) {
e.printStackTrace();
}
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
new PopupTest();
}
});
}
}
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);
}
}
回答3:
Thanks Mikle Garin for a great solution, you helped me a lot to solve a similar problem! I would like to share my solution - based on Mikle's - with a little difference, that was significant in my case.
What I was looking for: transparent undecorated Windows behind JPopupMenu (my custom popups are shown with fancy speech-balloon border, so Window behind it should be invisible).
One thing that didn't work well enough with PropertyChangeListener: window appearance gets adjusted AFTER the window is shown on the screen. On Mac OS X 10.10 with java 8 window behind the popup is first shown with a white background and a thin border (L&F default), and gets adjusted a while (about 0.1 - 0.3 sec) later. Pretty annoying.
After looking a while where to place adjustment code I came up with the following simple solution: adjust window RIGHT AFTER the menu is added to the window and BEFORE showing the window. Following sample shows how to extend JPopupMenu to achieve this:
import java.awt.Color;
import java.awt.Window;
import javax.swing.JPopupMenu;
import javax.swing.Popup;
import javax.swing.RootPaneContainer;
import javax.swing.SwingUtilities;
import javax.swing.plaf.PopupMenuUI;
public class MyPopup extends JPopupMenu
{
public MyPopup()
{
super();
//...
setUI(new MyPopupMenuUI());
}
//...
private void adjustHeavyWeightWindowIfThereIsOne()
{
// Retrieve popup menu window
// on Windows we won't find it if this popup is inside of parent frame
// on Mac we may find it even when DefaultLightWeightPopupEnabled is set to true
final Window ancestor = SwingUtilities.getWindowAncestor(MyPopup.this);
if (ancestor != null && ancestor.getClass().getCanonicalName().endsWith("HeavyWeightWindow"))
{
adjustWindowAppearance(ancestor);
}
}
private void adjustWindowAppearance(Window w)
{
w.setBackground(new Color(0, 0, 0, 0));
if (w instanceof RootPaneContainer)
{
((RootPaneContainer)w).getRootPane().setBorder(null);
((RootPaneContainer)w).getRootPane().setBackground(new Color(0, 0, 0, 0));
}
}
class MyPopupMenuUI extends PopupMenuUI
{
public Popup getPopup(JPopupMenu popup, int x, int y)
{
Popup toreturn = super.getPopup(popup, x, y);
adjustHeavyWeightWindowIfThereIsOne();
return toreturn;
}
}
}
Actually extending JPopupMenu is not necessary, but setting a custom PopupMenuUI that triggers an adjustment is essential. Window adjustment code can be easily modified to meet particular needs, and can be moved to custom PopupMenuUI.
来源:https://stackoverflow.com/questions/18918367/java-jpopupmenu-bug