JPanel 在调整大小时被重绘多次,有时根本不重绘

JPanel gets repainted several times on resize, sometimes not at all

首先,一些背景。我正在 Java 中构建我的第一个 GUI 应用程序,我不确定我是否以正确的方式进行它,因为我不熟悉 GUI 类。最终目标是根据 Open Street Map 数据构建可调整大小、可缩放、可滚动的地图。

我现在拥有的是 JPanel 的子类,我在 JFrame 中调用 LinePanel,并使用 Graphics 对象绘制代表道路的线条。这对于检查我是否正确解析和解释数据的目的来说效果很好,但它似乎不够而且天真。我已经遇到了一个问题,即 JPanel 在调整大小时被重新绘制多次,导致地图错误到我的应用程序需要癫痫警告的地步。

这是我现在的代码:

package map;

import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;

import javax.swing.JFrame;
import javax.swing.JPanel;

public class MapDisplay {
    private JFrame frame;
    private LinePanel jpanel;

    public MapDisplay(Map map) {
        this.frame = new JFrame();
        frame.setLayout(new BorderLayout());
        frame.addComponentListener(new ComponentAdapter() {
            public void componentResized(ComponentEvent e) {
                jpanel.repaint();
            }
        });
        jpanel = new LinePanel(map);
        frame.add(jpanel, BorderLayout.CENTER);
    }

    public void display() {
        frame.setSize(710, 935);
        frame.setVisible(true);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        jpanel.repaint();
    }

    class LinePanel extends JPanel {
        private static final long serialVersionUID = 1965018056953712219L;
        private Map map;
        private int width;
        private int height;

        private int latAsY(double lat) {
            return height
                    - (int) (height * (lat - map.getMinLat()) / (map
                            .getMaxLat() - map.getMinLat()));
        }

        private int lonAsX(double lon) {
            return (int) (width * (lon - map.getMinLong()) / (map.getMaxLong() - map
                    .getMinLong()));
        }

        private void recalculateDimensions() {
            double mapRatio = (map.getMaxLat() - map.getMinLat())
                    / (map.getMaxLong() - map.getMinLong());
            double panelRatio = this.getHeight() / (double) this.getWidth();
            if (mapRatio > panelRatio) {
                width = (int) (this.getHeight() / mapRatio);
                height = this.getHeight();
            } else {
                width = this.getWidth();
                height = (int) (mapRatio * this.getWidth());
            }
        }

        public LinePanel(Map map) {
            super();
            this.map = map;
        }

        public void repaint() {
            if (map != null) {
                recalculateDimensions();
                Graphics2D g = (Graphics2D) this.getGraphics();
                if (g != null) {
                    g.setStroke(new BasicStroke(2));
                    g.clearRect(0, 0, jpanel.getWidth(), jpanel.getHeight());
                    g.setColor(Color.WHITE);
                    g.fillRect(0, 0, width, height);
                    g.setColor(Color.BLACK);
                    for (String wayId : map.getWays()) {
                        Way way = map.getWay(wayId);
                        Node prev = null;
                        for (String nodeId : way.getNodes()) {
                            Node cur = map.getNode(nodeId);
                            if (prev != null) {
                                int y1 = latAsY(prev.getLatitude());
                                int x1 = lonAsX(prev.getLongitude());
                                int y2 = latAsY(cur.getLatitude());
                                int x2 = lonAsX(cur.getLongitude());
                                g.drawLine(x1, y1, x2, y2);
                            }
                            prev = cur;
                        }
                    }
                }
            }
        }
    }
}

我在调整大小时调用重绘,因为它不会自动执行此操作,这是我做错事的另一个迹象。我还使用 g.fillRect 手动清除 JPanel,因为在重绘地图之前调用 super.repaint() 不会导致任何内容出现...

本质上,我只是想从更高级的 Java 程序员那里得到一些指导,以了解我应该如何进行这整个努力。如果我走在正确的轨道上,请随时将我推向正确的方向,而不是让我走上一条新路,但我怀疑情况是否如此。

  • public void repaint() { 不,不,不
  • Graphics2D g = (Graphics2D) this.getGraphics(); - 不,不,不

这不是在 Swing 中绘画的方式。有关如何在 Swing 中完成绘画的更多详细信息,请参阅 Performing Custom Painting and Painting in AWT and Swing

您应该首先摆脱 repaint 方法并将其替换为 paintComponent 方法...

public class MapDisplay {

    private JFrame frame;
    private LinePanel jpanel;

    public MapDisplay(Map map) {
        this.frame = new JFrame();
        frame.setLayout(new BorderLayout());
        frame.addComponentListener(new ComponentAdapter() {
            public void componentResized(ComponentEvent e) {
                jpanel.repaint();
            }
        });
        jpanel = new LinePanel(map);
        frame.add(jpanel, BorderLayout.CENTER);
    }

    public void display() {
        frame.setSize(710, 935);
        frame.setVisible(true);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        jpanel.repaint();
    }

    class LinePanel extends JPanel {

        private static final long serialVersionUID = 1965018056953712219L;
        private Map map;
        private int width;
        private int height;

        private int latAsY(double lat) {
            return height
                            - (int) (height * (lat - map.getMinLat()) / (map
                            .getMaxLat() - map.getMinLat()));
        }

        private int lonAsX(double lon) {
            return (int) (width * (lon - map.getMinLong()) / (map.getMaxLong() - map
                            .getMinLong()));
        }

        private void recalculateDimensions() {
            double mapRatio = (map.getMaxLat() - map.getMinLat())
                            / (map.getMaxLong() - map.getMinLong());
            double panelRatio = this.getHeight() / (double) this.getWidth();
            if (mapRatio > panelRatio) {
                width = (int) (this.getHeight() / mapRatio);
                height = this.getHeight();
            } else {
                width = this.getWidth();
                height = (int) (mapRatio * this.getWidth());
            }
        }

        public LinePanel(Map map) {
            super();
            this.map = map;
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            if (map != null) {
                recalculateDimensions();
                Graphics2D g2d = (Graphics2D) g.create();
                g2d.setStroke(new BasicStroke(2));
                g2d.clearRect(0, 0, jpanel.getWidth(), jpanel.getHeight());
                g2d.setColor(Color.WHITE);
                g2d.fillRect(0, 0, width, height);
                g2d.setColor(Color.BLACK);
                for (String wayId : map.getWays()) {
                    Way way = map.getWay(wayId);
                    Node prev = null;
                    for (String nodeId : way.getNodes()) {
                        Node cur = map.getNode(nodeId);
                        if (prev != null) {
                            int y1 = latAsY(prev.getLatitude());
                            int x1 = lonAsX(prev.getLongitude());
                            int y2 = latAsY(cur.getLatitude());
                            int x2 = lonAsX(cur.getLongitude());
                            g2d.drawLine(x1, y1, x2, y2);
                        }
                        prev = cur;
                    }
                }
                g2d.dispose();
            }
        }
    }
}

是的,在调整组件大小时可能会多次生成重绘事件,但重绘事件也可以通过 RepaintManager 自动减少(即 RepaintManager 可能是调用 100 次重绘一个组件,可能只会产生 10 个实际的重绘事件——举个例子)...