JPanel 何时绘制(或重新绘制)其子组件?

发布于 2024-10-20 02:46:23 字数 2189 浏览 3 评论 0原文

我有一个 JButton,它是使用自定义 UI 委托(CustomButtonUI 扩展 BasicButtonUI)绘制的。 CustomButtonUI 的paint() 方法绘制具有圆角“抗锯齿”角的按钮,以使外观尽可能“平滑”。

不知何故,每次我将鼠标拖动到按钮上时,按钮的“抗锯齿”边缘都会消失 按钮。这使得按钮边缘看起来“像素化”。但是,一旦我添加一行代码来重新绘制按钮的父级,即使我将鼠标拖动到按钮上,抗锯齿也会启动。

现在,我的问题是这是否是一个好主意?毕竟我会从子组件重新绘制父组件。我想知道这是否会导致重新绘制循环?如果家长 尝试重新绘制其子级,而子级尝试重新绘制其父级 - 那么我假设我们正在讨论一个循环。

我已附上我的代码作为参考。非常欢迎任何意见!

public class JCustomButtonUI extends BasicButtonUI {

    @Override
    public void installUI(JComponent c) {
        super.installUI(c);

        AbstractButton b = (AbstractButton) c;
        b.setBorderPainted(false);
    }

    @Override
    public void paint(Graphics g, JComponent c) {

        //Cast the Graphics instance to a Graphics2D instance.
        Graphics2D g2d = (Graphics2D) g;
        JButton b = (JButton) c;

        //Repaint parent component to make sure that we get "antialiased"
        //edges.
        b.getParent().repaint();

        //Get the component's height and width.
        int w = (int) g.getClipBounds().getWidth();
        int h = ((int) g.getClipBounds().getHeight());

        //Make sure the button is drawn with "antialiased" edges.
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 
            RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.setColor(Color.GRAY);
        g2d.fillRoundRect(0, 0, w, h, w, h);       
    }
}

更新1

只是为了说明别名和抗锯齿边框,请看下面两张图片。当我(从 ButtonUI 的 Paint() 方法)手动调用父 JPanel 的重绘方法时,所有边框始终都完美地抗锯齿。但是,当我不手动调用父 JPanel 的重绘方法时,一旦我将鼠标悬停在按钮上,边框就不再抗锯齿。

别名 Antialiased

更新 2

我已经在 Snipt 上共享了整个“组件”,其中包括一个 JPanel、一个 JSlider 和几个 JButton 。请从 http://snipt.org/wnllg 获取。

更新 3

看来我已经设法让它工作了。我没有在其 PaintComponent() 方法中绘制 JPanel 的背景,而是创建了一个安装在 JPanel 上的 JCustomPanelUI。我认为这不是解决方案本身,但我没有使用 Graphics 实例中的宽度和高度,而是尝试使用 JPanel 本身的宽度和高度。我不太清楚为什么当我使用 Graphics 实例的宽度和高度时会出现问题。我认为 Graphics 实例的宽度和高度已经根据 JPanel 组件的尺寸“准备好”。您可以在此处查看最终组件:http://snipt.org/wnlli

I've got a JButton which is painted using a custom UI delegate (CustomButtonUI extends BasicButtonUI). The CustomButtonUI's paint() method draws the button with rounded "antialiased" corners, to make the apperance as "smooth" as possible.

Somehow the "antialiased" edges of the button disappears each time i drag the mouse over the
button. This makes the button edges look "pixelized". However, once I add a line of code to repaint the parent of the button, the antialiasing kicks in even when i drag the mouse over the button.

Now, my question relates to wether this is a good idea? I do after all repaint the parent component from a child component. I wonder if this lead to a loop of repaints? If the parent
tries to repaint its children and the children tries to repaint its parent - then i assume we're talking about a loop.

I've attached my code as a reference. Any comments are very welcome!

public class JCustomButtonUI extends BasicButtonUI {

    @Override
    public void installUI(JComponent c) {
        super.installUI(c);

        AbstractButton b = (AbstractButton) c;
        b.setBorderPainted(false);
    }

    @Override
    public void paint(Graphics g, JComponent c) {

        //Cast the Graphics instance to a Graphics2D instance.
        Graphics2D g2d = (Graphics2D) g;
        JButton b = (JButton) c;

        //Repaint parent component to make sure that we get "antialiased"
        //edges.
        b.getParent().repaint();

        //Get the component's height and width.
        int w = (int) g.getClipBounds().getWidth();
        int h = ((int) g.getClipBounds().getHeight());

        //Make sure the button is drawn with "antialiased" edges.
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 
            RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.setColor(Color.GRAY);
        g2d.fillRoundRect(0, 0, w, h, w, h);       
    }
}

Update 1

