JavaFX MVC und Observer

flaco

Mitglied
Hallo,

ich muss eine Applikation erstellen, die eine Liste von Personen verwaltet. Dabei gibt es drei Operationen ADD, RENAME und REMOVE. Außerdem muss ich Observer und MVC anwenden.
Nun bin ich mir nicht sicher, was in den Controller und was in die View gehört. Beim Klick auf einen Button wird zb ja eine Person hinzugefügt. Wie kann zb ich den Klick in der Controller-Klasse "einfangen"?
Mir ist bewusst, dass ich die View und Controller noch umschreiben muss. Da es bei mir aber mit einer Main-class noch nicht funktioniert hat, ist es vorübergehend so:

View:
Java:
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;

public class ViewStudentList extends Application implements Observer {

    private StudentManager studMan = new StudentManager();
    private ListView<Student> list = new ListView<Student>();
    private ObservableList<Student> data;
    private Controller c = new Controller(studMan, this);

    @Override
    public void start(Stage stage) {
        studMan.addStudent("Name1", "Surname1", 123);
        studMan.addStudent("Name2", "Surname2", 456);
        studMan.addStudent("Name3", "Surname3", 789);
        data = FXCollections.observableArrayList(studMan.getList());
        VBox box = new VBox();

        stage.setTitle("ListViewSample");
        final Label label = new Label("StudentList");
        label.setFont(Font.font("Arial", 20));
        box.getChildren().addAll(label, list);
        VBox.setVgrow(list, Priority.ALWAYS);

        final Label status = new Label();

        final TextField nameField = new TextField();
        nameField.setMinHeight(25);
        nameField.setPromptText("Name");
        final TextField surnameField = new TextField();
        surnameField.setMinHeight(25);
        surnameField.setPromptText("Surname");
        final TextField matnrField = new TextField();
        matnrField.setMinHeight(25);
        matnrField.setPromptText("MatNr");

        HBox hb1 = new HBox();
        hb1.getChildren().addAll(nameField, surnameField, matnrField);
        hb1.setSpacing(5);
        hb1.setAlignment(Pos.CENTER);

        Button addButton = new Button("ADD");
        addButton.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                if (nameField.getText() != null && !nameField.getText().isEmpty() && surnameField.getText() != null
                        && !surnameField.getText().isEmpty() && matnrField.getText() != null && !matnrField.getText().isEmpty()) {
                    if (matnrField.getText().matches("\\d+")) {
                        if (c.add(nameField.getText(), surnameField.getText(), Integer.parseInt(matnrField.getText()))) {
                            status.setText("Added Student");
                        } else {
                            status.setText("Adding not possible, MatNr already exists!");
                        }
                    } else {
                        status.setText("MatNr must be a number!");
                    }
                } else {
                    status.setText("Adding failed!");
                }
            }
        });

        Button renameButton = new Button("RENAME");
        renameButton.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {

                if (surnameField.getText() != null && !surnameField.getText().isEmpty()) {
                    final int selectedIdx = list.getSelectionModel().getSelectedIndex();
                    if (selectedIdx != -1) {
                        Student itemToRemove = list.getSelectionModel().getSelectedItem();
                        final int newSelectedIdx = (selectedIdx == list.getItems().size() - 1) ? selectedIdx - 1 : selectedIdx;
                        if (c.rename(surnameField.getText(), itemToRemove.getMatnr())) {
                            status.setText("Remamed " + itemToRemove);
                            list.getSelectionModel().select(newSelectedIdx);
                            data = FXCollections.observableArrayList(c.getList());
                            list.setItems(data);
                        } else {
                            status.setText("Renaming " + itemToRemove + " not possible!");
                        }
                    }
                } else {
                    status.setText("Renaming failed!");
                }
            }
        });

        Button removeButton = new Button("REMOVE");

        removeButton.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                final int selectedIdx = list.getSelectionModel().getSelectedIndex();
                if (selectedIdx != -1) {
                    Student itemToRemove = list.getSelectionModel().getSelectedItem();
                    final int newSelectedIdx = (selectedIdx == list.getItems().size() - 1) ? selectedIdx - 1 : selectedIdx;
                    if (c.removeStudent(itemToRemove.getMatnr())) {
                        status.setText("Removed " + itemToRemove);
                        list.getSelectionModel().select(newSelectedIdx);
                    } else {
                        status.setText("Removing " + itemToRemove + " not possible!");
                    }
                }
            }
        });
        
        Button clearButton = new Button("CLEAR");
        clearButton.setOnAction(new EventHandler<ActionEvent>() {

            @Override
                public void handle(ActionEvent e) {
                    nameField.clear();
                    surnameField.clear();
                    matnrField.clear();
                    status.setText(null);
                }
            });

        HBox hb2 = new HBox();
        hb2.getChildren().addAll(addButton, removeButton, renameButton, clearButton);
        hb2.setSpacing(5);
        hb2.setAlignment(Pos.CENTER);

        box.getChildren().addAll(hb2, hb1, status);
        box.setSpacing(5);
        Scene scene = new Scene(box, 300, 400);
        list.setItems(data);

        stage.setScene(scene);
        stage.show();
    }

    public void addController(Controller c) {
        this.c = c;
    }

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void update() {
        data = FXCollections.observableArrayList(c.getList());
        list.setItems(data);
    }

}

