I am trying to understand how painting with Swing works. For that purpose I've been reading the Oracle tutorial: http://docs.oracle.com/javase/tutorial/uiswing/painting/step3.html
My question is a simple one:
Why do the two calls to the same function (repaint) have different behaviour? How does it come that the UI Delegate paints the background on the previously painted rectangle, but paints a new rectangle on the new area? I don't see any particular reason on paintComponent() for that.
I have also read http://docs.oracle.com/javase/tutorial/uiswing/painting/closer.html trying to understand the situation. It seems there is some connection to the opaque property of a component. Does this property change after we create a new rectangle, so that it is true (thus, as mentioned, the ui.update() will set it to the background color). How come it paintComponent() doesn't paint a new rectangle there?
The function is called with different parameters:
repaint(squareX,squareY,squareW+OFFSET,squareH+OFFSET);
squareX=x;
squareY=y;
repaint(squareX,squareY,squareW+OFFSET,squareH+OFFSET);
In the first call to repaint() squareX and squareY indicates location of the previously painted object. In second the second call, squareX and squareY is changed to the current mouse location.
From JComponent API documentation on repaint():
Adds the specified region to the dirty region list if the component is
showing. The component will be repainted after all of the currently
pending events have been dispatched.
That is, the first call to repaint() marks the previous location as dirty, the second, marks the current location as dirty. When the event (moveSquare) is completed, paintComponent is re-executed, and these two areas are updated. The red rectangle is only placed in the new location, the old location is updated to be "empty".
You are right about the opaque property, which is true for typical PanelUI implementations. In particular, paintComponent() says,
if you do not invoke super's implementation, you must honor the opaque property...If you do not honor the opaque property you will likely see visual artifacts.
Your code does honor the opaque property by invoking super.paintComponent(g). Both calls to repaint() clear the drawing rectangle to the background color. Try setting a different background color in the MyPanel constructor and omitting the super call to see the effect:
public MyPanel() {
setBorder(BorderFactory.createLineBorder(Color.black));
setBackground(Color.cyan);
...
}
#Override
protected void paintComponent(Graphics g) {
//super.paintComponent(g);
g.drawString("This is my custom Panel!", 10, 20);
...
}
Related
JPanel.setBackground method does nothing (although opaque attribute is true) if super.paintComponent father method isn't being called.
I have read a lot of similar questions here about this issue, and in each one of them I've only found solutions without explanation that helped me to understand why setBackground method when written before adding the JPanel to JFrame changes the JPanel background color, while when the setBackground is written inside paintComponent nothing is changed (only when calling father's paintComponent method as already mentioned).
Is it somehow related to Graphics object?
I have tried to change JPanel's opaque attribute to true and using setBackground(COLOR.BLACK) in paintComponent() method I had overriden in a class that extends JPanel
paintComponent(Graphics g)
{
this.setOpaque(true);
this.setBackground(COLOR.BLACK);
}
I expect that the JPanel background color will be black
Instead, background color is the default one
Well, first of all if you're using paintComponent(Graphics g) method, the first line you need to have inside is: super.paintComponent(g) otherwise you're breaking the paint-chain.
This will allow the parent component to draw the default component, before any customizations you do to it. If you don't do it, well, is like having a drawing in a piece of paper, imagine a circle, then cutting that circle and then trying to paint the outside.
Here's a more in-depth answer to How does super.paintComponent(g) works
However I wouldn't write
this.setOpaque(true);
this.setBackground(COLOR.BLACK);
inside the paintComponent(...) method, as it gets called several times and you can't control when it will ever get called. I would put those lines in a constructor, unless you want to change it later in your program while it's being painted depending on the state of your program or a gradient maybe.
For this part:
why setBackground method when written before adding the JPanel to JFrame changes the JPanel background color
Honestly, I don't understand what you mean.
Why do you say that if i won't call super.paintComponent(),it will break the chain? It's still drawing all the shapes and lines i want using graphics object.
From the docs:
The JPanel has a UI delegate which performs the background painting for itself. You call it by using super.paintComponent(g) and we pass the Graphics component to prevent irrevocable changes such as Graphics.translate
Your JPanel knows how to paint its children, but requires some help to paint itself, and this help comes from its parent.
When I mentioned "break the paint chain" I didn't mean nothing would paint, but that you would get strange behaviors such as the one of the JPanel's background disappearing or not being set.
In addition,something weird happens if the argument i'm sending to setBackground method is a random color(using Random object). JPanel changing color very quickly although i'm not doing anything(not minimizing,not resizing,etc).Can you consider why?
As I said before, the paintComponent gets called several times and you don't have control over when it will be called, even moving your mouse or something else will trigger the panel to repaint.
After calling a method with these two lines of code:
final Graphics canvasGraphics = screenCanvas.getGraphics();
canvasGraphics.fill3DRect(rectangleX, rectangleY, 500, 100, true);
The rectangle flashes when the program runs, and then disappears. However, when I put this same line in an anonymous inner class with my MouseListener:
screenCanvas.addMouseListener(new java.awt.event.MouseAdapter(){
public void mousePressed(MouseEvent event){
canvasGraphics.fill3DRect(rectangleX, rectangleY, 500, 100, true);
}
}
The rectangle stays there. What is causing this behavior?
first you have to know that the Graphics of a JComponent is changed (repainted) on every change (resizing, minimizing, moving ...), and this happen inside the method public void paintComponent(Graphics g).
in the first scenario the two lines of code are executed just once and they have an effect (you noticed the flash), but the the Component is painted very often on every change, so when launching your application the canvasGraphics is filled by the two lines of code, but just after that the method paintComponent is called , so it repaints the Graphics again and you changes are lost.
in the second scenario you are not drawing on the Graphics directly, the Graphics is changed once the mouse is clicked so the paintComponent get executed when the program launches and the window is shown, once the mouse is Pressed inside the Canvas your reimplemented method is called and the Graphics is changed but this time the paintComponent method will not ommit your changes because nothing is happening to the element.
a solution is to reimplement the paintComponent method on the wanted element to be sure that your changes is performed each time the elements get repainted.
This is The Code :
public void paint(Graphics g) {
g.fillOval(x, y, 25, 25);
repaint();
}
This will create an output like This where if i move the pointer it doesn't clear the previous Graphics and creates a Path .
Output of Above Code
And by adding the super statement like this it Doesn't show the Path but only the Current location of the Oval Graphic.
public void paint(Graphics g) {
super.paint(g);
g.fillOval(x, y, 25, 25);
repaint();
}
The output is This
I just want to the Mechanism and The Reason Behind This.
Sorry, but there is so much wrong with your code, that we have to fix it:
First off, don't override a JComponent's (or JFrame's) paint method but rather override the JComponent's or JPanel's paintComponent. This way you avoid mistakingly messing up painting of borders or child components, both of which paint is responsible for. Also since paintComponent has automatic double buffering, your animation will be much smoother.
Next, you almost always do want to call the super's painting method, which for paintComponent is super.paintComponent(g) as this will continue to propagate the painting chain, whereas by not calling this you break this chain. Calling the super's method will call the component's housekeeping painting including the overpainting of "dirty" pixels which is why the trails are removed. This is likely the answer that you're looking for.
Most important never call repaint() from within any painting method. Doing this results in a poor-man's animation that is completely uncontrollable, and puts too much responsibility on the painting method which should focus on its sole job -- to paint the component. Use a game loop such as a Swing Timer instead.
Most important -- read the tutorials as most of this is already explained there:
Lesson: Performing Custom Painting: introductory tutorial to Swing graphics
Painting in AWT and Swing: advanced tutorial on Swing graphics
Straight from the docs
public void paint(Graphics g)
Paints the container. This forwards the paint to any lightweight
components that are children of this container. If this method is
reimplemented, super.paint(g) should be called so that lightweight
components are properly rendered. If a child component is entirely
clipped by the current clipping setting in g, paint() will not be
forwarded to that child.
As he said that super.paint() cleans the dirty pixels. This says it all .!! :)
The paint method:
Invoked by Swing to draw components. Applications should not invoke paint directly,
but should instead use the repaint method to schedule the component for redrawing.
This method actually delegates the work of painting to three protected methods:
paintComponent, paintBorder, and paintChildren. They're called in the order listed
to ensure that children appear on top of component itself. Generally speaking,
the component and its children should not paint in the insets area allocated to the
border. Subclasses can just override this method, as always. A subclass that just
wants to specialize the UI (look and feel) delegate's paint method should just
override paintComponent.
Parameters:
g the Graphics context in which to paint
public void paint(Graphics g)
I've read many times not to override paint(), but to override paintComponent() instead. As seen above, the documentation also suggests to override paintComponent() if you want to specialize the UI.
So, I wanted to trace through the code to see why this is so.
protected void paintComponent(Graphics g)
{
if (ui != null)
{
Graphics scratchGraphics = (g == null) ? null : g.create();
try
{
ui.update(scratchGraphics, this);
}
finally
{
scratchGraphics.dispose();
}
}
}
There are many methods to trace through, but for conciseness, here's update():
public void update(Graphics g, JComponent c)
{
if (c.isOpaque())
{
g.setColor(c.getBackground());
g.fillRect(0, 0, c.getWidth(),c.getHeight());
}
paint(g, c);
}
An overloaded version of paint() is called, and paint(), itself, calls paintComponent(). So, I guess I'm just trying to figure out what's going on here exactly. I'm very new to Swing; there are a lot of classes and it's pretty difficult to trace through all of them, especially with my limited knowledge of using GUIs, in general.
Are these methods constantly calling each other, thereby giving the illusion of an image on the screen? If so, why does it matter so much if you override paint() instead of paintComponent? I imagine that my logic is in some way flawed or lacking, considering that for applications, the documentation advises against invoking paint() directly, and instead suggests to invoke repaint(). So, basically, I'm just trying to get some insight into the overall relationship between paint(), paintComponent(), repaint(), etc.
Firstly take a look at...
Performing Custom Painting and
Painting in AWT and Swing
The second should be compulsory reading for all Swing developers.
paint is (within the context of this question) the top level method called (update is actually called first, which just forwards to paint) when the RepaintManager wants a component to be painted.
paint is responsible for (amongst other things) calling paintComponent, paintBorder and paintChildren. If you're not careful you could compromise your components ability to paint. So if you HAVE to override paint, make sure you are calling super.paint.
It's important to note, the if a child component is updated, the parent component may not be repainted, so it don't try and use paint to provide overlays for your component.
paintComponent is responsible for painting the "background" of the component. This basically delegates to the look and feel.
In the case of something like JPanel, it will fill the background with the background color (if set to opaque). In the case of something like JTable, it will paint the rows, columns and cell values.
It is very important to ALWAYS call super.paintComponent, especially when dealing with transparent components.
repaint will make a request to the RepaintManager which may consolidate the paint requests so as to reduce the actual number of paint events that are raised. This helps improve performance.
The important thing to note here is that you don't control the paint process, so don't try, work with it instead.
Your paint method is also expected to run quickly, so avoid time consuming operations, like loading resources, or compound loops where possible. If you can, create backing buffers and paint those instead
ps- Paint is fun ;)
I'm trying to implement a simple window scale in java's swing library. The goal is simply to double the window height and width, and paint the window and each of its components in scale.
Here's an example of the code I'm using:
public class MyWindow extends JFrame {
...
public void paint(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
g2d.scale(2,2);
super.paint(g);
}
}
The position and size for each my components in this window is set manually using setBounds with a null layout for the window.
When I actually run the program, what happens is that the first paint for the window seems successful-- everything is sized appropriately. Each subsequent repaint by the components, however, is neither twice the size, nor in the proper location. Here's what I mean:
As you can see, portions of the screen which have components that call repaint manually (the animating bits), don't seem to be using the Graphics2D scale of the JFrame. I looked in the source code, and tried overloading a few other methods (update and repaint, mostly), but all of them seemed to produce the same result. I further looked at the paint and repaint methods of the component and container classes, but they all seem to call a specified repaint of their parent. Shouldn't my Window be the "biggest" parent? If so, why haven't these repaint calls reached my Window?
My big question to you is, therefore: what repaint methods of the parent component do the child components call? Why aren't the calls properly routed to my JFrame's paint call? Is there any other (better) way that I can scale my window? Any and all help is appreciated!
As discussed in Painting in AWT and Swing: The Paint Methods, "Swing programs should override paintComponent() instead of overriding paint()." A common approach is to create a view by overriding paintComponent() in a JComponent (or subclass), as shown here. Let your view listen for changes to the game's model, as discussed here.
SwingUtilities.updateComponentTreeUI() should be used to change the Look & Feel, not update the view.
Use
javax.swing.SwingUtilities.updateComponentTreeUI(parentComponent);
when you need to make sure all of the parentComponents child component's look and feel are updated properly.