Why does super.repaint() not call paint method? - java

SOLUTION: I needet to add the JPanel into a JFrame. The calling of the paint method is bound to the visibility of the JFrame. thanks #matt
This is my first question so I am sorry if something is missing.
My Problem:
I want to create a class which extends JPanel so that I can draw things onto my JFrame. I also want it to be able to repaint itself. So far the run method works but super.repaint() does not. "test1" is being printed and "test2" not.
Thanks in advance :)
public class Main {
public static GUI gui;
public static void main(String[] args) {
gui = new GUI();
}
}
public class GUI extends JFrame{
public Draw draw;
public Thread drawT;
public GUI() {
draw = new Draw();
drawT = new Thread(draw);
drawT.start();
}
}
public class Draw extends JPanel implements Runnable{
#Override
public void run() {
while (true) {
System.out.println("test1");
super.repaint();
}
}
#Override
public void paint(Graphics graphics) {
System.out.println("test2");
Graphics2D g = (Graphics2D)graphics;
}
}

repaint schedules you component to be painted. If the component is not visible then paint will not get called.
A simplified way to look at it. Swing/AWT has the EventDispatchThread which continuously runs, processes events and paints components. When you call "repaint" it tells swing to schedule a paint event for your component. It doesn't call paint. When the EDT gets around to it, it will paint your component.
In your 'run' method you have a loop that prints out a statement to a terminal, "test1" and then it calls repaint(), and then it repeats. The slowest thing here is the System.out.println.
You have a really fast loop swamping AWT with repaints requests but that loop will run faster than the EDT loop will run paint events. So maybe you print test1 and call repaint 100 times before the EDT actually performs a single paint. I suspect if you look close, test2 is buried in your output somewhere.
import java.awt.*;
public class MainS{
int paints = 0;
int tags = 0;
public void runGui(){
JFrame frame = new JFrame("testing");
JPanel panel = new JPanel(){
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
paints++;
}
};
frame.add(panel, BorderLayout.CENTER);
JLabel label = new JLabel("times painted ...");
frame.add(label, BorderLayout.NORTH);
frame.setSize(200, 400);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Timer t = new Timer( 1000, evt->{
label.setText(paints + "/" + tags);
paints = 0;
tags = 0;
});
t.start();
new Thread( ()->{
while(true){
tags++;
panel.repaint();
}
}).start();
}
public static void main(String[] args){
EventQueue.invokeLater( new MainS()::runGui );
}
}
Here is a simple example, where the component actually gets repainted. It will display the number of times paintComponent was called and the number of times repaint was called at the top of the frame.

Related

Why does my Java repaint() method not work?

