Saubere Trennung Model, View, Controller Javafx

Jamil

Mitglied
Nachdem mir Joreyk den Tipp gegeben hat die View in einer separaten Klasse zu verwalten, habe ich es versucht umzusetzen und es funktioniert. Allerdings frage ich mich, ob meine Umsetzung stilistisch in Ordnung ist, oder ob man doch noch was ändern muss.

Zunächst eine grafische Darstellung des Programmabschnitts, um die es hier gehen soll:

listStage1.png

Hier der Code der Klasse MyView:
(ich habe die buttons und textfields in ner ArrayList verpackt, um den externen Zugriff zu erleichtern.)

Java:
public class MyView{
private ObservableList<String> listFr = FXCollections.observableArrayList();
     private ListView<String> frenchList;
     private ObservableList<String> listDe = FXCollections.observableArrayList();
     private ListView<String> germanList;
     private ArrayList<TextField> txtFldList;
     private ArrayList<Button> btnList;
      
     private final String NAME_LIST_TXTFLD[] = {"sheed name", "file name", "in french", "in german", "search a word"};
     private final String NAME_LIST_BTN[] = {"export list", "safe", "search", "delete","delete list", "exit"};      
     private Text displayedText;
    
     public MyView() {        
        frenchList  = new ListView<>(listFr);
        germanList = new ListView<>(listDe);
        txtFldList = new ArrayList<>();
        btnList = new ArrayList<>();        
    }
    public Scene setView(){
            
            controler = new Text("Click on the List \nor write the word");
            VBox vBox = new VBox();
            HBox hBoxBtnList = new HBox();
            HBox hBoxTxtFldList = new HBox();
            HBox hBox3 = new HBox(displayedText);
            HBox hBoxListContainer = new HBox(new VBox(new Label("Liste Deutsch:"), germanList), new VBox(new Label("Liste foreign language:"), frenchList));
            
            germanList.getStyleClass().add("listDe");
            frenchList.getStyleClass().add("listFr");
            germanList.setPrefSize(450, 550);
            frenchList.setPrefSize(450, 550);
            
            vBox.getChildren().addAll(hBoxBtnList,hBoxTxtFldList,hBox3,hBoxListContainer);
            
            for(String btnName : NAME_LIST_BTN ) {
                Button btn = new Button(btnName);
                btnList.add(btn);
                hBoxBtnList.getChildren().add(btn);
            }
            
            for(String txtFldName : NAME_LIST_TXTFLD) {
                TextField txtFld = new TextField();
                txtFld.setPromptText(txtFldName);
                txtFld.getStyleClass().add("textfield");
                txtFldList.add(txtFld);
                hBoxTxtFldList.getChildren().add(txtFld);    
            }
            for(int i = 0; i < vBox.getChildren().size(); i++) {
                vBox.getChildren().get(i).getStyleClass().add("hbox" + String.valueOf(i+1));
            }
            
            hBoxBtnList.getChildren().get(0).getStyleClass().add("first-btn");
            displayedText.getStyleClass().add("text1");
            vBox.getStyleClass().add("vbox");
            
            
            // click on the List item in order to delete them
            frenchList.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
            germanList.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);    

            Scene scene = new Scene(vBox, 900, 600);
            scene.getStylesheets().add(getClass().getResource("stylesheet2.css").toExternalForm());
            return scene; 
    }
    public ObservableList<String> getStringListDe() {
        return listDe;
    }
    public ObservableList<String> getStringListFr() {
        return listFr;
    }
    public ListView<String> getListViewFr() {
        return frenchList;
    }
    public ListView<String> getListViewDe() {
        return germanList;
    }
    
    public Button getButton(int index) {    
        return btnList.get(index);
    }
    public String getTxt(int index) {
        return txtFldList.get(index).getText();
    }    
    public TextField getTxtField(int index) {
        return txtFldList.get(index);      
    }
    public void clearTxtfldDeFr() {
          txtFldList.get(3).clear();
          txtFldList.get(2).clear();       
        }
    public Text getControler() {
      return displayedText;
    }
}

Und hier ein kleiner Abschnitt wie ich die View im Anschluss funktionalisiert habe
Java:
public class ListStage { 
  private ExportList exportList;
  private MyView myView;
  private Stage stage;
  public ListStage() {
      myView = new MyView();
      stage = new Stage();
  }
  
