Platform.runLater 调用函数时 JavaFX 图像没有改变

JavaFX image not changing when function is called by Platform.runLater

我有一个函数可以在 JavaFX GUI 中更改图像及其不透明度:

private static Image image = null;
private static ImageView imageView = new ImageView();
   
// some code to add image in GUI

public static void changeImage() {
    imageView.setOpacity(0.5);
    imageView.setImage(null);
}

当我在 JavaFX 实例中调用此函数时,如果我对 setImage() 使用图像而不是 null,图像会消失或发生变化。我尝试通过按下按钮来调用该函数。

在这种情况下,一切都如我所料。

当我从另一个 class 调用此函数时,实际图像会改变其不透明度,但图像本身永远不会改变。我按以下方式调用该函数:

public static void changeImg() {
    Platform.runLater(() -> FX_Gui.changeImage());
}

更改标签、进度条...都可以,但我没能更改图像。

同时我发现没有更改图像的原因是我 运行 changeImage() 在 GUI 初始化完成之前。如果我在发送 changeImage() 命令之前等待大约 500 毫秒,一切正常。

下面是演示我遇到的问题的最少代码:

import javafx.application.Application;
import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;

public class Control_Min {

    public static void changeImg() {
        Platform.runLater(() -> Fx_Min.changeImage());
    }
    
    public static void main(String[] args) {
        new Thread() {
            public void run() {
                Application.launch(Fx_Min.class);
            }
        }.start();
        // JFXPanel will initialize the JavaFX toolkit. 
        new JFXPanel();  
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        changeImg();        
    }
}

以及 Gui 本身:

public class Fx_Min extends Application {
    private static Stage stage;
    private static GridPane rootPane;
    private static Scene scene;
    private static Image image = null;  
    private static ImageView imageView = new ImageView();
    
    @Override
    public void start(Stage primaryStage) {
        stage = primaryStage;
        rootPane = new GridPane();
        scene = new Scene(rootPane,800,600);            
        try {
            image = new Image(new FileInputStream("C:\Users\Peter\eclipse-workspace\FX_Test\src\application\Image1.jpg"));
        } catch (FileNotFoundException e1) {
            e1.printStackTrace();
        }
        imageView.setImage(image);
        rootPane.add(imageView, 1, 0);  
        stage.setScene(scene);
        stage.setResizable(true);
        stage.show();   
        System.out.println("Gui is ready");
    }
    
    public static void changeImage() {
        try {
            image = new Image(new FileInputStream("C:\Users\Peter\eclipse-workspace\FX_Test\src\application\Image2.jpg"));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        imageView.setImage(image);
        System.out.println("Image Changed");
    }
    
    public static void main(String[] args) {
        launch(args);
    }
}

这段代码工作正常。 在控制台中我得到:
桂已准备就绪
图片已更改

当我删除 Thread.sleep(500) 时,图像不会改变。 在控制台中我得到:
图像变化
桂已准备就绪
我的结论是我在FX运行time初始化之前发送了运行later方法
(还没有解决静态问题,因为这不是问题。我稍后会在我的原始程序中解决。)

我的任务如下:
我在我的 PC 上为我的网络广播播放器编写了一个 GUI。 GUI 控制收音机并轮询正在播放的内容。 我也想用红外遥控器控制收音机。 我已经有一个与遥控器通信的 Raspberry Pi。
因此,我的计划是 运行 PC 上的服务器套接字,它接收来自 Raspberry Pi 的命令。 服务器将 运行 在它自己的线程中。我想使用 运行Later 命令来更新 GUI。 有没有更好的方法从服务器更新 GUI? 目标是当我按下遥控器上的按钮时,GUI 将立即更新。
根据我对 JavaFX 的最新了解,我现在将直接在 FX class 中启动应用程序,并从 FX class

启动服务器线程

这个问题有很多方面没有意义。

一般来说,JavaFX 中的 GUI 在其执行过程中是独立的和非线性的。编写外部方法以假设 GUI 的某些状态,然后根据该假设直接操作 GUI 不是正确的方法。因此,任何通过混入 Thread.sleep() 调用来 了解 GUI 状态的尝试本质上都是不正确的。

不需要 new JFXPanel() 调用,因为 Application.launch() 将初始化 JavaFX。据推测,这是在放入 sleep(500) 之前添加的,因为如果 运行 在 Thread.start() 命令之后立即调用 changeImg() 会失败,因为 launch() 不会还没来得及开始。

如前所述,应该在 FX_Min.start(Stage) 方法中完成在屏幕完成初始化后更换某种启动图像,尽管您甚至不太可能看到第一张图像。

这个问题似乎旨在设计一种应用程序,其中 GUI 只是其中的一小部分,主应用程序将继续进行冗长的处理,然后触发 GUI 以响应该处理的结果。或者也许主应用程序正在监视外部 API 并定期向 GUI 提供更新。然而,在大多数情况下,GUI 通常会被初始化,以便它可以控制操作,启动后台线程来进行冗长的处理,并使用 JavaFX 工具来处理 GUI 更新的触发和结果的接收。

如果设计确实需要以GUI以外的东西作为中央控制,那么使用Application似乎并不合适。毕竟,它旨在控制 Application,并在 GUI 启动后监视 GUI 的状态,以便在 GUI 关闭时关闭所有内容。这就是为什么 OP 必须将 Application.launch() 调用放在单独的线程中 - launch() 在 GUI 关闭之前不会 return。

如果 GUI 之外的应用程序要控制一切,那么最好使用 Platform.startup() 手动启动 JavaFX,并手动处理所有监控。以下代码不进行任何监控,但它确实启动了 GUI 并毫无问题地更改了图像:

public class Control_Min {

