When multiple JFrames are queued for repaint, the Swing Repaint Event paints the contents of all frames onto all frames, as though it is sharing the JPanels with the other frames.
In the example below, 'Frame 1' should only paint the square, and 'Frame 2' should only paint the oval. Instead, their graphics are somehow overlapping. The JPanels are not shared in any way, shape or form, so this should be impossible.
What I want to see is this:
For some reason, this only happens when the frames are being drawn in a loop (e.g. at 60 FPS).
I'm running Java 1.8.0_25 on Windows 10 x64.
I've been at it for hours now, and I couldn't find anything online about this... Does anyone know of a way to avoid this issue? Or if there is a work-around?
Here's the code:
JFrame frame1 = new JFrame("Frame 1");
frame1.setSize(256, 256);
frame1.setLocation(0, 0);
JPanel panel1 = new JPanel() {
public void paintComponent(Graphics g) {
g.fillRect(32, 32, 32, 32);
}
};
frame1.add(panel1);
frame1.setVisible(true);
JFrame frame2 = new JFrame("Frame 2");
frame2.setSize(256, 256);
frame2.setLocation(300, 0);
JPanel panel2 = new JPanel() {
public void paintComponent(Graphics g) {
g.fillOval(66, 32, 32, 32);
}
};
frame2.add(panel2);
frame2.setVisible(true);
while(true) {
Thread.sleep(16);
panel1.repaint();
panel2.repaint();
}
Solution (thanks MadProgrammer):
I did not add super.paintComponent(g) right after overriding the paintComponent() method. This fixed the issue. Unfortunately this also erases the previous graphics contents of the JPanel.
Related
I have a custom JLayeredPane, and I am repainting it in my game loop. There are two custom JPanels added into the JLayeredPane. These are foreground and background JPanels. How do I successfully only draw my background JPanel once, (And repaint when window is re-sized or any other reason) to reduce impact on system resources, while continuing to update my foreground JPanel constantly.
To re-iterate, I dont want to constantly repaint the background JPanel in a loop. I want to repaint it only when it is nessessary, as the background does not change. and is large.
In my attempt to do this, I have only drawn the background once. However. the background JPanel is simply not visible. while the foreground JPanel updates as normal. It is almost as if the foreground JPanel paints ontop of the background JPanel, even though I have both of the JPanels set to setOpaque(false)
I have made a mvce which shows my attempt at only drawing the background JPanel once, while updating the foreground JPanel constantly.
The problem with my code is that the background JPanel does not show.
Now. I know that if I were to draw it constantly it would show. But that defeats the purpose of what i'm trying to do. I am trying to only draw it once, and have be seen at the same time
My code successfully only draws the background JPanel once. The problem is that the background JPanel does not show. How do I fix THIS problem
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class Main extends JLayeredPane {
static JFrame frame;
static Main main;
static Dimension screenSize;
public Main() {
JPanel backPanel = new BackPanel();
JPanel frontPanel = new FrontPanel();
add(backPanel, new Integer(7));
add(frontPanel, new Integer(8));
new Thread(() -> {
while (true){
repaint();
}
}).start();
}
public static void main(String[] args) {
screenSize = Toolkit.getDefaultToolkit().getScreenSize();
frame = new JFrame("Game"); // Just use the constructor
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
main = new Main();
frame.add(main, BorderLayout.CENTER);
frame.pack();
frame.setSize(screenSize);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public class BackPanel extends JPanel{
public boolean drawn = false;
public BackPanel(){
setVisible(true);
setOpaque(false);
setSize(screenSize);
JLabel test1 = new JLabel("Test1");
JLabel test2 = new JLabel("Test2");
add(test1);
add(test2);
}
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
drawOnce(g);
}
public void drawOnce(Graphics g){
if (!drawn){
g.setColor(Color.red);
g.fillRect(0, 0, screenSize.width, 200);
drawn=true;
}
}
}
public class FrontPanel extends JPanel{
public FrontPanel(){
setVisible(true);
setOpaque(false);
setSize(screenSize);
JLabel test = new JLabel("Test");
add(test);
}
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
g.setColor(Color.blue);
g.fillRect(0+screenSize.width/2, 0, screenSize.width/4, 300);
}
}
}
Try RepaintManager.currentManager(component).markCompletelyClean(component). It will prevent the component from repainting. You might need to do this after each time you add new components.
http://docs.oracle.com/javase/6/docs/api/javax/swing/RepaintManager.html#markCompletelyClean%28javax.swing.JComponent%29
I don't know if this two lines of code
super.paintComponent(g);
drawOnce(g);
are the root of problem, I sincerly don't remember how paintComponent works (a test could help) but try to swap them :
drawOnce(g);
super.paintComponent(g);
maybe, on your original version, you tells JVM to paint the whole component and, only after the AWTEvent has been added to the queue, to draw what you need.
I guess that the awt's documentation will explain it.
I am relatively new to Java Graphics. I want to draw 20 x 80 rectangle at (X,Y) coordinates in JPanel when user clicks a JButton. (where 'X' and 'Y' are coming from 2 JTextFields) .
I have read many questions and tutorial, but could not solve a problem. In some cases, I can draw rectangle but cannot draw new rectangle without emptying JPanel.
Here is my code :
public class CustomPanel extends JPanel {
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g); // first draw a clear/empty panel
g.draw3DRect(Integer.parseInt(x.getText()),Integer.parseInt(y.getText()), 20, 80, true);
// then draw using your custom logic.
}
}
/**
* Initialize the contents of the frame.
*/
private void initialize() {
//Code for frame
//Code for JTextfields x and y
JButton btnDraw = new JButton("Draw");
btnDraw.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
panel= new CustomPanel();
panel.setBounds(406, 59, 407, 297);
frame.getContentPane().add(panel);
frame.revalidate();
}
});
btnDraw.setBounds(286, 339, 89, 23);
frame.getContentPane().add(btnDraw);
}
You ActionListener code is wrong. You don't want to create a new panel, you want to add a Rectangle to the existing panel.
When you create the GUI you should add two panels to the GUI:
The first panel will be an empty panel that will do your custom painting. You would generally add this to the CENTER of the frame
The second panel will contain the "Draw" button. You would generally add this panel to the PAGE_END. Then when you click the Draw button you invoke a method like addRectangle(...) in your custom painting panel so the panel can paint the Rectangle.
Check out Custom Painting Approaches for the two common ways to do custom painting:
Keep a List of Object to paint and then in the paintComponent() method you iterate the LIst an paint each object.
Create a BufferedImage and then just paint the Rectangle onto the BufferedImage, then you can just paint the BufferedImage either in a JLabel or in your paintComponent() method.
When spending a few minutes tweaking my desktop clock, I discovered a problem that I seem unable to resolve without help... I read some posts with similar problem but solutions did not work for me.
The clock (in typical java form with an Action Listener and Calendar) works just fine. The intended tweak: To set the Frame, ContentPane and Label backgrounds to transparent so only the time/text shows.
What happens is this: When the label background is transparent (or until it's opaque enough by setting the Alpha when Opaque is true), the underlying previous display stay's and does not clear.
To help figure this out, I put together the following code - the time and date Calendar stuff/etc is excluded. This code is just one version of many I tried with/without opaque, placement of calls...etc.
What does make a difference is use of the Action Listener - if the Action Listener is commented/deleted, label's display fine. Un-comment the Action Listener and the problem occurs.
See the images… Any help appreciated… Thanks!
fyi - below: the code sans imports and comments...
Screenshot of clock with black bg
Screenshot of the problem:
public class Clear extends JFrame {
private JPanel contentPane;
Color ppColor = new Color(255, 255, 0, 0); // r,g,b,a
Color lblColor = new Color(225, 200, 200, 0);
Color lbl2Color = new Color(225, 200, 200, 254);
int delay = 1000;
JLabel lblTime = new JLabel("TESTING");
JLabel lblTime2 = new JLabel("XXXXXX");
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
final Clear frame = new Clear();
frame.setVisible(true);
}
catch (Exception e) {
e.printStackTrace();
}
}
});
}
public Clear() {
setUndecorated(true);
setBackground(ppColor);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(1680, 975, 128, 74);
contentPane = new JPanel();
contentPane.setBackground(ppColor);
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
setContentPane(contentPane);
contentPane.setLayout(null);
lblTime.setOpaque(true);
lblTime.setBackground(lblColor);
lblTime.setBounds(0, 0, 125, 30);
contentPane.add(lblTime);
lblTime2.setOpaque(true);
lblTime2.setBackground(lbl2Color);
lblTime2.setBounds(0, 33, 125, 16);
contentPane.add(lblTime2);
ActionListener myTaskPerformer = new ActionListener() {
#Override
public void actionPerformed(ActionEvent evt) {
lblTime.setText("Does it");
lblTime2.setText("work? ");
}
};
new Timer(delay, myTaskPerformer).start();
}
}
Swing components do not work well with alpha based colors. They are either completely transparent or completely opaque.
If you specifiy that a component is isOpaque, but fill it using a translucent (alpha) color, the repaint manager won't update the area behind the component and the Graphics context will not be cleared properly. Remember, the Graphics context is shared resource, so everything that was painted before your component will still be painted
You can take a look at Java Swing Graphical Glitches Dealing with Transparency and Images for more details.
However. The simplest solution would be to create a TranslucentPane, the extends from something like JPanel, make it transparent (not opaque), override it's paintComponent method and paint the translucent (alpha) color from within it. Then add your label onto this.
Check out Performing Custom Painting and Painting in AWT and Swing for more details
I have searched (I think) thoroughly for an answer to my problem. I'm a beginner so I may just not know what to look for. I am trying to make an overview of an office layout (tables, chairs), which I coded using Graphics2D and GeneralPath, and JLabels with names by each chair.
If this has already been answered I apologize but I did look.
(Note: the graphics are super simple for now: table is just a square and chairs are just lines.)
public class DemoReception extends JApplet{
#Override
public void paint(Graphics g){
//draws table
Graphics2D g2 = (Graphics2D) g;
g2.setStroke(new BasicStroke(4.0f));
g2.setColor(Color.BLACK);
int[] xPoints={150,700,700,150};
int[] yPoints={250,250,550,550};
GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD,xPoints.length);
path.moveTo(xPoints[0], yPoints[0]);
for (int i = 0; i < xPoints.length; i++) {
path.lineTo(xPoints[i], yPoints[i]);
}
path.closePath();
g2.draw(path);
//draws chairs
g2.setColor(Color.RED);
path = new GeneralPath(GeneralPath.WIND_NON_ZERO);
path.moveTo(260,240);//Person1
path.lineTo(310,240);
path.moveTo(510,240);//Person2
path.lineTo(560,240);
path.moveTo(260,560);//Person3
path.lineTo(310,560);
path.moveTo(510,560);//Person4
path.lineTo(560,560);
path.closePath();
g2.draw(path);
}
And here is the main method:
public static void main(String[] args) {
int labelwidth = 50;
int labelheight = 10;
JFrame testFrame = new JFrame("Test Layout");
testFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JApplet demo = new DemoReception();
testFrame.setBackground(Color.white);
testFrame.getContentPane().add(demo);
testFrame.pack();
testFrame.setMinimumSize(new Dimension(1000,710));
testFrame.setSize(new Dimension(1000,710));
JPanel testPanel = new JPanel();
testPanel.setAlignmentX(0);
testPanel.setAlignmentY(0);
label1 = new JLabel("Person1");
label2 = new JLabel("Person2");
label3 = new JLabel("Person3");
label4 = new JLabel("Person4");
label1.setAlignmentX(260);
label1.setAlignmentY(235);
label1.setSize(labelwidth, labelheight);
label1.setVisible(true);
testPanel.add(label1);
label2.setAlignmentX(510);
label2.setAlignmentY(235);
label2.setSize(labelwidth, labelwidth);
label2.setVisible(true);
testPanel.add(label2);
label3.setAlignmentX(260);
label3.setAlignmentY(565);
label3.setSize(labelwidth, labelwidth);
label3.setVisible(true);
testPanel.add(label3);
label4.setAlignmentX(510);
label4.setAlignmentY(565);
label4.setSize(labelwidth, labelwidth);
label4.setVisible(true);
testPanel.add(label4);
testFrame.getContentPane().add(testPanel);
testFrame.setVisible(true);
}
When I run it all I get is the JFrame with the graphics but the JLabels don't show up.
Any help would be appreciated.
The JLabel does not appear in the JApplet as JFrame#pack is called before all labels have been added. The result is that those components are not validated so dont appear
The solution is to invoke the method before calling setVisible
testFrame.pack();
testFrame.setVisible(true);
However further changes are necessary as the applet window itself will not appear when this is done. This is because the statement
testFrame.getContentPane().add(testPanel);
will cause the JPanel testPanel to be displaced as implemented in the earlier statement
testFrame.getContentPane().add(demo);
BorderLayout can only contain one component at the CENTER location.
To fix, remove the testPanel and add the JLabel components directly to the JApplet demo instead.
Also add
super.paint(g);
to the paint method to ensure that the JLabels are painted by Swing.
Of course paint should never be used for custom painting in Swing. Rather use paintComponent
As a future exercise, make sure to replace the paint functionality by using a JComponent based class instead and overriding paintComponent. Remember to invoke super.paintComponent(g). Follow the steps outlined in Performing Custom Painting
I'm working on a simple painting application in Java but Swing doesn't want to cooperate. At first I tried making a JTabbedPane that holds the DrawArea and a settings panel, but for some bizarre reason the second mouseDragged() (and by extension the drawArea.repaint() method) was triggered, the JTabbedPane seemed to duplicate. Imagine two identical tab panes stacked vertically. I thought this was just some bug in JTabbedPane, but I rewrote everything to a very simple custom menu using JButtons on a panel and the exact same thing happened. It's not like the GUI actually duplicated the top area; it's unusable and I can paint over it. Check it out:
Edit: I found a similar question. If I call super.paintComponent(g), the problem goes away (and drawArea.setBackground(color) actually works!) but the function called on super (a new keyword for me) basically repaints the drawArea so the paint trail is no longer saved.
If I paint a rectangle to fill the drawArea it overwrites the issue even though mouseDragged is still fired. Here's the rendering code:
#Override
public void mouseDragged(MouseEvent e) {
x = e.getX(); y = e.getY();
drawArea.repaint();
}
// (subclass):
class DrawArea extends JPanel {
public void paintComponent(Graphics g) {
g.setColor(Color.WHITE);
g.fillOval(x-3, y-3, 7, 7);
}
}
And here's the GUI code:
frame = new JFrame("Jadra");
frame.setSize(650, 600);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
menu = new JPanel();
settingsButton = new JButton("Settings");
clearButton = new JButton("Clear");
exitButton = new JButton("Quit");
menu.setLayout(new FlowLayout());
menu.add(settingsButton);
menu.add(clearButton);
menu.add(exitButton);
menu.setBackground(new Color(30, 90, 60));
drawArea = new DrawArea();
drawArea.setBackground(Color.red);
drawArea.setOpaque(true);
drawArea.addMouseMotionListener(this);
frame.getContentPane().add(menu, BorderLayout.NORTH);
frame.getContentPane().add(drawArea, BorderLayout.CENTER);
Thread pt = new Thread(new Painter());
frame.setVisible(true);
pt.start();
Please tell me I did something really stupid. Otherwise this looks like a really annoying bug. I really appreciate your help. Thanks!
Your DrawPanel.paintComponent() method should chain upward to the method it overrides from JPanel to allow default painting to occur. Simply add
super.paintComponent(g);
As the first line to this method.