为什么透明的 JButton 在添加透明度时显示框架的图像?

Why is a transparent JButton showing images of the frame when transparency is added?

我正在为 Java 应用程序开发 GUI,我想要一个背景图像。问题是我有一种充满按钮的 "drawer" 类型,当按钮被选中时,按钮会以红色高亮显示。

我正在使用方法 buttonName.setBackground(new Color(255, 102, 102, 200));同时设置高亮按钮和透明度。问题是,尽管该方法有效并使按钮透明,但透明度会显示按钮后面框架的随机部分,这些是 header,另一个按钮,按钮所在的 JScrollPane 的滚动条等. 按钮的文本仍然显示,并且按钮有效,但背景显示来自其他按钮或框架部分的文本。

此外,我意识到,如果我单击一个按钮并将鼠标多次移到所选按钮上,透明度就会开始累积,直至变成纯色。

package buttonsbug;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import javax.imageio.ImageIO;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;

/**
 *
 * @author F&H
 */
public class ButtonsBug extends JFrame implements ActionListener {

    private ArrayList<JButton> botones;
    private JLabel panelPrin, panelNav, panelUser, panelImgUser, nombre, puesto;
    private JButton logout, planDis, consuEmpleados, funConsultarPiezas, btnCalidad, compraMat, soySuper, histProy, crearProyecto, clientes, adminConsProye;
    private JPanel buttonScroll;
    private JScrollPane navScroll;
    private BufferedImage img;
    private Dimension screenSize;

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        java.awt.EventQueue.invokeLater(new Runnable() {
            public void run() {
                try {
                    new ButtonsBug().setVisible(true);
                } catch (Exception e) {
                    System.out.println(e.getMessage());
                    System.exit(0);
                }
            }
        });
    }

    public ButtonsBug() {
        botones = new ArrayList<>();

        screenSize = Toolkit.getDefaultToolkit().getScreenSize();
        this.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
        setSize(ajustarDimensiones(1400), ajustarDimensiones(800));

        setContentPane(panelPrin = new JLabel());
        panelPrin.setSize(ajustarDimensiones(1400), ajustarDimensiones(800));
        try {
            img = ImageIO.read(new File("src/tw3.png"));
            Image dimg1 = img.getScaledInstance(panelPrin.getWidth(), panelPrin.getHeight(), Image.SCALE_SMOOTH);
            ImageIcon imageIcon = new ImageIcon(dimg1);
            panelPrin.setIcon(imageIcon);
        } catch (IOException z) {
            System.out.println(z.getMessage());
            JOptionPane.showMessageDialog(this, "¡Error en la lectura de imagen!", "Error", JOptionPane.ERROR_MESSAGE);
        }

        panelPrin.setBackground(java.awt.Color.white);
        panelPrin.add(panelNav = new JLabel());
//        panelPrin.setOpaque(true);

//        panelNav.setOpaque(true);
        panelNav.setBorder(BorderFactory.createLineBorder(Color.BLACK, 1));
//        panelNav.setBackground(new Color(0, 0, 0, 150));
        panelNav.setBounds(0, 0, ajustarDimensiones(305), ajustarDimensiones(771));
        panelNav.add(panelUser = new JLabel());
        panelNav.add(logout = new JButton());
        logout.setContentAreaFilled(false);
//        logout.setOpaque(true);

//        panelUser.setOpaque(true);
        panelUser.setBounds(ajustarDimensiones(1), ajustarDimensiones(1), ajustarDimensiones(303), ajustarDimensiones(88));
        panelUser.add(panelImgUser = new JLabel());
        panelUser.add(nombre = new JLabel());
        panelUser.add(puesto = new JLabel());
        nombre.setText("Wil Fonseca");
        puesto.setText("Production manager");

        nombre.setBounds(ajustarDimensiones(55), ajustarDimensiones(25), ajustarDimensiones(245), ajustarDimensiones(20));
        puesto.setBounds(ajustarDimensiones(55), ajustarDimensiones(45), ajustarDimensiones(245), ajustarDimensiones(20));
        nombre.setFont(new java.awt.Font("Arial", 1, ajustarDimensiones(14)));
        puesto.setFont(new java.awt.Font("Arial", 1, ajustarDimensiones(12)));
        nombre.setForeground(Color.white);
        puesto.setForeground(Color.white);

        logout.setText("Logout");
        logout.setFont(new java.awt.Font("Arial", 1, ajustarDimensiones(34)));
        logout.setBounds(ajustarDimensiones(1), ajustarDimensiones(691), ajustarDimensiones(303), ajustarDimensiones(88));
        logout.setBackground(Color.white);
        logout.setForeground(Color.red);
        logout.addActionListener(this);
        logout.setBorder(null);
        logout.setBorderPainted(false);
        logout.setFocusPainted(false);

        panelImgUser.setBounds(ajustarDimensiones(3), ajustarDimensiones(24), ajustarDimensiones(40), ajustarDimensiones(40));

        try {
            img = ImageIO.read(new File("src/Usuario.png"));
            Image dimg1 = img.getScaledInstance(panelImgUser.getWidth(), panelImgUser.getHeight(), Image.SCALE_SMOOTH);
            ImageIcon imageIcon = new ImageIcon(dimg1);
            panelImgUser.setIcon(imageIcon);
        } catch (IOException z) {
            System.out.println(z.getMessage());
        }

        setTitle("ButtonsBug");
        setLocationRelativeTo(null);
        setResizable(false);
        setVisible(true);

        buttonGenerator();

    }

    public int ajustarDimensiones(int coo) {
        int newC = 0;
        double res = (screenSize.getHeight());
        float newRes;
        if (res < 1080) {
            if (coo == 1400) {
                return 1208;
            } else if (coo == 800) {
                return 680;
            }
        }
        if (coo == 0) {
            return newC;
        } else {
            if (res < 1080) {
                newRes = (918f / 1080f);
                if (coo == 305) {
                    newC = (int) (newRes * coo) - 1;
                } else if (coo == 90) {
                    newC = (int) (newRes * coo) - 1;
                } else if (coo == 224) {
                    newC = (int) (newRes * coo) - 1;
                } else if (coo == 601) {
                    newC = (int) (newRes * coo) + 3;
                } else if (coo == 1066) {
                    newC = (int) (newRes * coo) - 1;
                } else if (coo == 1474 || coo == 1576) {
                    newC = (int) (newRes * coo) + 1;
                } else if (coo == 1059) {
                    newC = (int) (newRes * coo) - 10;
                } else if (coo == 1095) {
                    newC = (int) (newRes * coo) + 14;
                } else {
                    newC = (int) (newRes * coo);
                }
            } else {
                newRes = (float) (res / 1080f);
                newC = (int) (newRes * coo);
            }
            if (newC < 0) {
                newC = 1;
            }

        }

        return newC;
    }

    public void buttonGenerator() {

        int y = 0;
        panelNav.add(navScroll = new JScrollPane(buttonScroll = new JPanel(), JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER));
        navScroll.setBorder(BorderFactory.createEmptyBorder());
        navScroll.setBounds(ajustarDimensiones(1), ajustarDimensiones(90), ajustarDimensiones(303), ajustarDimensiones(600));
//        navScroll.setBackground(Color.white);
        navScroll.setOpaque(false);

        navScroll.getVerticalScrollBar().setUnitIncrement(30);
        navScroll.setPreferredSize(new Dimension(ajustarDimensiones(305), ajustarDimensiones(601)));
//        buttonScroll.setBackground(Color.white);
        buttonScroll.setPreferredSize(new Dimension(ajustarDimensiones(305), ajustarDimensiones(601)));
        buttonScroll.setLayout(null);
        navScroll.setViewportView(buttonScroll);
        buttonScroll.setOpaque(false);
        navScroll.getViewport().setOpaque(false);

        buttonScroll.add(funConsultarPiezas = new JButton());
        funConsultarPiezas.setContentAreaFilled(false);
        //           funConsultarPiezas.setOpaque(true);
        funConsultarPiezas.setText("Consultar pieza");
        funConsultarPiezas.setFont(new java.awt.Font("Arial", 1, ajustarDimensiones(30)));
        funConsultarPiezas.setBounds(ajustarDimensiones(1), ajustarDimensiones(0), ajustarDimensiones(301), ajustarDimensiones(80));
        //        funConsultarPiezas.setBackground(java.awt.Color.white);
        funConsultarPiezas.setForeground(Color.white);
        funConsultarPiezas.addActionListener(this);
        funConsultarPiezas.setBorder(null);
        funConsultarPiezas.setBorderPainted(false);
        funConsultarPiezas.setFocusPainted(false);
        botones.add(funConsultarPiezas);

        y += 81;

        buttonScroll.add(btnCalidad = new JButton());
        btnCalidad.setContentAreaFilled(false);
        //     btnCalidad.setOpaque(true);
        btnCalidad.setText("Quality Check");
        btnCalidad.setBounds(ajustarDimensiones(1), ajustarDimensiones(y), ajustarDimensiones(301), ajustarDimensiones(80));
        btnCalidad.setFont(new java.awt.Font("Arial", 1, ajustarDimensiones(30)));
        //        btnCalidad.setBackground(Color.white);
        btnCalidad.setForeground(Color.white);
        btnCalidad.addActionListener(this);
        btnCalidad.setBorder(null);
        btnCalidad.setBorderPainted(false);
        btnCalidad.setFocusPainted(false);
        botones.add(btnCalidad);

        y += 81;
        buttonScroll.add(planDis = new JButton());
        planDis.setContentAreaFilled(false);
        //       planDis.setOpaque(true);
        planDis.setText("Diseño y planear");
        planDis.setBounds(ajustarDimensiones(1), ajustarDimensiones(y), ajustarDimensiones(301), ajustarDimensiones(80));
        planDis.setFont(new java.awt.Font("Arial", 1, ajustarDimensiones(30)));
        //         planDis.setBackground(Color.white);
        planDis.setForeground(Color.white);
        planDis.addActionListener(this);
        planDis.setBorder(null);
        planDis.setBorderPainted(false);
        planDis.setFocusPainted(false);
        botones.add(planDis);

        y += 81;

        buttonScroll.add(compraMat = new JButton());
        compraMat.setContentAreaFilled(false);
//            compraMat.setOpaque(true);
        compraMat.setText("Compra Material");
        compraMat.setBounds(ajustarDimensiones(1), ajustarDimensiones(y), ajustarDimensiones(301), ajustarDimensiones(80));
        compraMat.setFont(new java.awt.Font("Arial", 1, ajustarDimensiones(30)));
        //compraMat.setBackground(Color.white);
        compraMat.setForeground(Color.white);
        compraMat.addActionListener(this);
        compraMat.setBorder(null);
        compraMat.setBorderPainted(false);
        compraMat.setFocusPainted(false);
        botones.add(compraMat);

        y += 81;

        buttonScroll.add(soySuper = new JButton());
        soySuper.setContentAreaFilled(false);
//            soySuper.setOpaque(true);
        soySuper.setText("Liberar piezas");
        soySuper.setBounds(ajustarDimensiones(1), ajustarDimensiones(y), ajustarDimensiones(301), ajustarDimensiones(80));
        soySuper.setFont(new java.awt.Font("Arial", 1, ajustarDimensiones(30)));
        //soySuper.setBackground(Color.white);
        soySuper.setForeground(Color.white);
        soySuper.addActionListener(this);
        soySuper.setBorder(null);
        soySuper.setBorderPainted(false);
        soySuper.setFocusPainted(false);
        botones.add(soySuper);

        y += 81;

        buttonScroll.add(crearProyecto = new JButton());
        crearProyecto.setContentAreaFilled(false);
//            crearProyecto.setOpaque(true);
        crearProyecto.setText("Crear proyecto");
        crearProyecto.setBounds(ajustarDimensiones(1), ajustarDimensiones(y), ajustarDimensiones(301), ajustarDimensiones(80));
        crearProyecto.setFont(new java.awt.Font("Arial", 1, ajustarDimensiones(30)));
        //crearProyecto.setBackground(Color.white);
        crearProyecto.setForeground(Color.white);
        crearProyecto.addActionListener(this);
        crearProyecto.setBorder(null);
        crearProyecto.setBorderPainted(false);
        crearProyecto.setFocusPainted(false);
        botones.add(crearProyecto);

        y += 81;

        buttonScroll.add(clientes = new JButton());
        clientes.setContentAreaFilled(false);
//            clientes.setOpaque(true);
        clientes.setText("Clientes");
        clientes.setBounds(ajustarDimensiones(1), ajustarDimensiones(y), ajustarDimensiones(301), ajustarDimensiones(80));
        clientes.setFont(new java.awt.Font("Arial", 1, ajustarDimensiones(30)));
        //clientes.setBackground(Color.white);
        clientes.setForeground(Color.white);
        clientes.addActionListener(this);
        clientes.setBorder(null);
        clientes.setBorderPainted(false);
        clientes.setFocusPainted(false);
        botones.add(clientes);

        y += 81;

        buttonScroll.add(adminConsProye = new JButton());
        adminConsProye.setContentAreaFilled(false);
//            adminConsProye.setOpaque(true);
        adminConsProye.setText("Consultar proyectos");
        adminConsProye.setBounds(ajustarDimensiones(1), ajustarDimensiones(y), ajustarDimensiones(301), ajustarDimensiones(80));
        adminConsProye.setFont(new java.awt.Font("Arial", 1, ajustarDimensiones(26)));
        //adminConsProye.setBackground(Color.white);
        adminConsProye.setForeground(Color.white);
        adminConsProye.addActionListener(this);
        adminConsProye.setBorder(null);
        adminConsProye.setBorderPainted(false);
        adminConsProye.setFocusPainted(false);
        botones.add(adminConsProye);

        y += 81;

        buttonScroll.add(histProy = new JButton());
        histProy.setText("Historial");
        histProy.setContentAreaFilled(false);
//            histProy.setOpaque(true);
        histProy.setBounds(ajustarDimensiones(1), ajustarDimensiones(y), ajustarDimensiones(301), ajustarDimensiones(80));
        histProy.setFont(new java.awt.Font("Arial", 1, ajustarDimensiones(30)));
        //histProy.setBackground(Color.white);
        histProy.setForeground(Color.white);
        histProy.addActionListener(this);
        histProy.setBorder(null);
        histProy.setBorderPainted(false);
        histProy.setFocusPainted(false);
        botones.add(histProy);

        y += 81;

        buttonScroll.add(consuEmpleados = new JButton());
        consuEmpleados.setText("Trabajadores");
        consuEmpleados.setContentAreaFilled(false);
//            consuEmpleados.setOpaque(true);
        consuEmpleados.setBounds(ajustarDimensiones(1), ajustarDimensiones(y), ajustarDimensiones(301), ajustarDimensiones(80));
        consuEmpleados.setFont(new java.awt.Font("Arial", 1, ajustarDimensiones(30)));
        //consuEmpleados.setBackground(Color.white);
        consuEmpleados.setForeground(Color.white);
        consuEmpleados.addActionListener(this);
        consuEmpleados.setBorder(null);
        consuEmpleados.setBorderPainted(false);
        consuEmpleados.setFocusPainted(false);
        botones.add(consuEmpleados);

        y += 81;

        buttonScroll.setPreferredSize(new Dimension(ajustarDimensiones(305), ajustarDimensiones(y)));
    }

    public void botonSeleccionado(JButton but) {
        for (JButton b : botones) {
            if (b.getText().equalsIgnoreCase(but.getText())) {
                b.setOpaque(true);
                b.setBackground(new Color(255, 102, 102, 200));
                b.setForeground(Color.white);
            } else {
                b.setOpaque(false);
                //b.setBackground(Color.white);
                b.setForeground(Color.white);
            }
            b.revalidate();
            b.repaint();
        }
    }

    @Override
    public void actionPerformed(ActionEvent ae) {
        JButton obj = (JButton) ae.getSource();
        if (obj != logout) {
            botonSeleccionado(obj);
        }
    }

}

如果有人知道如何制作干净透明的按钮,我将不胜感激。

我在这里留下了源代码和我正在使用的测试图像。 https://drive.google.com/file/d/1l8R52WTDyP93L0UhTNd3oorD7Qhv-TcP/view?usp=sharing

在这个 image 中,您可以看到我遇到的 3 种错误,在第一个中,您可以看到背景中有另一个按钮,滚动条显示在按钮的左侧按钮。在第二个中,它是导航面板的header。第三个,我把鼠标多次移到选中的按钮上,它变成了纯色,而不是透明的。

编辑:

如果我将它应用到导航面板,我决定检查错误是否仍然存在,因为当应用程序完成时它也必须是透明的。于是我在上面代码的第82行和83行添加了如下几行代码:

        panelNav.setBackground(new Color(0, 0, 0, 200));
        panelNav.setOpaque(true);

在另一个 image 中,我将透明度应用于整个导航面板,即 JLabel。在第一个图像中显示了显示框架时出现的内容,甚至还有部分框架显示在导航面板下方。第二个显示了当我使用一次滚动条时会发生什么。

编辑 2: 我将所有用作 JPanel 的 JLabel 替换为实际的 JPanel。可悲的是,错误仍然存​​在。我在主 JPanel 中添加了一个额外的按钮,我这样做是因为我认为错误起源于向 JScrollPane 添加按钮。但似乎问题直接在于我如何实现方法 buttonName.setBackground().

