JPanel inside JScrollPane Painting Issue - java

I have placed a JPanel object inside a JScrollPane and scrolling works as expected. By overriding paintComponent() I have attempted to do custom painting within the JPanel object. However when the JPanel object is placed within the JScrollPane the JPanel no longer paints properly (instead shows only its background color).
Because my application demands the JPanel to be updated constantly, a separate thread is constructed to repaint the JPanel at a specific interval.
The following excerpts of code show my current project:
a) paintComponent() from my JPanel (This method has been cut down to only the painting, the actual paint will be a constantly updating BufferedImage supplied from another thread instead of this big pink static box):
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
//Render Frame
// 'RXDisplayCanvas' is the JPanel.
Graphics2D G2D = (Graphics2D)RXDisplayCanvas.getGraphics();
G2D.setColor(Color.PINK);
//800 and 600 are arbitrary values for this example, real values are calculated at runtime. The value calculation code is verified to work (as its used elsewhere in a similar scenario)
G2D.fillRect(0, 0, 800, 600);
G2D.dispose();
}
b) The 'updater' thread that periodically repaints the frame:
#Override
public void run() {
long MaxFrameTime;
long Time;
while(isVisible()){
// 'FPSLimit' is a integer value (default to 30)
MaxFrameTime = Math.round(1000000000.0 / FPSLimit);
Time = System.nanoTime();
try{
SwingUtilities.invokeAndWait(new Runnable(){
#Override
public void run() {
// 'RXDisplayCanvas' is the JPanel.
RXDisplayCanvas.repaint(); //When using this, the JPanel does not display correctly.
//RXDisplayCanvas.paintImmediately(0, 0, RXDisplayCanvas.getWidth(), RXDisplayCanvas.getHeight()); When using this, the JPanel renders correctly but flickers.
}
});
}catch(InterruptedException | InvocationTargetException e){}
Time = System.nanoTime() - Time;
if(Time < MaxFrameTime){
try{
Thread.sleep(Math.round((MaxFrameTime - Time)/1000000.0));
}catch(InterruptedException ex){}
}
}
}
I have taken into account that repaint() does not cause a immediate repainting of the screen but the issue lies with the incorrect rendering of the screen. When the program is left alone, it merely renders the background color of the JPanel until the JScrollPane is scrolled in which it renders correctly for one frame before the next repaint() call paints the incorrect display.
When switching repaint() out for paintImmediately() (in excerpt b) the frame rendered correctly but heavy flickering was present where it constantly alternated between painting the background color and painting the pink box. I have tried adding and removing layout managers, disabling the repaint manager as well enabling and disabling the 'double buffered' flag for both components in which all resulted in one of the two behaviors mentioned above (rendering only background or flickering).
Can anyone aid me on this issue?
N.B: I am well aware of Java's variable naming convention, since this is a private project I am choosing to start variable names with capital letters because I think it looks nicer, please do not post comments regarding this.

1) I am not sure about this:
public void paintComponent(Graphics g){
super.paintComponent(g);
// 'RXDisplayCanvas' is the JPanel.
Graphics2D G2D = (Graphics2D)RXDisplayCanvas.getGraphics();
..
G2D.dispose();
}
I would recommend doing:
public void paintComponent(Graphics g){
super.paintComponent(g);
Graphics2D G2D = (Graphics2D)g;
G2D.setColor(Color.PINK);
G2D.fillRect(0, 0, 800, 600);
}
Note how I have omitted the getGraphics, and use the current passed in graphic context of paintComponent.
Also note I do not call g2d.dipose() as this causes problem it should only be done on Graphics that you create Component.getGraphics() but in your case you shouldn't even be creating the Graphics context as it already has been created and passed in to the paintComponent method. (see this similar question)
2) No need for SwingUtilities.invokeXXX block for repaint() as it is thread safe. But especially no need for SwingUtilities.invokeAndWait (as this is a blocking call and wait until all pending AWT events gets processed and run() method completes) which is not good and may also be adding to the onscreen visual artefacts you see.
3) I have tried adding and removing layout managers, disabling the repaint manager as well enabling and disabling the 'double buffered' flag for both components in which all resulted in one of the two behaviours mentioned above (rendering only background or flickering). undo all that as I cannot see how that affected the painting.
It would be even more helpful if I had an SSCCE which illustrates the unwanted behaviour. As I could attempt to reproduce your error but I most likely wont be able to (due to the specific conditions that apply to your app that may be causing these visual artefacts)

Related

JFrame + custom LayoutManeger + work with Graphisc2D (rotation)

