flask mvc模式开发_JavaFX桌面应用MVC模式开发,“真香”

使用mvc模块开发JavaFX桌面应用在JavaFX系列文章第一篇 [JavaFX桌面应用开发-HelloWorld] 已经提到过,这里单独整理使用mvc模式开发开发的流程。

对于mvc模式,用struts2或springmvc开发JavaEE项目的程序员来说并不陌生,mvc模式分为control(控制层)、 model(模型层)和view(视图层)。

以springmvc为例:

@Controller 对应控制层(struts2对应的是action)model 对应模型层(java bean)jsp及各种视图模板 对应视图层

那么对JavaFX桌面应用来说,对应关系如下:

fx:controller  控制层javafx.beans.property 模型层fxml 视图层

下面是一个简单的mvc模式的JavaFX案例:

1. 控制层

JavaFX的控制层可以是一个简单的Java类,如果需要进行初始化那么需要实现Initializable接口。

public class TableUI implements Initializable {    // 对应视图层的Label标签,fx:id="time"    public Label time;    // 模型层的model    private TableModel model = new TableModel();    @Override    public void initialize(URL location, ResourceBundle resources) {        // 将视图层的Label控件和模型层的time属性进行双向绑定,这个跟vue的双向绑定有点类似。        time.textProperty().bindBidirectional(model.timeProperty());        // 启动新线程定时改变模型中的time属性,        executeTimeWork();    }    private void executeTimeWork() {        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");        new Thread(() -> {            while (true) {                // 注意:这里只需要改变model中的time属性即可,视图层的Label信息会跟着调整                // 因为在initialize方法中已经将time属性绑定在Label控件中了。                Platform.runLater(() -> model.setTime(sdf.format(new Date())));                try {                    TimeUnit.SECONDS.sleep(1);                } catch (InterruptedException ignore) {                }            }        }).start();    }}

2. 模型层

模型层其实就是一个贫血模式的Java Bean,不过需要注意的是模型字段声明需要用到javafx.beans.property的相关类,因为这些类实现了观察者模式,当model的值更新时,会更新UI。

public class TableModel {    // 这里必须使用property的相关类    private StringProperty time = new SimpleStringProperty();    // Getter/Setter推荐使用IDEA来生成会生成下面3个方法,如果是用eclipse将只生成普通的getter/setter    public String getTime() {        return time.get();    }    public StringProperty timeProperty() {        return time;    }    public void setTime(String time) {        this.time.set(time);    }}

3 视图层

视图层比较简单,使用fxml排版即可,这里仅使用一个Label来显示时间。

  fx:controller="com.itqn.gui.javafx.wx.table.TableUI">                                                                                          

~ 最终效果图 ~3dedb045db1c19de675969dd83e6d16f.png

总的来说,使用mvc模式来开发JavaFX要比手动管理UI控件也业务数据简便很多很多,而且model和UI控件是双向绑定的。

4. 复合模型层

如果只是简单的model数据,可以为每个控件声明一个队名的属性,并将双方绑定即可,但是如果遇到一些复杂的模型层数据,可能就要用到复合模型了,比如表格等。

06bf7eb00eb71d66733f935e235d75ae.png

对于UI层中还有表格且还有其他UI控件的情况,可以使用复合模型层,因为表格这些控件需要为行单独定义模型,所以需要将多个模型进行组合。

  1. 单独定义行模型TableColumnModel

假设表格的每一行是一项任务,由id和标题(title)组成,可以将模型定义如下, 其中selected是每一个行前面的复选框,progress是任务完成的进度。

public class TableColumnModel {    private Work work;    private BooleanProperty selected = new SimpleBooleanProperty();    private IntegerProperty id = new SimpleIntegerProperty();    private StringProperty title = new SimpleStringProperty();    private DoubleProperty progress = new SimpleDoubleProperty();    public static TableColumnModel fromWork(Work work) {        TableColumnModel model = new TableColumnModel();        model.work = work;        model.setSelected(false);        model.setId(work.getId());        model.setTitle(work.getTitle());        model.setProgress(0);        return model;    }    // 这里省略getter/setter}
  1. 使用复合模型定义整个UI的模型

这里UI有一个Label和一个Table,而Table的模型是一个List集合,Table的行模型使用TableColumnModel。

public class TableModel {    // 时间Label模型    private StringProperty time = new SimpleStringProperty();    // 表格组合TableColumnModel模型    private ObservableList tableList = FXCollections.observableArrayList();    public String getTime() {        return time.get();    }    public StringProperty timeProperty() {        return time;    }    public void setTime(String time) {        this.time.set(time);    }    public ObservableList getTableList() {        return tableList;    }    public void setTableList(ObservableList tableList) {        this.tableList = tableList;    }}

这样复合模型就定义好了。

5. 自定义表格列控件

表格列控件可以使用fxml直接定义,也可以使用java代码来构建,一般来说只是简单的显示一些数据的表格可以在fxml中直接定义控件,如果需要有复杂的控件或者组合控件,那么推荐使用java代码来构建。

ca925445170e06f5586f7517210b62e8.png

像这种比较复杂的列控件,就可以使用java代码来构建了,列控件使用TableCell来构建,JavaFX提供了一些默认的实现:

CheckBoxTableCellChoiceBoxTableCellComboBoxTableCellProgressBarTableCellTextFieldTableCell

除了JavaFX提供的TableCell,可以通过column.setCellFactory()构建自定义的TabelCell。对于上面的4种控件,可以分别通过以下方式来构建:

