Consider the example:
#SuppressWarnings("serial")
public static void main(final String[] args) {
final var frame = new JFrame();
frame.setPreferredSize(new Dimension(150, 150));
frame.add(new JPanel() {
#Override protected void paintComponent(final Graphics g) {
super.paintComponent(g);
System.out.println("painting panel");
}
});
frame.pack();
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
SwingUtilities.invokeLater(() -> frame.setVisible(true));
}
There is a JFrame with one JPanel with overridden paintComponent method, simply printing something to the console each time this method is called.
Every time I run this program, I can see "painting panel" line 3 times in my console.
Why is method paintComponent of JPanel called multiple times instead of once? Is there a way to avoid unnecessary repainting of components?
Essentially, no. But it’s not as problematic as you think.
Painting requests are controlled by the underlying system’s graphics driver. Sometimes an application may be allocated only a small amount of video memory, in which case the system will paint a window one small piece at a time.
Which means that just because paintComponent is called multiple times doesn’t mean a full repaint of the component is taking place; before the method is called, the Graphics object may have a clip set to only paint part of the window. paintComponent methods don’t need to concern themselves with this, as most Graphics operations will return immediately if they can know they won’t affect any pixels outside of the current clip bounds. The point here is that multiple calls to paintComponent aren’t necessarily as wasteful as you think.
Also, as Gilbert pointed out, calling pack() will “realize” the Window; that is, the Window gets assigned an actual native desktop window. (Java refers to this as becoming displayable.) That will trigger a paint. Showing the window may also trigger a paint. Moving it may trigger a paint. Moving the mouse over it may trigger a paint.
It is never safe to make assumptions about how often painting occurs. To paint reliably, assume your paintComponent method may be called any number of times. Never change the data which is the basis for painting from within a painting method.
In general, there is no way to control when painting happens. Of course, if your program calls repaint, you do have control over how and when you call that.
Related
I have a JFrame which contains a single panel.
In the panel I use the paintComponent method to resize its elements according the size of Jframe. The elements of the JPanel are an image as a background and 4 JLabel that cointains 4 ImageIcon and work like buttons. The method paintComponent of Jpanel is like below
public class MyPanel extends JPanel
{
//Declarations
private BufferedImage backGround;
public MyPanel()
{
//Some code here
}
public void paintComponent(Graphics graphics)
{
super.paintComponent(graphics);
Graphics2D graphics2d = (Graphics2D) graphics;
if(backGround != null)
{
graphics2d.drawImage(backGround, 0, 0, getWidth(), getHeight(), this);
}
/* This code is repeated 4 times because I have 4 labels */
label1.setSize(getWidth()/7 , getHeight()/10);
label1.setLocation(getWidth()/2 - getWidth()/14 , getHeight()/3 );
image1 = button1.getScaledInstance(label1.getWidth(), label1.getHeight(),
Image.SCALE_SMOOTH);
label1.setIcon(new ImageIcon(image1));
}
}
The frame has just a simple method , add(myPanel) so I did not write it here.
When I run the application , it takes me around 300 MB of ram and around 30% of CPU (Inter core i5-6200U) , which is quite unsual for me , expecially the amount of CPU. What is causing my application to take so much resources and is there any way I can reduce it ?
Whenever you repaint your component you change your labels' dimensions and create resources (the Image and the ImageIcon derived from it) and assign them as a new icon. These are changes to visible parts of your application and hence must cause repainting the components in question. Basically your paintComponent method
causes a repaint every time it is called effectively creating an endless loop and
is very heavyweight because it allocates expensive resources.
Both of these points are pretty bad ideas. Your paintComponent method should do just what the name suggests, i.e. painting the component. All actions that cause a repaint (changing icons or text, adding or removing components from the tree etc.) must not occur in it.
See also:
The API documentation on paintComponent(Graphics)
Painting in AWT and Swing
EDIT: When you want to resize components dependent on the size of other components create a ComponentListener and add it to the component you want to depend on by calling addComponentListener(ComponentListener). The ComponentListener instance will then have its componentResized(ComponentEvent) method called whenever the size changes.
Similar problem here but didn't find an answer: Why is paintComponent() continuously and asynchronously being called without explicit repaint() call?
I have a JPanel that I am drawing onto
class DrawPanel extends JPanel {
final void paintComponent(Graphics g) {
super.paintComponent(g);
// some graphics drawing stuff
} }
and then adding this to a JScrollPane. However if I put a system.out.println() in the paintComponent method I can see its continuously being called. Any way to stop this? According to the link its possible due to the jpanel being covered
Generally paintComponent() does not paint continually. It get invoked occasionally when Swing determines it needs be painted.
If your method is being invoked continually then I can think of a couple of possible problems. You are:
manually invoking repaint()
changing a property of the component in the paintComponent() method which then automatically invokes repaint()
The paintComponent calls come from Swing's Event Dispatch Thread. It gets called everytime the component needs to be repainted.
If you resize the component or bring it back from minimized state, then it's repainted. Of course if you cover it with another component then the repainting will be called less. The other component will have a paintComponent method too though.
Nothing to worry about.
I try to optimize the drawing of my application. Currently i have implemented an animation as well as several GUI components. Some are separated exactly, some overlap each other. Currently i face a problem within the overlapping of swing components. A part of the GUI, which overlaps my animation, needs to draw a lot of Strings and - into an Jlist placed - common swing components.
In result the overlapping GUI becomes redrawn as often as the animation gets an update. I tried using quite a lot of different methods to make sure, whats drawn in front of each other. Things like GlassPane, Jlayeredpane. Unfortunately in any of these tries, the overlapping Menus paintcomponent method, which only need to become called as the user interacts with them, gets called frequently due to animation and causes a quite high cpu usage.
Ive tried to position the Menus in a lower order within the Layeredpane, i.e.:
getLayeredPane().add(map, JLayeredPane.DEFAULT_LAYER);
getLayeredPane().add(mapController, JLayeredPane.PALETTE_LAYER);
getLayeredPane().add(settings, JLayeredPane.PALETTE_LAYER);
getLayeredPane().add(painter, JLayeredPane.POPUP_LAYER);
During the paint process of the painter i tried to modify the area - i.e.:
#Override
protected void paintComponent(Graphics g) {
g2 = (Graphics2D)g;
g2.setRenderingHints(DefaultResources.getRenderQuality());
g2.clip(JMain.getInstance().getMapBounds());
...}
Well - as soon as the painter component !isOpague(); All components underneath become redrawn. Unfortunately if i do place the menus in a higher order, they as well need to become redrawn with any animation update.
Does somebody has any idea, how to avoid a permanent redraw of overlapping components with an animated component?
The only solution i have seen was using heavyweight containers. Unfortunately the relative positioning has also shown a behavior during moving purposes, which aren't appropriate.
Thanks for any kind of advice!!
Well, its pretty obvious that if you have overlapping non-opaque components all of them will be repainted on any changes in one of them unless you optimize your animation repaint calls to some specific rectangles, so there won't be any useless operations.
Let me describe how Swing works a bit more - all of the paintings you do in paint, paintComponent and other methods (which get called on each component repaint) are done onto subimages of a single image that holds a "cached" version of the whole frame interface.
Now imagine that you change something in your UI (add/remove/repaint component) - that final image (or atleast a small part of it that contains your component) must be properly updated. To do that in case your component is NOT opaque - all of sub-components will be repainted with your component bounds as a repaint rect to create a proper background for your component. If your component is opaque - it will be the only one repainted, but it also has to fill the whole bounds rect on its own, otherwise you will see awful painting artifacts behind your component on each repaint.
To summ up - to avoid pointless repainting of overlapping components there are a few approaches:
Optimize animation repaint calls to areas you actually need to repaint
Use opaque components, unless you want to paint something transparent in your component
Optimize all overlapping components painting operations so repaints won't take much time
There still might be more optimization approaches depending on your specific case, but you will have to find them on your own, since that is impossible without seeing the whole picture.
You can also find a lot of useful information about optimizations in Swing in this book: Filthy Rich Clients
Well in terms of optimization i do have a component, which is causing quite some trouble, but that one i have planned to rewrite. Therefore i just want to make sure, that painting areas are properly. Following that point, i have computed all necessary areas and filled them into a list, which i pass as data has changed.
Its working properly as long as i only have one rectangle applied. As soon as i pass a second one, it seems its y - extension becomes ignored. For instance:
[x=0,y=0,width=322,height=20]
[x=0,y=620,width=322,height=20]
There everything between y=20 and y=620 also gets redrawn.
for (Rectangle rec : clippingAreas) {
painter.repaint(rec);
}
Ok i have tried paintImmediately within the EDT, which does work for now, but i wonder if this is a proper way to go:
SwingUtilities.invokeLater(new Runnable() {
public void run() {
for (Rectangle rec : clippingAreas) {
painter.paintImmediately(rec);
}
}
});
Ok there we go:
package animationissue;
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.JLayeredPane;
import javax.swing.JPanel;
public class AnimationIssue extends JFrame {
JPanel a = new JPanel() {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
System.out.println("map has become repainted");
}
};
JPanel b = new JPanel() {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
System.out.println("menu as well");
}
};
public AnimationIssue() {
this.setSize(500, 500);
this.setLayout(null);
a.setSize(400, 400);
b.setSize(400, 200);
this.getLayeredPane().add(a, JLayeredPane.DEFAULT_LAYER); // Map
this.getLayeredPane().add(b, JLayeredPane.PALETTE_LAYER); // Menu
a.setLocation(0, 0);
b.setLocation(0, 100);
a.setBackground(Color.red);
b.setBackground(Color.blue);
Thread t = new Thread(new Runnable() {
#Override
public void run() {
// doin some computations for animation
// cast a repaint after having finished new
//animation information i.e. interpolation
while (true) {
try {
Thread.sleep(2000);
} catch (Exception e) {
}
// case 1 - just a repaint of the whole component - triggering map redraw results in menu redraw
// a.repaint();
// case 2 - repaint of specified rectangle
// Either passing one - the menu does not get repainted, or passing both - menu also gets repainted
//a.repaint(0, 0, 400, 100);
//a.repaint(0, 300, 400, 100);
// paintimmediately works for now
//a.paintImmediately(0, 0, 400, 100);
//a.paintImmediately(0, 300, 400, 100);
// Just repainting Menu does not trigger map to become repainted, except if its opague, but then it should become repainted
b.repaint();
}
}
});
t.start();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
// TODO code application logic here
AnimationIssue f = new AnimationIssue();
f.setVisible(true);
}
}
I was really looking forward to optimize the behavior, that no menu gets redrawn, if its not necessary. You have to imagine the menu of being a component holding several JLists with a lot of String drawing tasks, which really have a massive impact on cpu usage. I havent been wondering, since it gets redrawn approximately 25 times per sec.
I am just uncertain for the current solution, if using paintImmediately is proper. Apart from this - if you or somebody has an alternative - better - way to prevent useless redrawing (i really thought Glasspane or JLayeredPane or isOptimizedDrawing or isOpaque might help out), i am really thankful.
Best regards.
I have the following code being called:
while(true){
view.onTick();
trySleep(55);
}
The onTick() method is described as such:
public void onTick() {
mainFrame.paintAll(mainFrame.getGraphics());
}
Here is where I set up my JFrame and JPanels etc (mainFrame is a JFrame):
private void runProgramSetup(){
JPanel canvas = new JPanel();
canvas.setLayout(new BoxLayout(canvas, BoxLayout.Y_AXIS));
mainFrame.getContentPane().add(canvas);
//create the main game panel
mapPanel = new MapPanel(model, this);
mapPanel.setPreferredSize(new Dimension(TOTAL_FRAME_WIDTH, MAP_PANEL_HEIGHT));
mapPanel.setBackground(Color.cyan);
//create the menu panel
menuPanel = new MenuPanel(model, this);
menuPanel.setLayout(new BoxLayout(menuPanel, 0));
menuPanel.setPreferredSize(new Dimension(TOTAL_FRAME_WIDTH, MENUS_PANEL_HEIGHT));
//add the panels to the window
canvas.add(mapPanel);
canvas.add(menuPanel);
//make both panels visible
mapPanel.setVisible(true);
menuPanel.setVisible(true);
}
Now here is my problem. Everything repaints when repaintAll() is called EXCEPT mapPanel's overridden paint(Graphics g) method:
#Override
public void paint(Graphics g) {
transformedImages.transformAndStoreImages(model);
paintGrid(g);
paintScenery(g);
paintElements(g);
paintDraggedElement(g);
paintUIOverlay(g);
}
It is only called once. That is it. However, every other component continues to repaint. It is only mapPanel that paints once. Here is what is even more strange. I am running on Ubuntu and get this problem. The rest of my team is running on Macs and they do not have this problem. The only way I have been able to solve this is to replace onTick() with two paint calls:
public void onTick() {
mainFrame.repaint();
mainFrame.paintAll(mainFrame.getGraphics());
}
This is all that has worked for me. I need both calls. Neither works alone. I don't like doing this though obviously because of inefficiency.. :/
Any ideas?
Thanks!
you should be overriding JPanel's
paintComponent(Graphics g)
not paint
The reason mainFrame.repaint() forces the map to refresh is because repaint() calls repaint(0, 0, 0, width, height), which marks the entire mainFrame's area to be marked as "dirty" for the RepaintManager. This is designed this way on purpose, because you usually do not want to repaint every pixel in the JFrame just to update a component. So, in onTick(), when mainFrame.paintAll() is being called, my guess is that the mapPanel's area has not been marked dirty, so the RepaintManager skips it, to save processing time. If you are very sure that you want to repaint the whole mapPanel every time onTick() is called, the simplest way would be to call mapPanel.repaint() inside of your onTick() method. This will mark the whole mapPanel as dirty so it will be redrawn asap. Also, if your menuPanel is just using regular swing JComponents, you don't need to manually cause them to repaint, they will be repainted when their values change, if you are using the API correctly.
I know this is a kind of old question, but I figured I'd answer in case anyone else runs into something similar.
I'm currently using an animation engine I designed that takes objects of type Drawable and adds them to a List. Drawable is an interface that has one method:
public void draw(Graphics2D g2d);
The extending animation manager iterates through this list and calls the draw() method on every object, passing the Graphics2D object obtained from the Swing component.
This method seemed to work well at first, but as I feared, it seems to be unable to handle multiple objects in the long run.
With merely two Drawables registered, both drawing images on screen, I'm seeing a bit of flashing after 30-60 seconds.
Is there a way to optimize this method? It currently calls upon the AWT thread (invokeLater) to handle all of the tasks. Concurrent drawing isn't really an option as this nearly always causes issues in Swing/AWT, in large part due to the fact that Graphics isn't synchronized.
If this just simply is a bad way of animating, what is a better method when you have multiple objects that all need things rendered with their own specific variables cough game cough?
Any help would be appreciated. Thanks!
EDIT:
I can't use repaint() beacuse my engine already calls the AWT thread to paint stuff. If I call invokeLater from the AWT thread, the image never gets painted for some reason.
I should also add that I'm using a system of ticks and fps. 60 ticks # 120 fps.
Each tick updates the game logic, while each frame render calls draw on the frame manager.
Is this a bad idea? Should I just use FPS and not ticks?
I think it would be more appropriate to override paintComponent(Graphics g) and regularly call the repaint method on the JPanel or whatever you're drawing on with a Timer. Your problems may be due to to you trying to draw and then Swing doing it's own draw.
public class Main {
public static void main(String[] args) {
JFrame frame = new JFrame();
JPanel panel = new JPanel() {
public void paintComponent(Graphics g) {
//draw here
}
};
panel.setPreferredSize(800, 600);
frame.add(panel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true)
new Timer(16, new ActionListener() {
public void actionPerformed(ActionEvent event) {
panel.repaint();
}
}).start();
}
}