2015-09-02 2 views
14

Я новичок в шаблоне проектирования GUI world/OO, и я хочу использовать шаблон MVC для моего приложения GUI, я прочитал небольшое руководство по шаблону MVC, модель будет содержать данные, представление будет содержать визуальный элемент, а контроллер будет связываться между представлением и моделью.Применение MVC с JavaFx

У меня есть представление, которое содержит узел ListView, и ListView будет заполнен именами из класса Person (Model). Но я немного смущен об одном.

Что я хочу знать, является ли загрузка данных из файла ответственностью Контроллера или Модели? И наблюдаемый список имен: должен ли он храниться в контроллере или модели?

+1

Этот ПАК был проницательным http://stackoverflow.com/questions/23187932/mvc-with-javafx?rq=1 – Cobusve

+0

Возможно, вы захотите взглянуть на шаблон MVVM, который является вариацией шаблона MVC, который работает очень хорошо с JavaFX imho. – findusl

ответ

44

Существует множество различных вариантов этого рисунка. В частности, «MVC» в контексте веб-приложения интерпретируется несколько иначе, чем «MVC» в контексте приложения с толстым клиентом (например, настольного) (поскольку веб-приложение должно сидеть поверх цикла запроса-ответа). Это всего лишь один подход к реализации MVC в контексте толстого клиентского приложения с использованием JavaFX.

Ваш класс 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, lastName и email свойствами человека. Это контроллер может выглядеть следующим образом:

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

Теперь, если вы установите вещи так, как эти контроллеры разделяют ту же модель, редактор будет редактировать выбранный элемент в списке.

Загрузка и сохранение данных должны выполняться с помощью модели.Иногда вы даже учитываете это в отдельном классе, к которому у модели есть ссылка (что позволяет легко переключаться между файловым загрузчиком на основе файлов и загрузчиком данных базы данных или, например, реализацией, которая обращается к веб-службе). В простом случае вы можете сделать

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

As Я сказал, что существует множество вариаций этого шаблона (и это, вероятно, больше вариант модели-представления-презентатора или «пассивного представления»), но это один из подходов (один из которых я в основном предпочитаю). Немного более естественно представить модель контроллерам через их конструктор, но тогда гораздо сложнее определить класс контроллера с атрибутом fx:controller. Этот шаблон также сильно подкрепляется рамками внедрения инъекций.

Обновление: Полный код для этого примера - here.

+3

Отличное резюме шаблона проектирования, но если бы ваш код не всегда выдавал исключение, когда вы устанавливали модель, потому что оператор if ссылается на параметр модели (надеюсь, не нуль), а не на переменную экземпляра модели? Вы должны использовать if (this.model! = Null). – mipa

+1

О, красивое место: спасибо. Это то, что происходит от ввода кода непосредственно здесь, а не для его запуска в первую очередь. Обновлено с исправлением. –

+0

@James_D есть причина, по которой вы не привязываете двунаправленную привязку listView.getSelectionModel(). SelectedItemProperty() к model.currentPersonProperty()? Afaik selectedItemProperty также является нулевым, когда ничего не выбрано. – findusl

1

Что я хочу знать, так это то, что если загрузка данных из файла несет ответственность за контроллер или модель?

Для меня модель несет ответственность только за создание требуемых структур данных, представляющих логику бизнес-приложения.

Действие по загрузке данных из любого источника должно выполняться с помощью уровня контроллера. Вы также можете использовать repository pattern, который может помочь вам абстрагироваться от источника, когда вы получаете данные из представления. При этом реализуется вы не должны заботиться, если Repository implentation загружает данные из файла, SQL, NoSQL, WebService ...

И ObservableList имена будут сохранены в контроллере или модели?

Для меня ObservableList является частью представления. Это тип структуры данных, которую вы можете привязать к элементам управления javafx. Так, например, ObservableList может быть заполнен Strings из модели, но ссылка ObservableList должна быть атрибутом некоторого класса View's. В Javafx его самый удобный клиент связывает элементы управления javafx с наблюдаемыми свойствами, поддерживаемыми объектами домена из модели.

Вы также можете взглянуть на viewmodel concept. Для меня компонент JavaFx, поддерживаемый POJO, можно рассматривать как модель представления, вы можете увидеть его как модельный объект, готовый к представлению в представлении. Например, если ваше представление должно показать некоторое общее значение, рассчитанное из двух атрибутов модели, это общее значение может быть атрибутом модели представления. Этот атрибут не будет сохраняться, и он будет вычислен в любое время, когда вы покажете представление.

Смежные вопросы