Heyo, for my college homework I need to draw some images and race them, but I am stuck even at drawing the images (or in this case, icons). I want to draw ANYTHING (that's why I have the drawLine method, just to test it out) as of right now on one of many JPanels, but my repaint() method does not call my paintComponent method, why?
import java.awt.*;
import javax.swing.*;
import java.lang.Math;
public class Races{
private int numberOfRacers;
public Races(int numberOfRacers){
this.numberOfRacers = numberOfRacers;
JFrame frame = new JFrame("Races - Name Surname");
Icon icon = new ImageIcon("races.jpg");
frame.setLayout(new GridLayout(numberOfRacers, 1));
frame.setSize(icon.getIconWidth()*20, (icon.getIconWidth()*2)*numberOfRacers);
frame.setVisible(true);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
try{
Thread.sleep(1000); // Sleeping 1 sec
System.out.println("Sleeping one second for the user!");
}catch(InterruptedException ie){
System.out.println(ie);
}
for(int i = 0; i < numberOfRacers; i++){
innerRacer racer = new innerRacer();
frame.add(racer.panel);
//JLabel iconLabel = new JLabel(icon);
//iconLabel.setHorizontalAlignment(JLabel.LEFT);
//panel.add(iconLabel);
Thread t = new Thread(racer);
t.start();
}
}
public static void main(String args[]){
if(args.length > 0 && Integer.parseInt(args[0]) > 0 && Integer.parseInt(args[0]) < 100){
Races races = new Races(Integer.parseInt(args[0])); // From command line number of racers
System.out.println("Number of racers: " + args[0]);
}else{
Races races = new Races(5); // Default number of racers
System.out.println("Number of racers: 5");
}
}
public class innerRacer extends JPanel implements Runnable{
JPanel panel;
Icon icon;
public innerRacer(){
panel = new JPanel();
panel.setBackground(Color.WHITE);
icon = new ImageIcon("races.jpg");
} //end of innerRacer constructor
#Override
public void run(){
repaint();
System.out.println("TEST");
}
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
g.drawLine(10, 20, 30, 40);
System.out.println("Why is this one not called?");
icon.paintIcon(panel, g, 0, 0);
}
} // end of innerRacer class
} //end of Races class
Thanks in advance.
You've added the JPanel that innerRacer contains rather than the JPanel that innerRacer is.
Change:
frame.add(racer.panel);
to:
frame.add(racer);
Also I see you are adding components to the frame after setting it to visible. When you do this in AWT/Swing they wont get automatically laid out or painted. You will need to follow with the line:
frame.revalidate();
Alternatively, the setVisible line could be moved down.
As general notes:
It's a good idea to stick to Java naming conventions - always initial caps for types.
JPanel panel; in innerRacer can be deleted.
Swing components should always be accessed from the AWT Event Dispatch Thread (EDT). Use java.awt.EventQueue.invokeLater.
Lots of tutorials suggest overriding JPanel without adding any components if you want to paint anything. This appears to be because it is opaque by default. Neither the API docs for JPanel nor even the OpenJDK source code mention opaqueness. Indeed, it isn't guaranteed. It's just a hack. Prefer setOpaque(true) to make a component opaque.
Having a class extend another and implement an interface (or implement multiple interfaces (not markers, Comparable, etc)) isn't great. Use a lambda method, method reference, inner class or just another outer class as appropriate.

Why doesn't repaint() work when called by a method of an object of the same class?

In a program I'm building for my class I have the same class extending a Swing JPanel and implementing MouseListener, for which I use two instantiations - one to function as a JPanel, and the other as a mouse listener for that JPanel.
But when I click in the window, repaint() the MouseClicked method in the mouse listener fails to call the first object's paintComponent() method. For example:
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class TestPanel extends JPanel implements MouseListener{
static boolean black;
static TestPanel test = new TestPanel();
public void mouseExited(MouseEvent e){}
public void mousePressed(MouseEvent e){}
public void mouseReleased(MouseEvent e){}
public void mouseEntered(MouseEvent e){}
public void mouseClicked(MouseEvent e){ //Expected behavior: the square turns black immediately
System.out.println("CLICK!");
black = true;
test.repaint(); //this fails
try{
Thread.sleep(3000);
}catch(Exception ex){}
}
public void paintComponent(Graphics g){
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
System.out.println("Painting...");
g2d.setColor(Color.white);
if(black){
g2d.setColor(Color.black);
}
g2d.fillRect(0, 0, 200, 200);
}
public static void main(String[] args) throws InterruptedException{
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
test.addMouseListener(new TestPanel());
test.setPreferredSize(new Dimension(200, 200));
frame.add(test);
frame.pack();
frame.setVisible(true);
while (true){
black = false;
test.repaint();
Thread.sleep(100);
}
}
}
If you watch what happens on a click, the screen stays white for the 3 seconds after the click is registered, until the loop starts up again, i.e., the repaint() call in the mouse listener didn't work. Why does this happen?
I'm guessing it would work if I made different classes for the objects, but I'm mostly curious as to why it doesn't work this way.
for which I use two instantiations - one to function as a JPanel, and the other as a mouse listener for that JPanel.
There is no need to do that. All you need is a single instance of the TestPanel class.
In the constructor of your TestPanel class you just add:
addMouseListener( this);
The get rid of the static variable for the TestPanel class.
Then the code in your main method should look something like:
//test.addMouseListener(new TestPanel());
//test.setPreferredSize(new Dimension(200, 200));
//frame.add(test);
frame.add( new TestPanel() );
Also, the TestPanel class should override the getPreferredSize() method to return the Dimension of your panel.
Read the section from the Swing tutorial on Custom Painting for a working example with a MouseListener.
The AWT thread is responsible for calling MouseListener and for repaint.
Inside the repaint(); method, the AWT thread is told to call the paint();
Just call it using a different thread. In general, it is a bad idea to do anything intensive with the AWT thread. It already does a lot, taking too much of its time will mess your GUI up.
Depending on your needs, this might work:
new Thread(()->{repaint();}).start();

