JPanel什么时候绘制(或重绘)其子组件?

我有一个使用自定义UI委托绘制的JButton(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的重绘方法时,一旦我将鼠标hover在按钮上,边框就不再是抗锯齿的。

别名抗锯齿

更新2

我已经共享了整个“组件”,它包含一个JPanel,一个JSlider和一些Snipt上的JButton。 请从http://snipt.org/wnllg获取。

更新3

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

我已经将示例简化为抗锯齿,我无法重现该问题。 它似乎不依赖于平台。 我不确定你为什么使用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(); } }); } } 

要使抗锯齿工作一致,您的组件需要为isOpaque返回false 。 否则,RepaintManager可以跳过绘制组件后面的区域。

我怀疑,如果你使用屏幕放大镜来查看“不带锯条”的边缘,你会发现它们确实是抗锯齿的。 但这是针对黑色未上漆的背景,而不是父母的背景。

知道他们正在谈论什么的Swing专家很快就会在这里。 在此期间,让我对此发表评论:

现在,我的问题涉及到这是一个好主意吗? 我会从子组件重绘父组件。 我想知道这是否会导致重绘的循环? 如果父级尝试重绘其子级并且子级尝试重新绘制其父级 – 那么我假设我们正在讨论循环。

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

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

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

通常,Swing在自己的线程中运行,因此重复重绘不应该停止应用程序逻辑。 但是,它可能正在使用处理能力,如果系统总是重新绘制ui,即使没有变化。

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

编辑:有一个名为RepaintManager的类,您可能会感兴趣。

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