简体   繁体   中英

why Java repaint() method not working?

The code below is a very simple test which involves an image. It should repaint an image whenever I send "a" to System.in and it should exit the program whenever I send "q".

The problem is that only the exit works: the method paint() is never called, and I don't why.

I checked for the call to "super.paint()", tried replacing paint(Graphics g) with paintCompoenent(Graphics g) but nothing seems to work: simply there's no call.

Is the problem involving the Scanner in main()?

The path in the program is not the same I used, and the first paint is right, so the problem shouldn't be there.

NB if it's useful, I'm using Eclipse Oxygen and Java9 SE

Thanks to all!

code paste:

public class TestImagePanel extends JPanel {

    private BufferedImage image;
    private int xpos = 0;
    private int ypos = 0;
    private String _imagePath = "//myFolder//image.png";

    public TestImagePanel() {
        try {
            image = ImageIO.read(new File(_imagePath));
        } catch (IOException ex) {}
    }

    public void paint(Graphics g) {
        super.paint(g);
        System.out.println("painting LOG");
        g.drawImage(image, this.xpos++, this.ypos++, this);
    }

    public void update(String a) {
        System.out.print("Receiving:" + a + "---" + xpos + ":" + ypos);
        if (a.equals("a"))
            repaint();
        else if (a.equals("q")) {
            System.out.println("LOGOUT");
            System.exit(0);
        }
    }

    public static void main(String[] args) {
        JFrame frame = new JFrame("test");
        TestImagePanel testimg = new TestImagePanel();
        frame.add(new TestImagePanel());
        frame.setSize(new Dimension(600, 600));
        frame.setVisible(true);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        Scanner in = new Scanner(System.in);
        while (true)
            testimg.update( in .next());
    }
}

So, there's a few mistakes...

Let's start here...

JFrame frame = new JFrame("test");
TestImagePanel testimg = new TestImagePanel();
frame.add(new TestImagePanel());

//...

Scanner in = new Scanner(System.in);
while (true)
    testimg.update( in .next());

You're creating two instances of TestImagePanel and you're only updating the instance which is not on the screen

Something like...

JFrame frame = new JFrame("test");
TestImagePanel testimg = new TestImagePanel();
frame.add(testimg);

//...

Scanner in = new Scanner(System.in);
while (true)
    testimg.update( in .next());

will help.

Next...

public void paint(Graphics g) {
    super.paint(g);
    System.out.println("painting LOG");
    g.drawImage(image, this.xpos++, this.ypos++, this);
}

Okay, you should avoid overriding paint , as a general preference it's recommend to override paintComponent instead.

Because painting can occur at any time for any number of reasons, you should never update or modify the state of the UI from within paint methods, painting is for painting the current state

So, it should be something more like...

protected void paintComponent(Graphics g) {
    super.paint(g);
    g.drawImage(image, this.xpos, this.ypos, this);
}

Okay, so, then how do we update the xpos and ypos values? In your case, the update method is probably the obvious choice....

public void update(String a) {
    xpos++;
    ypos++;
    System.out.print("Receiving:" + a + "---" + xpos + ":" + ypos);
    if (a.equals("a"))
        repaint();
    else if (a.equals("q")) {
        System.out.println("LOGOUT");
        System.exit(0);
    }
}

Now, this raises an issue. xpos and ypos are needed by the paintComponent method, this means that these values should not be updated outside the context of the Event Dispatching Thread

A simple fix might be to do something like...

public void update(String a) {
    if (!EventQueue.isDispatchThread()) {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                update(a);
            }
        });
    }
    xpos++;
    ypos++;
    System.out.print("Receiving:" + a + "---" + xpos + ":" + ypos);
    if (a.equals("a")) {
        repaint();
    } else if (a.equals("q")) {
        System.out.println("LOGOUT");
        System.exit(0);
    }
}

This ensures that the contents of the update method is executed within the context of the EDT.

This, IMHO, is kind of a mess. A better solution would be to use a SwingWorker

SwingWorker<Void, String> worker = new SwingWorker<Void, String>() {
    @Override
    protected Void doInBackground() throws Exception {
        Scanner in = new Scanner(System.in);
        while (true) {
            publish(in.next());
        }
    }

    @Override
    protected void process(List<String> chunks) {
        for (String text : chunks) {
            testimg.update(text);
        }
    }

};

This takes care of putting the updates onto the EDT for us.

This generates a solution which looks something like this...

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Scanner;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingWorker;

public class TestImagePanel extends JPanel {

    private BufferedImage image;
    private int xpos = 0;
    private int ypos = 0;
    private String _imagePath = "//myFolder//image.png";

    public TestImagePanel() {
        try {
            image = ImageIO.read(new File(_imagePath));
        } catch (IOException ex) {
        }
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        System.out.println("painting LOG");
        g.drawImage(image, this.xpos, this.ypos, this);
    }

    public void update(String a) {
        System.out.print("Receiving:" + a + "---" + xpos + ":" + ypos);
        if (a.equals("a")) {
            xpos++;
            ypos++;
            repaint();
        } else if (a.equals("q")) {
            System.out.println("LOGOUT");
            System.exit(0);
        }
    }

    public static void main(String[] args) {
        JFrame frame = new JFrame("test");
        TestImagePanel testimg = new TestImagePanel();
        frame.add(new TestImagePanel());
        frame.setSize(new Dimension(600, 600));
        frame.setVisible(true);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        SwingWorker<Void, String> worker = new SwingWorker<Void, String>() {
            @Override
            protected Void doInBackground() throws Exception {
                Scanner in = new Scanner(System.in);
                while (true) {
                    publish(in.next());
                }
            }

            @Override
            protected void process(List<String> chunks) {
                for (String text : chunks) {
                    testimg.update(text);
                }
            }

        };

    }
}

Now, the question is, why are you taking input from the console in a GUI program? You should be inputing data via the GUI? The above might be a good solution for reading content from a file or other automated source, but should be avoid for user input ... this isn't how GUIs are suppose to work.

First, you should not override the paint() method; you shoul;d override paintComponent() instead.

Second, you are not doing this on the EventDispatchThread. And even if you were, putting the updates in a loop will block the event dispatch thread until the loop ends, resulting in only one final repaint.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM