代码之家  ›  专栏  ›  技术社区  ›  APK

从后台线程更新可观察列表的正确方法

  •  2
  • APK  · 技术社区  · 7 年前

    我正试图在测试项目中遵循MVC,因此我的模型应该完全独立于我的视图,但是我不确定如何更新在后台线程中更新的可观察列表(它被赋予了通过FTP上传文件的字符串),以便消息显示在ListView的UI上。

    现在,以下代码实际上“按预期工作”,只是没有正确建模,我不确定如何正确建模。一些初步的研究表明,我可能需要使用Observer和observable,中间还有一个类作为我的observable列表,但我不确定该如何设置。

    所以我有一个可观察的列表,它在后台线程上更新:

    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());
    }
    
    
    }
    
    2 回复  |  直到 7 年前
        1
  •  4
  •   kleopatra Aji kattacherry    7 年前

    基本设置:

    • 不要使用平台。在模型中稍后运行
    • 不要将模型/经理的消息列表直接设置为列表视图中的项目
    • 一定要保留一个单独的项目观察列表,并将其设置为列表

    一个非常原始的片段来说明设置:

    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();
    }
    
        2
  •  1
  •   Tobias Diez    5 年前

    您需要一个围绕列表的包装器,将更改发布到正确的线程上。

    ObservableList<String> source = FXCollections.observableArrayList();
    ObservableList<String> items = new UiThreadList(source);
    ListView<String> list = new ListView<>(items);
    

    具有

    import javafx.application.Platform;
    import javafx.collections.ListChangeListener;
    import javafx.collections.ObservableList;
    import javafx.collections.transformation.TransformationList;
    
    class UiThreadList<T> extends TransformationList<T, T> {
        public UiThreadList(ObservableList<? extends T> source) {
            super(source);
        }
    
        @Override
        protected void sourceChanged(ListChangeListener.Change<? extends T> change) {
            Platform.runLater(() -> fireChange(change));
        }
    
        @Override
        public int getSourceIndex(int index) {
            return index;
        }
    
        @Override
        public T get(int index) {
            return getSource().get(index);
        }
    
        @Override
        public int size() {
            return getSource().size();
        }
    }
    

    这个解决方案的思想类似于上面的@kleopatra。