• javaFX在非FX线程中更新UI


       如果使用了javaFX的FXML开发方式,那么就会非常明确的感受到MVC模式的气息,在FX程序运行的时候,我们的FX线程是保持在主线程里的,但是当我们在处理事件的时候想更新界面中元素的状态时,经常会遇到错误提示:在非FX线程中更新了UI。这个时候该如何解决呢?

      解决方法有两种:1、Platform.runLater(()->{........});方式       2、使用Task方式。

      1、 Platform.runLater方式

      我一开始是使用Platform.runLater方式解决的,但是很多时候我们更新的UI内容很可能是controller里面处理的变量,而Platform.runLater不允许使用非final的变量,所以问题就来了,你想在UI上表现出来你的处理进度的时候这种方式就不行。

      

    @FXML
    private Text progressText;//界面上用来显示处理进度的Text
    @FXML
    private Button startBtn;//用来启动任务处理的按钮
    @FXML
    private void initialize(){
        startBtn.setOnAction(e->startHandler());//注册按钮处理事件
    }
    private void startHandler(){
        for(int a=0;a<100;a++){
            Platform.runLater(()->{
                progressText.setText("处理进度("+(a+1)+"/100):"+a);//其实这里就已经报错了
            });
            Thread.sleep(300)
        }
    }
    

      如果按照以上的方式运行,除非变量a是final的,否则编译无法通过,但是如果a是final的,我的循环该怎么执行?

      或许你可以想到变一下,把Platform.runLater放在外面,for放在里面,如果你想尝试,可以自行尝试,我可以告诉你的是,你可以编译通过了,而且看起来也没啥问了,但是啃爹的就是你会看到界面上的变化是那个Text的内容直接变为for循环最后一次循环后的结果,而不会看到它随着循环的执行而动态的变!

      

      2、使用Task方式(正确的姿势)

      其实使用Task的方式才是解决这种问题的根本,至少我学到现在,对我来说是这样的。那么下面例子,看看Task方式如何解决。

      FXMLDocumentController.java

      

    public class FXMLDocumentController implements Initializable {
        
        @FXML
        private ProgressBar process1;
        @FXML
        private ProgressIndicator process2;
        @FXML
        private Text process3;
        @FXML
        private Button bt1;
        
        //按钮事件处理函数
        private void handleButtonAction(ActionEvent event) {
            Service<String> service=new Service<String>() {
                @Override
                protected Task<String> createTask() {
                    return new Task<String>() {
                        @Override
                        protected String call() throws Exception {
                           for(int a=1;a<=100;a++){
                               //更新service的value属性
                               updateValue("process:"+a+"%");
                               //更新service的progress属性
                               updateProgress(a, 100d);
                               Thread.sleep(100);
                           }
                            return "success";
                        }
                    };
                }
            };
            //绑定process1的progress属性为service的progress属性
             process1.progressProperty().bind(service.progressProperty());
             //同上
             process2.progressProperty().bind(service.progressProperty());
             //绑定process3的text属性为service的text属性
             process3.textProperty().bind(service.valueProperty());
             //任务完成时会调用
             service.setOnSucceeded((WorkerStateEvent event) -> {
                 System.out.println("任务处理完成!");
             });
             //启动任务start()一定是最后才调用的
             service.start();
        }
       
        
        @Override
        public void initialize(URL url, ResourceBundle rb) {
            bt1.setOnAction(e->handleButtonAction(e));//按钮注册事件处理函数
        }    
        
    }
    

      在循环中更新了progress属性以及value属性,所以所有绑定了这两个属性的元素也会被跟着更新掉。因为一开始声明的Service<String>,所以value值就是个String,可以直接给process3使用!

      

      FXMLDocument.fxml

      

    <?xml version="1.0" encoding="UTF-8"?>
    
    <?import javafx.scene.control.Button?>
    <?import javafx.scene.control.Label?>
    <?import javafx.scene.control.ProgressBar?>
    <?import javafx.scene.control.ProgressIndicator?>
    <?import javafx.scene.layout.AnchorPane?>
    <?import javafx.scene.text.Text?>
    
    <AnchorPane id="AnchorPane" prefHeight="400.0" prefWidth="500.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8.0.65" fx:controller="proces.FXMLDocumentController">
        <children>
            <Label fx:id="label" layoutX="126" layoutY="120" minHeight="16" minWidth="69" />
          <ProgressBar fx:id="process1" layoutX="37.0" layoutY="35.0" prefHeight="23.0" prefWidth="393.0" progress="0.0" />
          <ProgressIndicator fx:id="process2" layoutX="37.0" layoutY="84.0" prefHeight="72.0" prefWidth="88.0" progress="0.0" />
          <Text fx:id="process3" layoutX="37.0" layoutY="181.0" strokeType="OUTSIDE" strokeWidth="0.0" text="Text" wrappingWidth="401.6513671875" />
          <Button fx:id="bt1" layoutX="226.0" layoutY="264.0" mnemonicParsing="false" text="Button" />
        </children>
    </AnchorPane>
    

      

      Proces.java

      

    package proces;
    
    import javafx.application.Application;
    import javafx.fxml.FXMLLoader;
    import javafx.scene.Parent;
    import javafx.scene.Scene;
    import javafx.stage.Stage;
    
    /**
     *
     * @author Administrator
     */
    public class Proces 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.show();
        }
    
        /**
         * @param args the command line arguments
         */
        public static void main(String[] args) {
            launch(args);
        }
        
    }
    

      

      目录结构:

          

      

      看到最后你发现了吧,想要在非FX线程中更新UI的话,建议使用Task()方式来做,不敢保证这就是最好的解决方案,但是至少是我目前学到的最好的解决方案,说不定日后会发现更多的解决方法!

  • 相关阅读:
    IDE IntelliJ IDEA ToolsTable 窗口找回
    Equals方法与“==”区别
    JAVA虚拟机内存分配与回收机制(转)
    java集合框架总结三--Set接口及其子类
    2014年4月5日 java集合框架总结2--List接口及其子类
    java集合框架总结一
    sql 操作数据库
    EF框架用中使用mysql数据库需要安装mysql-for-visualstudio
    mysql 服务启动不了;无法启动MYSQL服务”1067 进程意外终止”解决办法
    mysql 修改密码
  • 原文地址:https://www.cnblogs.com/ssh2/p/7765199.html
Copyright © 2020-2023  润新知