2016-02-17 32 views
1

在我的GUI中,我有一個TableView應該顯示一個加載文件的列表,在一個名爲PathGetter的類完成將它們加載到一個ObservableArrayList之後,但我無法正確實現任務。Propper JavaFX線程實現

這是JavaFX類

browseButton.setOnAction(event -> { 
      File dir = folderPicker.showDialog(bwindow); 
      if(dir != null){ 
       directoryLocation.setText(String.valueOf(dir)); 
       bottom.getChildren().add(new javafx.scene.control.Label("Loading Tracks")); 
       //PathGetter.getPath(directoryLocation.getText()); 
       PathGetter task = new PathGetter(directoryLocation.getText()); 
       Thread th = new Thread(task); 
       try { 
        pjesme = FXCollections.observableArrayList(); 
       } catch (Exception e) { 
        e.printStackTrace(); 
       } 
       selection.setItems(pjesme); 
       chSelAll.setDisable(false); 
       chSelIncomplete.setDisable(false); 
       chSelNoCover.setDisable(false); 
      } 

     }); 

的重要組成部分,這是應該

public class PathGetter extends Task<ObservableList<Track>> { 

    static boolean getSubDirs; 
    static ArrayList <Track> allFiles; 
    public static int trNr = 0; 
    private static String fullPath; 

    public PathGetter(String path) { 
     fullPath = path; 
    } 

    public static int getTrNr() { 
     return trNr; 
    } 

    public static void setTrNr(int trNr) { 
     PathGetter.trNr = trNr; 
    } 

    public static boolean isSupported (File f){ 
     //supported file types 
     if(String.valueOf(f).endsWith(".flac") || String.valueOf(f).endsWith(".mp3") || String.valueOf(f).endsWith(".aiff") || String.valueOf(f).endsWith(".ogg") || String.valueOf(f).endsWith(".mp4")){ 
      return true; 
     }else{ 
      return false; 
     } 
    } 

    @Override 
    protected ObservableList<Track> call() throws Exception { 
     getSubDirs = Browser.chSubDirs.isSelected(); 
     allFiles = new ArrayList<Track>(); 
     Queue<File> dirs = new LinkedList<File>(); 
     dirs.add(new File(fullPath)); 
     while (!dirs.isEmpty()) { 
      for (File f : dirs.poll().listFiles()) { 
       if (f.isDirectory() && getSubDirs == true) { 
        dirs.add(f); 
       } else if (f.isFile() && isSupported(f)) { 
        allFiles.add(new Track(f)); 
        setTrNr(getTrNr()+1); 
       } 
      } 
     } 
     ObservableList<Track> returnList = FXCollections.observableArrayList(allFiles); 
     return returnList; 
    } 
} 

工作,我不明白如何讓TableView中等待任務的類而不會阻塞整個JavaFX線程,這基本上破壞了任務的目的。我希望它能夠實時顯示進度,只需顯示當時添加的曲目數量即可。

+0

問:

所以如下我會寫任務你似乎是跟蹤'處理的文件計數trNr'。這是你想要在UI中逐步顯示的值嗎?在你開始處理文件之前,你有沒有辦法知道總體情況?沒有這些,你顯然不能做任何「完全百分比」追蹤。 –

+0

@James_D事先沒有辦法知道文件的總數,除非我想先計算它們,現在我真的不想這樣做。我只想製作一個簡單的標籤,顯示迄今爲止添加了多少個文件,因此用戶知道該應用正在做些什麼。但我認爲這很容易,我不明白這些關於懸掛UI線程的主要問題的答案。 – DzeriMNE

回答

0

有特定的JavaFX兩種線程規則:

  1. 任何更改UI 必須在FX應用程序線程進行。不這樣做會導致在運行時拋出IllegalStateException,或者可能會使UI處於不一致的狀態,這可能會在將來的任意點導致不可預測的行爲。
  2. 需要很長時間運行的任何代碼應在後臺線程上執行。不這樣做會導致UI在代碼運行時無響應。

此外,還有約線程一般規則:

護理必須使用多個線程訪問的可變狀態時服用。特別是,應該確保給定線程中的操作是原子操作,並且可能需要特別注意確保在一個線程中所做數據狀態的更改對另一個線程可見。

獲得最後一部分的正確性尤其具有挑戰性。此外,在UI環境中使用後臺線程時,幾乎總是希望在後臺線程和UI線程之間共享進程的結果,有時還要共享在進程中計算的數據。因爲這很具有挑戰性,所以JavaFX提供了一個Task類(以及其他一些相關的類),用於處理大部分用例中的棘手部分。

特別地,Task類公開各種性能(包括stateprogressmessage,和value),與線程安全updateXXX方法一起。更新方法可以安全地從任何線程中調用,並且可以確保在UI線程上更新屬性,並限制對它們的更新數量(基本上在更新UI期間發生更新時將更新結合在一起)。這意味着從後臺線程調用update方法並觀察UI線程中的屬性是安全的。此外,您可以隨時調用這些方法,而不會「氾濫」UI線程並導致其無法響應。