  public void start() {
   
    Scene scene = myView.setView();
    myView.getButton(0).setOnMouseClicked(e1 -> exporter());
    myView.getButton(1).setOnMousePressed(e2 -> safe());
    myView.getButton(2).setOnMouseClicked(e3 -> search());
    myView.getButton(3).setOnMouseClicked(e4 -> delete());
    myView.getButton(4).setOnMouseClicked(e5 -> deleteAll());
    myView.getButton(5).setOnMousePressed(e5 -> exit());
    
    // delete items by clicking on the list
    myView.getListViewDe().setOnMouseClicked(e6 -> clickOnTheList(e6));  
    myView.getListViewFr().setOnMouseClicked(e7 -> clickOnTheList(e7));
    
    // switch from french-textfield to german-textfield and safe the new Word by pressing ENTER
    myView.getTxtField(2).setOnKeyPressed(e8 -> switchTxtFieldsFrDe(e8));
    myView.getTxtField(3).setOnKeyPressed(e9 -> switchTxtFieldsDeFr(e9));
     
    stage.setMinWidth(790.0);
    stage.setScene(scene);
    stage.show();
  }

Irgendwie soll man da auch noch einen "Controler" unterbringen. Man müsste folglich das Programm in 3 Abschnitte unterteilen. Könnte man das so verstehen, dass falschangaben vom Controler abgefangen werden? Sollte ich eine Controler class hinzufügen?[/SIZE]
 
Zuletzt bearbeitet von einem Moderator:
Hm ok. Ich erstelle mir also eine Klasse Model, welche vom Controller mit Informationen versorgt wird, die dann im Model verarbeitet werden. Sobald das erledigt ist, wird im Anschluss der Input vom Model zurück zum Controller oder direkt zur View geschickt?
 
Danke, ich hab dieses Schema aus dem Vorlesungsfolien übernommen.

MVC.png


Der Controller kennt das Model und das Model returned back den Value

Controller:
Java:
public void safe() {  
        
        if (myView.getTxt(2).isEmpty() || myView.getTxt(3).isEmpty()) {
            myView.getControler().setText("a word missing");
            
        }else {
            String displayedText = model.setNewListItem(myView.getStringListDe(),myView.getTxtField(3), myView.getStringListFr(), myView.getTxtField(2));
            myView.getControler().setText(displayedText);
        } 
    }

Model:
Java:
public class Model {

    public String setNewListItem(ObservableList<String> stringListDe, TextField textFieldDe,ObservableList<String> stringListFr, TextField textFieldFr) {
        String newWordDe = String.valueOf(stringListDe.size()) + ". " + textFieldDe.getText();
        String newWordFr = String.valueOf(stringListFr.size()) + ". " + textFieldFr.getText();
        stringListDe.add(newWordDe);
        stringListFr.add(newWordFr);
        
        clearTxtflds(textFieldDe, textFieldFr);
        textFieldFr.requestFocus();
        String displayedText = "Word has been added";
        return displayedText;
    }
    
    private void clearTxtflds(TextField textField1, TextField textField2) {
        textField1.clear();
        textField2.clear();
    }
}
 
Zuletzt bearbeitet von einem Moderator:
wichtige Grundregel dabei: das Model weiß nichts über die View, es muss nicht mal wissen, dass überhaupt eine existiert.
In deinem Model kennt das Model aber Text-Felder, das sollte nicht so sein
 
Also eine Trennung von der View könnte, wenn ich nicht falsch liege, so aussehen. Jedoch
ist das kein MVC sondern MVVC. Ist das die richtige Herangehensweise?
mvvc.png

hab nochmal die Vorlesungsfolien von mihe7 überflogen und diesen Code Schnipsel gefunden

mvvc2.png
 
Zuletzt bearbeitet:
MVVM ist eine Abwandlung von MVC, die Beziehungen vom Model zum Rest sind in beiden Modellen identisch 🙂


Um mal näher an deinem Problem zu bleiben: das Model (das Konzept, nicht eine konkrete Klasse) ist in deinem Fall ja eine Liste von Wörtern und deren Übersetzungen. Der Liste kann man vermutlich Wörter hinzufügen (und potentiell auch löschen, ich nehm das einfach mal an).

Das ganze könnte man zB in einer Klasse "Wörterbuch" darstellen, intern hält ein Wörterbuch eine Liste von Einträgen, und nach außen bietet es Methoden zum Abfragen aller Wörter und zum Hinzufügen bzw. Löschen von Einträgen an.
Außerdem ist das Model "Beobachtbar", zB mit Observer- oder Listener-Pattern, und wenn Wörter hinzugefügt oder gelöscht werden, werden die Beobachter darüber informiert.


Die View würde dann ein "Wörterbuch" bekommen und dieses beobachten. Zum Anzeigen würde sie die Einträge des Wörterbuchs abfragen, und wenn sie über Änderungen informiert sind, aktualisiert sie die Abfrage.
Der Controller würde sich dagegen bei der View "registrieren" und sich über "Aktionen" innerhalb dieser informieren lassen, zB Klicks auf Buttons.

Wenn in der View jetzt was in Textfelder eingegeben wird, und dann die Eingabe mit einem Klick auf den Button bestätigt wird, löst der Beton-Klick eine Methode im Controller aus. Der Controller würde dann die Textfelder abfragen (sich also die Daten aus der View holen) und mit den Daten die "Hinzufügen-Funktion" des Wörterbuchs aufrufen. Das Wörterbuch aktualisiert sich dann und informiert die Beobachter. Beobachter ist in dem Fall nur die View, diese aktualisiert sich daraufhin.
 
Super, vielen Dank euch 🙂 . Ich denke, dass ich das Prinzip von MVC und die Umsetzung nun verstanden habe. Zumindest konnte ich mein Projekt grundlegend verbessern. Ich hab die View komplett auslagern können und auch den Controller schön unterbringen können, was den Code in allen 3 Klassen viel lesbarer gemacht hat. Jetzt brauche ich nur noch ein wenig Übung, damit mir die Trennung der Programmabschnitte ohne großem Zeitaufwand gelingt. Wobei ich mir auch nochmal die Observable und Listener-Konzepte genauer anschauen muss.
 

Neue Themen


Zurück
Oben