2017-10-08 61 views
1

我試圖按照MVC進行測試項目,所以我的模型應該完全獨立於我的視圖,但是我不知道如何更新Observable List在後臺線程中更新(它被給予Strings關於通過FTP上傳文件),以便消息出現在ListView中的UI上。從後臺線程更新可觀察列表的正確方法

我正在使用JavaFX並試圖讓我的程序儘可能鬆散耦合。在當前時刻,視圖包中的GUI取決於我的模型使用Platform.runLater(...)更新我的列表這一事實 - 據我所知,我的模型應該完全獨立於視圖,並且不應該「必須符合View的需求。

現在下面的代碼實際上「按預期工作」,它只是沒有正確建模,我不知道如何正確建模。一些最初的研究提出,我可能不得不使用觀察者和可觀察的 - 並在中間有另一個類作爲我的可觀察列表 - 但我不知道如何設置它。

所以我這是在後臺線程更新可觀察的名單:

private ObservableList<String> transferMessages; 

public FTPUtil(String host, int port, String user, String pass) { 
    this.host = host; 
    this.port = port; 
    this.username = user; 
    this.password = pass;  

    transferMessages = FXCollections.observableArrayList(); 

    connect(); 
} 

public void upload(File src) { 
    System.out.println("Uploading: " + src.getName()); 
    try { 
     if (src.isDirectory()) {    
      ftpClient.makeDirectory(src.getName()); 
      ftpClient.changeWorkingDirectory(src.getName()); 
      for (File file : src.listFiles()) { 
       upload(file); 
      } 
      ftpClient.changeToParentDirectory(); 
     } else { 
      InputStream srcStream = null; 
      try { 
       addMessage("Uploading: " + src.getName()); 
       srcStream = src.toURI().toURL().openStream(); 
       ftpClient.storeFile(src.getName(), srcStream); 
       addMessage("Uploaded: " + src.getName() + " - Successfully."); 

      } catch (Exception ex) { 
       System.out.println(ex); 
       addMessage("Error Uploading: " + src.getName() + " - Speak to Administrator."); 
      } 
     } 
    } catch (IOException e) { 
     // TODO Auto-generated catch block 
     e.printStackTrace(); 
    } 

} 

private void addMessage(String message){ 

    Platform.runLater(() -> transferMessages.add(0, message)); 

} 

的FTPUtil類是我的模型。

我也有一個模型管理器類,這是什麼控制這FTPUtil類:

public class ModelManager { 

private ObservableList<String> fileAndFolderLocations; 


private FTPUtil ftpUtil; 

public ModelManager(String host, int port, String user, String pass) { 

    ftpUtil = new FTPUtil(host, port, user, pass); 
    fileAndFolderLocations = FXCollections.observableArrayList(); 

} 

public boolean startBackup() { 

    Task task = new Task() { 
     @Override 
     protected Object call() throws Exception { 

      System.out.println("I started"); 
      ftpUtil.clearMessages(); 

      for(String location : fileAndFolderLocations){ 
       File localDirPath = new File(location);   
       ftpUtil.upload(localDirPath); 
      }    
      return null; 
     }   
    };  
    new Thread(task).start(); 

    return true; 
} 

public void addFileOrFolder(String fileOrFolder){ 
    if(!fileAndFolderLocations.contains(fileOrFolder)){ 
     fileAndFolderLocations.add(fileOrFolder); 
    }  
} 

public boolean removeFileOrFolder(String fileOrFolder){ 
    return fileAndFolderLocations.remove(fileOrFolder); 
} 

public ObservableList<String> getFilesAndFoldersList() { 
    return fileAndFolderLocations; 
} 

public ObservableList<String> getMessages() { 
    return ftpUtil.getMessages(); 
} 

} 

最後是我的GUI:

public class BackupController { 

private Main main; 
private ModelManager mm; 

@FXML 
private ListView<String> messagesList; 

@FXML 
void forceBackup(ActionEvent event) { 
    mm.startBackup();   

} 

public void initController(Main main, ModelManager mm) { 
    this.main = main; 
    this.mm = mm; 

    messagesList.setItems(mm.getMessages()); 
} 


} 
+0

添加隔離層:保持模型無知的視圖(也就是不使用runLater),保持一個單獨的列表顯示項目,聆聽原始的變化nal消息並將它們傳播到fx線程上的單獨列表中(在這裏使用runLater) – kleopatra

+0

@kleopatra嗨,就像隔離層一樣,你的意思是一個只保存另一個可觀察列表的類,或者擴展了一個可觀察列表 - >然後觀察我的模型(它應該是可觀察的),當它的列表發生變化時? – AntonyKimpton

+0

與你現在做的相同的地方,BackgroundController對於任務看起來很好 – kleopatra

回答

2

基本設置:

  • 不在模型中使用Platform.runLater
  • 請勿設置模型/管理消息直接作爲項目到ListView
  • 也保持項目的獨立的observableList,並設置到列表
  • 安裝管理器的列表上的聽衆,保持與信息同步的項目的讀者的名單:包裹在這些修改Platform.runLater

一個非常原始片斷來說明設置:

private Parent getContent() { 
    ModelManager manager = new ModelManager(); 
    ObservableList<String> uploading = FXCollections.observableArrayList("one", "two", "three"); 

    ObservableList<String> items = FXCollections.observableArrayList(); 
    manager.getMessages().addListener((ListChangeListener) c -> { 

     while (c.next()) { 
      if (c.wasAdded()) { 
       Platform.runLater(() -> 
        items.addAll(c.getFrom(), c.getAddedSubList())); 
      } 
      if (c.wasRemoved()) { 
       Platform.runLater(() -> 
        items.removeAll(c.getRemoved())); 
      } 
     } 
    }); 


    ListView<String> list = new ListView<>(items); 
    Button button = new Button("start"); 
    button.setOnAction(ev -> { 
     uploading.stream().forEach(e -> manager.addFile(e)); 
     manager.startBackup(); 
    }); 
    BorderPane pane = new BorderPane(list); 
    pane.setBottom(button); 
    return pane; 
} 

@Override 
public void start(Stage stage) throws Exception { 
    Scene scene = new Scene(getContent()); 
    stage.setScene(scene); 
    stage.show(); 
} 
+0

太棒了,這個工作就像一個魅力:DI仍然有一個問題:我的模型包含的列表當前是一個ObservableList - 如果它保持這種方式或變成一個數組列表。由於視圖不再依賴於更新UI的模型,我應該在模型中使用JavaFX ObservableList嗎?如果我要將它更改爲ArrayList,我將無法添加偵聽器嗎? – AntonyKimpton

+1

恰恰...沿鏈的某處必須是可觀察的東西 – kleopatra

+0

太好了。謝謝你幫助我:D我試着+1你的回覆,但我沒有足夠的代表。 – AntonyKimpton