How to successfully draw background JPanel once and update foreground JPanel constantly?

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.

Painting Grahpics2D Object on Frame with GridBagLayout

I am trying to draw a coloured bar, which gets bigger as time goes on. It works when I use the default layoutmanager, but when im trying to implement this with GridBagLayout it wont. I wrote a test project just for testing purposes to investigate what the problem is. I added a few buttons just to have something else apart from the graphics2D object, so it kinda looks like my actual project im working on. But after a couple of days, i have to admit that i dont have a clue, what is going wrong. I hope someone can help me!
Just in advance, dont get confused by some words i used in the code. my mother tongue isn't english.
import java.awt.*;
import javax.swing.*;
import java.util.TimerTask;
import java.util.Timer;
public class FarbBalkenTest extends JPanel {
static Timer timer = new Timer ();
static TimerTask task ;
static int time;
public static void main(String[] args) {
FarbBalkenTest fbt = new FarbBalkenTest();
fbt.init();
}
public static void addComponent(Container cont, GridBagLayout gbl, Component c, int x, int y, int width, int height, double weightx, double weighty ){
GridBagConstraints gbc = new GridBagConstraints();
gbc.fill = GridBagConstraints.BOTH;
gbc.gridx=x;
gbc.gridy=y;
gbc.gridwidth=width;gbc.gridheight=height;
gbc.weightx=weightx;gbc.weighty=weighty;
gbl.setConstraints(c,gbc);
cont.add(c);
}
public void init (){
final JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().setBackground(new Color(230,230,230));
Container c = frame.getContentPane();
GridBagLayout gbl = new GridBagLayout();
c.setLayout(gbl);
ZeichenTest z_test = new ZeichenTest();
addComponent(c,gbl, new Button("top left"), 0,0,1,1,1.0,1.0);
addComponent(c,gbl, new Button("top right"), 2,0, 1,1,1.0,1.0);
addComponent(c, gbl,z_test, 1,1,1,1,1.0,1.0);
addComponent(c,gbl, new Button("down left"),0,2,1,1,1.0,1.0);
addComponent(c,gbl, new Button("down right"),2,2,1,1,1.0,1.0);
frame.setSize(500,500);
frame.setVisible(true);
task = new TimerTask(){
public void run(){
frame.repaint();
time+= 20;
System.out.println(time);
}
};
timer.scheduleAtFixedRate(task, 0, 1000);
}
public class ZeichenTest extends JComponent {
public void paintComponent (Graphics g){
Graphics2D g2d = (Graphics2D) g;
Color startgreen = new Color(50,205,50);
Color endred = new Color (255, 97, 3);
GradientPaint startend = new GradientPaint(0,25 , startgreen, 400, 25 , endred );
g2d.setPaint (startend);
g2d.fillRect(50, 200 , time, 50);
}
}
}
"It works when I use the default layoutmanager, but when im trying to implement this with GridBagLayout it wont."
GBL respects preferred sizes. Your ZeichenTest has none. You need to explicitly set it by overriding getPreferredSize(). (The default layout of JFrame which is BorderLayout doesn't respect preferred sizes, and will stretch you panel to fit)
public class ZeichenTest extends JComponent {
...
#Override
public Dimension getPreferredSize() {
return new Dimension( ... , ... );
}
}
Also note, after setting the preferred size of the component, just call pack() on the frame, instead of setSize(). The pack() will "fit" everything according to the preferred sizes. You can actually test that your component currently has no preferred size by calling pack() instead of setSize() and you will see the frame shrink on launching. But if you override the getPreferredSize() of the component and call pack, the frame will be size according the new preferred size.
Other Important notes:
Use java.swing.Timer instead of TimerTask. repaints should be done on the EDT and Swing Timer handles this for you. See more at How to Use Swing Timer
I think you mean to repaint() the instance of ZeichenTest and not the frame. It makes a difference.
Always call super.paintComponent in your paintComponent method, as to not leave nasty paint atrifacts
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
}
Swing apps should always be run on the Event Dispatch Thread (EDT). See Initial Threads. Basically, in this case, just wrap the main code in a SwingUtilities.invokeLater(...)
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable(){
public void run() {
FarbBalkenTest fbt = new FarbBalkenTest();
fbt.init();
}
});
}