Controller:
Java:
import java.util.ArrayList;

public class Controller{

    StudentManager studMan;
    ViewStudentList viewList;
    
    public Controller(StudentManager studMan, ViewStudentList viewList){
        this.studMan = studMan;
        this.viewList = viewList;
        studMan.attach(viewList);
    }
    
    
    public boolean removeStudent(int matnr){
        return studMan.removeStudent(matnr);
    }
    
    public ArrayList<Student> getList() {
        return studMan.getList();
    }

    public boolean rename(String surname, int matnr) {
        return studMan.rename(surname, matnr);
    }

    public boolean add(String name, String surname, int matnr) {
        return studMan.addStudent(name, surname, matnr);
    }  
    
}

Model:
Java:
import java.util.ArrayList;

public class StudentManager implements Subject {

    private ArrayList<Observer> observers = new ArrayList<Observer>();
    private ArrayList<Student> list = new ArrayList<Student>();

    public boolean addStudent(String name, String surname, int matnr) {
        for (Student s : list) {
            if (s.getMatnr() == matnr) {
                System.out.println("MatNr already exists!");
                return false;
            }
        }
        list.add(new Student(name, surname, matnr));
        notifyObservers();
        return true;
    }

    public boolean removeStudent(int matnr) {
        int i = 0;
        for (Student s : list) {
            if (s.getMatnr() == matnr) {
                list.remove(i);
                notifyObservers();
                return true;
            }
            i++;
        }
        System.out.println("Student with this MatNr doesn't exist!");
        return false;
    }

    public boolean rename(String surname, int matnr) {
        for (Student s : list) {
            if (s.getMatnr() == matnr) {
                s.setSurname(surname);
                notifyObservers();
                return true;
            }
        }
        return false;
    }

    public void printList() {
        for (Student s : list) {
            System.out.println(s);
        }
    }
    
    public ArrayList<Student> getList(){
        return list;
    }

    @Override
    public void attach(Observer o) {
        observers.add(o);
    }

    @Override
    public void detach(Observer o) {
        observers.remove(o);
    }

    @Override
    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update();
        }
    }
}

Ich denke, die ButtonActions gehören in den Controller, oder?

Danke schonmal,
flaco
 
Zuletzt bearbeitet:

MichaProgs

Aktives Mitglied
Hallo flaco,

ein sehr interssantes Thema, da ich mich ebenfalls zur Zeit mit JavaFX und dem MVC-Pattern beschäftige.

Ich habe die View komplett mit FXML geschrieben, da hierdurch nochmal eindeutiger die View von der Logik unterschieden wird. Über FXML ist es meiner Meinung nach auch einfacher die Listener an die Komponenten zu übergeben. Vielleicht wäre das auch eine Option, dich etwas in die FXML einzuarbeiten.

Um das Thema MVC allgemein zu behandeln: es ist wichtig, das in deiner View keinerlei Listener oder Logiken übergeben werden. Im View sind wirklich nur die einzelnen GUI-Komponenten. Diese kannst du mittels Getter- und Setter-Methoden an den Controller weitergeben. Im Controller schreibst du deine Listener. Also z.B. was passiert, wenn du deinen Button drückst. Das Model ist eine alleinstehende Klasse. Sie hat keinerlei Bezug zu den anderen (damit du theoretisch auch ohne GUI arbeiten könntest). Hier werden dann z.B. Rechenoperationen oder Datenbankabfragen durchgeführt.