Just to illustrate the alias and antialiased border, please have a look at the below two pictures. When i (from the ButtonUI's paint() method) manually invoke the parent JPanel's repaint method, all borders are perfectly antialiased all the time. However, when i do not manually invoke the parent JPanel's repaint method, then the borders are no longer antialiased once i hoover the mouse over the button.

Aliased
Antialiased

Update 2

I have shared the entire "component" which consists of a JPanel, a JSlider and a couple of JButtons on Snipt. Please get it from http://snipt.org/wnllg.

Update 3

It seems that i have managed to get it working. Instead of painting the JPanel's background in its paintComponent() method, i created a JCustomPanelUI which i installed on the JPanel. I don't think that was the solution itself, but instead of using width and height from the Graphics instance, I tried using widht and height from the JPanel itself. I'm not quite sure why things go wrong when i use width and height from the Graphics instance. I thought the width and height from the Graphics instance was already "prepared" with regard to dimensions from the JPanel component. You can have a look at the final component here: http://snipt.org/wnlli,

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

那支青花 2024-10-27 02:46:23

我已将示例简化为仅抗锯齿,但无法重现该问题。它似乎不依赖于平台。我不确定您为什么使用 getClipBounds()

附录:

JPanel 背景(渐变)需要闪耀……

我已更新示例以在透明按钮后面使用渐变背景;我并排放置了抗锯齿(左)和锯齿(右)示例。我没有看到任何意外的行为。

ButtonUITest.png

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.RenderingHints;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.plaf.basic.BasicButtonUI;

/** @see http://stackoverflow.com/questions/5169647 */
public class ButtonUITest extends JPanel {

    public ButtonUITest() {
        this.setLayout(new GridLayout(1, 0));
        this.setPreferredSize(new Dimension(640, 480));
        this.add(new CustomButton(true));
        this.add(new CustomButton(false));
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        int w = this.getWidth();
        int h = this.getHeight();
        Graphics2D g2d = (Graphics2D) g;
        g2d.setPaint(new GradientPaint(0, 0, Color.blue, w, h, Color.red));
        g2d.fillRect(0, 0, w, h);
    }

    private void display() {
        JFrame f = new JFrame("ButtonUITest");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.add(this);
        f.pack();
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }

    private static class CustomButton extends JButton {

        public CustomButton(boolean antialiased) {
            this.setOpaque(false);
            this.setUI(new CustomButtonUI(antialiased));
        }
    }

    private static class CustomButtonUI extends BasicButtonUI {

        private boolean antialiased;

        public CustomButtonUI(boolean antialiased) {
            this.antialiased = antialiased;
        }

        @Override
        public void paint(Graphics g, JComponent c) {
            int w = c.getWidth();
            int h = c.getHeight();
            Graphics2D g2d = (Graphics2D) g;
            if (antialiased) {
                g2d.setRenderingHint(
                    RenderingHints.KEY_ANTIALIASING,
                    RenderingHints.VALUE_ANTIALIAS_ON);
            }
            g2d.setColor(Color.LIGHT_GRAY);
            g2d.fillOval(0, 0, w, 2 * h);
        }
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                new ButtonUITest().display();
            }
        });
    }
}

I've reduced the example to just the anti-aliasing, and I am unable to reproduce the problem. It doesn't appear to be platform dependent. I'm not sure why you are using getClipBounds().

Addendum:

The JPanel background (a gradient) needs to shine through…

I've update the example to use a gradient background behind a transparent button; I've put anti-aliased (left) and aliased (right) examples side-by-side. I see no unexpected behavior.

ButtonUITest.png

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.RenderingHints;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.plaf.basic.BasicButtonUI;

/** @see http://stackoverflow.com/questions/5169647 */
public class ButtonUITest extends JPanel {

    public ButtonUITest() {
        this.setLayout(new GridLayout(1, 0));
        this.setPreferredSize(new Dimension(640, 480));
        this.add(new CustomButton(true));
        this.add(new CustomButton(false));
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        int w = this.getWidth();
        int h = this.getHeight();
        Graphics2D g2d = (Graphics2D) g;
        g2d.setPaint(new GradientPaint(0, 0, Color.blue, w, h, Color.red));
        g2d.fillRect(0, 0, w, h);
    }

    private void display() {
        JFrame f = new JFrame("ButtonUITest");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.add(this);
        f.pack();
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }

    private static class CustomButton extends JButton {

        public CustomButton(boolean antialiased) {
            this.setOpaque(false);
            this.setUI(new CustomButtonUI(antialiased));
        }
    }

    private static class CustomButtonUI extends BasicButtonUI {

        private boolean antialiased;

        public CustomButtonUI(boolean antialiased) {
            this.antialiased = antialiased;
        }

