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()方式來做,不敢保證這就是最好的解決方案,但是至少是我目前學到的最好的解決方案,說不定日后會發現更多的解決方法!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM