|
| 1 | +--- |
| 2 | +title: Model-View 框架 |
| 3 | +order: 2 |
| 4 | +category: |
| 5 | + - 架构设计 |
| 6 | +tag: |
| 7 | + - 设计模式 |
| 8 | + - Model-View |
| 9 | + - Qt |
| 10 | +editLink: false |
| 11 | +--- |
| 12 | + |
| 13 | +Qt 的 Model-View 框架是一种用于处理和显示数据的灵活机制,特别适合需要在用户界面中展示大量或复杂数据的应用程序。通过将数据和显示逻辑解耦,Model-View 框架提供了更高的灵活性和可维护性。Qt 的 Model-View 框架包含三个主要组件:Model(模型)、View(视图)和 Delegate(委托)。 |
| 14 | + |
| 15 | +### Model-View 框架的核心概念 |
| 16 | + |
| 17 | +#### Model(模型) |
| 18 | + |
| 19 | +Model 是数据的抽象表示,负责存储和管理数据,并向 View 提供访问这些数据的接口。Model 不直接与 UI 打交道,而是通过信号和槽机制通知 View 数据的变化。Qt 提供了几个现成的模型类,如 `QStandardItemModel` 和 `QStringListModel`,开发者也可以通过继承 `QAbstractItemModel`、`QAbstractTableModel` 或 `QAbstractListModel` 来自定义模型。 |
| 20 | + |
| 21 | +**Model 的主要职责**: |
| 22 | +- **管理数据**:模型负责存储和管理数据,包括对数据的增删改查操作。 |
| 23 | +- **提供接口**:模型通过标准化的接口提供数据给 View。 |
| 24 | +- **通知数据变更**:模型会在数据发生变化时发出信号,通知 View 更新显示。 |
| 25 | + |
| 26 | +Qt 中常用的 Model 类: |
| 27 | +- **QAbstractItemModel**:所有自定义模型的基类,适合树状结构数据。 |
| 28 | +- **QAbstractListModel**:用于一维列表数据的基类。 |
| 29 | +- **QAbstractTableModel**:用于二维表格数据的基类。 |
| 30 | +- **QStandardItemModel**:提供标准的项目模型,实现了表格和树状结构的支持。 |
| 31 | + |
| 32 | +**示例**:使用 `QStandardItemModel` 来存储和操作数据。 |
| 33 | +```cpp |
| 34 | +QStandardItemModel *model = new QStandardItemModel(); |
| 35 | +model->setHorizontalHeaderLabels({"Name", "Age"}); |
| 36 | + |
| 37 | +QStandardItem *item1 = new QStandardItem("John"); |
| 38 | +QStandardItem *item2 = new QStandardItem("25"); |
| 39 | + |
| 40 | +model->appendRow({item1, item2}); |
| 41 | +``` |
| 42 | +
|
| 43 | +#### View(视图) |
| 44 | +
|
| 45 | +View 负责展示数据,是用户与数据进行交互的界面部分。View 通过访问 Model 提供的数据,并将这些数据渲染到屏幕上。常用的视图类包括 `QListView`、`QTableView` 和 `QTreeView`,它们分别用于显示列表、表格和树形数据。 |
| 46 | +
|
| 47 | +**View 的主要职责**: |
| 48 | +- **显示数据**:View 从 Model 中获取数据并进行显示。 |
| 49 | +- **用户交互**:View 处理用户的输入,如点击、编辑等。 |
| 50 | +- **数据同步**:View 会自动更新显示,确保与 Model 数据保持一致。 |
| 51 | +
|
| 52 | +Qt 中常用的 View 类: |
| 53 | +- **QListView**:用于显示列表数据。 |
| 54 | +- **QTableView**:用于显示表格数据。 |
| 55 | +- **QTreeView**:用于显示树形结构数据。 |
| 56 | +
|
| 57 | +**示例**:使用 `QTableView` 显示 `QStandardItemModel` 的数据。 |
| 58 | +```cpp |
| 59 | +QTableView *view = new QTableView(); |
| 60 | +view->setModel(model); |
| 61 | +view->show(); |
| 62 | +``` |
| 63 | + |
| 64 | +#### Delegate(委托) |
| 65 | + |
| 66 | +Delegate 是 View 和 Model 之间的中介,用于定制数据的显示和编辑。通过委托,开发者可以控制单元格的外观和编辑方式。Qt 默认的委托类是 `QStyledItemDelegate`,你可以继承这个类来自定义数据的显示和编辑行为。 |
| 67 | + |
| 68 | +**Delegate 的主要职责**: |
| 69 | +- **渲染数据**:委托负责将 Model 中的数据渲染为可视化的控件或元素。 |
| 70 | +- **编辑数据**:委托还负责处理数据的编辑,提供自定义的编辑控件和逻辑。 |
| 71 | + |
| 72 | +**示例**:自定义委托,通过 `QStyledItemDelegate` 渲染数据。 |
| 73 | +```cpp |
| 74 | +class CustomDelegate : public QStyledItemDelegate { |
| 75 | + Q_OBJECT |
| 76 | + |
| 77 | +public: |
| 78 | + CustomDelegate(QObject *parent = nullptr) : QStyledItemDelegate(parent) {} |
| 79 | + |
| 80 | + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override { |
| 81 | + painter->save(); |
| 82 | + // 自定义绘制逻辑 |
| 83 | + painter->drawText(option.rect, Qt::AlignCenter, index.data().toString()); |
| 84 | + painter->restore(); |
| 85 | + } |
| 86 | +}; |
| 87 | + |
| 88 | +// 在视图中使用自定义委托 |
| 89 | +view->setItemDelegate(new CustomDelegate()); |
| 90 | +``` |
| 91 | +
|
| 92 | +### Model-View 的数据流 |
| 93 | +
|
| 94 | +Model-View 模式的关键在于数据与 UI 的分离,数据流的方向如下: |
| 95 | +1. **View 获取数据**:View 通过 `setModel()` 设置模型,并通过模型的标准接口(如 `data()`、`rowCount()`、`columnCount()`)获取数据并显示在界面上。 |
| 96 | +2. **用户交互**:用户在 View 中进行操作(如点击、编辑),这些操作会通过委托发送到 Model,Model 根据操作更新数据。 |
| 97 | +3. **Model 通知 View**:当 Model 数据发生变化时,Model 通过信号通知 View,View 自动刷新显示。 |
| 98 | +
|
| 99 | +### 数据绑定与更新 |
| 100 | +
|
| 101 | +在 Qt 的 Model-View 模式中,数据更新主要依赖于信号与槽机制。Model 通过 `dataChanged()` 等信号通知 View 数据已更改,View 会根据这些信号自动更新显示内容。常用的更新信号包括: |
| 102 | +- **dataChanged()**:当模型中的某个数据项发生变化时发出。 |
| 103 | +- **layoutChanged()**:当模型的布局发生变化时发出,例如排序、筛选等操作。 |
| 104 | +- **rowsInserted()**、**rowsRemoved()**:当插入或删除行时发出。 |
| 105 | +
|
| 106 | +### Qt 中的 Model-View 与 MVC |
| 107 | +
|
| 108 | +Qt 的 Model-View 框架类似于传统的 MVC(Model-View-Controller)模式,但它没有显式的 Controller。Qt 的设计将 Controller 的职责分散到 View 和 Delegate 中: |
| 109 | +- **View**:负责展示数据,并处理用户交互。 |
| 110 | +- **Delegate**:负责处理数据的呈现和编辑。 |
| 111 | +
|
| 112 | +通过这种设计,Qt 的 Model-View 框架能够更加灵活地支持各种数据展示场景,开发者可以更自由地定制数据的显示和交互行为。 |
| 113 | +
|
| 114 | +### 自定义模型 |
| 115 | +
|
| 116 | +虽然 Qt 提供了多种现成的模型类,但在实际应用中,经常需要创建自定义模型,以适应复杂的数据结构和特定的业务需求。自定义模型需要继承 `QAbstractItemModel`、`QAbstractTableModel` 或 `QAbstractListModel`,并实现相关的接口方法。 |
| 117 | +
|
| 118 | +**常见需要实现的方法**: |
| 119 | +- **rowCount()**:返回模型的行数。 |
| 120 | +- **columnCount()**:返回模型的列数。 |
| 121 | +- **data()**:返回指定索引处的数据。 |
| 122 | +- **setData()**:用于修改指定索引处的数据。 |
| 123 | +- **headerData()**:返回模型的表头数据。 |
| 124 | +
|
| 125 | +**示例**:自定义一个简单的表格模型。 |
| 126 | +```cpp |
| 127 | +class CustomTableModel : public QAbstractTableModel { |
| 128 | + Q_OBJECT |
| 129 | +
|
| 130 | +public: |
| 131 | + CustomTableModel(QObject *parent = nullptr) : QAbstractTableModel(parent) {} |
| 132 | +
|
| 133 | + int rowCount(const QModelIndex &parent = QModelIndex()) const override { |
| 134 | + return 5; // 5 行 |
| 135 | + } |
| 136 | +
|
| 137 | + int columnCount(const QModelIndex &parent = QModelIndex()) const override { |
| 138 | + return 3; // 3 列 |
| 139 | + } |
| 140 | +
|
| 141 | + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override { |
| 142 | + if (role == Qt::DisplayRole) { |
| 143 | + return QString("Row%1, Column%2").arg(index.row()).arg(index.column()); |
| 144 | + } |
| 145 | + return QVariant(); |
| 146 | + } |
| 147 | +}; |
| 148 | +``` |
| 149 | + |
| 150 | +### 代理模型 |
| 151 | + |
| 152 | +Qt 提供了一些代理模型(Proxy Model)来对原始模型的数据进行转换或过滤。常用的代理模型有: |
| 153 | +- **QSortFilterProxyModel**:用于对数据进行排序和过滤。 |
| 154 | +- **QIdentityProxyModel**:直接映射原始模型,适合做数据的装饰或修改。 |
| 155 | + |
| 156 | +使用代理模型可以在不修改原始模型的情况下,对数据进行排序、过滤或其他转换操作。 |
| 157 | + |
| 158 | +**示例**:使用 `QSortFilterProxyModel` 进行排序。 |
| 159 | +```cpp |
| 160 | +QSortFilterProxyModel *proxyModel = new QSortFilterProxyModel(); |
| 161 | +proxyModel->setSourceModel(model); |
| 162 | +proxyModel->sort(0, Qt::AscendingOrder); // 对第 0 列进行升序排序 |
| 163 | + |
| 164 | +view->setModel(proxyModel); |
| 165 | +``` |
| 166 | +
|
| 167 | +::: tip 总结 |
| 168 | +
|
| 169 | +Qt 的 Model-View 框架通过将数据和显示逻辑解耦,提供了更高的灵活性和可维护性。无论是使用现成的模型类,还是通过自定义模型,开发者都能够以更清晰的方式管理复杂的数据展示和用户交互。通过委托机制,Qt 允许开发者定制数据的显示和编辑,使得 UI 的表现力更强。而代理模型则进一步扩展了数据处理的能力,使得数据的排序、过滤等操作更加方便。 |
| 170 | +::: |
0 commit comments