2017-10-08 1 views
1

J'essaie de suivre MVC pour un projet de test, donc mon modèle devrait être complètement indépendant de ma vue, mais je ne suis pas sûr de la façon dont je devrais mettre à jour une liste observable qui obtient mis à jour dans un thread d'arrière-plan (il est donné chaînes de télécharger des fichiers via FTP) afin que les messages apparaissent sur l'interface utilisateur dans un ListView. J'utilise JavaFX et j'essaie d'obtenir un couplage le plus faible possible de mon programme. À ce moment, l'interface graphique dans le paquet de vue dépend du fait que mon modèle met à jour ma liste en utilisant Platform.runLater (...) - à ma connaissance, mon modèle devrait fonctionner complètement indépendamment de la vue, et shouldn ' Je dois me conformer aux besoins de View. Maintenant le code suivant "fonctionne comme prévu" il n'est tout simplement pas modélisé correctement, et je ne suis pas sûr comment je peux le modéliser correctement. Certaines recherches initiales ont montré que je devrais utiliser Observateur et observable - et avoir une autre classe au milieu pour agir comme ma liste observable - mais je ne suis pas sûr de la façon dont je mettrais cela en place.Correct façon de mettre à jour la liste Observable du thread d'arrière-plan

J'ai donc une liste Observable qui est mis à jour sur un fil de fond:

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)); 

} 

La classe FTPUtil est mon modèle.

J'ai aussi un modèle de classe Manager qui est ce qui contrôle cette classe 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(); 
} 

} 

est enfin mon 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

ajouter une couche d'isolation: garder le modèle ignorant de la vue (qui est de ne pas utiliser runLater), gardez une liste séparée pour montrant les éléments, écouter les changements de l'origi nal messages et les propager à la liste séparée sur le thread fx (c'est-à-dire runLater ici) – kleopatra

+0

@kleopatra Hey, comme par couche d'isolation, voulez-vous dire une classe qui contient simplement une autre liste observable, ou étend une liste observable -> est alors observe mon modèle (qui devrait être observable) pour quand sa liste change? – AntonyKimpton

+0

même endroit que vous le faites maintenant, BackgroundController semble très bien pour la tâche – kleopatra

Répondre

2

La configuration de base:

  • ne le font pas utilisez Platform.runLater dans le modèle
  • ne pas définir le modèle/manag liste des messages de er directement en tant qu'éléments à la listeView
  • garder une observableList séparée des éléments et définir cela à la liste
  • installer un écouteur sur la liste du gestionnaire qui maintient les éléments en synchronisation avec les messages: enveloppez ces modifications dans Platform.runLater

un très d'extrait brut pour illustrer la configuration:

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

Très bien, cela fonctionne comme un charme: DI a encore une question: la liste que mon modèle contient est actuellement une liste Observable - devrait-elle rester de cette façon ou se transformer en ArrayList. Comme la vue ne dépend plus du modèle pour mettre à jour l'interface utilisateur, devrais-je utiliser JavaFX ObservableList dans le modèle? Si je devais le changer en ArrayList, je ne serais pas en mesure d'ajouter un auditeur moi? – AntonyKimpton

+1

exactement ... quelque part le long de la chaîne doit être quelque chose d'observable – kleopatra

+0

Great. Merci de m'avoir aidé: D J'ai essayé de +1 votre réponse, mais je n'ai pas assez de rep. – AntonyKimpton