I know Swing very poorly, so sorry for stupid question. What I need to do is to make my custom components (they are ancestors of JPanel
) with painting performed from the buffer ( BufferedImage
instance). 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).
What is wrong with my code that it simply does not draw data on overlapping 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.
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:
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)
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. 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. 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). 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).
So, what I "think" you have is a fundamental misunderstanding how, well, everything works in 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.
You seem to want to develop overlapping components, which can show the child components below them, but you use 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
@Override
public void repaint() {
draw();
}
Okay, important lesson in Swing, you don't control the paint process, so don't try. 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
). 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.
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.
But, let's start with the Dr
panel...
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.
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...
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.
ps- The red rectangle is for debugging purposes, you don't need it.
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. Block
had repaint
method overridden to draw from the buffer, but hadn't overridden paintComponent
method in current version. 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. paint
method wasn't overridden either.
As a result, after I added my children with call to add
, Swing called the repaint
on my Block
object. But my version of repaint
simply updated the internal buffer, and that was all. 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.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.