0

我的应用程序旨在使用 L-Systems 绘制一棵树。我从一个公理开始,为下一代应该替换什么提供一个规则。我将 JFrame/JPanel 组合用于一个按钮(将来可能会更多)/JComponent 用于绘图区域。我编写了一个小乌龟图形方法(前进、右转、左转、推动当前变换、弹出变换)。每次单击“生成”按钮时,我都会调用 repaint(),然后再调用 turtleDraw()。

package com.flak;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Stack;

@SuppressWarnings("serial")
public class LSystemTree extends JFrame{
    JButton generateBut;
    int currentAction = 1;

    public static void main(String[] args) {
        new LSystemTree();
    }

    public LSystemTree() {
        this.setSize(600, 600);
        this.setTitle("L-System Tree");
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setResizable(false);
        JPanel buttonPanel = new JPanel();
        Box box = Box.createHorizontalBox();
        generateBut = makeButton("Generate", 1);
        box.add(generateBut);
        buttonPanel.add(box);
        Map<String, String> rules = new HashMap<>();
        rules.put("F", "FF+[+F-F-F]-[-F+F+F]");
        this.add(buttonPanel,  BorderLayout.NORTH);
        this.add(new TreeDrawing("F", 22.5, rules), BorderLayout.CENTER);
        this.setVisible(true);
    }

    public JButton makeButton(String text, final int actionNum) {
        JButton theBut = new JButton();
        theBut.setText(text);
        theBut.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                currentAction = actionNum;
                System.out.println("actionNum: " + actionNum);
                repaint();
            }
        });
        return theBut; 
    }

    private class TreeDrawing extends JComponent{
        private String axiom;
        private String sentence;
        private double angle;
        private Map<String, String> rules;
        private int len;
        private Stack<AffineTransform> transformStack;
        public TreeDrawing(String axiom, double angle, Map<String, String> rules) {
            this.axiom = axiom;
            this.sentence = axiom;
            this.angle = Math.toRadians(angle);
            this.rules = rules;
            len = 100;
            transformStack = new Stack<>();
        }

        public void generate() {
            len /= 2;
            String nextSentence = "";
            for(int i = 0; i < sentence.length(); i++) {
                char current = sentence.charAt(i);
                boolean found = false;
                if(rules.containsKey(String.valueOf(current))) {
                    found = true;
                    nextSentence += rules.get(String.valueOf(current));
                }
                if(!found) {
                    nextSentence += current;
                }
            }
            sentence = nextSentence;
        }

        private void turtleDraw(Graphics2D g2d) {
            g2d.translate(getWidth() / 2, getHeight());
            for(int i = 0; i < sentence.length(); i++) {
                char current = sentence.charAt(i);
                if(current == 'F') {
                    g2d.drawLine(0, 0, 0, -len);
                    g2d.translate(0, -len);
                } else if(current == '+') {
                    g2d.rotate(angle);
                } else if(current == '-') {
                    g2d.rotate(-angle);
                } else if(current == '[') {
                    transformStack.push(g2d.getTransform());
                } else if(current == ']') {
                    g2d.setTransform(transformStack.pop());
                }
            }
            generate();
            System.out.println(sentence);
        }

        @Override
        public void paintComponent(Graphics g) {
            Graphics2D g2d = (Graphics2D) g;
            g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            turtleDraw(g2d);
            g2d.dispose();
        }
    }
}

我遇到的第一个问题是,当我运行应用程序时,又调用了 paintComponent()(我认为)的 paint() 已经被调用了两次。这已经很烦人了。我将 setResizable() 设置为 false,因为这会重新绘制窗口。有没有办法解决这个问题,只有当我点击“生成”时才画线?根据我的发现,当应用程序“需要这样做”时,没有实际的方法可以阻止 paint() 被调用,所以也许还有另一种方法来调整代码。

4

2 回答 2

0

您需要将线条的绘制与 API 的刷新行为分离。

然而,与此同时,一个简单的技巧是通过缓存完全绘制的图像的图像来手动管理重绘行为,然后将图像设置为 null 以强制重绘。

private Image turtleImage = null;
private void turtleDraw(Graphics2D g2d) {
    if(turtleImage == null) { //Anywhere else, set turtleImage to null to force a redraw
        turtleImage = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB);
        Graphics2D graphics = turtleImage.createGraphics();
        graphics.translate(getWidth() / 2, getHeight());
        for(int i = 0; i < sentence.length(); i++) {
            char current = sentence.charAt(i);
            if(current == 'F') {
                graphics.drawLine(0, 0, 0, -len);
                graphics.translate(0, -len);
            } else if(current == '+') {
                graphics.rotate(angle);
            } else if(current == '-') {
                graphics.rotate(-angle);
            } else if(current == '[') {
                transformStack.push(graphics.getTransform());
            } else if(current == ']') {
                graphics.setTransform(transformStack.pop());
            }
        }
        generate();
        System.out.println(sentence);
    }
    g2d.drawImage(turtleImage, 0, 0, null, null);
}

使用此代码,您的类中的任何其他方法都可以设置turtleImagenull,并且当操作系统执行其正常重绘行为时,屏幕将被重绘。

于 2018-04-30T21:57:07.450 回答
0

我遇到的第一个问题是,当我运行应用程序时,又调用了 paintComponent()(我认为)的 paint() 已经被调用了两次。这已经很烦人了。

绘画可以在任何时候出于任何原因发生,paint/Component重新绘制组件的当前状态作为响应是方法的责任。这是由绘画子系统控制的。

我将 setResizable() 设置为 false,因为这会重新绘制窗口。有没有办法解决这个问题,只有当我点击“生成”时才画线?根据我的发现,当应用程序“需要这样做”时,没有实际的方法可以阻止 paint() 被调用,

一般来说 - 没有。Swing 使用被动渲染算法,这意味着绘画以不规则的时间间隔发生,并且仅在系统认为需要更新某些内容时发生

paintComponent的有点不对。你不应该调用你没有创建dispose的上下文,这可能会对需要在你之后绘制的其他组件产生不利影响Graphics

@Override
public void paintComponent(Graphics g) {
    Graphics2D g2d = (Graphics2D) g;
    g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    turtleDraw(g2d);
    //g2d.dispose();
}

所以也许还有另一种方法来调整代码。

那么,第一个问题是,为什么这是一个问题?询问的原因,因为任何其他解决方案可能会引入超出其隐含优势的更多开销和复杂性。

您可以使用双缓冲方法,将算法的更新绘制到图像上,然后通过paintComponent. 假设您在每次绘制过程中绘制数万次迭代,这可以减少重新绘制的复杂性/时间。

您可以使用java.awt.CanvasBufferStrategy,它可以让您完全控制绘画过程。这是一个复杂的解决方案,对于其他简单的问题可能不需要

于 2018-04-30T22:53:28.503 回答