I'm working on project, which uses custom Layout Manager. My goal is rotate all components by 180°.
I have some screen which consists of JFrame. I also have some LayoutHandler, which defines some layers of layout. Each layer is instance of LayerLayout which implements LayerManager2 from java.awt
The flow works probably like this:
I have JFrame, which represents the display. This JFrame has some default contentpane. The application set to this contentpane Layout which is represented by LayerHandler:
graphicalDisplayJFrame.getContentPane().setLayout(new LayerLayout(layerHandler));//graphicalDisplayJFrame is JFrame which represents the display
The layerHandler just somehow manage the layers which are LayerLayouts which implemetns LayerLayout2 from java.awt. So it looks like layout of layouts or whatever it is. I'm not much experienced with creating custom layouts.
There I have a situation which shows creating of JFrame.
graphicalDisplayJPanel = new JPanel() {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g2d = (Graphics2D) g;
g2d.rotate(Math.PI, anchorx, anchory); //rotation 180°
}
#Override
public void paintChildren(Graphics g) {
super.paintChildren(g);
Graphics2D g2d2 = (Graphics2D) g;
g2d2.rotate(Math.PI, anchorx, anchory);
}
};
graphicalDisplayJFrame = new JFrame(); //creation of JFrame
graphicalDisplayJFrame.setContentPane(graphicalDisplayJPanel); //setting JFrame to apply rotation
The first part should make the rotation 180°, but it does not, even when i call repaint on graphicalDisplayJFrame. BUT when I add this:
JLabel hwLabel = new JLabel("Hello world.");
hwLabel.setVisible(true);
graphicalDisplayJFrame.add(hwLabel);
I cause strange thing. The JFrame and all of the components on it is accidentaly rotated by 180°. But its only static behaviour, because there are some text components on display and when there is changed the text on them its again rendered in non rotated way.
So the question is why this could happens?
If i divide it in two parts:
a) Why the Graphics2D on JFrame does not affect the children components on its content pane. Why it only works when I add the JLabel which has no sense here but it was just experiment which caused what I wanted (= repaint with right rotation).
b) I should probably somehow apply the rotation => something like overriding the paint method on each components which are added on the contentPane of JFrame. But how should can I do it? Becouse upper described layout handler just manage a bunch of layouts (layers which are probably custom layouts implementig LayoutManager2 from java.awt) and it is just feeding the components which are put on the layouts and put them on content pane. So the question should probably be where could be the place where I want to implement my custom paining which means apply the rotation (which is this part of code g2d.rotate(Math.PI, anchorx, anchory); //rotation 180°).
I hope it understandable, I made pretty bit effort to understand where could be problem and trying find out the solution, but its behave really strange. So I hope someone with more experience with this could help me out off this struggling and show me the way.
You are applying the rotation at very strange points. Graphics2D.rotate() applies only to things painted after the rotate call, but your paint-overides rotate after calling super.
As for why adding a label causes rotation, hard to tell without seeing the entire code. Probably an effect of partial repaint.
You should not modify essentials like the graphics translation or rotation of the graphics passed to your paint methods. It has the potential to affect the parent components drawing in unexpected way and the effect can easily change depending on JRE version. Instead create a new graphics to pass down to your child components:
#Override
public void paintChildren(Graphics g) {
Graphics2D g2d = (Graphics2D) g.create();
try {
g2d.rotate(Math.PI, anchorx, anchory);
super.paintChildren(g2d);
} finally {
g2d.dispose();
}
}
This method leaves the original Graphics untouched, at least allowing the parent component to render normally. Rotating the graphics is still tricky and will probably have unintended side effects in the rendering process, but at least these effects are now carefully limited to the children of the rotating component.

Is `super.paintComponent(g)` mandatory?

I was taught in my class and also seen in this book (Big Java Early Objects) to include draw instructions in a class extending JComponent as:
public class Component extends JComponent {
public void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
Rectangle r = new Rectangle(0,0,20,10);
g2.draw(r);
}
}
However, someone pointed out that the first line in the paintComponent method should be: super.paintComponent(g);
Based on my limited knowledge, I believe it's calling JComponent's version of the method (now overridden). Why does this need to happen? What happens if I just ignore this statement as I've been doing until now?
Why does this need to happen? What happens if I just ignore this statement as I've been doing until now?
A component is responsible for painting itself completely. The default painting may be different for each LAF, so by invoking super.paintComponent() you make sure you get the default painting which will basically just be the background.
If you don't invoke this method you have the potential for painting artifacts to occur. Then may not always occur, but you don't want to waste time debugging.
Read the API for the paintComponent() method of JComponent. Among other things it states:
Further, if you do not invoker super's implementation you must honor the opaque property, that is if this component is opaque, you must completely fill in the background in a non-opaque color. If you do not honor the opaque property you will likely see visual artifacts.
So at a minimum you need:
g.fillRect(0, 0, getWidth(), getHeight());
to make sure the background is cleared before you start your custom painting.

