我最近使用 JavaFX 来生成一个使用鼠标输入的绘画程序。我在尝试将矩形放到 Canvas 上时遇到了一个问题 - 它似乎没有到达我想要的位置。我花了几个小时试图解决这个问题,我想我需要一些帮助。代码如下:
问题似乎是当我移动鼠标时坐标不断更新,这是我不希望发生的。我想要的是用户单击并拖动鼠标,最后,将根据鼠标移动的坐标生成矩形。线条工具也会发生同样的情况,因为它的工作方式与画笔工具一样,当鼠标在屏幕上移动时,会放下越来越多的形状。所有这些都在 Controller 类中。
主类
package sample;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Main extends Application {
@Override
public void start(Stage stage) throws Exception {
stage.setScene(new Scene(FXMLLoader.load(getClass().getResource("sample.fxml"))));
stage.setTitle("Paint");
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Controller
package sample;
import javafx.application.Platform;
import javafx.embed.swing.SwingFXUtils;
import javafx.fxml.FXML;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ColorPicker;
import javafx.scene.control.TextField;
import javafx.scene.control.ToggleButton;
import javafx.scene.image.Image;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Ellipse;
import javafx.scene.shape.Line;
import javafx.scene.shape.Rectangle;
import javax.imageio.ImageIO;
import javax.swing.*;
import java.io.File;
public class Controller {
int prevX, prevY, startX, startY, x , y;
boolean dragging;
@FXML
private Canvas canvas;
@FXML
private ColorPicker colorPicker;
@FXML
private TextField brushSize;
@FXML
private ToggleButton eraser;
@FXML
private ToggleButton line;
@FXML
private ToggleButton rectangle;
public void initialize() {
GraphicsContext g = canvas.getGraphicsContext2D();
canvas.setOnMousePressed(e -> {
startX = prevX = x = (int) e.getX();
startX = prevY = y = (int) e.getY();
dragging = true;
});
canvas.setOnMouseReleased(e -> {
dragging = false;
});
canvas.setOnMouseDragged(e -> {
double size = Double.parseDouble(brushSize.getText());
if(!dragging)
return;
double x = e.getX();
double y = e.getY();
if(eraser.isSelected()) {
g.clearRect(x, y, size, size);
} else if(line.isSelected()) {
g.strokeLine(prevX, prevY, x, y); // also experiencing issues
} else if(rectangle.isSelected()) {
putRect(g, false, startX, startY, x, y); // this puts the rectangle on the canvas
} else {
g.setStroke(colorPicker.getValue());
g.setLineWidth(size);
g.strokeLine(prevX, prevY, x, y);
}
prevX = (int) x;
prevY = (int) y;
});
}
public void putRect(GraphicsContext g, boolean filled, int x1, int y1, int x2, int y2) { // to create the rectangle
if(x1 == x2 || y1 == y2) {
return;
} else {
int x = Math.min(x1,x2); // get upper left corner, (x,y)
int y = Math.min(y1,y2);
int w = Math.abs(x1 - x2); // get width and height
int h = Math.abs(y1 - y2);
if (filled) {
g.fillRect(x, y, w, h);
} else {
g.strokeRect(x, y, w, h);
}
}
}
public void onSave() {
try{
Image snapshot = canvas.snapshot(null, null);
ImageIO.write(SwingFXUtils.fromFXImage(snapshot, null), "png", new File("paintsave.png"));
} catch (Exception e) {
System.out.println("Failed to save: " + e);
}
}
public void onExit() {
Platform.exit();
}
}
FXML
<?import javafx.geometry.Insets?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.MenuBar?>
<?import javafx.scene.control.Menu?>
<?import javafx.scene.control.MenuItem?>
<?import javafx.scene.control.ToolBar?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.control.ColorPicker?>
<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.canvas.Canvas?>
<?import javafx.scene.control.ToggleButton?>
<BorderPane fx:controller="sample.Controller"
xmlns:fx="http://javafx.com/fxml"
prefHeight="600.0" prefWidth="600.0">
<top>
<VBox>
<MenuBar>
<Menu text="file">
<MenuItem text="Save" onAction="#onSave"/>
<MenuItem text="Exit" onAction="#onExit"/>
</Menu>
</MenuBar>
<ToolBar>
<HBox alignment="CENTER" spacing="5">
<TextField fx:id="brushSize" text="18"/>
<ColorPicker fx:id="colorPicker"/>
<ToggleButton fx:id="eraser" text="Eraser"/>
<ToggleButton fx:id="line" text="Line"/>
<ToggleButton fx:id="rectangle" text="Rectangle"/>
</HBox>
</ToolBar>
</VBox>
</top>
<center>
<Canvas fx:id="canvas" width="600" height="600"/>
</center>
</BorderPane>
最佳答案
以下部分代码有问题:
canvas.setOnMouseDragged(e -> {
double size = Double.parseDouble(brushSize.getText());
if(!dragging)
return;
double x = e.getX();
double y = e.getY();
if(eraser.isSelected()) {
g.clearRect(x, y, size, size);
} else if(line.isSelected()) {
g.strokeLine(prevX, prevY, x, y); // also experiencing issues
} else if(rectangle.isSelected()) {
putRect(g, false, startX, startY, x, y); // this puts the rectangle on the canvas
} else {
g.setStroke(colorPicker.getValue());
g.setLineWidth(size);
g.strokeLine(prevX, prevY, x, y);
}
prevX = (int) x;
prevY = (int) y;
});
你看,每次移动鼠标时都会调用此函数。这允许您看到线路的动态变化,但会带来一些额外的问题。每次移动鼠标时,都会使用新坐标绘制新对象,但以前的对象不会被删除,尽管它应该被删除。
我建议两种不同的方法来解决这个问题:
- 保存上次调用该函数时鼠标所在位置的坐标。因此,每次调用函数时,您都应该重新绘制上次的对象,但用背景颜色填充它。这应该像“删除”您上次绘制的对象一样。如果您的对象不重叠,这应该有效。如果他们这样做,这可能会删除其中的一部分。
- 在开始绘图之前创建 Canvas 的副本。因此,每次调用函数时,将 Canvas 重置为副本的状态,然后绘制对象。
希望本文中的某些内容对您有用。
关于JavaFX 绘制形状时出现鼠标输入问题?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61040994/