这是新版本的代码: https://drive.google.com/file/d/1PuHMkEYNbBoafqs5XiyUaeCkIyXfnHFJ/view?usp=sharing

在 99% 的情况下,您在示例应用程序中看到的任何问题都是由于不透明 and/or non-opaque 组件的不正确混合造成的。

正如我从代码中看到的那样 - 您正在使用 setOpaque(...) 来更改各种组件的不透明度,但它非常混乱。很快,opaque 属性 的作用 - 它会影响 Swing 重新绘制特定 UI 元素 (panel/label/button/etc) 的方式,只要该元素需要进行视觉更新。

例如,当您将鼠标悬停在按钮上时 - 如果它具有不同的悬停状态,则可能需要重新绘制它,无论它只是一个图标还是 slightly/completely 不同的样式。这就是不透明度发挥作用的地方 - opaque=true 组件永远不会将重绘调用 "under" 传递给它们自己(在 other/proper 术语中 - 到它们的父组件)。这意味着如果您在面板上有一个不透明的按钮,并且当它变为 "hover" 状态时必须重新绘制它 - 该按钮将是唯一要重新绘制的组件,因为没有理由重新绘制它下面的任何东西,因为它是不透明的,你实际上不应该能够看透它,所以图形应该用不透明的颜色填充该按钮边界内的所有像素。