    public static void main(String[] args) {
        Platform.startup(() -> new Fx_Min().start(new Stage()));
        Platform.runLater(() -> Fx_Min.changeImage());
    }
}

请注意,Fx_Min 中的 OP 代码无需更改。但是,Fx_Min 没有理由再扩展 Application,其 start() 方法中的代码可以放在任何地方。

还应注意,尽管这可行,但它确实超出了 JavaFX 应用程序的标准。 OP 的情况可能确实需要这种架构,但这会将其放入极少数应用程序中。围绕 Application.launch() 设计应用程序并通过提供的 JavaFX 工具在后台线程中启动冗长的处理几乎总是更好的方法。


好的,鉴于来自 OP 的新信息,很明显这应该基于 Application 并且 GUI 应该启动某种可能会阻止等待输入的套接字侦听器。

任何阻塞都不能在 FXAT 上 运行,并且需要有一种方法允许套接字侦听器在接收数据时与 GUI 通信。理想情况下,套接字侦听器应该是 JavaFX 不知道的,只是普通的 Java.

IMO,最好的方法是提供一个 Consumer 来接受来自套接字侦听器的信息,并在其构造函数中将其传递给套接字侦听器。这样,GUI 对套接字侦听器的性质一无所知,除了它依赖于需要消息消费者。同样,套接字侦听器不知道调用它的是什么,只是它给了它一个消息消费者。

这限制了耦合,您可以自由编写 GUI 而不必担心套接字侦听器的任何内部工作,反之亦然。

这是 GUI,经过清理和简化,以便更容易理解套接字侦听器的内容。基本上,GUI 只是将来自套接字侦听器的消息扔到屏幕上已经存在的 Text 中。消息使用者处理 Platform.runLater() 以便套接字侦听器甚至不知道它:

public class Fx_Min extends Application {

    @Override
    public void start(Stage primaryStage) {
        ImageView imageView = new ImageView(new Image("/images/ArrowUp.png"));
        Text text = new Text("");
        primaryStage.setScene(new Scene(new VBox(10, imageView, text), 800, 600));
        primaryStage.setResizable(true);
        primaryStage.show();
        imageView.setImage(new Image("/images/Flag.png"));
        new SocketListener(socketMessage -> Platform.runLater(() -> text.setText(socketMessage))).startListening();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

这是套接字侦听器。显然,这不会监听套接字,但它会围绕 sleep() 循环以模拟 Pi 上发生的动作。这里的消息格式是 String,只是为了保持一切简单,但显然这是实际实现的更糟糕的选择。建立一个特殊的消息class:

public class SocketListener {

    private Consumer<String> messageConsumer;

    public SocketListener(Consumer<String> messageConsumer) {
        this.messageConsumer = messageConsumer;
    }

    public void startListening() {
        Thread listenerThread = new Thread(() -> listenForIRCommand());
        listenerThread.setDaemon(true);
        listenerThread.start();
    }

    private void listenForIRCommand() {
        for (int x = 0; x < 100; x++) {
            try {
                Thread.sleep(5000);
                messageConsumer.accept("Station " + x);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

很明显,由于对 listenForIRCommand() 的调用是从后台线程内部执行的,因此它完全不受任何 JavaFX 约束的影响。 Java 中通常可能的任何事情都可以从那里完成,而不必担心它对 GUI 的影响。