paintComponent() vs paint() and JPanel vs Canvas in a paintbrush-type GUI
Solution 1
I've been trying to find a workarounds, but have not found one, especially for the getGraphics() method: how else can the graphics be added to the panel?
You remember what needs to be painted as a variable and use that in paintComponent(). For example, what you seemed to be trying to do in your other question would look like:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class PaintRectangle extends JPanel {
private Point mouseLocation;
public PaintRectangle() {
setPreferredSize(new Dimension(500, 500));
MouseAdapter listener = new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
updateMouseRectangle(e);
}
private void updateMouseRectangle(MouseEvent e) {
mouseLocation = e.getPoint();
repaint();
}
@Override
public void mouseDragged(MouseEvent e) {
updateMouseRectangle(e);
}
@Override
public void mouseReleased(MouseEvent e) {
mouseLocation = null;
repaint();
}
};
addMouseListener(listener);
addMouseMotionListener(listener);
}
private Rectangle getRectangle() {
if(mouseLocation != null) {
return new Rectangle(mouseLocation.x - 5, mouseLocation.y - 5, 10, 10);
}
else {
return null;
}
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Rectangle rectangle = getRectangle();
if(rectangle != null) {
Graphics2D gg = (Graphics2D) g;
gg.setColor(Color.BLUE);
gg.fill(rectangle);
gg.setColor(Color.BLACK);
gg.draw(rectangle);
}
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.getContentPane().add(new PaintRectangle());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
}
See also http://docs.oracle.com/javase/tutorial/uiswing/painting/
Solution 2
Heavy vs light weight
Basically speaking, a heavy weight component is linked to its own native peer, where light weight components share a common native peer.
In general, it's not a good idea to mix heavy and light weight components as there are issues with the z-order and in my experience (even though it's supposed to be better now) there are painting issues that can crop up.
This is the reason why you have been discouraged from using the Canvas
class, probably because you were trying to place it onto a light weight component...I guess
The illusion of control
One of the biggest issues for newcomers to the Swing API is the illusion that you have some kind of control over the painting process, you don't. It's easier to just accept it.
The best you can do is request that the repaint manager perform an update at its earliest convenience.
Also, calling getGraphics
is not guaranteed to return a non-null value.
The correct order of things
paint
vs paintComponent
The problem here is paint
does a number of important jobs, calling paintComponent
is just one of them.
In Swing we are greatly encouraged to use paintComponent
whenever we want to perform custom painting, this is, generally, the lowest level on the component and is called before the child components are painted.
If you override paint
and then paint on the Graphics
after the call to super.paint
you will end up painting on top of everything, this isn't always the desired result
Even if it were, child components can be painted independently of their parent container, making the paint "over" any paint effects you might have added
Useful links
- Painting in Swing (talks about the how the paint process works)
- Performing Custom Paining (in Swing)
- 2D Graphics Trail
Parting thoughts
Only components that are actually added to a component, which is attached to a native peer will ever have there paint
method called. So trying to paint to a component that hasn't been added to a container yet is rather pointless...
Solution 3
..paintbrush-type GUI..
Use a BufferedImage
as the painting surface. Display it in a JLabel
. Put the label in the center of a panel inside a JScrollPane
.
Call bufferedImage.getGraphics()
as needed but remember to dispose()
of it when done, and then call label.repaint()
.
Use Swing components throughout, and don't override anything.
Here is an example of using an image as painting surface.
And here is a better one!
I did not say the screen-shot was better, it is the code that is better. ;)
Alex
Just getting back to programming, learning opensource stuff and maybe C#
Updated on November 13, 2020Comments
-
Alex over 3 years
I got some interesting ideas and criticism from this, this and this post (see last post for the code of the GUI in question). Nevertheless, I'm still quite confused about some things. Mainly, what is the least expensive way of displaying user-introduces graphics?
More specifically, I used a
paintComponent()
method fromJPanel
class by making an object of this class in theMouseDragged()
method together withpaintComponent(getGraphics())
method (AuxClass2
andAuxClass1
accordingly).Apparently, using
getGraphics()
andpaintComponent()
instead ofrepaint()
are bad ideas, I suspect something to do with memory use. Also calling theAuxClass2
every time the user drags the mouse is also a bad idea.Also JPanel vs Canvas (i.e. swing vs awt) is a bit confusing. What is used and when?
I've been trying to find a workarounds, but have not found one, especially for the
getGraphics()
method: how else can the graphics be added to the panel? -
Alex over 11 yearsThanks, the problem is, calling
repaint()
erases the previously painted stuff. Is it possible to avoid it? -
Walter Laan over 11 yearsNo, because you don't know what needs repainting (the user could have moved another window across your window), so paint component should repaint the entire component (inside the specified clip). You can use Andrew approach of painting to an image and paint that image in paintComponent.
-
Alex over 11 yearsDoes this mean that, in case I need to keep the whole 'painting', e.g. to save to a hard drive, I should not use
Canvas
orJPanel
at all, instead useBufferedImage
? -
searchengine27 over 8 yearsThis should not be the accepted answer. There is practically no explanation, and one of the two sentences that is part of the explanation is an incomplete sentence that makes no sense, and can't be made sense of since there is no context to infer anything from.
-
searchengine27 over 8 yearsI kind of want to give this a thumbs up, but the explanation of "The correct order of things" left me a bit confused. The part about 'paint' makes sense to me, but how it fits in with paintComponent is less clear to me (other than it happens before the child components are painted EDIT: link "Painting in Swing" is broken). I will try the links you provided and see if that gives me any more insight.
-
MadProgrammer over 8 years@searchengine27 Generally speaking, the paint chain looks something like
paint
->paintComponent
->paintBorder
->paintChildren
. It's to easy to miss callingsuper.paint
and having strange and weird painting results, but also, it's to easy to have painting either overridden (by callingsuper.paint
AFTER you've done custom painting) or painting done over the child components, which might sound like a good idea, but, each child component can be painted independently of the parent container, meaning that it's possible that anything you originally painted over the top to be be undone -
searchengine27 over 8 yearsI definitely have some problem like that, but I'm having trouble figuring out how to fix it. Essentially, I have a JPanel with a BorderLayout. Inside there are 3 JLists, 2 that have borders and one that does not, all with varying content. I need the JList to have already been painted to have the Graphics Object initialized, in order to use the Graphics to figure out the width of the text inside of the list, and of the Border text (using Border with text). The problem is, I need the parent JPanel to resize based on the max width of any text in any list, but I cant seem to figure out the order
-
Alex Mandelias about 3 yearsHonestly +1 for the art of a true programmer
-
Andrew Thompson about 3 years@AAAlex123 Thanks! I was waiting for someone to say that. (checks date on post) Lucky I wasn't holding my breath waiting. ;)