理论上。在实践中,如果您将按钮设置为不透明状态,但保持其图形内容透明或 semi-transparent(这显然是一个错误,但 Swing 永远不会告诉您)——您最终会看到各种视觉伪像,例如您在应用程序中看到的那些。发生这种情况是因为 Graphics2D 实现经常在 (0,0) 坐标执行不同的绘制操作以优化它们的速度——知道这一点并不重要,但这部分是为什么你可能会看到其他组件 "parts"当它是透明的时候混合在你的组件边界中。它比那更复杂一点,但它不应该真正重要,因为它只是一个内部 Swing 优化。

类似的视觉问题也可能是由于在同一布局上混合 opaque=trueopaque=false 组件造成的。您的问题很可能也是如此。我确实很快尝试将您的演示中的所有内容都设置为 opaque=false,它确实解决了问题,但这并不是解决问题的正确方法,尤其是当您想让某些组件不透明时。这只是意味着问题在于在单个容器中将具有不同不透明度类型的组件相互混合的方式。

我个人的建议 - 永远不要在一个布局中混合不透明和 non-opaque 组件,即使它们有可能重叠(这意味着它们的边界将在布局内相交)。甚至更好——永远不要在单个容器中将组件重叠在一起。使用具有适当 non-null 布局的多个嵌套容器,这对您将来轻松修改 UI.

