• JavaFx自定义事件


    关于JavaFx自定义事件: 

    JavaFX Documentation Projecthttps://fxdocs.github.io/docs/html5/index.html#_event_handling上面的文档已经做了简要说明,但是在实际应用中发现其并不够详细,搜索现有网上的自定义事件其内容大都并不十分清晰,因此写篇博客站在我的角度描述一下这个问题,我这里使用的JDK8

    首先我的需求:

    如图所示,需求十分清晰,就是做一个点击按钮计数器,当点击按钮,下面的计数器的数字会发生变化。

    实现方式一: 

    1. import javafx.application.Application;
    2. import javafx.scene.Scene;
    3. import javafx.scene.control.Button;
    4. import javafx.scene.control.Label;
    5. import javafx.scene.layout.VBox;
    6. import javafx.stage.Stage;
    7. import java.util.concurrent.atomic.AtomicInteger;
    8. public class EventTestApp extends Application {
    9. public static AtomicInteger atomicInteger = new AtomicInteger();
    10. public static final String labelPrefix = "点击次数:";
    11. @Override
    12. public void start(Stage primaryStage) throws Exception {
    13. Button btn = new Button();
    14. btn.setText("点击加一");
    15. Label label = new Label(labelPrefix + "0");
    16. btn.setOnAction(event -> {
    17. // 设置Label显示文字
    18. label.setText(labelPrefix + atomicInteger.incrementAndGet());
    19. });
    20. VBox root = new VBox();
    21. root.getChildren().addAll(btn, label);
    22. Scene scene = new Scene(root, 300, 250);
    23. primaryStage.setTitle("javaFx自定义事件测试");
    24. primaryStage.setScene(scene);
    25. primaryStage.show();
    26. }
    27. public static void main(String[] args) {
    28. launch(args);
    29. }
    30. }

    实现方式非常简单,就是当按钮发生点击时引用Label实例,然后设置Label的值,同时也可以看到一些缺陷: 就是Label的初始化必须在Button的前面。

    实现方式二:

    1. import javafx.application.Application;
    2. import javafx.scene.Scene;
    3. import javafx.scene.control.Button;
    4. import javafx.scene.control.Label;
    5. import javafx.scene.layout.VBox;
    6. import javafx.stage.Stage;
    7. import java.util.concurrent.atomic.AtomicInteger;
    8. public class EventTestApp extends Application {
    9. public static AtomicInteger atomicInteger = new AtomicInteger();
    10. public static final String labelPrefix = "点击次数:";
    11. @Override
    12. public void start(Stage primaryStage) throws Exception {
    13. Button btn = new Button();
    14. btn.setText("点击加一");
    15. btn.setOnAction(event -> {
    16. UserEvent userEvent = new UserEvent(UserEvent.CLICKED);
    17. // 发射自定义事件
    18. btn.fireEvent(userEvent);
    19. });
    20. Label label = new Label(labelPrefix + "0");
    21. // 添加自定义事件处理方法
    22. label.addEventFilter(UserEvent.ANY,event -> {
    23. // 设置Label显示文字
    24. label.setText(labelPrefix + atomicInteger.incrementAndGet());
    25. });
    26. VBox root = new VBox();
    27. root.getChildren().addAll(btn, label);
    28. Scene scene = new Scene(root, 300, 250);
    29. primaryStage.setTitle("javaFx自定义事件测试");
    30. primaryStage.setScene(scene);
    31. primaryStage.show();
    32. }
    33. public static void main(String[] args) {
    34. launch(args);
    35. }
    36. }

    1. import javafx.event.Event;
    2. import javafx.event.EventType;
    3. public class UserEvent extends Event {
    4. public static final EventType ANY = new EventType<>(Event.ANY, "ANY");
    5. public static final EventType CLICKED = new EventType<>(ANY,"CLICKED");
    6. public UserEvent(EventType eventType) {
    7. super(eventType);
    8. }
    9. }

    由于这种方式我们使用了自定义事件,因此创建了自定义事件的类,当按钮点击时,创建自定义事件,然后发送事件,同时在Lable初始化后监听了该事件,此时我们发现Button与Lable实现了解耦。

    然而一切看起来十分美好,但是当点击按钮时,却发现Label并未做出任何反应,此时按照开头文档,应该是没有问题的才对。

    开启Debug模式,调试 

    1. // 发射自定义事件
    2. btn.fireEvent(userEvent);

    看他做了什么。

    简单的调试过后,我们发现其实调用的是 com.sun.javafx.event.EventUtil.fireEvent()方法,他其实是一个静态方法。

    EventUtil类

    1. public static Event fireEvent(EventTarget eventTarget, Event event) {
    2. // 通过调试发现,此时event.getTarget()为null,而eventTarget为button
    3. if (event.getTarget() != eventTarget) {
    4. // 顾名思义,似乎是一个事件拷贝的方法
    5. event = event.copyFor(event.getSource(), eventTarget);
    6. // 重要的是当该方法执行完成event.getTarget()竟然指向了button,也就是说事件发射源和接收者指向了同一个组件
    7. }
    8. if (eventDispatchChainInUse.getAndSet(true)) {
    9. // the member event dispatch chain is in use currently, we need to
    10. // create a new instance for this call
    11. return fireEventImpl(new EventDispatchChainImpl(),
    12. eventTarget, event);
    13. }
    14. try {
    15. return fireEventImpl(eventDispatchChain, eventTarget, event);
    16. } finally {
    17. // need to do reset after use to remove references to event
    18. // dispatchers from the chain
    19. eventDispatchChain.reset();
    20. eventDispatchChainInUse.set(false);
    21. }
    22. }

    我把调试的发现写在了代码里,由于事件发射源与事件接收者是同一个组件,那么我就可以直接给button添加EventHandler。

    于是代码更新为:

    1. import javafx.application.Application;
    2. import javafx.scene.Scene;
    3. import javafx.scene.control.Button;
    4. import javafx.scene.control.Label;
    5. import javafx.scene.layout.VBox;
    6. import javafx.stage.Stage;
    7. import java.util.concurrent.atomic.AtomicInteger;
    8. public class EventTestApp extends Application {
    9. public static AtomicInteger atomicInteger = new AtomicInteger();
    10. public static final String labelPrefix = "点击次数:";
    11. @Override
    12. public void start(Stage primaryStage) throws Exception {
    13. Button btn = new Button();
    14. btn.setText("点击加一");
    15. btn.setOnAction(event -> {
    16. System.out.println("btn发送事件");
    17. UserEvent userEvent = new UserEvent(UserEvent.CLICKED);
    18. // 发射自定义事件
    19. btn.fireEvent(userEvent);
    20. });
    21. btn.addEventHandler(UserEvent.ANY,event -> {
    22. System.out.println("btn接收到事件");
    23. });
    24. Label label = new Label(labelPrefix + "0");
    25. // 添加自定义事件处理方法
    26. label.addEventFilter(UserEvent.ANY,event -> {
    27. // 设置Label显示文字
    28. label.setText(labelPrefix + atomicInteger.incrementAndGet());
    29. System.out.println("Label接收到事件");
    30. });
    31. VBox root = new VBox();
    32. root.getChildren().addAll(btn, label);
    33. Scene scene = new Scene(root, 300, 250);
    34. primaryStage.setTitle("javaFx自定义事件测试");
    35. primaryStage.setScene(scene);
    36. primaryStage.show();
    37. }
    38. public static void main(String[] args) {
    39. launch(args);
    40. }
    41. }

    运行代码,并且点击按钮,得到结果是

    发现果然是button把事件发送给了自己。

    那么问题就来了,为什么button会把事件发送给自己,而不是Label呢,其实通过调试我们也可以发现,一个重要的参数是EventTarget。而EventTarget是一个接口。

    EventTarget类
    1. public interface EventTarget {
    2. EventDispatchChain buildEventDispatchChain(EventDispatchChain tail);
    3. }

    然后找EventTarget的子类,发现都是一些control下的类,比如Button、Pane、Box等组件。

    那么是不是我们在发射组件时指定EventTarget就可以了呢?已知,EventTarget的子类时control下的类,那么我们的Label应该也是EventTarget的实例

    于是启动类变更为:

    1. import javafx.application.Application;
    2. import javafx.event.Event;
    3. import javafx.scene.Scene;
    4. import javafx.scene.control.Button;
    5. import javafx.scene.control.Label;
    6. import javafx.scene.layout.VBox;
    7. import javafx.stage.Stage;
    8. import java.util.concurrent.atomic.AtomicInteger;
    9. public class EventTestApp extends Application {
    10. public static AtomicInteger atomicInteger = new AtomicInteger();
    11. public static final String labelPrefix = "点击次数:";
    12. @Override
    13. public void start(Stage primaryStage) throws Exception {
    14. Button btn = new Button();
    15. btn.setText("点击加一");
    16. Label label = new Label(labelPrefix + "0");
    17. btn.setOnAction(event -> {
    18. System.out.println("btn发送事件");
    19. UserEvent userEvent = new UserEvent(UserEvent.CLICKED);
    20. // 发射自定义事件
    21. btn.fireEvent(userEvent);
    22. Event.fireEvent(label,userEvent);
    23. });
    24. btn.addEventHandler(UserEvent.ANY,event -> {
    25. System.out.println("btn接收到事件");
    26. });
    27. // 添加自定义事件处理方法
    28. label.addEventFilter(UserEvent.ANY,event -> {
    29. // 设置Label显示文字
    30. label.setText(labelPrefix + atomicInteger.incrementAndGet());
    31. System.out.println("Label接收到事件");
    32. });
    33. VBox root = new VBox();
    34. root.getChildren().addAll(btn, label);
    35. Scene scene = new Scene(root, 300, 250);
    36. primaryStage.setTitle("javaFx自定义事件测试");
    37. primaryStage.setScene(scene);
    38. primaryStage.show();
    39. }
    40. public static void main(String[] args) {
    41. launch(args);
    42. }
    43. }

    启动,点击按钮:

    发现Label监听到了事件,但是缺点也是十分明显,就是Label的初始化依然在Button的前面,似乎又回到开始的地方,那么有没有现成的方案去获取
    EventTarget呢,其实是有的,通过kookup()方法进行查找,不过在查找对应EventTarget之前,需要先将EventTarget设置一个标识,即通过setId()方法。此时启动类变更为:

    1. import javafx.application.Application;
    2. import javafx.event.Event;
    3. import javafx.scene.Node;
    4. import javafx.scene.Scene;
    5. import javafx.scene.control.Button;
    6. import javafx.scene.control.Label;
    7. import javafx.scene.layout.VBox;
    8. import javafx.stage.Stage;
    9. import java.util.concurrent.atomic.AtomicInteger;
    10. public class EventTestApp extends Application {
    11. public static AtomicInteger atomicInteger = new AtomicInteger();
    12. public static final String labelPrefix = "点击次数:";
    13. @Override
    14. public void start(Stage primaryStage) throws Exception {
    15. Button btn = new Button();
    16. btn.setText("点击加一");
    17. btn.setOnAction(event -> {
    18. System.out.println("btn发送事件");
    19. UserEvent userEvent = new UserEvent(UserEvent.CLICKED);
    20. // 发射自定义事件
    21. // btn.fireEvent(userEvent);
    22. Node lookup = btn.getScene().lookup("#test-label");
    23. Event.fireEvent(lookup,userEvent);
    24. });
    25. btn.addEventHandler(UserEvent.ANY,event -> {
    26. System.out.println("btn接收到事件");
    27. });
    28. Label label = new Label(labelPrefix + "0");
    29. // 设置EventTarget的id
    30. label.setId("test-label");
    31. // 添加自定义事件处理方法
    32. label.addEventFilter(UserEvent.ANY,event -> {
    33. // 设置Label显示文字
    34. label.setText(labelPrefix + atomicInteger.incrementAndGet());
    35. System.out.println("Label接收到事件");
    36. });
    37. VBox root = new VBox();
    38. root.getChildren().addAll(btn, label);
    39. Scene scene = new Scene(root, 300, 250);
    40. primaryStage.setTitle("javaFx自定义事件测试");
    41. primaryStage.setScene(scene);
    42. primaryStage.show();
    43. }
    44. public static void main(String[] args) {
    45. launch(args);
    46. }
    47. }

    启动,执行结果为:

    此时Label组件执行了,而button的监听并没有执行,这说明如果指定了EventTarget那么只有指定的EventTarget才能被触发,当然如何让button也能执行呢,其实我们只需要发送多次事件即可,如同代码中的注释。

    同时可以看到在使用lookup()方法之前,需要先执行getScene()方法,这是因为寻找EventTarget是从上到下寻找,因此我们从Scene开始找就一定可以找到。

    关于javaFx的结构图,我从网上找了一张。

    同时采用了lookup()方法,组件间不需要相互持有引用,因此组件初始化顺序就变得灵活了。

    以上我们的代码是直接写在启动类中的,其实这并不符合我们的开发直觉,因为不管CS程序还是BS程序,都有一些公共的区域,比如页头区域,页尾,按钮组区域等,因此我们需要把内容单独写到一个组件中。

    自定义一个组件,把内容放在自定义组件中。

    1. import javafx.scene.control.Button;
    2. import javafx.scene.control.Label;
    3. import javafx.scene.layout.VBox;
    4. import java.util.concurrent.atomic.AtomicInteger;
    5. public class CustomPane extends VBox {
    6. public static AtomicInteger atomicInteger = new AtomicInteger();
    7. public static final String labelPrefix = "点击次数:";
    8. public CustomPane() {
    9. Button btn = new Button();
    10. btn.setText("点击加一");
    11. btn.setOnAction(event -> {
    12. System.out.println("btn发送事件");
    13. UserEvent userEvent = new UserEvent(UserEvent.CLICKED);
    14. // 发射自定义事件
    15. btn.fireEvent(userEvent);
    16. });
    17. btn.addEventHandler(UserEvent.ANY,event -> {
    18. System.out.println("btn接收到事件");
    19. });
    20. Label label = new Label(labelPrefix + "0");
    21. // 添加自定义事件处理方法
    22. label.addEventFilter(UserEvent.ANY,event -> {
    23. // 设置Label显示文字
    24. label.setText(labelPrefix + atomicInteger.incrementAndGet());
    25. System.out.println("Label接收到事件");
    26. });
    27. getChildren().addAll(btn, label);
    28. // 自定义组件添加监听
    29. addEventHandler(UserEvent.ANY,e->{
    30. System.out.println("自定义组件接收到事件");
    31. // 设置Label显示文字
    32. label.setText(labelPrefix + atomicInteger.incrementAndGet());
    33. });
    34. }
    35. }

     启动类精简为:

    1. import javafx.application.Application;
    2. import javafx.scene.Scene;
    3. import javafx.stage.Stage;
    4. public class EventTestApp extends Application {
    5. @Override
    6. public void start(Stage primaryStage) throws Exception {
    7. Scene scene = new Scene(new CustomPane(), 300, 250);
    8. primaryStage.setTitle("javaFx自定义事件测试");
    9. primaryStage.setScene(scene);
    10. primaryStage.show();
    11. }
    12. public static void main(String[] args) {
    13. launch(args);
    14. }
    15. }

    启动,点击按钮得到结果:

    Label没有监听到事件在意料之中,因为上面代码在发射事件时并没有指定EventTarget,但是在自定义组件的代码里同时设置了监听,结果也监听到了。并且由于Label是在自定义组件中进行初始化的,那么自定义组件本身自然可以也引用的到Label。

    那么为什么自定义组件本身也可以监听的到事件呢?

    继续调试:

    在com.sun.javafx.event.EventUtil类中找到该方法:

    1. private static Event fireEventImpl(EventDispatchChain eventDispatchChain,
    2. EventTarget eventTarget,
    3. Event event) {
    4. // eventTarget.buildEventDispatchChain(eventDispatchChain)是个十分重要的方法,即构建事件分发链。
    5. // 构建好后的分发链包含了Buuton的父节点即自定义组件,因此自定义组件也可以监听事件,可以试试在启动类
    6. // 中添加addEventHandler(),其实是添加不上的。
    7. final EventDispatchChain targetDispatchChain =
    8. eventTarget.buildEventDispatchChain(eventDispatchChain);
    9. return targetDispatchChain.dispatchEvent(event);
    10. }

    我们看看是如何构建事件分发链的。

    javafx.scene.Node类
    1. public EventDispatchChain buildEventDispatchChain(
    2. EventDispatchChain tail) {
    3. if (preprocessMouseEventDispatcher == null) {
    4. preprocessMouseEventDispatcher = (event, tail1) -> {
    5. event = tail1.dispatchEvent(event);
    6. if (event instanceof MouseEvent) {
    7. preprocessMouseEvent((MouseEvent) event);
    8. }
    9. return event;
    10. };
    11. }
    12. tail = tail.prepend(preprocessMouseEventDispatcher);
    13. // prepend all event dispatchers from this node to the root
    14. Node curNode = this;
    15. do {
    16. if (curNode.eventDispatcher != null) {
    17. final EventDispatcher eventDispatcherValue =
    18. curNode.eventDispatcher.get();
    19. if (eventDispatcherValue != null) {
    20. tail = tail.prepend(eventDispatcherValue);
    21. }
    22. }
    23. // 重点是这个方法
    24. final Node curParent = curNode.getParent();
    25. curNode = curParent != null ? curParent : curNode.getSubScene();
    26. } while (curNode != null);
    27. if (getScene() != null) {
    28. // prepend scene's dispatch chain
    29. tail = getScene().buildEventDispatchChain(tail);
    30. }
    31. return tail;
    32. }

    这个方法是Node类即节点类,我们在方法里面看到了getParent()方法,疑问也就可以解答了,在构建了事件分发链时取了父节点。同时我们在看看节点链类的大致结构:

    com.sun.javafx.event.EventDispatchChainImpl

    1. public class EventDispatchChainImpl implements EventDispatchChain {
    2. /** Must be a power of two. */
    3. private static final int CAPACITY_GROWTH_FACTOR = 8;
    4. private EventDispatcher[] dispatchers;
    5. private int[] nextLinks;
    6. private int reservedCount;
    7. private int activeCount;
    8. private int headIndex;
    9. private int tailIndex;
    10. public EventDispatchChainImpl() {
    11. }
    12. /** 略 */
    13. /**
    14. * 重点方法 事件分发
    15. */
    16. @Override
    17. public Event dispatchEvent(final Event event) {
    18. if (activeCount == 0) {
    19. return event;
    20. }
    21. // push current state
    22. final int savedHeadIndex = headIndex;
    23. final int savedTailIndex = tailIndex;
    24. final int savedActiveCount = activeCount;
    25. final int savedReservedCount = reservedCount;
    26. final EventDispatcher nextEventDispatcher = dispatchers[headIndex];
    27. headIndex = nextLinks[headIndex];
    28. --activeCount;
    29. // 重点
    30. final Event returnEvent =
    31. nextEventDispatcher.dispatchEvent(event, this);
    32. // pop saved state
    33. headIndex = savedHeadIndex;
    34. tailIndex = savedTailIndex;
    35. activeCount = savedActiveCount;
    36. reservedCount = savedReservedCount;
    37. return returnEvent;
    38. }
    39. }

    里面包含了需要分发的数组。还有一个分发函数dispatchEvent()。它最终又调用了其他方法。

    第三种方式:

    通过第二种方式,其实就已经讲明白了事件分发是怎么回事,其实第三种方式是用来说明我认为最合适的方式,由于前面已经说明了EventTarget,那么我们在日常开发时,尽量要做到组件化,比如按钮组就是一个组件,里面是很多的按钮,内容显示区是单独的一个组件,因此,我们创建两个自定义组件,一个组件专门用来放置按钮,一个组件专门用来控制Label,按钮组件发射事件,Label组件监听组件并响应变化。

    自定义按钮组件

    1. import com.sun.javafx.event.EventUtil;
    2. import javafx.geometry.Insets;
    3. import javafx.geometry.Pos;
    4. import javafx.scene.Node;
    5. import javafx.scene.control.Button;
    6. import javafx.scene.layout.Background;
    7. import javafx.scene.layout.BackgroundFill;
    8. import javafx.scene.layout.CornerRadii;
    9. import javafx.scene.layout.HBox;
    10. import javafx.scene.paint.Color;
    11. /**
    12. * 自定义Button组件
    13. */
    14. public class ButtonPane extends HBox {
    15. public ButtonPane() {
    16. setPrefHeight(30);
    17. setAlignment(Pos.CENTER);
    18. // 设置子组件间距
    19. setSpacing(30);
    20. Color blue = Color.BLUE;
    21. // 四个角半径即填充有弧度
    22. CornerRadii cornerRadii = new CornerRadii(0);
    23. Insets insets = new Insets(0, 0, 0, 0);
    24. BackgroundFill backgroundFill = new BackgroundFill(blue, cornerRadii, insets);
    25. setBackground(new Background(backgroundFill));
    26. Button btn = new Button("点击+1");
    27. Button btn2 = new Button("生成随机数");
    28. btn.setOnAction(event -> {
    29. // 注意构造方法中选择正确的事件类型
    30. UserEvent userEvent = new UserEvent(UserEvent.CLICKED);
    31. Node eventTarget = btn.getScene().lookup("#LabelPane");
    32. // 发射自定义事件(点击+1)
    33. EventUtil.fireEvent(eventTarget, userEvent);
    34. });
    35. btn2.setOnAction(event -> {
    36. // 注意构造方法中选择正确的事件类型
    37. UserEvent userEvent = new UserEvent(UserEvent.RANDOM);
    38. Node eventTarget = btn2.getScene().lookup("#LabelPane");
    39. // 发射自定义事件(随机数)
    40. EventUtil.fireEvent(eventTarget, userEvent);
    41. });
    42. getChildren().addAll(btn, btn2);
    43. }
    44. }

    自定义Label组件

    1. import javafx.geometry.Insets;
    2. import javafx.geometry.Pos;
    3. import javafx.scene.control.Label;
    4. import javafx.scene.layout.Background;
    5. import javafx.scene.layout.BackgroundFill;
    6. import javafx.scene.layout.CornerRadii;
    7. import javafx.scene.layout.HBox;
    8. import javafx.scene.paint.Color;
    9. import java.util.Random;
    10. import java.util.concurrent.atomic.AtomicInteger;
    11. /**
    12. * 自定义Label组件
    13. */
    14. public class LabelPane extends HBox {
    15. public static AtomicInteger atomicInteger = new AtomicInteger();
    16. public static final String labelPrefix = "点击次数:";
    17. public static final String label2Prefix = "随机数:";
    18. public LabelPane() {
    19. setId("LabelPane");
    20. setPrefHeight(30);
    21. setAlignment(Pos.CENTER);
    22. // 设置子组件间距
    23. setSpacing(30);
    24. Color green = Color.GREEN;
    25. // 四个角半径即填充有弧度
    26. CornerRadii cornerRadii = new CornerRadii(0);
    27. Insets insets = new Insets(0, 0, 0, 0);
    28. BackgroundFill backgroundFill = new BackgroundFill(green, cornerRadii, insets);
    29. setBackground(new Background(backgroundFill));
    30. Label label = new Label(labelPrefix + "0");
    31. Label label2 = new Label(label2Prefix + "0");
    32. // 添加自定义事件处理方法(点击+1),注意此处类型要设置正确
    33. addEventHandler(UserEvent.CLICKED, event -> {
    34. // 设置Label显示文字
    35. label.setText(labelPrefix + atomicInteger.incrementAndGet());
    36. });
    37. // 添加自定义事件处理方法(随机数),注意此处类型要设置正确
    38. addEventHandler(UserEvent.RANDOM, event -> {
    39. // 设置Label显示文字
    40. label2.setText(label2Prefix + new Random().nextInt());
    41. });
    42. getChildren().addAll(label, label2);
    43. }
    44. }

    自定义事件类:

    1. import javafx.event.Event;
    2. import javafx.event.EventType;
    3. /**
    4. * 自定义事件类
    5. */
    6. public class UserEvent extends Event {
    7. public static final EventType ANY = new EventType<>(Event.ANY, "ANY");
    8. /**
    9. * 点击+1 事件类型
    10. */
    11. public static final EventType CLICKED = new EventType<>(ANY,"CLICKED");
    12. /**
    13. * 随机数 事件类型
    14. */
    15. public static final EventType RANDOM = new EventType<>(ANY,"RANDOM");
    16. public UserEvent(EventType eventType) {
    17. super(eventType);
    18. }
    19. }

    启动类

    1. import javafx.application.Application;
    2. import javafx.scene.Scene;
    3. import javafx.scene.layout.VBox;
    4. import javafx.stage.Stage;
    5. public class EventTestApp extends Application {
    6. @Override
    7. public void start(Stage primaryStage) throws Exception {
    8. VBox root = new VBox();
    9. // 初始化自定义组件
    10. ButtonPane buttonPane = new ButtonPane();
    11. LabelPane labelPane = new LabelPane();
    12. // 将初始化好的组件,放入到根布局中
    13. root.getChildren().addAll(buttonPane, labelPane);
    14. // 根布局设置到场景中
    15. Scene scene = new Scene(root, 300, 250);
    16. primaryStage.setTitle("javaFx自定义事件测试");
    17. primaryStage.setScene(scene);
    18. primaryStage.show();
    19. }
    20. public static void main(String[] args) {
    21. launch(args);
    22. }
    23. }

    最终效果展示:

     

  • 相关阅读:
    学习八股文的知识点~~1
    谈谈HMI 的自动化生成技术
    Python爬虫-IP隐藏技术与代理爬取
    Android相机调用-CameraX【外接摄像头】【USB摄像头】
    【论文精读】Attention is all you need
    Android 13.0 首次开机进入Launcher3前黑屏几秒的几种情况问题的总结
    Django实现音乐网站 ⒂
    排序算法-堆排序和TopK算法
    【前端】前端权限管理的实现方式:基于Vue项目的详细指南
    基于PHP+MySQL学生创新作品展示系统的设计与实现
  • 原文地址:https://blog.csdn.net/kanyun123/article/details/127814063