Ich hoffe ich konnte es einigermaßen verständlich erklären. Wenn du möchtest, kann ich dir gerne meine Ansätze mal zeigen.

MfG
TB94
 

flaco

Mitglied
Hallo flaco,

ein sehr interssantes Thema, da ich mich ebenfalls zur Zeit mit JavaFX und dem MVC-Pattern beschäftige.

Ich habe die View komplett mit FXML geschrieben, da hierdurch nochmal eindeutiger die View von der Logik unterschieden wird. Über FXML ist es meiner Meinung nach auch einfacher die Listener an die Komponenten zu übergeben. Vielleicht wäre das auch eine Option, dich etwas in die FXML einzuarbeiten.

Ja, habe ich was drüber gelesen, allerdings bin ich mir nicht sicher, ob ich das verwenden darf. Außerdem habe ich erst vor ein paar Tagen mit JavaFX angefangen, da habe ich noch Nachholbedarf.

Um das Thema MVC allgemein zu behandeln: es ist wichtig, das in deiner View keinerlei Listener oder Logiken übergeben werden.

Das habe ich mir fast schon gedacht, dass das so ist.

Im View sind wirklich nur die einzelnen GUI-Komponenten. Diese kannst du mittels Getter- und Setter-Methoden an den Controller weitergeben. Im Controller schreibst du deine Listener. Also z.B. was passiert, wenn du deinen Button drückst.

Also übergibt man einzelne GUI-Komponenten? Ich dachte, man übergibt nur die Action...
Wie sieht, dass dann ungefähr aus? Hast du ein paar Codestücke?

Das Model ist eine alleinstehende Klasse. Sie hat keinerlei Bezug zu den anderen (damit du theoretisch auch ohne GUI arbeiten könntest). Hier werden dann z.B. Rechenoperationen oder Datenbankabfragen durchgeführt.

Ja, das war mir bereits klar. Hab da eh alles beachtet, oder?


Ich hoffe ich konnte es einigermaßen verständlich erklären. Wenn du möchtest, kann ich dir gerne meine Ansätze mal zeigen.

MfG
TB94

Auf jeden Fall, vielen Dank! Wie oben schon geschrieben, wären ein paar Ansätze auf jeden Fall nicht schlecht :)
MfG
flaco
 

dzim

Top Contributor
Dennoch wäre es sinnvoll, FXML zu verwenden, weil man hier - richtig implementiert - eigentlich sogar noch einen Schritt weiter geht: MVVM
JavaFX – Decouple the View and its behavior to create a testable UI | buildpath.de

Ich mach meist nur "simples" MVC damit, aber ich finde es schon praktisch, das UI (FXML) von der Logik (Controller des FXMLs, kann auch als ViewModel fungieren) zu trennen. Das Model existiert bei mir, aber ich mache viel mit Events in meinen Anwendungen und irgendwo gibt es dann immer eine Komponente, die z.B. die DB-Logik entkoppelt.

Die Frage ist, ob mit JavaFX überhaupt noch die alten Programmierparadigma verwenden sollte und nicht lieber die Verwendet, die die API "vorgibt". Meine Meinung.
 

MichaProgs

Aktives Mitglied
Du kannst dir ja mal mein (zu dem Zeitpunkt noch Swing) Projekt ansehen. http://www.java-forum.org/codeschni...-verwaltungssoftware-crm-erp-mvs-0-2-1-a.html
Mein JavaFX-Code ist leider noch nicht so vorzeigbar ;)

Da die GUI in Swing als auch in JavaFX (als Java-Code) als private deklariert sind, musst du diese in der View als Getter- und Setter schreiben, um später im Controller Zugriff auf die Felder zu haben. (Auch zu sehen in o.g. Projekt)

Wie auch dizem angesprochen, wäre es sinnvoll die GUI via FXML zu erstellen. Ist zwar eine umgewöhnung, da die GUI bzw die View-Klasse erst geladen werden muss und die Controller praktisch schon in der FXML-Datei zugeordnet sind, aber wenn man sich damit ein bisschen beschäftigt ist es definitiv eine sehr gute Alternative zu Swing usw.

Gruß
TB94
 

Ähnliche Java Themen


Oben