JComponent Child 在重写 Parent PaintComponent 函数后不绘制

问题描述 投票:0回答:1

我有一个 JButton,其中我重写了

paintComponent(Graphics)
函数 与一个孩子 JLabel (我意识到这听起来很愚蠢,我保证不是) 我有
mouseEntered(MouseEvent)
mouseExited(MouseEvent)
函数,可以更改标签的可见性,并设置布尔值告诉
paintComponent
在按钮上绘制半透明覆盖层

预期的行为是

JLabel
绘制在按钮覆盖层上。如果没有覆盖(覆盖
paintComponent
),这可以完美地工作。

(我假设这不仅限于按钮,尽管我还没有测试过这个理论)

按钮类:

import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

public class HoverButton extends JButton {

    private final JLabel label;
    private final String title;

    private boolean entered = false;

    public HoverButton(String title) {
        label = new JLabel(title);
        this.title = title;
        int startChar = title.indexOf(']') + 1;
        String regex = new StringBuilder("\\[[a-zA-Z0-9]+\\]").append("| \\[[A-Za-z0-9]+ [A-Za-z0-9]+\\]")
                .append("| \\(decen\\)").append("| \\(eng, decen\\)").append("| \\(eng\\)")
                .append("|\\{.+\\}").toString();
        String text = String.format("<html><p><b>%s</b></p></html>",
                title.substring(startChar).replaceAll(regex, "").trim());
        label.setVisible(false);
        add(label);
        addMouseListener(new MouseAdapter() {
            @Override
            public void mouseEntered(MouseEvent e) {
                super.mouseEntered(e);
                entered = true;
                label.setVisible(true);
            }

            @Override
            public void mouseExited(MouseEvent e) {
                super.mouseExited(e);
                entered = false;
                label.setVisible(false);
            }
        });
    }

    public String getTitle() {
        return title;
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        if (g instanceof Graphics2D g2d) {
            getIcon().paintIcon(this, g, getInsets().left, getInsets().top);
            int xMax = getWidth() - getInsets().right - getInsets().left;
            int yMax = getHeight() - getInsets().top - getInsets().bottom;
            if (entered) {
                g2d.setColor(new Color(0x88000000, true));
                g2d.fillRect(getInsets().left, getInsets().top, xMax, yMax);
            }
            g2d.dispose();
        }
    }

    @Override
    public void setPreferredSize(Dimension d) {
        super.setPreferredSize(new Dimension((int) (d.getWidth() + getInsets().right + getInsets().left),
                (int) (d.getHeight() + getInsets().top + getInsets().bottom)));
        label.setMinimumSize(d);
    }
}

底部的

setPreferredSize(Dimension)
可以确保标签不会调整按钮的大小

    button.setIcon(icon);
    button.setPreferredSize(new Dimension(icon.getWidth(), icon.getHeight()));

应该在调用类中

java swing jbutton jcomponent
1个回答
0
投票

您可以使用

JLayer
进行此类叠加绘画,其中事件将穿过标签。

JLayer
有两个组成部分:

  1. 景色。这是被
    JLayer
    包裹的组件。
    JLayer
    会将其事件转发到视图。例如,在本例中,我们希望将事件传递给按钮。
  2. 玻璃板。这可以用作绘画区域。它是一个
    JPanel
    ,您可以像其他任何东西一样使用它。事件将通过它及其后代。例如,在这种情况下,我们可以将标签添加到玻璃窗格(
    JLabel
    s 可以接受
    Icon
    和文本,或
    Icon
    本身)。

这样您就不必执行以下操作:

  1. 跟踪鼠标事件。这已经在按钮中实现了,因此您可以直接监听此类事件(通过
    ButtonModel
    )。
  2. Icon
    。该标签将为您做到这一点。
  3. 覆盖首选尺寸。
    JLayer
    会处理这个问题。
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Rectangle;
import javax.swing.BorderFactory;
import javax.swing.ButtonModel;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JLayer;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class MainWithJLayer {
    
    /**
     * Changes the alpha component of the given {@code Color}.
     * @param c
     * @param alpha
     * @return
     */
    public static Color withAlpha(final Color c,
                                  final int alpha) {
        return new Color(c.getRed(), c.getGreen(), c.getBlue(), alpha);
    }
    
    /** A {@code JPanel} which always draws its background color (dishonoring opaque property). */
    private static class AlwaysDrawBackgroundPanel extends JPanel {
        @Override
        protected void paintComponent(final Graphics g) {
            final Color originalColor = g.getColor();
            try {
                final Rectangle clipBounds = g.getClipBounds();
                g.setColor(getBackground());
                g.fillRect(clipBounds.x, clipBounds.y, clipBounds.width, clipBounds.height);
            }
            finally {
                g.setColor(originalColor);
                super.paintComponent(g);
            }
        }
    }
    
    public static void main(final String[] args) {
        SwingUtilities.invokeLater(() -> {
            
//            try {
//                UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
//            }
//            catch (final ClassNotFoundException | IllegalAccessException | InstantiationException | UnsupportedLookAndFeelException exception) {
//                System.err.println("Failed to set system L&F.");
//            }
            
            final JButton button = new JButton("always clickable...                                                        ...always clickable");
            button.addActionListener(e -> System.out.println("Clicked!"));
            
            final JLabel label = new JLabel("Label overlay!", JLabel.CENTER);
            label.setForeground(Color.RED);
            
            final JPanel glassPane = new AlwaysDrawBackgroundPanel();
            glassPane.setLayout(new BorderLayout());
            glassPane.setBackground(withAlpha(Color.BLACK, 0x88)); //new Color(0x88000000,true)
            glassPane.add(label, BorderLayout.CENTER);
            
            final JLayer<JButton> layer = new JLayer<>(button);
            layer.setGlassPane(glassPane);
            glassPane.setOpaque(false); //This is mandatory in order to show the button under the label.
            
            final JPanel contents = new JPanel(new BorderLayout());
            contents.setBorder(BorderFactory.createEmptyBorder(100, 100, 100, 100));
            contents.add(layer, BorderLayout.CENTER);
            
            //Change glass pane visibility when hovering the button:
            final ButtonModel buttonModel = button.getModel();
            buttonModel.addChangeListener(e -> glassPane.setVisible(buttonModel.isRollover()));
            
            final JFrame frame = new JFrame("Button overlay label");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.add(contents);
            frame.pack();
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);
        });
    }
}

请注意,只有当我们将鼠标悬停在按钮上时,玻璃窗格(带有标签)才可见,并且按钮正常接收事件(无论标签是否可见)。

按钮和标签还有多个属性(例如边距、边框、对齐方式、文本位置、图标-文本-间隙)来帮助解决两个组件之间的图标(和/或文本)放置问题。

© www.soinside.com 2019 - 2024. All rights reserved.