Appliquer MVC avec JavaFx

je suis nouveau dans le monde GUI/oo design pattern et je veux utiliser MVC pattern pour mon application GUI, j'ai lu un petit tutoriel sur MVC pattern, Le Model contiendra les données, la vue contiendra l'élément visuel et le Controller liera entre la vue et le Model.

j'ai une vue qui contient un noeud ListView, et le ListView sera rempli de noms, d'une classe Personne (Model). Mais je suis un peu confus à propos d'une chose.

ce que je veux savoir si charger les données d'un fichier est la responsabilité du Controller ou du Model?? Et L'ObservableList des noms: doit-il être stocké dans le Controller ou le Model?

19
demandé sur Brad Turek 2015-09-02 03:25:19

2 réponses

Il existe de nombreuses variantes de ce modèle. En particulier, "MVC "dans le contexte d'une application web est interprété un peu différemment de" MVC " dans le contexte d'un client épais (par exemple ordinateur de bureau) application (parce qu'une application web doit reposer sur le cycle de demande-réponse). Ce n'est qu'une approche pour implémenter MVC dans le contexte d'une application client épaisse, en utilisant JavaFX.

Person classe n'est pas vraiment le modèle, sauf si vous avez un très simple application: il s'agit généralement de ce que nous appelons un objet de domaine, et le modèle contiendra des références à celui-ci, ainsi que d'autres données. Dans un contexte restreint, comme lorsque vous êtes penser ListView, vous pouvez penser à l' Person comme votre modèle de données (il modélise les données dans chaque élément de la ListView), mais dans le contexte plus large de l'application, il y a plus de données et d'État à considérer.

Si vous affichez un ListView<Person> les données dont vous avez besoin, au minimum, est un ObservableList<Person>. Vous pourriez aussi vouloir une propriété comme currentPerson, qui pourrait représenter l'élément sélectionné dans la liste.

Si le vue que vous avez est le ListView, alors créer une classe séparée pour stocker ceci serait exagéré, mais n'importe quelle application réelle finira habituellement avec des vues multiples. À ce stade, le partage des données dans un modèle devient un moyen très utile pour les différents contrôleurs de communiquer entre eux.

ainsi, par exemple, vous pourriez avoir quelque chose comme ceci:

public class DataModel {

    private final ObservableList<Person> personList = FXCollections.observableArrayList();

    private final ObjectProperty<Person> currentPerson = new SimpleObjectPropery<>(null);

    public ObjectProperty<Person> currentPersonProperty() {
        return currentPerson ;
    }

    public final Person getCurrentPerson() {
        return currentPerson().get();
    }

    public final void setCurrentPerson(Person person) {
        currentPerson().set(person);
    }

    public ObservableList<Person> getPersonList() {
        return personList ;
    }
}

Maintenant vous avez peut-être un contrôleur pour le ListView affichage qui ressemble à ceci:

public class ListController {

    @FXML
    private ListView<Person> listView ;

    private DataModel model ;

    public void initModel(DataModel model) {
        // ensure model is only set once:
        if (this.model != null) {
            throw new IllegalStateException("Model can only be initialized once");
        }

        this.model = model ;
        listView.setItems(model.getPersonList());

        listView.getSelectionModel().selectedItemProperty().addListener((obs, oldSelection, newSelection) -> 
            model.setCurrentPerson(newSelection));

        model.currentPersonProperty().addListener((obs, oldPerson, newPerson) -> {
            if (newPerson == null) {
                listView.getSelectionModel().clearSelection();
            } else {
                listView.getSelectionModel().select(newPerson);
            }
        });
    }
}

ce controller ne fait que lier les données affichées dans la liste aux données du model, et assure que le model est currentPerson est toujours l'élément sélectionné dans la vue de liste.

Maintenant vous pouvez avoir une autre vue, par exemple un éditeur, avec trois champs de texte pour le firstName,lastName et email propriétés d'un personne. Son contrôleur pourrait ressembler à:

public class EditorController {

    @FXML
    private TextField firstNameField ;
    @FXML
    private TextField lastNameField ;
    @FXML
    private TextField emailField ;

    private DataModel model ;

    public void initModel(DataModel model) {
        if (this.model != null) {
            throw new IllegalStateException("Model can only be initialized once");
        }
        this.model = model ;
        model.currentPersonProperty().addListener((obs, oldPerson, newPerson) -> {
            if (oldPerson != null) {
                firstNameField.textProperty().unbindBidirectional(oldPerson.firstNameProperty());
                lastNameField.textProperty().unbindBidirectional(oldPerson.lastNameProperty());
                emailField.textProperty().unbindBidirectional(oldPerson.emailProperty());
            }
            if (newPerson == null) {
                firstNameField.setText("");
                lastNameField.setText("");
                emailField.setText("");
            } else {
                firstNameField.textProperty().bindBidirectional(newPerson.firstNameProperty());
                lastNameField.textProperty().bindBidirectional(newPerson.lastNameProperty());
                emailField.textProperty().bindBidirectional(newPerson.emailProperty());
            }
        });
    }
}

Maintenant, si vous définissez les choses tellement ces deux contrôleurs sont en partageant le même modèle, l'éditeur va modifier l'élément actuellement sélectionné dans la liste.

le chargement et la sauvegarde des données doivent être effectués via le modèle. Parfois, vous allez même en tenir compte dans une classe séparée à laquelle le modèle a une référence (vous permettant de passer facilement d'un chargeur de données basé sur un fichier à un chargeur de données basé sur une base de données, ou une implémentation qui accède à un service web, par exemple). Dans le cas simple vous pourriez faire

public class DataModel {

    // other code as before...

    public void loadData(File file) throws IOException {

        // load data from file and store in personList...

    }

    public void saveData(File file) throws IOException {

        // save contents of personList to file ...
    }
}

Alors vous pourriez avoir un contrôleur qui permet d'accéder à cette fonctionnalité:

public class MenuController {

    private DataModel model ;

    @FXML
    private MenuBar menuBar ;

    public void initModel(DataModel model) {
        if (this.model != null) {
            throw new IllegalStateException("Model can only be initialized once");
        }
        this.model = model ;
    }

    @FXML
    public void load() {
        FileChooser chooser = new FileChooser();
        File file = chooser.showOpenDialog(menuBar.getScene().getWindow());
        if (file != null) {
            try {
                model.loadData(file);
            } catch (IOException exc) {
                // handle exception...
            }
        }
    }

    @FXML
    public void save() {

        // similar to load...

    }
}

Maintenant, vous pouvez facilement assembler une application:

public class ContactApp extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception {

        BorderPane root = new BorderPane();
        FXMLLoader listLoader = new FXMLLoader(getClass().getResource("list.fxml"));
        root.setCenter(listLoader.load());
        ListController listController = listLoader.getController();

        FXMLLoader editorLoader = new FXMLLoader(getClass().getResource("editor.fxml"));
        root.setRight(editorLoader.load());
        EditorController editorController = editorLoader.getController();

        FXMLLoader menuLoader = new FXMLLoader(getClass().getResource("menu.fxml"));
        root.setTop(menuLoader.load());
        MenuController menuController = menuLoader.getController();

        DataModel model = new DataModel();
        listController.initModel(model);
        editorController.initModel(model);
        menuController.initModel(model);

        Scene scene = new Scene(root, 800, 600);
        primaryStage.setScene(scene);
        primaryStage.show();
    }
}

comme je l'ai dit, il y a beaucoup de variations de ce modèle (et c'est probablement plus une variation de modèle-vue-présentateur, ou "vue passive" variation), mais c'est une approche (une que je favorise fondamentalement). C'est un peu plus naturel pour fournir le model aux controllers via leur constructeur, mais ensuite c'est beaucoup plus difficile de définir la classe controller avec un fx:controller l'attribut. Ce schéma se prête également très bien aux structures d'injection de dépendances.