  1. 复选框

表格中的复选框直接使用CheckBoxTableCell来构建即可。

public static TableColumn checkboxColumn(String text, String field, int width) {    TableColumn column = new TableColumn();    column.setText(text);    column.setPrefWidth(width);    column.setCellValueFactory(new PropertyValueFactory(field));    column.setCellFactory(CheckBoxTableCell.forTableColumn(column));    return column;}
  1. 普通文本

表格中的普通文本不需要额外设置CellFactory。

public static TableColumn textColumn(String text, String field, int width) {    TableColumn column = new TableColumn();    column.setText(text);    column.setPrefWidth(width);    column.setCellValueFactory(new PropertyValueFactory(field));    return column;}
  1. 进度条

进度条可以需要改变一下显示效果,即在进度条后面显示进度,采用Label和ProgressBar组合而成。

public static TableColumn progressColumn(String text, String field, int width) {    TableColumn column = new TableColumn();    column.setText(text);    column.setPrefWidth(width);    column.setCellValueFactory(new PropertyValueFactory(field));    column.setCellFactory(v -> {        return new TableCell() {            private HBox hBox = new HBox();            private Label progressLabel = new Label("0% ");            private ProgressBar progressBar = new ProgressBar();            {                progressLabel.setPrefWidth(50);                progressLabel.setAlignment(Pos.CENTER_RIGHT);                hBox.getChildren().addAll(progressBar, progressLabel);            }            @Override                protected void updateItem(Double item, boolean empty) {                super.updateItem(item, empty);                if (empty) {                    setGraphic(null);                } else {                    progressBar.setProgress(item);                    progressLabel.setText((int) ((item * 100)) + "% ");                    setGraphic(hBox);                }                setContentDisplay(ContentDisplay.GRAPHIC_ONLY);            }        };    });    return column;}
  1. 操作按钮组

操作按钮组的实现跟进度条的构建方式是一样的,只是将Label和ProgressBar换成两个Button即可,这里不再贴代码。


所有列控件构建好之后,只需要将所有列加入到表格中即可。

private void buildTableColumn() {    TableColumn selected = TableColumnBuilder.checkboxColumn("", "selected", 40);    TableColumn id = TableColumnBuilder.textColumn("ID", "id", 60);    TableColumn title = TableColumnBuilder.textColumn("名称", "title", 180);    TableColumn progress = TableColumnBuilder.progressColumn("进度", "progress", 150);    TableColumn operator = TableColumnBuilder.operatorColumn("操作", "id", 130, this::operatorConsumer);    table.getColumns().addAll(selected, id, title, progress, operator);}

6. 结合业务使用

一般来说,表格的数据是有业务模块加载出来的出来的,为了模拟真正的流程,这里采用三层架构来实现业务分层,即表示层(mvc),业务层(service),数据层(dao)。

这里模拟实现的功能是:

  1. Controller从Service拉取数据放到Table中。

  2. 当用户点击加载的时候,模拟任务处理进度。

  3. 当用户点击删除的时候,将任务从列表中删除。

完整是Service实现如下:

public class TableService {    private TableModel model;    public TableService(TableModel model) {        this.model = model;    }    public void loadTableList() {        // 这里省略了dao层,直接随机生成模拟数据        String[] works = new String[]{"Hi IT青年", "JavaFX MVC", "https://www.cnblogs.com/itqn/", "Wx公众号:HiIT青年"};        for (int i = 0; i < 10; i++) {            model.getTableList().add(TableColumnModel.fromWork(new Work(i + 1, works[(int) (Math.random() * works.length)])));        }    }    // 处理任务加载,进度更新    public void executeLoadWork(Integer id) {        if (id == null) {            return;        }        Optional opt = model.getTableList().stream().filter(i -> i.getId() == id).findFirst();        if (opt.isPresent()) {            TableColumnModel cm = opt.get();            new Thread(() -> {                while (cm.getProgress() < 1) {                    cm.setProgress(cm.getProgress() + 0.01);                    try {                        TimeUnit.MILLISECONDS.sleep(100);                    } catch (InterruptedException ignore) {                    }                }                cm.setProgress(1);            }).start();        }    }    // 删除任务,将任务从模型中删除,实际可以还需要操作dao.    public void executeDeleteWork(Integer id) {        if (id == null) {            return;        }        model.getTableList().removeIf(i -> i.getId() == id);    }}

最终的效果:

79f36611f8f01761b4be83d0aa34602a.png


项目源码可以在公众号上发送“javafx-mvc” 获取。

推荐阅读:

JavaFX桌面应用-为什么应用老是“未响应”

JavaFX让UI更美观-CSS样式

JavaFX布局神器-SceneBuilder

JavaFX桌面应用开发-HelloWorld

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值