Time Delay using Thread.sleep() for paintComponent(Graphics g) not working as expected

I am making an Animated ProgressBar, in which i used multiple fillRect() method of class javax.swing.Graphics.
To put a delay after each rectangle is painted, I am using Thread.sleep(500) method for making a delay, (Suggested by many Forums, for making a delay).
The Problem is, instead of making a delay of 0.5sec after each Rectangle box is displayed, it takes the whole delay required by all the rectangles, in the start, and then displays the final image, thats the Progress Bar.
Question 1
TO make a delay for every single bar, i put the delay "Thread.sleep(500)" along with the bars "fillRect()" in a single for() loop, i would like to know, Why does it takes all the delay in the beginning and then dislplays the completed ProgressBar.
Question 2
How can i change my code, so that the delay can occur simultaneously with each rectangle bar, so when i run the program it should generate an Animated Progress Bar.
Code:
import javax.swing.JOptionPane;
import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.JPanel;
import java.awt.Color;
class DrawPanel extends JPanel
{
public paintComponent(Graphics g)
{
super.paintComponent(g);
g.setColor(new Color(71,12,3));
g.fillRect(35,30,410,90);
for ( int i=1; i<=40; i+=2)
{
Color c = new Color (12*i/2,8*i/2,2*i/2);
g.setColor(c);
g.fillRect( 30+10*i,35,20,80);
try
{ Thread.sleep(500); }
catch(InterruptedException ex)
{ Thread.currentThread().interrupt(); }
}
}
}
class ProgressBar
{
public static void main (String []args)
{
DrawPanel panel = new DrawPanel();
JFrame app = new JFrame();
app.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
app.add(panel);
app.setSize(500,200);
app.setVisible(true);
}
}
Your help is highly appreciated, Thankyou.
Don't block the EDT (Event Dispatch Thread) - the GUI will 'freeze' when that happens. Instead of calling Thread.sleep(n) implement a Swing Timer for repeated tasks. See Concurrency in Swing for more details. Also be sure to check the progress bar tutorial linked by #Brian. It contains working examples.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
class DrawPanel extends JPanel
{
int i = 0;
public DrawPanel() {
ActionListener animate = new ActionListener() {
public void actionPerformed(ActionEvent ae) {
repaint();
}
};
Timer timer = new Timer(50,animate);
timer.start();
}
public void paintComponent(Graphics g)
{
super.paintComponent(g);
g.setColor(new Color(71,12,3));
g.fillRect(35,30,410,90);
Color c = new Color (12*i/2,8*i/2,2*i/2);
g.setColor(c);
g.fillRect( 30+10*i,35,20,80);
i+=2;
if (i>40) i = 0;
}
}
class ProgressBar
{
public static void main (String []args)
{
DrawPanel panel = new DrawPanel();
JFrame app = new JFrame();
app.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
app.add(panel);
app.setSize(500,200);
app.setVisible(true);
}
}
I really wouldn't do this. The Swing refresh thread isn't supposed to be used like this. You're much better off using another thread (perhaps using a TimerTask) and redrawing rectangles upon demand.
Check out the Oracle ProgressBar tutorial for more info, code etc.

Categories

Resources