web-dev-qa-db-fra.com

Application de MVC avec JavaFx

Je suis nouveau dans le modèle de conception du monde GUI/OO et je veux utiliser le modèle MVC pour mon application GUI, j'ai lu un petit tutoriel sur le modèle MVC, le modèle contiendra les données, la vue contiendra l'élément visuel et le Le contrôleur fera le lien entre la vue et le modèle.

J'ai une vue qui contient un nœud ListView, et le ListView sera rempli de noms, à partir d'une classe de personne (modèle). Mais je suis un peu confus à propos d'une chose.

Ce que je veux savoir, c'est si le chargement des données à partir d'un fichier est la responsabilité du contrôleur ou du modèle ?? Et la ObservableList des noms: doit-elle être stockée dans le contrôleur ou le modèle?

25
karim

Il existe de nombreuses variantes de ce modèle. En particulier, "MVC" dans le contexte d'une application Web est interprété quelque peu différemment de "MVC" dans le contexte d'une application cliente lourde (par exemple de bureau) (car une application Web doit être placée au sommet du cycle de demande-réponse). Ce n'est qu'une approche pour implémenter MVC dans le contexte d'une application cliente épaisse, à l'aide de JavaFX.

Votre classe Person n'est pas vraiment le modèle, sauf si vous avez une application très simple: c'est généralement ce que nous appelons un objet de domaine, et le modèle contiendra des références, ainsi que d'autres données. Dans un contexte étroit, comme lorsque vous pensez juste à la pensée de ListView, vous pouvez considérer la Person comme votre modèle de données (elle modélise les données dans chaque élément du 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, sont un ObservableList<Person>. Vous pouvez également souhaiter une propriété telle que currentPerson, qui pourrait représenter l'élément sélectionné dans la liste.

Si la vue seulement que vous avez est la ListView, alors créer une classe distincte pour stocker cela serait exagéré, mais toute application réelle se retrouvera généralement avec plusieurs vues. À ce stade, le partage des données dans un modèle devient un moyen très utile pour 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 ;
    }
}

Vous pouvez maintenant avoir un contrôleur pour l'affichage ListView 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 contrôleur lie essentiellement les données affichées dans la liste aux données du modèle et garantit que le currentPerson du modèle est toujours l'élément sélectionné dans la vue de liste.

Vous pouvez maintenant avoir une autre vue, par exemple un éditeur, avec trois champs de texte pour les propriétés firstName, lastName et email d'une 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 configurez les choses de manière à ce que ces deux contrôleurs partagent le même modèle, l'éditeur modifiera 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 factoriser cela dans une classe distincte à laquelle le modèle a une référence (vous permettant de basculer facilement entre un chargeur de données basé sur un fichier et un chargeur de données de base de données, ou une implémentation qui accède à un service Web, par exemple). Dans le cas simple que 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 ...
    }
}

Ensuite, vous pourriez avoir un contrôleur qui donne accès à 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 existe de nombreuses variantes de ce modèle (et il s'agit probablement davantage d'une variante modèle-vue-présentateur, ou variante "vue passive"), mais c'est une approche (celle que je privilégie essentiellement). Il est un peu plus naturel de fournir le modèle aux contrôleurs via leur constructeur, mais il est alors beaucoup plus difficile de définir la classe de contrôleur avec un fx:controller attribut. Ce modèle se prête également fortement aux cadres d'injection de dépendances.

Mise à jour: le code complet de cet exemple est ici .

67
James_D

Ce que je veux savoir, c'est que si le chargement des données à partir d'un fichier est la responsabilité du contrôleur ou du modèle?

Pour moi, le modèle est uniquement chargé d'apporter les structures de données requises qui représentent la logique commerciale de l'application.

L'action de charger ces données à partir de n'importe quelle source doit être effectuée par la couche contrôleur. Vous pouvez également utiliser le modèle de référentiel , qui peut vous aider à extraire du type de source lorsque vous accédez aux données de la vue. Avec cette implémentation, vous ne devez pas vous soucier si l'implantation du référentiel charge les données à partir de fichiers, sql, nosql, webservice ...

Et l'ObservableList des noms sera stocké dans le contrôleur ou le modèle?

Pour moi, la ObservableList fait partie de la vue. C'est le type de structure de données que vous pouvez lier aux contrôles javafx. Ainsi, par exemple, une ObservableList peut être remplie avec des chaînes du modèle, mais la référence ObservableList doit être un attribut de la classe de View. Dans Javafx, il est très agréable de lier des contrôles javafx avec des propriétés observables soutenues par des objets de domaine du modèle.

Vous pouvez également consulter concept viewmodel . Pour moi, un bean JavaFx soutenu par un POJO peut être considéré comme un modèle de vue, vous pouvez le voir comme un objet modèle prêt à être présenté dans la vue. Ainsi, par exemple, si votre vue doit afficher une valeur totale calculée à partir de 2 attributs de modèle, cette valeur totale peut être un attribut du modèle de vue. Cet attribut ne serait pas conservé et serait calculé chaque fois que vous afficherez la vue.

2
alvaro