【问题标题】:Why is this not drawing consistently?为什么这不是一致的绘制?
【发布时间】:2013-08-30 17:32:22
【问题描述】:

我编写了一个快速测试程序,它接受字符串输入并将其绘制在 JFrame 上。超级简单。但是由于某种原因,该程序的绘制不一致。例如,一旦输入,它并不总是将字符串绘制到屏幕上。为了尝试调试,我添加了一个 repaint();到paintComponent(它反复要求输入绘制,因此我添加了双缓冲区)。当程序决定不绘制到框架时,它没有让它 repaint();并且没有出现新的输入对话框。我很难理解为什么。

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();

}
}

请注意:最初,输入法位于一个单独的类中,因为我正在测试它的项目中将位于该类中。为了简洁起见,我把它移到了这里,因为无论方法在哪里,问题仍然会弹出。

【问题讨论】:

  • 使用JPanel进行绘图,默认为Double Buffered,并覆盖它的paintComponent()方法,而不是在JFrame上绘图:-)此外,请不要尝试过分聪明,在其他地方更改状态并从该位置调用repaint(),而不是从paintComponent() 方法中的asking for/changing 状态:-) 换句话说,尝试在某处获取String 值否则,立即调用repaint() 使新状态可见。通过调用模态 JOptionPane... 而不是阻塞绘制过程
  • 我只有一个问题,paint() 的文档使它看起来像是必须从paintComponent、paintBorder 或paintChildren 中调用repaint。公平地说,可能很重要的是要提到我是 Swing 和 awt 的新手,而且我对 Java 还很陌生。我只是想弄清楚什么是有效的,什么是愚蠢的。
  • 请参阅有关Solving Common Painting Problems 的Java 教程,第一个问题及其解决方案将以最恰当的方式回答您的问题 :-) 不,repaint() 不会被调用在paintComponent() 内部,而不是paint() 本身调用paintComponent() 以及paintBorder() and paintChildren() 方法。在paintComponent() 中添加repaint() 将导致递归行为。

标签: java swing jframe awt joptionpane


【解决方案1】:

从我的评论中获取理论:-)

为我的 cmets 添加更多积分:

  • 不要在JFrame 上使用setSize(),而是覆盖getPreferredSize() 方法,用于您扩展的任何JComponent/JPanel。现在只需在JFrame 上调用pack(),让它计算自己的大小。
  • 请阅读Concurrency in Swing

这是我所说的工作示例:

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);
    }
}

编辑 1:

如果您不喜欢使用JButton 来询问输入,您可以在您绘制的JPanel 上添加MouseListener,如下例所示:

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);
    }
}

【讨论】:

  • 有时,第二个代码只会在您调整文本大小时更新文本。这只发生在字符串越过屏幕时,但该死的。然后我从文档中的解释中学到了更多。非常感谢。
  • @JaimeWissner :你可以参考@trashgod 的这篇精彩的帖子,关于getStringBounds() 的使用,这可能会给你一个小小的想法,如何限制在范围之外发送String当前窗口 :-) 对于其余的,您最欢迎并保持微笑 :-)
【解决方案2】:

你的代码有很多问题,我写了一个新程序来做你想做的事情。我确实保留了你的输入法。

我建议你花点时间研究Oracle Swing Tutorial

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.font.FontRenderContext;
import java.awt.font.TextLayout;
import java.awt.geom.Rectangle2D;

import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class SimpleInput implements Runnable {

    @Override
    public void run() {
        JFrame frame = new JFrame("String Display");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLocationByPlatform(true);

        DrawingPanel drawingPanel = new DrawingPanel();
        frame.add(drawingPanel);

        frame.pack();
        frame.setVisible(true);

        new Thread(new DisplayRunnable(drawingPanel)).start();
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new SimpleInput());
    }

    public class DrawingPanel extends JPanel {
        private static final long   serialVersionUID    = 7580353846463376668L;

        private String text;

        public DrawingPanel() {
            this.setPreferredSize(new Dimension(300, 200));
            this.text = " ";
        }

        public void setText(String text) {
            this.text = text;
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);

            Graphics2D g2d = (Graphics2D) g;
            FontRenderContext frc = g2d.getFontRenderContext();
            TextLayout layout = new TextLayout(text, g.getFont(), frc);
            Rectangle2D bounds = layout.getBounds();

            int width = (int) Math.round(bounds.getWidth());
            int height = (int) Math.round(bounds.getHeight());
            int x = (getWidth() - width) / 2;
            int y = height + (getHeight() - height) / 2;

            layout.draw(g2d, (float) x, (float) y);
        }

    }

    public class DisplayRunnable implements Runnable {

        private String displayString = " ";

        private DrawingPanel drawingPanel;

        public DisplayRunnable(DrawingPanel drawingPanel) {
            this.drawingPanel = drawingPanel;
        }

        @Override
        public void run() {
            long sleep = 5L;

            while ((displayString != null) && (!displayString.equals(""))) {
                SwingUtilities.invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        drawingPanel.setText(displayString);
                        drawingPanel.repaint();
                    }   
                });


                try {
                    Thread.sleep(sleep);
                } catch (InterruptedException e) {
                }


                displayString = input();
                sleep = 3000L;
            }

        }

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

    }
}

【讨论】:

  • 这段代码和我的有同样的问题。它偶尔不会更新。我发现这个和我的唯一区别是你的将保留最后一个字符串而不是清除屏幕。不过,这确实向我展示了如何相对于框架运行输入框,所以谢谢你。
  • @Jaime Wissner:很抱歉。当我测试代码时,它每次都会更新绘图。
  • 好吧,那也许是我的电脑。嗯。
  • @Jaime Wissner:有时间考虑一下,我执行了一个 invokeLater 来更新绘图。大多数时候(几乎所有时间),绘图是在 3 秒计时器启动之前完成的。我想在某些情况下可能不会。我对使用 invokeAndWait 方法犹豫不决,因为它通常不是必需的。我无法想象是什么能让重绘持续 3 秒。
猜你喜欢
  • 2011-03-29
  • 1970-01-01
  • 1970-01-01
  • 2011-12-14
  • 2011-06-29
  • 2012-06-24
  • 2016-06-17
  • 1970-01-01
相关资源
最近更新 更多