Flickering in Java awt (how to use a buffer?)

Part of my Code is this paint method:
public void paint(Graphics g) {
super.paint(g);
refreshSize();
paintSquares(g);
if (drawGrid) {
drawGrid(g);
}
}
how can I use a buffer to stop it from flickering?
You can reduce flicker by overwriting the update(Graphics g) method with an empty implementation (update's default implementation renders the background). If you do this, you must ensure you paint the entire area of the component.
(Note: Overwriting update only really helps on a Frame - other components will still flicker because the containing Frame)
That said, do yourself a favor and switch to Swing.
Edit: Digging around a bit, the background rendering occurs in the update() implementation of java.awt.Container (Frame being a subclass of Container ofc). Combined with backbuffer rendering (that you would need to handle yourself, e.g. a BufferedImage) you could get to what Swing automatically delivers with AWT.

Faulty repaint() of swing components when using custom background colors

When I use JCheckboxes or JScrollPane (applied to the main component that holds all others in order to generate a scrollable window) together with components that use
component.setBackground(new Color(R, G, B, A));
to define their background color, I am getting some obnoxious repaint() issues. Hovering over JCheckboxes activates MouseListener and the background of the JCheckbox will suddenly display a random other part of the window. This remains even when taking the mouse off the JCheckbox.
The issue disappears when using
JCheckbox.setRollOverEnabled(false);
BUT will still occur when selecting the checkbox!
The scrollpane will also not properly repaint. ONLY the parts that are outside of the visible frame will be painted several times in a row in direction of scrolling when they come back into the frame. It looks similar to that error on Windows OS when a program crashes and you can "draw" with the window on the screen because it "generates" a new window every time you move it (http://i.stack.imgur.com/L5G5Q.png).
The most interesting part is that the issue completely disappears when I use
Color.grey (or any other pre-generated color)
It also disappears when not selecting a custom background color at all.
So is there an issue with revalidate() and repaint() hidden anywhere in this? Is the use of RGBA a problem, specifically the A (= opacity) part since Color.AnyColor works?
Is the use of RGBA a problem, specifically the A (= opacity) part
Yes, Swing does not support transparent backgrounds.
Swing expects a component to be either:
opaque - which implies the component will repaint the entire background with an opaque color first before doing custom painting, or
fully transparent - in which case Swing will first paint the background of the first opaque parent component before doing custom painting.
The setOpaque(...) method is used to control the opaque property of a component.
In either case this makes sure any painting artifacts are removed and custom painting can be done properly.
If you want to use tranparency, then you need to do custom painting yourself to make sure the background is cleared.
The custom painting for the panel would be:
JPanel panel = new JPanel()
{
protected void paintComponent(Graphics g)
{
g.setColor( getBackground() );
g.fillRect(0, 0, getWidth(), getHeight());
super.paintComponent(g);
}
};
panel.setOpaque(false); // background of parent will be painted first
Similar code would be required for every component that uses transparency.
Or, you can check out Background With Transparency for custom class that can be used on any component that will do the above work for you.

panel with image background and mouse draw

How can I use a image as background in a JPanel if the paint () method is already used for other purposes? (I'm tried to draw over a image in a panel).
Here is my code to draw as a pencil, but I don´t know how to add the image as background ?
#Override
public void paint(Graphics g) {
if (x >= 0 && y >= 0) {
g.setColor(Color.BLACK);
g.fillRect(x, y, 4, 4);
}
}
Thanks Diego
Suggestions:
Don't draw in the JPanel's paint(...) method but rather use it's paintComponent(...) method. There are several reasons for this, one being that if you use the paint(...) method, then you are also responsible for drawing the JPanel's borders and child components and at risk of messing up the rendering of these guys. Also you lose Swing's automatic double buffering.
First call the parent class's super method before calling any other code in the method. This will allow the JPanel to refresh its background and do any graphics housekeeping that may need to be done.
Next draw your background image using g.drawImage(...),
Then do your pencil drawing.
Hovercraft Full Of Eels gave good advice on one direction to take. Here is another.
Display the image in a (ImageIcon in a) JLabel.
When it comes time to paint:
Call createGraphics() on the BufferedImage to gain a Graphics2D object.
paint the lines or other visual elements to the graphics instance.
dispose of the graphics instance.
Call repaint() on the label.
E.G. as seen in this answer.

Categories