我使用 JavaFX 开发了一个相对简单的应用程序,除了允许用户操纵该对象之外,还能够在用户每次按下按钮时向界面添加圆形形状。
我需要将程序分成类,所以我考虑为圆圈创建一个类,但我不知道该怎么做。你能帮我吗?
这是我的 AddCircleApp.java:
package addcircleapp;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class AddCircleApp extends Application {
@Override
public void start(Stage stage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("FXMLDocument.fxml"));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.setTitle("Interacting with Objects");
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
这是我的 FXMLDocumentController.java:
package addcircleapp;
import java.net.URL;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.ColorPicker;
import javafx.scene.control.Slider;
import javafx.scene.input.*;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Random;
import java.util.ResourceBundle;
import javafx.scene.control.ButtonBar.ButtonData;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Dialog;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.scene.shape.Circle;
import javafx.util.Pair;
public class FXMLDocumentController implements Initializable {
@FXML
private Pane containerPane;
@FXML
private Button addCircleButton;
@FXML
private Button editButton;
@FXML
private Button deleteButton;
@FXML
private ColorPicker colorPicker;
@FXML
private Slider sizeSlider;
@FXML
private Label colorSelectLabel;
@FXML
private Label sizeSelectLabel;
private List<Circle> circles = new ArrayList<>();
private Circle selectedCircle = null;
private double xOffset = 0;
private double yOffset = 0;
@Override
public void initialize(URL url, ResourceBundle rb) {
// TODO
sizeSelectLabel.setText("Size: " + String.valueOf(sizeSlider.getValue()));
sizeSlider.valueProperty().addListener((observable, oldValue, newValue) -> {
sizeSelectLabel.setText("Size: " + String.valueOf(newValue.intValue()));
});
}
public void handleAddCircleButton() {
Random rand = new Random();
int x = rand.nextInt(500);
int y = rand.nextInt(200);
Color selectedColor = colorPicker.getValue();
double selectedSize = sizeSlider.getValue();
Circle circle = new Circle(x, y, selectedSize, selectedColor);
circle.setOnMouseClicked((MouseEvent e) -> {
handleCircleClick(circle);
});
circle.setOnKeyPressed((KeyEvent e) -> {
handleCircleMovement(e, circle);
});
circle.setOnMousePressed((MouseEvent e) -> {
handleCircleDragStart(e, circle);
});
circle.setOnMouseDragged((MouseEvent e) -> {
handleCircleDrag(e, circle);
});
circle.setOnMouseReleased((MouseEvent e) -> {
handleCircleDragEnd(e, circle);
});
circles.add(circle);
containerPane.getChildren().add(circle);
System.out.println("New circle was created!");
}
public void handleEditButton() {
if (selectedCircle != null) {
Dialog<Pair<Color, Double>> editDialog = new Dialog<>();
editDialog.setTitle("Edit Circle");
editDialog.setHeaderText("Edit the color and size of the selected circle.");
ButtonType confirmButton = new ButtonType("OK", ButtonData.OK_DONE);
editDialog.getDialogPane().getButtonTypes().addAll(confirmButton, ButtonType.CANCEL);
ColorPicker colorPicker = new ColorPicker((Color) selectedCircle.getFill());
Slider sizeSlider = new Slider(10, 100, selectedCircle.getRadius());
VBox vbox = new VBox(10);
Label colorLabel = new Label("Color:");
Label sizeLabel = new Label("Size:");
sizeLabel.setText("Size: " + String.valueOf((int) sizeSlider.getValue()));
sizeSlider.valueProperty().addListener((observable, oldValue, newValue) -> {
sizeLabel.setText("Size: " + String.valueOf((int) sizeSlider.getValue()));
});
vbox.getChildren().addAll(colorLabel, colorPicker, sizeLabel, sizeSlider);
editDialog.getDialogPane().setContent(vbox);
editDialog.setResultConverter((ButtonType dialogButton) -> {
if (dialogButton == confirmButton) {
return new Pair<>(colorPicker.getValue(), sizeSlider.getValue());
}
return null;
});
Optional<Pair<Color, Double>> result = editDialog.showAndWait();
result.ifPresent(newValues -> {
selectedCircle.setFill(newValues.getKey());
selectedCircle.setRadius(newValues.getValue());
});
}
}
public void handleDeleteButton(ActionEvent event) {
if (selectedCircle != null) {
circles.remove(selectedCircle);
containerPane.getChildren().remove(selectedCircle);
selectedCircle = null;
}
}
private void handleCircleClick(Circle clickedCircle) {
if (selectedCircle != null) {
selectedCircle.setStroke(Color.TRANSPARENT);
}
selectedCircle = clickedCircle;
Color fillColor = (Color) selectedCircle.getFill();
Color complementary = Color.color(1.0 - fillColor.getRed(), 1.0 - fillColor.getBlue(), 1.0 - fillColor.getGreen());
selectedCircle.setStroke(complementary);
selectedCircle.setStrokeWidth(2.0);
selectedCircle.requestFocus();
}
public void handleContainerPaneClick(MouseEvent event) {
if (selectedCircle != null && !selectedCircle.contains(event.getX(), event.getY())) {
selectedCircle.setStroke(Color.TRANSPARENT);
selectedCircle = null;
}
}
private void handleCircleMovement(KeyEvent event, Circle circle) {
if (selectedCircle != null && null != event.getCode()) {
switch (event.getCode()) {
case LEFT:
circle.setCenterX(circle.getCenterX() - 10);
event.consume();
break;
case RIGHT:
circle.setCenterX(circle.getCenterX() + 10);
event.consume();
break;
case UP:
circle.setCenterY(circle.getCenterY() - 10);
event.consume();
break;
case DOWN:
circle.setCenterY(circle.getCenterY() + 10);
event.consume();
break;
default:
break;
}
}
}
public void handleCircleDragStart(MouseEvent event, Circle circle) {
xOffset = event.getSceneX() - circle.getCenterX();
yOffset = event.getSceneY() - circle.getCenterY();
}
public void handleCircleDrag(MouseEvent event, Circle circle) {
double x = event.getSceneX() - xOffset;
double y = event.getSceneY() - yOffset;
circle.setCenterX(x);
circle.setCenterY(y);
}
public void handleCircleDragEnd(MouseEvent event, Circle circle) {
xOffset = 0;
yOffset = 0;
}
}
这是我的 FXMLDocument.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ColorPicker?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Slider?>
<?import javafx.scene.layout.Pane?>
<Pane fx:id="containerPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" onMouseClicked="#handleContainerPaneClick" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="addcircleapp.FXMLDocumentController">
<children>
<Button fx:id="addCircleButton" layoutX="345.0" layoutY="362.0" mnemonicParsing="false" onAction="#handleAddCircleButton" text="AddCircle" />
<ColorPicker fx:id="colorPicker" layoutX="14.0" layoutY="361.0" />
<Slider fx:id="sizeSlider" blockIncrement="1.0" layoutX="180.0" layoutY="367.0" min="1.0" value="50.0" />
<Button fx:id="editButton" layoutX="432.0" layoutY="362.0" mnemonicParsing="false" onAction="#handleEditButton" prefHeight="25.0" prefWidth="67.0" text="Edit" />
<Button fx:id="deleteButton" layoutX="519.0" layoutY="361.0" mnemonicParsing="false" onAction="#handleDeleteButton" prefHeight="25.0" prefWidth="67.0" text="Delete" />
<Label fx:id="colorSelectLabel" layoutX="14.0" layoutY="340.0" text="Select a color:" />
<Label fx:id="sizeSelectLabel" layoutX="180.0" layoutY="340.0" text="Size:" />
</children>
</Pane>
我设法创建了一个“MyCircle”类并添加了一些事件处理程序,但某些功能无法正常工作,例如handleCircleClick方法。另外,我不知道如何解析handleEditButton和handleDeleteButton方法。
我的 FXMLDocumentController.java 文件如下所示:
package addcircleapp;
import java.net.URL;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.ColorPicker;
import javafx.scene.control.Slider;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Random;
import java.util.ResourceBundle;
import javafx.scene.control.ButtonBar.ButtonData;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Dialog;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.VBox;
import javafx.util.Pair;
public class FXMLDocumentController implements Initializable {
@FXML
private Pane containerPane;
@FXML
private Button addCircleButton;
@FXML
private Button editButton;
@FXML
private Button deleteButton;
@FXML
private ColorPicker colorPicker;
@FXML
private Slider sizeSlider;
@FXML
private Label colorSelectLabel;
@FXML
private Label sizeSelectLabel;
private List<MyCircle> circles = new ArrayList<>();
private MyCircle selectedCircle = null;
//private double xOffset = 0;
//private double yOffset = 0;
@Override
public void initialize(URL url, ResourceBundle rb) {
// TODO
sizeSelectLabel.setText("Size: " + String.valueOf(sizeSlider.getValue()));
sizeSlider.valueProperty().addListener((observable, oldValue, newValue) -> {
sizeSelectLabel.setText("Size: " + String.valueOf(newValue.intValue()));
});
}
public void handleAddCircleButton() {
Random rand = new Random();
int x = rand.nextInt(500);
int y = rand.nextInt(200);
Color selectedColor = colorPicker.getValue();
double selectedSize = sizeSlider.getValue();
MyCircle circle = new MyCircle(x, y, selectedSize, selectedColor);
/*circle.setOnMouseClicked((MouseEvent e) -> {
handleCircleClick(circle);
});
circle.setOnKeyPressed((KeyEvent e) -> {
handleCircleMovement(e, circle);
});
circle.setOnMousePressed((MouseEvent e) -> {
handleCircleDragStart(e, circle);
});
circle.setOnMouseDragged((MouseEvent e) -> {
handleCircleDrag(e, circle);
});
circle.setOnMouseReleased((MouseEvent e) -> {
handleCircleDragEnd(e, circle);
});*/
circles.add(circle);
containerPane.getChildren().add(circle);
System.out.println("New circle was created!");
}
public void handleEditButton() {
if (selectedCircle != null) {
Dialog<Pair<Color, Double>> editDialog = new Dialog<>();
editDialog.setTitle("Edit Circle");
editDialog.setHeaderText("Edit the color and size of the selected circle.");
ButtonType confirmButton = new ButtonType("OK", ButtonData.OK_DONE);
editDialog.getDialogPane().getButtonTypes().addAll(confirmButton, ButtonType.CANCEL);
ColorPicker colorPicker = new ColorPicker((Color) selectedCircle.getFill());
Slider sizeSlider = new Slider(10, 100, selectedCircle.getRadius());
VBox vbox = new VBox(10);
Label colorLabel = new Label("Color:");
Label sizeLabel = new Label("Size:");
sizeLabel.setText("Size: " + String.valueOf((int) sizeSlider.getValue()));
sizeSlider.valueProperty().addListener((observable, oldValue, newValue) -> {
sizeLabel.setText("Size: " + String.valueOf((int) sizeSlider.getValue()));
});
vbox.getChildren().addAll(colorLabel, colorPicker, sizeLabel, sizeSlider);
editDialog.getDialogPane().setContent(vbox);
editDialog.setResultConverter((ButtonType dialogButton) -> {
if (dialogButton == confirmButton) {
return new Pair<>(colorPicker.getValue(), sizeSlider.getValue());
}
return null;
});
Optional<Pair<Color, Double>> result = editDialog.showAndWait();
result.ifPresent(newValues -> {
selectedCircle.setFill(newValues.getKey());
selectedCircle.setRadius(newValues.getValue());
});
}
}
public void handleDeleteButton(ActionEvent event) {
if (selectedCircle != null) {
circles.remove(selectedCircle);
containerPane.getChildren().remove(selectedCircle);
selectedCircle = null;
}
}
/*private void handleCircleClick(MyCircle clickedCircle) {
if (selectedCircle != null) {
selectedCircle.setStroke(Color.TRANSPARENT);
}
selectedCircle = clickedCircle;
Color fillColor = (Color) selectedCircle.getFill();
Color complementary = Color.color(1.0 - fillColor.getRed(), 1.0 - fillColor.getBlue(), 1.0 - fillColor.getGreen());
selectedCircle.setStroke(complementary);
selectedCircle.setStrokeWidth(2.0);
selectedCircle.requestFocus();
}*/
public void handleContainerPaneClick(MouseEvent event) {
if (selectedCircle != null && !selectedCircle.contains(event.getX(), event.getY())) {
selectedCircle.setStroke(Color.TRANSPARENT);
selectedCircle = null;
}
}
/*
private void handleCircleMovement(KeyEvent event, MyCircle circle) {
if (selectedCircle != null && null != event.getCode()) {
switch (event.getCode()) {
case LEFT:
circle.setCenterX(circle.getCenterX() - 10);
event.consume();
break;
case RIGHT:
circle.setCenterX(circle.getCenterX() + 10);
event.consume();
break;
case UP:
circle.setCenterY(circle.getCenterY() - 10);
event.consume();
break;
case DOWN:
circle.setCenterY(circle.getCenterY() + 10);
event.consume();
break;
default:
break;
}
}
}
public void handleCircleDragStart(MouseEvent event, MyCircle circle) {
xOffset = event.getSceneX() - circle.getCenterX();
yOffset = event.getSceneY() - circle.getCenterY();
}
public void handleCircleDrag(MouseEvent event, MyCircle circle) {
double x = event.getSceneX() - xOffset;
double y = event.getSceneY() - yOffset;
circle.setCenterX(x);
circle.setCenterY(y);
}
public void handleCircleDragEnd(MouseEvent event, MyCircle circle) {
xOffset = 0;
yOffset = 0;
}*/
}
我的 MyCircle.java 文件如下所示:
package addcircleapp;
import javafx.scene.input.*;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
public class MyCircle extends Circle {
double xOffset = 0;
double yOffset = 0;
MyCircle selectedCircle = null;
public MyCircle(int xPos, int yPos, double radius, Color color){
super(xPos, yPos, radius, color);
setOnMouseClicked((MouseEvent event) ->{
//Handle circle click
MyCircle selectedCircle = (MyCircle) event.getTarget();
if(selectedCircle.isSelected()){
selectedCircle.deselect();
} else {
selectedCircle.select();
}
});
setOnKeyPressed((KeyEvent event) ->{
//Handle circle movement
handleCircleMovement(event, this);
});
setOnMousePressed((MouseEvent event) ->{
//Handle drag start
handleCircleDragStart(event, this);
});
setOnMouseDragged((MouseEvent event) ->{
//Handle drag
handleCircleDrag(event, this);
});
setOnMouseReleased((MouseEvent event) ->{
//Handle drag end
handleCircleDragEnd(this);
});
}
public void select(){
if(selectedCircle != null){
selectedCircle.setStroke(Color.TRANSPARENT);
}
Color fillColor = (Color) getFill();
Color complementary = Color.color(1.0 - fillColor.getRed(), 1.0 - fillColor.getBlue(), 1.0 - fillColor.getGreen());
setStroke(complementary);
setStrokeWidth(2.0);
requestFocus();
}
public void deselect(){
setStroke(Color.TRANSPARENT);
}
public boolean isSelected(){
return getStroke() != Color.TRANSPARENT;
}
public void handleCircleMovement(KeyEvent event, MyCircle circle){
if(null != event.getCode())switch (event.getCode()) {
case LEFT:
circle.setCenterX(circle.getCenterX() - 10);
event.consume();
break;
case RIGHT:
circle.setCenterX(circle.getCenterX() + 10);
event.consume();
break;
case UP:
circle.setCenterY(circle.getCenterY() - 10);
event.consume();
break;
case DOWN:
circle.setCenterY(circle.getCenterY() + 10);
event.consume();
break;
default:
break;
}
}
public void handleCircleDragStart(MouseEvent event, MyCircle circle){
xOffset = event.getSceneX() - circle.getCenterX();
yOffset = event.getSceneY() - circle.getCenterY();
}
public void handleCircleDrag(MouseEvent event, MyCircle circle){
double x = event.getSceneX() - xOffset;
double y = event.getSceneY() - yOffset;
circle.setCenterX(x);
circle.setCenterY(y);
}
public void handleCircleDragEnd(MyCircle circle){
xOffset = 0;
yOffset = 0;
}
}
这是一个相当广泛的问题,对于这个网站来说可能有点过于基于意见,但我会尝试给出一些建议。
您的每个类都应该有一个单一的职责,并且应该只包含与该职责相关的字段和方法。如果您有一个名为
MyCircle
的类,则该类的每个实例都代表一个圆。 “选择哪个圆”不是该类的责任的一部分(它是更广泛的应用程序的一部分)。事实上,您正在实施一项业务规则(也许您实际上没有用语言表达),即一次只能选择一个圆圈。实施该规则(或跟踪选择了多个圆圈中的哪一个)当然不是 MyCircle
(同样,仅代表单个圆圈)的责任。
另一方面,“这个圈子是否被选中”可能是
MyCircle
类的责任。因此,某种布尔 selected
属性在这里可能很合适。不难看出如何更改此类属性以响应鼠标单击。
这实际上是您在实施此操作时遇到困难的原因;您试图在每个圆实例中维持“选择哪个圆”的状态。
优先选择组合而不是继承。这是 Joshua Bloch 的书《Effective Java》(每个 Java 程序员都应该阅读)中的关键内容之一。我的具体经验法则是,如果您不向类添加功能,而只是提供一种方便的方法来配置它的实例,那么您不应该对其进行子类化。即使您要添加一些功能(在您的情况下,您可以说您是:您正在添加选择的概念),您可能会喜欢组合。 从类继承意味着您可以访问该类的更多内部工作方式,因此您在理解类如何工作方面有更多的责任,以免编写可能无法按您想要的方式工作的代码。 (引用 Bloch 的话,“继承违反了封装”。)类越复杂(UI 类也非常复杂),您在子类化它们时就应该更加小心。我不建议在这里子类化
Circle
。
第一点产生了一个潜在的问题:我们确实需要某个地方来跟踪选择了哪个圆。如果每个圆圈只知道它何时被选择,我们需要知道任何selected
执行此操作的机制。这些基本上是“可观察”属性:这意味着我们可以向它们
注册一个侦听器,并且如果属性值发生变化,则调用侦听器。 所以我会这样做:
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
public class SelectableCircle {
double xOffset = 0;
double yOffset = 0;
private final Circle uiComponent;
private final BooleanProperty selected = new SimpleBooleanProperty(false);
public BooleanProperty selectedProperty() {
return selected;
}
public final boolean isSelected() {
return selectedProperty().get();
}
public final void setSelected(boolean selected) {
selectedProperty().set(selected);
}
public SelectableCircle(int xPos, int yPos, double radius, Color color){
uiComponent = new Circle(xPos, yPos, radius, color);
uiComponent.setOnMouseClicked(e -> {
setSelected( ! isSelected() );
});
uiComponent.setOnKeyPressed((KeyEvent event) ->{
//Handle circle movement
handleCircleMovement(event);
});
uiComponent.setOnMousePressed((MouseEvent event) ->{
//Handle drag start
handleCircleDragStart(event);
});
uiComponent.setOnMouseDragged((MouseEvent event) ->{
//Handle drag
handleCircleDrag(event);
});
// update circle ui according to selected state:
selected.addListener((obs, wasSelected, isNowSelected) -> {
if (isNowSelected) {
Color fillColor = (Color) uiComponent.getFill();
Color complementary = Color.color(1.0 - fillColor.getRed(), 1.0 - fillColor.getBlue(), 1.0 - fillColor.getGreen());
uiComponent.setStroke(complementary);
uiComponent.requestFocus();
} else {
uiComponent.setStroke(Color.TRANSPARENT);
}
});
}
public void handleCircleMovement(KeyEvent event){
switch (event.getCode()) {
case LEFT:
uiComponent.setCenterX(uiComponent.getCenterX() - 10);
event.consume();
break;
case RIGHT:
uiComponent.setCenterX(uiComponent.getCenterX() + 10);
event.consume();
break;
case UP:
uiComponent.setCenterY(uiComponent.getCenterY() - 10);
event.consume();
break;
case DOWN:
uiComponent.setCenterY(uiComponent.getCenterY() + 10);
event.consume();
break;
default:
break;
}
}
public void handleCircleDragStart(MouseEvent event){
xOffset = event.getSceneX() - uiComponent.getCenterX();
yOffset = event.getSceneY() - uiComponent.getCenterY();
}
public void handleCircleDrag(MouseEvent event){
double x = event.getSceneX() - xOffset;
double y = event.getSceneY() - yOffset;
uiComponent.setCenterX(x);
uiComponent.setCenterY(y);
}
public Circle getUIComponent() {
return uiComponent;
}
}
现在您需要对控制器代码进行一些更改。添加圆圈时,您需要执行以下操作:
public void handleAddCircleButton() {
Random rand = new Random();
int x = rand.nextInt(500);
int y = rand.nextInt(200);
Color selectedColor = colorPicker.getValue();
double selectedSize = sizeSlider.getValue();
SelectableCircle circle = new SelectableCircle(x, y, selectedSize, selectedColor);
// if selected state of circle changes, update our selectedCircle:
circle.selectedProperty().addListener((obs, wasSelected, isNowSelected) -> {
if (isNowSelected) {
selectedCircle = circle;
}
});
circles.add(circle);
containerPane.getChildren().add(circle.getUIComponent());
System.out.println("New circle was created!");
}
然后在各个地方(基本上在使用 UI 时)您将需要参考
selectedCircle.getUIComponent()
而不仅仅是
selectedCircle
。例如:
public void handleDeleteButton(ActionEvent event) {
if (selectedCircle != null) {
circles.remove(selectedCircle);
containerPane.getChildren().remove(selectedCircle.getUIComponent());
selectedCircle = null;
}
}