2015-09-02 26 views
14

我是新來的GUI世界/ OO設計模式,我想爲我的GUI應用程序使用MVC模式,我已經閱讀了關於MVC模式的一個小教程,模型將包含數據,視圖將包含視覺元素,控制器將在視圖和模型之間建立聯繫。對JavaFx應用MVC

我有一個視圖,包含一個ListView節點,並且ListView將填充名稱,從一個人類(模型)。但是我對一件事有點困惑。

我想知道的是,如果從文件加載數據是控制器或模型的責任??而名稱的ObservableList:它是否應該存儲在Controller或Model中?

+1

這個PAQ是有見地的http://stackoverflow.com/questions/23187932/mvc-with-javafx?rq=1 – Cobusve

+0

你可能想看看MVVM模式,它是MVC模式的變體非常適合JavaFX imho。 – findusl

回答

44

這種模式有許多不同的變化。特別是,在Web應用程序的上下文中,「MVC」在厚客戶端(例如桌面)應用程序的上下文中與「MVC」稍有不同,因爲Web應用程序必須位於請求響應週期之上。這只是在使用JavaFX的胖客戶端應用程序環境中實現MVC的一種方法。

您的Person類不是真正的模型,除非您有一個非常簡單的應用程序:這通常是我們所稱的域對象,並且該模型將包含對它的引用以及其他數據。在一個狹窄的範圍內,當你只是思考ListView,你能想到的Person爲您的數據模型(它機型在ListView的每個元素的數據),但在應用的大背景,例如,有更多的數據和狀態需要考慮。

如果您顯示的是ListView<Person>,您至少需要的數據是ObservableList<Person>。您可能還需要一個屬性,例如currentPerson,它可能代表列表中的所選項目。

如果你有唯一觀點是ListView,然後創建一個單獨的類來存儲,這將是矯枉過正,但任何真正的應用程序通常將多個視圖結束。此時,在模型中共享數據成爲不同控制器相互通信的非常有用的方法。

因此,舉例來說,你可能有這樣的事情:

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 ; 
    } 
} 

現在,你可能會爲ListView顯示,看起來像這樣的控制器:

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); 
      } 
     }); 
    } 
} 

該控制器本質上只是結合列表中的數據顯示在模型中的數據中,並確保模型的currentPerson始終是列表視圖中的選定項目。

現在,您可能會看到另一個視圖(編輯器),其中有一個人的firstName,lastNameemail屬性有三個文本字段。它的控制器看起來像:

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()); 
      } 
     }); 
    } 
} 

現在,如果你設置的東西,所以這兩個控制器都共享同一個模型,編輯器會在列表編輯當前選定的項目。

加載和保存數據應通過模型完成。有時甚至可以將它分解到模型具有引用的單獨類中(例如,您可以輕鬆地在基於文件的數據加載器和數據庫數據加載器或訪問Web服務的實現之間切換)。在簡單的情況下,你可能會做

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 ... 
    } 
} 

那麼你可能有一個控制器,提供了訪問此功能:

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... 

    } 
} 

現在您可以輕鬆組建一個應用程序:

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(); 
    } 
} 

由於我說過,這種模式有很多變化(這可能更多的是模型視圖演示者或「被動視圖」變體),但這是一種方法(我基本上贊成)。將模型通過構造函數提供給控制器會更自然一些,但是使用fx:controller屬性來定義控制器類會困難得多。這種模式也強烈依賴於依賴注入框架。

更新:本示例的完整代碼是here

+3

設計模式的很好的總結,但是當你設置模型時你的代碼不會總是拋出異常,因爲if語句引用(希望非空)模型參數而不是模型實例變量?你應該使用if(this.model!= null)來代替。 – mipa

+1

哦,很好的地方:謝謝。這就是直接在這裏輸入代碼的原因,而不是先實際運行代碼。更新與修復。 –

+0

@James_D是否有一個原因,你不雙向綁定listView.getSelectionModel()。selectedItemProperty()到model.currentPersonProperty()?當沒有選擇任何東西時,Afaik selectedItemProperty也是null。 – findusl

1

我想知道的是,如果從文件加載數據是控制器或模型的責任?

對我來說,模型只負責提供表示應用程序業務邏輯的requiered數據結構。

加載來自任何源的數據的操作應該由控制器層完成。您也可以使用repository pattern,這可以幫助您在從視圖中訪問數據時從源類型中進行抽象。有了這個實現的,你不應該關心的庫implentation從文件,SQL,NoSQL的,web服務加載數據...

而且名字的ObservableList將被存儲在控制器或模型?

對我來說,ObservableList是視圖的一部分。這是可以綁定到javafx控件的那種數據結構。例如,ObservableList可以用模型中的字符串填充,但ObservableList引用應該是某些View類的屬性。 在Javafx中,使用由模型中的域對象支持的Observable屬性綁定javafx控件非常令人滿意。你可以看看viewmodel concept。對於我來說,一個由POJO支持的JavaFx bean可以被視爲一個視圖模型,您可以將它看作一個準備好在視圖中呈現的模型對象。因此,例如,如果您的視圖需要顯示從2個模型屬性計算出的總值,則此總值可能是視圖模型的一個屬性。這個屬性不會被保留下來,只要你顯示視圖就會計算出來。