我的应用程序是使用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();
}
}
}
我遇到的第一个问题是,当我运行应用程序时,paint()调用paintComponent()(我认为)已经被调用了两次。那已经很烦了。我将setResizable()设置为false,因为这会再次重新绘制窗口。有没有办法绕过这个,只有当我点击“生成”时画线?根据我的发现,当应用程序“需要”时,没有实际的方法可以阻止paint()被调用,所以也许还有另一种调整代码的方法。
答案 0 :(得分: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);
}
使用此代码,您的类中的任何其他方法都可以将turtleImage
设置为null
,并且当操作系统执行其正常的重绘行为时,将重新绘制屏幕。
答案 1 :(得分:0)
我遇到的第一个问题是,当我运行应用程序时,paint()调用paintComponent()(我认为)已经被调用了两次。那已经很烦人了。
绘画可以随时出于任何原因,paint/Component
方法负责重新绘制响应中组件的当前状态。这是由绘画子系统控制的。
我将setResizable()设置为false,因为这会再次重新绘制窗口。有没有办法绕过这个,只有当我点击&#34;生成&#34;?根据我的发现,当应用程序需要执行时,没有实际的方法可以阻止paint()被调用&#34;,
总的来说 - 没有。 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.Canvas
及其BufferStrategy
来完全控制绘画过程。这是一个复杂的解决方案,对于一个简单的问题可能不是必需的