mise à Jour: code complet pour cet exemple est ici.

54
répondu James_D 2015-10-21 19:49:36

ce que je veux savoir c'est que si charger les données d'un fichier est la responsabilité du contrôleur ou du model?

pour moi le modèle est seulement responsable d'apporter les structures de données requiered qui représentent la logique de business de l'application.

l'action de charger que les données de n'importe quelle source doivent être faites par la couche Controller. Vous pouvez également utiliser le motif du dépôt, ce qui peut vous aider dans l'abstraction de le type de source lorsque vous accédez aux données à partir de la vue. Avec cette implémentation, vous ne devriez pas vous soucier si l'implantation du dépôt charge les données à partir de file, sql, nosql, webservice ...

et L'ObservableList des noms sera stocké dans le controller ou le model?

pour moi L'ObservableList fait partie de la vue. C'est le genre de structure de données que vous pouvez lier javafx contrôles. Ainsi, par exemple, une liste Observablepourrait être peuplée avec les chaînes du modèle mais la référence ObservableList devrait être un attribut de certaines classes de vues. Dans Javafx son très agréable de lier les commandes javafx avec des propriétés observables soutenues par des objets de domaine du modèle.

Vous pouvez également avoir un coup d'oeil à ViewModel concept. Pour moi un haricot JavaFx soutenu par un POJO pourrait être considéré comme un view-model, vous pourriez le voir comme un objet model prêt à être présenté dans la view. Ainsi, par exemple, si votre vue doit montrer l' une valeur totale calculée à partir de 2 attributs du modèle, cette valeur totale pourrait être un attribut du modèle view. Cet attribut ne serait pas persisté et il serait calculé chaque fois que vous montrez la vue.

1
répondu alvaro 2017-06-16 04:43:03