也有很大帮助

我可以给你一个简单的例子来说明它为什么不好:

/**
 * @author Mikle Garin
 */
public class OpacityGlitch
{
    public static void main ( String[] args )
    {
        SwingUtilities.invokeLater ( () -> {
            final JFrame frame = new JFrame ( "Opacity glitch sample" );

            // Opaque by default
            final JPanel panel = new JPanel ( null );

            // Opaque by default, but might vary with L&F
            final JButton button1 = new JButton ( "1111111" );
            panel.add ( button1 );

            // Non-opaque to demonstrate the problem
            final JButton button2 = new JButton ( "2222222" );
            panel.add ( button2 );

            // Intersecting buttons
            button1.setBounds ( 100, 100, 150, 30 );
            button2.setBounds ( 130, 115, 150, 30 );

            frame.getContentPane ().add ( panel );

            frame.setDefaultCloseOperation ( JFrame.EXIT_ON_CLOSE );
            frame.setSize ( 500, 500 );
            frame.setLocationRelativeTo ( null );
            frame.setVisible ( true );
        } );
    }
}

理论上你应该得到的是 button1 总是在顶部(因为它被更早地添加并且最后被绘制在容器上),但在实践中 - 哪个按钮是完全可见的并且在顶部如果您尝试将鼠标悬停在其中一个按钮上,另一个将会改变。发生这种情况是因为两个按钮都是不透明的,并且重绘调用不会通过按钮组件到达它们的容器以及与它们进一步相交的任何内容。要解决这个特殊情况,使 button2 non-opaque 就足够了,因为它应该始终保持在 button1 以下,如果它是 non-opaque ,它将至少安全地通过重绘调用到它的容器:

button2.setOpaque ( false );

虽然我个人建议在这种情况下使所有涉及的组件不透明以避免其他可能的问题,如果将来可能更改组件顺序或由于任何用户交互 - 例如应用程序中的滚动条是主要的例子。容器不必是 non-opaque,因为它会正确地在放置在其中的组件之间传递重绘调用,并且也会正确地重绘自身。

将上述示例中的按钮更改为 non-opaque 后,由于正确处理了重绘,问题就会消失。

对于 Swing 的初学者来说,这可能是一个复杂的话题,但我强烈建议您充分理解为什么会发生这种情况以及如何发生,否则一旦您的应用程序变大,它将成为您未来的大问题.