        @Override
        public void paint(Graphics g, JComponent c) {
            int w = c.getWidth();
            int h = c.getHeight();
            Graphics2D g2d = (Graphics2D) g;
            if (antialiased) {
                g2d.setRenderingHint(
                    RenderingHints.KEY_ANTIALIASING,
                    RenderingHints.VALUE_ANTIALIAS_ON);
            }
            g2d.setColor(Color.LIGHT_GRAY);
            g2d.fillOval(0, 0, w, 2 * h);
        }
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                new ButtonUITest().display();
            }
        });
    }
}
故人如初 2024-10-27 02:46:23

为了使抗锯齿功能始终如一地工作,您的组件需要为 isOpaque 返回 false。否则,RepaintManager 可以随意跳过绘制组件后面的区域。

我怀疑如果您使用屏幕放大镜查看“无锯齿”边缘,您会发现它们确实是抗锯齿的。但它是在黑色、未绘制的背景下完成的,而不是父母的背景。

For antialiasing to work consistently, your component needs to return false for isOpaque. Otherwise, the RepaintManager is free to skip painting the region behind your component.

I suspect that if you use a screen magnifier to look at the "unaliased" edges, you will find they really are antialiased. But it was done against against a black, unpainted background, not the parent's background.

哑剧 2024-10-27 02:46:23

知道自己在说什么的 Swing 专家很快就会来到这里。同时,让我对此发表评论:

现在,我的问题涉及是否
这是个好主意吗?我毕竟
从 a 重新绘制父组件
子组件。我想知道这个线索是否
重新绘制循环?如果家长
尝试重新绘制它的孩子和
孩子们尝试重新粉刷其父母 -
那么我假设我们正在谈论一个
循环。

一旦您尝试并发现这在您的机器上不是问题,那么很可能在您尝试的所有 JVM 上都是如此。也就是说,证据就在布丁中,或者随机的装错通常会在 Swing 中带来积极的结果。在 Java 中,递归循环有一种导致程序很快停止的方法,所以答案是......如果这是完全错误的你已经知道了。另外你可以把 sysouts 放在那里看看如果发生这种情况(显然不是)。

也就是说,可能有更好的方法来处理您的问题,但如果您的答案有效,请暂时坚持下去。

The Swing experts who know what they're talking about will be here shortly. In the meantime, let me comment on this:

Now, my question relates to wether
this is a good idea? I do after all
repaint the parent component from a
child component. I wonder if this lead
to a loop of repaints? If the parent
tries to repaint its children and the
children tries to repaint its parent -
then i assume we're talking about a
loop.

Once you try it out and see that it's not a problem on your machine, chances are that it will be true on all JVMs you try. That is to say, the proof is in the pudding, or random bumbling does generally lead to positive results in Swing. Recursive loops have a way of causing the program to halt pretty quickly in Java, so the answer is... if this were totally wrong you'd already know. Plus you can put sysouts in there to see if this is happening (it's obviously not).

That said, there may be a better way to deal with your problem, but if your answer works, stick with it for now.

喵星人汪星人 2024-10-27 02:46:23

AFAIK, repaint() 只是告诉系统在下一个绘制周期中重新绘制该组件,即
如果你在paint()方法中调用repaint(),重绘可能会在下一个周期完成。

一般来说,Swing 在它自己的线程中运行,因此重复的重绘不应停止应用程序逻辑。然而,如果系统总是重新绘制用户界面,即使没有发生任何变化,它也可能正在使用处理能力。

您可以尝试编写一条日志消息来查看绘制按钮的时间和频率。
如果这种情况持续发生,即使没有发生任何会导致用户界面更新的情况,您可能也必须找到解决方案。如果没有,你应该没问题。

编辑:有一个名为 RepaintManager 的类你可能会觉得有趣。

AFAIK, repaint() just tells the system to repaint that component in the next paint cycle, i.e.
if you call repaint() in a paint() method the repaint might be done in the next cycle.

Generally, Swing runs in its own thread, so repeated repaint should not stop the application logic. However, it might be using processing power, if the system always repaints the ui even if there are no changes.

You could try and write a log message to see when and how often the button is painted.
If that happens continously even if nothing happens that would cause a ui update, you might have to find a solution. If not, you should be fine.

Edit: there is a class called RepaintManager that you might find interesting.

羁〃客ぐ 2024-10-27 02:46:23

在那里重新粉刷绝对是一个坏主意。尝试在 installUI() 中添加 b.setOpaque(false)。由于抗锯齿功能,您的按钮不再绘制其整个边界。您需要让背景显示出来以填补空白。

Doing a repaint in there is definitely a bad idea. Try adding b.setOpaque(false) in installUI(). Your button is no longer painting its entire bounds because of the antialiasing. You need to let the background show through to fill the gaps.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文