[英]Can't paint a custom component in Swing correctly
I know Swing very poorly, so sorry for stupid question. 我知道Swing非常差,所以很抱歉这个愚蠢的问题。 What I need to do is to make my custom components (they are ancestors of
JPanel
) with painting performed from the buffer ( BufferedImage
instance). 我需要做的是使我的自定义组件(它们是
JPanel
祖先)具有从缓冲区( BufferedImage
实例)执行的绘制。 It is a requirement, because the painting procedure might be very heavy, so paintComponent method is overridden to draw from that buffer and return imediately (but if you know better ways to tell Java not to repaint the object over 200-300 times a second consuming all the CPU - I would appreciate, maybe there are some public methods to mark regions of Graphics context as "not changed", so Swing does not try to repaint them). 这是一个要求,因为绘制过程可能非常繁琐,所以重写了PaintComponent方法以从该缓冲区绘制并立即返回(但是如果您知道更好的方法来告诉Java,第二次消耗对象的时间不应超过200-300次)所有的CPU-我将不胜感激,也许有一些公共方法可以将Graphics上下文的区域标记为“未更改”,因此Swing不会尝试重新绘制它们)。
What is wrong with my code that it simply does not draw data on overlapping JPanels. 我的代码有什么问题,它根本不会在重叠的JPanels上绘制数据。 I have made a reproducable example of the core of the problem.
我已经提出了一个可以重现该问题核心的例子。 See the code below:
请参见下面的代码:
public class Dr extends JPanel {
public Dr(int x, int y, int w, int h, int fw, int fh) {
left = x;
top = y;
width = w;
height = h;
full_width = fw;
full_height = fh;
setOpaque(true);
}
public void draw() {
if (buffer == null && width > 0 && height > 0) {
buffer = new BufferedImage(width, height, java.awt.image.BufferedImage.TYPE_INT_ARGB);
}
if (buffer == null) return;
Graphics g = buffer.getGraphics();
setBounds(0, 0, full_width, full_height);
Graphics2D g2d = (Graphics2D)g;
g2d.setBackground(Color.WHITE);
g2d.clearRect(0, 0, full_width, full_height);
g2d.setColor(new Color(255, 0, 80, 128));
g2d.fillRect(left, top, width, height);
System.out.println(left + ", " + top);
}
@Override
public void repaint() {
draw();
}
@Override
public void paintComponent(Graphics g) {
g.drawImage(buffer, 0, 0, null);
}
private BufferedImage buffer;
private int left;
private int top;
private int width;
private int height;
private int full_width;
private int full_height;
}
This is a sample class that does nothing but saving his coordinates to draw at and full dimensions (which I want to be equal to its parent dimensions, that's a requirement again because I need to support visible overflowing content to be able to be displayed) and drawing a red rectangle with half of the 100% opacity. 这是一个示例类,除了保存他的坐标以绘制完整尺寸(我想等于其父尺寸)外,什么也不做,这又是一个要求,因为我需要支持可见的溢出内容才能显示)绘制一个红色矩形,其不透明度为100%的一半。
Here is the outer code that uses it: 这是使用它的外部代码:
Dr dr1 = new Dr(4, 4, width-10, 80, width-2, height-2);
Dr dr2 = new Dr(4, 88, width-10, 80, width-2, height-2);
root.add(dr1);
root.add(dr2);
dr1.setBounds(0, 0, width-2, height-2);
dr2.setBounds(0, 0, width-2, height-2);
dr1.draw();
dr2.draw();
What is expected to be: 预期为:
What is displayed when I remove my buffer and draw in paintComponent
method directly: 当我删除缓冲区并直接绘制
paintComponent
方法时显示的内容:
What is displayed when I don't fill the background (here the background has system L&F color, but I need the color of element's parent (in my example it is white) 当我不填充背景时显示的内容(这里的背景具有系统L&F颜色,但是我需要元素父级的颜色(在我的示例中是白色)
What is displayed when I drraw from my buffer 当我从缓冲区拖动时会显示什么
The last one is completely amuzing. 最后一个完全模糊。 The red rectangle is partly cut off from the right and from the bottom, and the full image (including the white background) is clipped from the (0, 0) coordinate of the parent panel.
从右侧和底部部分切除了红色矩形,并从父面板的(0,0)坐标中剪切了整个图像(包括白色背景)。 The second object is not displayed at all.
第二个对象根本不显示。 The coordinates in the console are absolutely OK.
控制台中的坐标绝对可以。
I feel there is something I have to do with Swing to tell it not to perform its "magic" upon guessing what to paint and what not to paint. 我觉得我与Swing有一些关系,要它在猜测要绘画什么和不绘画什么时告诉它不要执行其“魔术”。 But how can I do that?
但是我该怎么办呢?
UPDATE: 更新:
I found that even if I don't call draw()
method of my child components after I added them, they still hide their parent's background (which should not happen, bacase JPanels are meant to be opaque). 我发现,即使我在添加子组件之后不对其子集的
draw()
方法进行调用,它们仍会隐藏其父级的背景(这不应该发生,因为bacase JPanels应该是不透明的)。 So the core problem is that I can't make my Dr
objects have opaque background (eg no background) where nothing is painted in them (it also explains why I see only the first rectangle - the z-order in Swing seems to be reversed, eg the last added component is painted at the bottom, as it was the farthest from us). 因此,核心问题是我无法使我的
Dr
对象具有不透明的背景(例如,没有背景),其中没有任何内容被绘画(这也解释了为什么我只看到第一个矩形-Swing中的z顺序似乎被反转了) ,例如,最后添加的组件画在底部,因为它离我们最远。
So, what I "think" you have is a fundamental misunderstanding how, well, everything works in Swing. 因此,我“认为”您有一个根本性的误解,那就是Swing的所有工作原理。
You "seem" to be having an issue with how the layout management system works, how the painting system works and how the coordinate system works 您似乎对布局管理系统如何工作,绘画系统如何工作以及坐标系如何工作存在问题
Let's start with your Dr
component. 让我们从您的
Dr
组件开始。
You seem to want to develop overlapping components, which can show the child components below them, but you use setOpaque(true);
您似乎想开发重叠的组件,这些组件可以在其下方显示子组件,但是您可以使用
setOpaque(true);
, which means that the component won't be see through ,这意味着该组件将无法看穿
This... 这个...
public void draw() {
if (buffer == null && width > 0 && height > 0) {
buffer = new BufferedImage(width, height, java.awt.image.BufferedImage.TYPE_INT_ARGB);
}
if (buffer == null) return;
Graphics g = buffer.getGraphics();
setBounds(0, 0, full_width, full_height);
Graphics2D g2d = (Graphics2D)g;
g2d.setBackground(Color.WHITE);
g2d.clearRect(0, 0, full_width, full_height);
g2d.setColor(new Color(255, 0, 80, 128));
g2d.fillRect(left, top, width, height);
System.out.println(left + ", " + top);
}
Seems odd to me. 对我来说似乎很奇怪。 You define the
BufferedImage
to be sized as width
by height
, but then use full_width
and full_height
to fill it...it seems, to me, to make more sense to do it the other way round 您将
BufferedImage
定义为height
width
,然后使用full_width
和full_height
填充它...在我看来,以另一种方式做起来更有意义
@Override
public void repaint() {
draw();
}
Okay, important lesson in Swing, you don't control the paint process, so don't try. 好的,这是Swing中的重要课程,您无法控制绘画过程,因此请勿尝试。 Swing has a well documented and well established painting process that provides well defined hooks into which you can inject your custom painting (ie
paintComponent
). Swing具有完善的记录和建立良好的绘画过程,可提供定义明确的挂钩,您可以在其中插入自定义绘画(即
paintComponent
)。 If you need "control", then you need to look towards using a BufferStrategy
, which will give you complete control to define you're own painting process. 如果需要“控制”,则需要考虑使用
BufferStrategy
,它将为您提供完全的控制以定义您自己的绘画过程。
Okay, so what's the answer? 好的,答案是什么?
Well, that's not so straight forward, because I'm not 100% sure I understand what the problem is you're trying to solve is. 嗯,这不是那么简单,因为我不确定100%知道我要解决的问题是什么。
But, let's start with the Dr
panel... 但是,让我们从
Dr
面板开始...
public class Dr extends JPanel {
public Dr(int x, int y, int w, int h, int fw, int fh) {
left = x;
top = y;
width = w;
height = h;
full_width = fw;
full_height = fh;
setOpaque(false);
setBounds(x, y, fw, fh);
}
public void draw() {
if (buffer == null && width > 0 && height > 0) {
buffer = new BufferedImage(getWidth(), getHeight(), java.awt.image.BufferedImage.TYPE_INT_ARGB);
}
Graphics g = buffer.getGraphics();
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(new Color(255, 0, 80, 128));
g2d.fillRect(0, 0, width, height);
g2d.dispose();
}
@Override
public void paintComponent(Graphics g) {
draw();
g.drawImage(buffer, 0, 0, this);
g.setColor(Color.RED);
g.drawRect(0, 0, getWidth() - 1, getHeight() - 1);
}
private BufferedImage buffer;
private int left;
private int top;
private int width;
private int height;
private int full_width;
private int full_height;
}
So, here, I've changed it so that the panel will be positioned at the x
, y
position you pass to the constructor and will be sized to the fw
and fh
properties. 因此,在这里,我对其进行了更改,以便将面板定位在传递给构造函数的
x
, y
位置,并将其大小设置为fw
和fh
属性。
In the draw
method, I then create a BufferedImage
sized to the component's current size and paint ... what ever ... based on the width
and height
properties ... questions are raised about why we have to sizes, but, there it is... 然后在
draw
方法中,我创建一个BufferedImage
大小,该大小调整为组件的当前大小并进行绘制...基于width
和height
属性的内容...提出了有关为什么必须调整大小的问题,但是在那里是...
The buffer is then draw to the top/left position of the component, this is important, Swing's coordinate system is based around the component itself, so 0x0
is always the top/left corner of the component, the coordinate system has nothing to do with the parent container. 然后将缓冲区绘制到组件的顶部/左侧位置,这一点很重要,Swing的坐标系基于组件本身,因此
0x0
始终是组件的顶部/左侧角,与坐标系无关父容器。
ps- The red rectangle is for debugging purposes, you don't need it. ps-红色矩形用于调试目的,您不需要它。
I then use... 然后我用...
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setContentPane(new JLayeredPane());
frame.add(new Dr(10, 10, 180, 180, 200, 200));
frame.add(new Dr(100, 100, 180, 180, 200, 200));
frame.setSize(400, 400);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
And voila, overlapping components... 瞧,重叠的组件...
Now, I strongly recommend that you stop and go have a read through: 现在,我强烈建议您停下来仔细阅读以下内容:
And, my gut feeling is, you probably really don't want separate components, what you want is one component which can paint many buffers 而且,我的直觉是,您可能真的不想使用单独的组件,而您想要的是一个可以绘制许多缓冲区的组件
I finally solved the issue. 我终于解决了这个问题。
The problem was in the code that I didn't post here. 问题出在我没有在此处发布的代码中。
I added my Dr
JPanels into Block
JPanel. 我将我的JPanels
Dr
添加到Block
JPanel中。 Block
had repaint
method overridden to draw from the buffer, but hadn't overridden paintComponent
method in current version. Block
已重写repaint
方法以从缓冲区进行绘制,但在当前版本中未重写paintComponent
方法。 It also had method forceRepaint
to rebuild the buffer, and methods draw(Graphics g)
and draw()
to draw on the internal buffer or on the passed Graphics context, respectively. 它还具有方法
forceRepaint
来重建缓冲区,而方法draw(Graphics g)
和draw()
分别在内部缓冲区或传递的Graphics上下文上进行绘制。 paint
method wasn't overridden either. paint
方法也没有被覆盖。
As a result, after I added my children with call to add
, Swing called the repaint
on my Block
object. 结果,在我给孩子
add
调用之后,Swing在我的Block
对象上调用了repaint
。 But my version of repaint
simply updated the internal buffer, and that was all. 但是我的
repaint
版本只是更新了内部缓冲区,仅此repaint
。 The result on the screen didn't change in any way. 屏幕上的结果没有任何改变。
So that was just a logical error. 因此,这只是一个逻辑错误。
The solution was to re-implement the overridden version of paintComponent
in Block
class, a class whose instance is the root
object where I add elements. 解决方案是在
Block
类中重新实现paintComponent
的重写版本,该类的实例是我在其中添加元素的root
对象。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.