Task類還公開處理程序用於從一個狀態到另一個狀態,如setOnSucceeded(當任務正常完成調用)和setOnFailed(當任務拋出異常調用的)過渡。這些也在FX應用程序線程中處理。

你的任務子類可以:

  1. 使用消息屬性來更新處理的磁道數
  2. 返回產生

從UI代碼的曲目列表,你可以綁定標籤的文本到消息屬性。您也可以使用onSucceeded處理程序在任務完成時更新UI。

爲確保您不會在線程之間共享可變狀態,而不是由Task機器正確管理的狀態,您應該正確地封裝類。這意味着不會暴露由任務本身操縱的任何狀態。你的狀態不應該是static(並且沒有明顯的原因你會想要這樣做)。

public class PathGetter extends Task<ObservableList<Track>> { 

    private final boolean getSubDirs; 
    private final String fullPath; 

    public PathGetter(String path, boolean getSubDirs) { 
     fullPath = path; 
     this.getSubDirs = getSubDirs ; 
    } 

    public static boolean isSupported (File f){ 
     String fileName = f.toString(); 
     //supported file types 
     return fileName.endsWith(".flac") 
       || fileName.endsWith(".mp3") 
       || fileName.endsWith(".aiff") 
       || fileName.endsWith(".ogg") 
       || fileName.endsWith(".mp4") ; 
    } 

    @Override 
    protected ObservableList<Track> call() throws Exception { 

     List<Track> allFiles = new ArrayList<Track>(); 
     Queue<File> dirs = new LinkedList<File>(); 
     dirs.add(new File(fullPath)); 
     while (!dirs.isEmpty()) { 
      for (File f : dirs.poll().listFiles()) { 
       if (f.isDirectory() && getSubDirs) { 
        dirs.add(f); 
       } else if (f.isFile() && isSupported(f)) { 
        allFiles.add(new Track(f)); 
        updateMessage("Number of tracks processed: "+allFiles.size()); 
       } 
      } 
     } 
     ObservableList<Track> returnList = FXCollections.observableArrayList(allFiles); 
     return returnList; 
    } 
} 

現在,從你可以做類似的UI:

browseButton.setOnAction(event -> { 
    File dir = folderPicker.showDialog(bwindow); 
    if(dir != null){ 
     directoryLocation.setText(String.valueOf(dir)); 
     Label label = new Label("Loading Tracks"); 
     bottom.getChildren().add(label); 

     PathGetter task = new PathGetter(directoryLocation.getText(), Browser.chSubDirs.isSelected()); 
     Thread th = new Thread(task); 

     // keep label showing message from task: 
     label.textProperty().bind(task.messageProperty()); 

     task.setOnSucceeded(e -> { 
      selection.setItems(task.getValue()); 
      chSelAll.setDisable(false); 
      chSelIncomplete.setDisable(false); 
      chSelNoCover.setDisable(false); 
     }); 

     task.setOnFailed(e -> { 
      // handle exception ... 

      // and log it 
      task.getException().printStackTrace(); 
     }); 

     chSelAll.setDisable(true); 
     chSelIncomplete.setDisable(true); 
     chSelNoCover.setDisable(true); 

     // make sure thread doesn't prevent application exit: 
     th.setDaemon(true); 

     // set it going: 
     th.start(); 

    } 

}); 
+0

感謝您作出適當的詳細回覆,我會明天更改代碼,然後提供更新。 – DzeriMNE

+0

我已經實現了代碼並可以確認它能正常工作。我確信這個線程可以幫助有同樣問題的人,因爲我之前無法找到適當的響應。 – DzeriMNE

-2

您可以將偵聽器添加到ObservableArrayList。

  pjesme.addListener(new ListChangeListener<Track>() { 
      @Override 
     public void onChanged(ListChangeListener.Change<? extends Track> c) { 
      /* Do your Stuff in the gui. Like having the status bar or things */ 
     } 
     }); 

已添加的偵聽器後,你只需把它交給構造

 public PathGetter(String path, ObservableList<Track> pjesme) { 
      fullPath = path; 
      this.pjesme = pjesme; 
     } 

,並改變你的PathGetter類ObersvableList。因此,每次向List添加內容時,eventlistener都會被調用,並有機會更新GUI中的內容。

+0

但是現在問題在於你在一個線程中修改列表並在另一個線程中觀察它。 'ObservableList'的實現不是線程安全的....所以爲了實現這個工作,你需要用'Platform.runLater()'修改列表的任務中的所有調用。正如[任務文檔](http://docs.oracle.com/javase/8/javafx/api/javafx/concurrent/Task.html)所述:「警告:不要將可變狀態傳遞給任務,然後操作它來自後臺線程。「。 –