如何在JavaFX中实现节点选择
How to implement selection of Nodes in JavaFX
我是 JavaFX 新手,昨天开始学习它。花了一整天的时间阅读文档,但什么也没学到...
这就是我想要做的,制作一个创建圆圈的简单 JavaFX 应用程序。点击它的笔画变成橙色(一些颜色)。在未被舔过的情况下(点击该圆圈以外的任何东西),笔划变成(某种颜色)白色。
这是我的 伪代码 目前我所拥有的。 我想制作一个单独的 class 来创建一个圆圈并处理事件(重要)。
public class Something extends Application {
@Override
public void start(Stage primaryStage) {
MyCircle c1 = new MyCircle();
c1.setCircle(20, 0, 0);
TilePane root = new TilePane();
root.getChildren().add(c1.getCircle());
//Some kind of mouse event listener, not sure which one should be
c1.getCircle().addEventListener(); //pseudo code
Scene scene = new Scene(root, 400, 400);
primaryStage.setTitle("Circle");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
这个 class 应该创建一个圆并处理所有事件,例如鼠标单击、鼠标位置、单击和拖动等等。
public class MyCircle implements EventHandler{
Circle circle = new Circle();
public void setCircle(int radius, int x, int y){
circle.setRadius(radius);
position(x,y);
circle.setStrokeWidth(3);
circle.setStroke(Color.valueOf("white"));
}
public Circle getCircle(){
return circle;
}
public void position(int x, int y){
circle.setTranslateX(x);
circle.setTranslateY(y);
}
public void selected(){
circle.setStroke(Color.valueOf("orange"));
}
public void unselected() {
circle.setStroke(Color.valueOf("white"));
}
@Override
public void handle(Event event) {
if (event == MOUSE_CLICKED){ //pseudo code
selected();
}
else if(event == MOUSE_UNCLICKED){ //pseudo code
unselected();
}
}
}
由于我是 JavaFX 的新手,因此我也非常感谢您的解释。谢谢!
编辑: 这是我的伪代码,我想将它转换成实际的工作代码。我不确定我该怎么做。任何帮助将不胜感激。
另一项编辑: 除了 3 个标记的地方外,所有内容都是代码。请在代码中查找我的评论 Psuedo Code
,我需要帮助将伪代码更改为实际代码。
处理节点的选择状态需要一些节点内不可用的知识。您将需要知道鼠标事件是否发生在其他地方(例如其他节点或根窗格),因此您可能必须向它传递有问题的参数。
一般来说,将鼠标事件处理委托给 MyCircle
并不是一个好主意。相反,最好在 class 中指定选择行为,并将选择处理委托给具有足够知识来处理问题的单独助手 class。我创建此 gist 是为了展示如何完成此任务。
public class SelectionDemo extends Application {
@Override
public void start(Stage primaryStage) {
Scene scene = new Scene(createPane(), 400, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
private Parent createPane() {
BorderPane root = new BorderPane();
SelectionHandler selectionHandler = new SelectionHandler(root);
root.addEventHandler(MouseEvent.MOUSE_PRESSED, selectionHandler.getMousePressedEventHandler());
MyCircle c1 = new MyCircle(40, 40, 20);
MyCircle c2 = new MyCircle(40, 100, 20);
MyCircle c3 = new MyCircle(40, 160, 20);
root.getChildren().addAll(c1, c2, c3);
return root;
}
public static void main(String[] args) {
launch(args);
}
}
我从jfxtras-labs借用并修改了一个接口来表示一个可选择的节点。最好有这个界面来区分可选节点和不可选节点:
/**
* This interface is based on jfxtras-labs <a href="https://github.com/JFXtras/jfxtras-labs/blob/8.0/src/main/java/jfxtras/labs/scene/control/window/SelectableNode.java">SelectableNode</a>
*/
public interface SelectableNode {
public boolean requestSelection(boolean select);
public void notifySelection(boolean select);
}
class希望被选中的用户必须实现此接口并在notifySelection方法的实现中指定他们的选择行为:
public class MyCircle extends Circle implements SelectableNode {
public MyCircle(double centerX, double centerY, double radius) {
super(centerX, centerY, radius);
}
@Override
public boolean requestSelection(boolean select) {
return true;
}
@Override
public void notifySelection(boolean select) {
if(select)
this.setFill(Color.RED);
else
this.setFill(Color.BLACK);
}
}
最后是选择处理程序 class:
public class SelectionHandler {
private Clipboard clipboard;
private EventHandler<MouseEvent> mousePressedEventHandler;
public SelectionHandler(final Parent root) {
this.clipboard = new Clipboard();
this.mousePressedEventHandler = new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent event) {
SelectionHandler.this.doOnMousePressed(root, event);
event.consume();
}
};
}
public EventHandler<MouseEvent> getMousePressedEventHandler() {
return mousePressedEventHandler;
}
private void doOnMousePressed(Parent root, MouseEvent event) {
Node target = (Node) event.getTarget();
if(target.equals(root))
clipboard.unselectAll();
if(root.getChildrenUnmodifiable().contains(target) && target instanceof SelectableNode) {
SelectableNode selectableTarget = (SelectableNode) target;
if(!clipboard.getSelectedItems().contains(selectableTarget))
clipboard.unselectAll();
clipboard.select(selectableTarget, true);
}
}
/**
* This class is based on jfxtras-labs
* <a href="https://github.com/JFXtras/jfxtras-labs/blob/8.0/src/main/java/jfxtras/labs/scene/control/window/Clipboard.java">Clipboard</a>
* and
* <a href="https://github.com/JFXtras/jfxtras-labs/blob/8.0/src/main/java/jfxtras/labs/util/WindowUtil.java">WindowUtil</a>
*/
private class Clipboard {
private ObservableList<SelectableNode> selectedItems = FXCollections.observableArrayList();
public ObservableList<SelectableNode> getSelectedItems() {
return selectedItems;
}
public boolean select(SelectableNode n, boolean selected) {
if(n.requestSelection(selected)) {
if (selected) {
selectedItems.add(n);
} else {
selectedItems.remove(n);
}
n.notifySelection(selected);
return true;
} else {
return false;
}
}
public void unselectAll() {
List<SelectableNode> unselectList = new ArrayList<>();
unselectList.addAll(selectedItems);
for (SelectableNode sN : unselectList) {
select(sN, false);
}
}
}
}
我就是这样做的...简单易行
主要Class
public class Lab05 extends Application {
@Override
public void start(Stage primaryStage) {
double width = 400;
double height = 400;
int num_of_circles = 5;
int radius_of_circles = 20;
BorderPane root = new BorderPane();
for (int i = 0; i < num_of_circles; i++) {
MyCircle temp = createCircle(radius_of_circles, (50 * i) + 1, 100);
temp.setFrame(width, height);
root.getChildren().add(temp.getCircle());
temp.getCircle().addEventFilter(MouseEvent.ANY, temp);
}
Scene scene = new Scene(root, width, height);
primaryStage.setTitle("Lab 05");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
public static MyCircle createCircle(int radius, int x, int y){
MyCircle circle = new MyCircle();
circle.setCircle(radius, x, y);
return circle;
}
}
我的圈子Class
public class MyCircle implements EventHandler<MouseEvent>{
private static double frameX = 0;
private static double frameY = 0;
private final Circle circle = new Circle();
private static final List<Circle> CIRCLES = new ArrayList<>();
public void setCircle(int radius, int x, int y){
circle.setRadius(radius);
position(x,y);
circle.setStrokeWidth(3);
circle.setStroke(Color.valueOf("white"));
CIRCLES.add(circle);
}
public void setFrame(double x, double y){
frameX = x;
frameY = y;
}
public Circle getCircle(){
return circle;
}
public void position(double x, double y){
if ( x < frameX && x > 0)
circle.setCenterX(x);
if ( y < frameY && y > 0)
circle.setCenterY(y);
}
public void selected(){
CIRCLES.stream().forEach((c) -> {
c.setStroke(Color.valueOf("white"));
});
circle.setStroke(Color.valueOf("orange"));
}
public void unselected() {
circle.setStroke(Color.valueOf("white"));
}
@Override
public void handle(MouseEvent event) {
if (event.getEventType() == MouseEvent.MOUSE_PRESSED){
selected();
}
else if (event.getEventType() == MouseEvent.MOUSE_DRAGGED){
position(event.getX(), event.getY());
}
}
}
您可以使用一些内置的 Java 功能来帮助您完成任务。
例如CSS PsuedoClasses and Toggles managed by a ToggleGroup.
没有必要这样做,您拥有的不使用这些其他 JavaFX 功能的解决方案就可以了。使用一些标准的 JavaFX 函数非常巧妙。
LightApp.java
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class LightApp extends Application {
@Override
public void start(final Stage stage) throws Exception {
final Bulb[] bulbs = {
new Bulb(),
new Bulb(),
new Bulb()
};
Scene scene = new Scene(new LightArray(bulbs));
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
LightArray.java
import javafx.geometry.Insets;
import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.HBox;
public class LightArray extends HBox {
public LightArray(Bulb... bulbs) {
super(10, bulbs);
setPadding(new Insets(10));
ToggleGroup toggleGroup = new ToggleGroup();
for (Bulb bulb: bulbs) {
bulb.setToggleGroup(toggleGroup);
}
setOnMouseClicked(event -> {
if (event.getTarget() instanceof Bulb) {
toggleGroup.selectToggle((Bulb) event.getTarget());
} else {
toggleGroup.selectToggle(null);
}
});
getStylesheets().add(
this.getClass().getResource("bulb.css").toExternalForm()
);
}
}
Bulb.java
import javafx.beans.property.*;
import javafx.css.PseudoClass;
import javafx.scene.control.*;
import javafx.scene.shape.Circle;
class Bulb extends Circle implements Toggle {
private ObjectProperty<ToggleGroup> toggleGroup = new SimpleObjectProperty<>();
Bulb() {
super(30);
getStyleClass().add("bulb");
}
@Override
public void setSelected(boolean selected) {
this.selected.set(selected);
}
@Override
public boolean isSelected() {
return selected.get();
}
@Override
public BooleanProperty selectedProperty() {
return selected;
}
public BooleanProperty selected =
new BooleanPropertyBase(false) {
@Override protected void invalidated() {
pseudoClassStateChanged(ON_PSEUDO_CLASS, get());
}
@Override public Object getBean() {
return Bulb.this;
}
@Override public String getName() {
return "on";
}
};
private static final PseudoClass
ON_PSEUDO_CLASS = PseudoClass.getPseudoClass("on");
@Override
public ToggleGroup getToggleGroup() {
return toggleGroup.get();
}
@Override
public void setToggleGroup(ToggleGroup toggleGroup) {
this.toggleGroup.set(toggleGroup);
}
@Override
public ObjectProperty<ToggleGroup> toggleGroupProperty() {
return toggleGroup;
}
}
bulb.css
.bulb {
-fx-fill: lightslategray;
}
.bulb:on {
-fx-fill: gold;
}
JavaFX 经常做的另一件常见事情(我在这里没有做过),是制作可以由 CSS 设置样式的项目(例如区域或窗格)然后将样式应用于它们。例如,不是扩展 Circle 的 Bulb,它可以扩展 StackPane,然后可以通过多层背景和 svg 形状在 css 中自定义灯泡形状(这是实现其他类似事物(例如单选按钮)的方式)。
我是 JavaFX 新手,昨天开始学习它。花了一整天的时间阅读文档,但什么也没学到...
这就是我想要做的,制作一个创建圆圈的简单 JavaFX 应用程序。点击它的笔画变成橙色(一些颜色)。在未被舔过的情况下(点击该圆圈以外的任何东西),笔划变成(某种颜色)白色。
这是我的 伪代码 目前我所拥有的。 我想制作一个单独的 class 来创建一个圆圈并处理事件(重要)。
public class Something extends Application {
@Override
public void start(Stage primaryStage) {
MyCircle c1 = new MyCircle();
c1.setCircle(20, 0, 0);
TilePane root = new TilePane();
root.getChildren().add(c1.getCircle());
//Some kind of mouse event listener, not sure which one should be
c1.getCircle().addEventListener(); //pseudo code
Scene scene = new Scene(root, 400, 400);
primaryStage.setTitle("Circle");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
这个 class 应该创建一个圆并处理所有事件,例如鼠标单击、鼠标位置、单击和拖动等等。
public class MyCircle implements EventHandler{
Circle circle = new Circle();
public void setCircle(int radius, int x, int y){
circle.setRadius(radius);
position(x,y);
circle.setStrokeWidth(3);
circle.setStroke(Color.valueOf("white"));
}
public Circle getCircle(){
return circle;
}
public void position(int x, int y){
circle.setTranslateX(x);
circle.setTranslateY(y);
}
public void selected(){
circle.setStroke(Color.valueOf("orange"));
}
public void unselected() {
circle.setStroke(Color.valueOf("white"));
}
@Override
public void handle(Event event) {
if (event == MOUSE_CLICKED){ //pseudo code
selected();
}
else if(event == MOUSE_UNCLICKED){ //pseudo code
unselected();
}
}
}
由于我是 JavaFX 的新手,因此我也非常感谢您的解释。谢谢!
编辑: 这是我的伪代码,我想将它转换成实际的工作代码。我不确定我该怎么做。任何帮助将不胜感激。
另一项编辑: 除了 3 个标记的地方外,所有内容都是代码。请在代码中查找我的评论 Psuedo Code
,我需要帮助将伪代码更改为实际代码。
处理节点的选择状态需要一些节点内不可用的知识。您将需要知道鼠标事件是否发生在其他地方(例如其他节点或根窗格),因此您可能必须向它传递有问题的参数。
一般来说,将鼠标事件处理委托给 MyCircle
并不是一个好主意。相反,最好在 class 中指定选择行为,并将选择处理委托给具有足够知识来处理问题的单独助手 class。我创建此 gist 是为了展示如何完成此任务。
public class SelectionDemo extends Application {
@Override
public void start(Stage primaryStage) {
Scene scene = new Scene(createPane(), 400, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
private Parent createPane() {
BorderPane root = new BorderPane();
SelectionHandler selectionHandler = new SelectionHandler(root);
root.addEventHandler(MouseEvent.MOUSE_PRESSED, selectionHandler.getMousePressedEventHandler());
MyCircle c1 = new MyCircle(40, 40, 20);
MyCircle c2 = new MyCircle(40, 100, 20);
MyCircle c3 = new MyCircle(40, 160, 20);
root.getChildren().addAll(c1, c2, c3);
return root;
}
public static void main(String[] args) {
launch(args);
}
}
我从jfxtras-labs借用并修改了一个接口来表示一个可选择的节点。最好有这个界面来区分可选节点和不可选节点:
/**
* This interface is based on jfxtras-labs <a href="https://github.com/JFXtras/jfxtras-labs/blob/8.0/src/main/java/jfxtras/labs/scene/control/window/SelectableNode.java">SelectableNode</a>
*/
public interface SelectableNode {
public boolean requestSelection(boolean select);
public void notifySelection(boolean select);
}
class希望被选中的用户必须实现此接口并在notifySelection方法的实现中指定他们的选择行为:
public class MyCircle extends Circle implements SelectableNode {
public MyCircle(double centerX, double centerY, double radius) {
super(centerX, centerY, radius);
}
@Override
public boolean requestSelection(boolean select) {
return true;
}
@Override
public void notifySelection(boolean select) {
if(select)
this.setFill(Color.RED);
else
this.setFill(Color.BLACK);
}
}
最后是选择处理程序 class:
public class SelectionHandler {
private Clipboard clipboard;
private EventHandler<MouseEvent> mousePressedEventHandler;
public SelectionHandler(final Parent root) {
this.clipboard = new Clipboard();
this.mousePressedEventHandler = new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent event) {
SelectionHandler.this.doOnMousePressed(root, event);
event.consume();
}
};
}
public EventHandler<MouseEvent> getMousePressedEventHandler() {
return mousePressedEventHandler;
}
private void doOnMousePressed(Parent root, MouseEvent event) {
Node target = (Node) event.getTarget();
if(target.equals(root))
clipboard.unselectAll();
if(root.getChildrenUnmodifiable().contains(target) && target instanceof SelectableNode) {
SelectableNode selectableTarget = (SelectableNode) target;
if(!clipboard.getSelectedItems().contains(selectableTarget))
clipboard.unselectAll();
clipboard.select(selectableTarget, true);
}
}
/**
* This class is based on jfxtras-labs
* <a href="https://github.com/JFXtras/jfxtras-labs/blob/8.0/src/main/java/jfxtras/labs/scene/control/window/Clipboard.java">Clipboard</a>
* and
* <a href="https://github.com/JFXtras/jfxtras-labs/blob/8.0/src/main/java/jfxtras/labs/util/WindowUtil.java">WindowUtil</a>
*/
private class Clipboard {
private ObservableList<SelectableNode> selectedItems = FXCollections.observableArrayList();
public ObservableList<SelectableNode> getSelectedItems() {
return selectedItems;
}
public boolean select(SelectableNode n, boolean selected) {
if(n.requestSelection(selected)) {
if (selected) {
selectedItems.add(n);
} else {
selectedItems.remove(n);
}
n.notifySelection(selected);
return true;
} else {
return false;
}
}
public void unselectAll() {
List<SelectableNode> unselectList = new ArrayList<>();
unselectList.addAll(selectedItems);
for (SelectableNode sN : unselectList) {
select(sN, false);
}
}
}
}
我就是这样做的...简单易行
主要Class
public class Lab05 extends Application {
@Override
public void start(Stage primaryStage) {
double width = 400;
double height = 400;
int num_of_circles = 5;
int radius_of_circles = 20;
BorderPane root = new BorderPane();
for (int i = 0; i < num_of_circles; i++) {
MyCircle temp = createCircle(radius_of_circles, (50 * i) + 1, 100);
temp.setFrame(width, height);
root.getChildren().add(temp.getCircle());
temp.getCircle().addEventFilter(MouseEvent.ANY, temp);
}
Scene scene = new Scene(root, width, height);
primaryStage.setTitle("Lab 05");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
public static MyCircle createCircle(int radius, int x, int y){
MyCircle circle = new MyCircle();
circle.setCircle(radius, x, y);
return circle;
}
}
我的圈子Class
public class MyCircle implements EventHandler<MouseEvent>{
private static double frameX = 0;
private static double frameY = 0;
private final Circle circle = new Circle();
private static final List<Circle> CIRCLES = new ArrayList<>();
public void setCircle(int radius, int x, int y){
circle.setRadius(radius);
position(x,y);
circle.setStrokeWidth(3);
circle.setStroke(Color.valueOf("white"));
CIRCLES.add(circle);
}
public void setFrame(double x, double y){
frameX = x;
frameY = y;
}
public Circle getCircle(){
return circle;
}
public void position(double x, double y){
if ( x < frameX && x > 0)
circle.setCenterX(x);
if ( y < frameY && y > 0)
circle.setCenterY(y);
}
public void selected(){
CIRCLES.stream().forEach((c) -> {
c.setStroke(Color.valueOf("white"));
});
circle.setStroke(Color.valueOf("orange"));
}
public void unselected() {
circle.setStroke(Color.valueOf("white"));
}
@Override
public void handle(MouseEvent event) {
if (event.getEventType() == MouseEvent.MOUSE_PRESSED){
selected();
}
else if (event.getEventType() == MouseEvent.MOUSE_DRAGGED){
position(event.getX(), event.getY());
}
}
}
您可以使用一些内置的 Java 功能来帮助您完成任务。
例如CSS PsuedoClasses and Toggles managed by a ToggleGroup.
没有必要这样做,您拥有的不使用这些其他 JavaFX 功能的解决方案就可以了。使用一些标准的 JavaFX 函数非常巧妙。
LightApp.java
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class LightApp extends Application {
@Override
public void start(final Stage stage) throws Exception {
final Bulb[] bulbs = {
new Bulb(),
new Bulb(),
new Bulb()
};
Scene scene = new Scene(new LightArray(bulbs));
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
LightArray.java
import javafx.geometry.Insets;
import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.HBox;
public class LightArray extends HBox {
public LightArray(Bulb... bulbs) {
super(10, bulbs);
setPadding(new Insets(10));
ToggleGroup toggleGroup = new ToggleGroup();
for (Bulb bulb: bulbs) {
bulb.setToggleGroup(toggleGroup);
}
setOnMouseClicked(event -> {
if (event.getTarget() instanceof Bulb) {
toggleGroup.selectToggle((Bulb) event.getTarget());
} else {
toggleGroup.selectToggle(null);
}
});
getStylesheets().add(
this.getClass().getResource("bulb.css").toExternalForm()
);
}
}
Bulb.java
import javafx.beans.property.*;
import javafx.css.PseudoClass;
import javafx.scene.control.*;
import javafx.scene.shape.Circle;
class Bulb extends Circle implements Toggle {
private ObjectProperty<ToggleGroup> toggleGroup = new SimpleObjectProperty<>();
Bulb() {
super(30);
getStyleClass().add("bulb");
}
@Override
public void setSelected(boolean selected) {
this.selected.set(selected);
}
@Override
public boolean isSelected() {
return selected.get();
}
@Override
public BooleanProperty selectedProperty() {
return selected;
}
public BooleanProperty selected =
new BooleanPropertyBase(false) {
@Override protected void invalidated() {
pseudoClassStateChanged(ON_PSEUDO_CLASS, get());
}
@Override public Object getBean() {
return Bulb.this;
}
@Override public String getName() {
return "on";
}
};
private static final PseudoClass
ON_PSEUDO_CLASS = PseudoClass.getPseudoClass("on");
@Override
public ToggleGroup getToggleGroup() {
return toggleGroup.get();
}
@Override
public void setToggleGroup(ToggleGroup toggleGroup) {
this.toggleGroup.set(toggleGroup);
}
@Override
public ObjectProperty<ToggleGroup> toggleGroupProperty() {
return toggleGroup;
}
}
bulb.css
.bulb {
-fx-fill: lightslategray;
}
.bulb:on {
-fx-fill: gold;
}
JavaFX 经常做的另一件常见事情(我在这里没有做过),是制作可以由 CSS 设置样式的项目(例如区域或窗格)然后将样式应用于它们。例如,不是扩展 Circle 的 Bulb,它可以扩展 StackPane,然后可以通过多层背景和 svg 形状在 css 中自定义灯泡形状(这是实现其他类似事物(例如单选按钮)的方式)。