Search code examples
javaswingjframeawtjoptionpane

Why is this not drawing consistently?


I wrote a quick test program that takes a string input and draws it on a JFrame. Super simple. But for some reason, the program isn't drawing consistently. As in, it doesn't always draw the string to the screen once the input is taken. To try and debug, I added a repaint(); to paintComponent (which repeatedly asked for an input to draw, hence why I added the double buffer). When the program decided not to draw to the frame, it didn't make it to repaint(); and a new input dialog didn't show up. I'm having trouble understanding why.

package test;
import java.awt.*;
import javax.swing.JFrame;
import javax.swing.JOptionPane;

public class Main extends JFrame {

private Image dbImage;
private Graphics dbg;

public Main() {
    setTitle("Test Project");
    setSize(300, 300);
    setVisible(true);
    setResizable(false);
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

}

public void paint(Graphics g) {
    dbImage = createImage(getWidth(), getHeight());
    dbg = dbImage.getGraphics();
    paintComponent(dbg);
    g.drawImage(dbImage, 0, 0, this);
}

public void paintComponent(Graphics g) {
    g.drawString(input(), 140, 130);
}

public static String input() {
    String x = ((String)JOptionPane.showInputDialog (
        null, "Enter a string", "Test Project",
        JOptionPane.QUESTION_MESSAGE,
        null, null, null));
    return x;
}

public static void main(String[] args) {
    Main main = new Main();

}
}

One note: initially, the input method was in a separate class, because that's where it will be in the project I'm testing this for. I moved it here for sake of brevity and because the problem still popped up no matter where the method was.


Solution

  • Take theory from my comment :-)

    Adding more points to my comments :

    • Instead of using setSize() on the JFrame, instead override getPreferredSize() method, for any JComponent/JPanel you extending. Now simply call pack() on the JFrame, for it to calculate it's own size.
    • Please read about Concurrency in Swing

    Here is working example, of what I am saying :

    import java.awt.*;
    import java.awt.event.*;
    import javax.swing.*;
    
    public class Main extends JPanel {
    
        private JButton button; 
        private String message;
    
        private ActionListener buttonAction = new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent ae) {
                message = input();
                repaint();
            }
        };
    
        public Main() {
            message = "Nothing to display yet...";
        }
    
        private void displayGUI() {
            JFrame frame = new JFrame("Painting Example");
            frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
    
            JPanel contentPane = new JPanel(new BorderLayout(5, 5));
            button = new JButton("Get Message");
            button.addActionListener(buttonAction);
    
            contentPane.add(this, BorderLayout.CENTER);
            contentPane.add(button, BorderLayout.PAGE_END);
    
            frame.setContentPane(contentPane);
            frame.pack();
            frame.setLocationByPlatform(true);
            frame.setVisible(true);
        }
    
        @Override
        public Dimension getPreferredSize() {
            return (new Dimension(200, 200));
        }
    
        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            g.drawString(message, 50, 50);
        }
    
        public String input() {
            String x = ((String)JOptionPane.showInputDialog (
                    null, "Enter a string", "Test Project",
                                JOptionPane.QUESTION_MESSAGE,
                                            null, null, null));
            return x;
        }
    
        public static void main(String[] args) {
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    new Main().displayGUI();
                }
            };
            EventQueue.invokeLater(runnable);
        }
    }
    

    EDIT 1 :

    If you do not like the use of JButton for asking input, you can add a MouseListener to the JPanel on which you drawing, as shown in the below example :

    import java.awt.*;
    import java.awt.event.*;
    import javax.swing.*;
    
    public class Main extends JPanel {
    
        private String message;
        private int x;
        private int y;
    
        private MouseAdapter mouseAction = new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent me) {
                message = input();
                x = me.getX();
                y = me.getY();
                repaint();
            }
        };
    
        public Main() {
            message = "Nothing to display yet...";
            x = y = 0;
        }
    
        private void displayGUI() {
            JFrame frame = new JFrame("Painting Example");
            frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
    
            JPanel contentPane = new JPanel(new BorderLayout(5, 5));
    
            addMouseListener(mouseAction);
            contentPane.add(this, BorderLayout.CENTER);
    
            frame.setContentPane(contentPane);
            frame.pack();
            frame.setLocationByPlatform(true);
            frame.setVisible(true);
        }
    
        @Override
        public Dimension getPreferredSize() {
            return (new Dimension(200, 200));
        }
    
        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            g.drawString(message, x, y);
        }
    
        public String input() {
            String x = ((String)JOptionPane.showInputDialog (
                    null, "Enter a string", "Test Project",
                                JOptionPane.QUESTION_MESSAGE,
                                            null, null, null));
            return x;
        }
    
        public static void main(String[] args) {
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    new Main().displayGUI();
                }
            };
            EventQueue.invokeLater(runnable);
        }
    }