聊城做网站推广费用,网页制作素材下载免费,如何在服务器上做网站,中通建设工程管理公司网站Model/View 编程入门 一、概述二、介绍1. 标准部件2. Model/View 控件3. Model/View控件概述4. 在表格单和 model 之间使用适配器 Adapters 三、 简单的 model / view 应用程序示例1. 一个只读表2. 使用role扩展只读示例3. 表格单元中的时钟4. 为列和行设置标题5. 最小编辑示例… Model/View 编程入门 一、概述二、介绍1. 标准部件2. Model/View 控件3. Model/View控件概述4. 在表格单和 model 之间使用适配器 Adapters 三、 简单的 model / view 应用程序示例1. 一个只读表2. 使用role扩展只读示例3. 表格单元中的时钟4. 为列和行设置标题5. 最小编辑示例 四、中间的话题1. TreeView2. 使用选择器3. 预定义模型4. Delegates 一、概述
每个UI开发人员都应该了解ModelView编程
表格格控件、列表格控件和树控件是gui中经常使用的组件。这些控件有两种不同的方式访问它们的数据。
1.传统方法 传统的方法就是让控件本身去储存数据在控件内部有数据容器这种方法非常直观但是在许多重要的应用程序中它会导致数据同步问题。2.Model/View 方法 Model/View 也叫 模型/ 视图 编程其中控件不维护内部数据容器。它们通过标准化接口访问外部数据从而避免了数据重复。乍一看这似乎很复杂但一旦仔细研究就会发现它不仅易于掌握而且Model/View编程的许多好处也变得更加清晰。
在这个过程中我们将学习Qt提供的一些基本技术比如:
标准部件和Model/View部件之间的区别表格单和 model 之间的适配器开发简单的Model/View应用程序预定义的 model中间主题如: 树 view 选择 代理model 试验调试
我们还会了解使用Model/View编程是否可以更容易地编写新应用程序或者传统的控件是否也可以正常工作。
二、介绍
Model/View是一种用于在处理数据集的控件中分离数据和 view 的技术。标准的控件不是为从 view 中分离数据而设计的这就是Qt有两种不同类型的控件的原因。虽然这两种类型的控件看起来是一样的但是它们与数据的交互方式不同。
标准控件使用的数据是控件的一部分。数据和界面是融在一起的 view 类对外部数据( 模型 )进行操作就是通过接口去分类界面和数据model 存储数据而view 则是 视图 类显示UI。
1. 标准部件
让我们仔细看看一个标准的表格控件。表格控件是用户可以更改的数据元素的2D数组。通过读写表格控件提供的数据元素可以将表格控件集成到程序流中。这种方法在许多应用程序中非常直观和有用但是使用标准表格控件显示和编辑数据库表格可能会有问题。
什么问题呢
那就是我们必须协调数据的两个副本一个在控件外部;一个在控件内。开发人员负责同步这两个版本。手动同步容易出风险。
除此之外表格示和数据的紧密耦合使得编写单元测试变得更加困难。不方便测试呐
2. Model/View 控件
Model/View逐步提供了一个使用更通用架构的解决方案。 Model/View消除了标准控件可能出现的数据一致性问题。
Model/View还使使用相同数据的多个 view 变得更容易因为一个 model 可以传递给多个 view 。
最重要的区别是Model/View控件不将数据存储在表格格单元格后面。事实上它们直接根据您的数据进行操作。由于 view 类不知道数据的结构因此需要提供一个包装器使数据符合QAbstractItemModel接口。 view 使用该接口读取和写入数据。
实现QAbstractItemModel的类的任何实例都被称为 model 。一旦 view 接收到一个 model 的指针它将读取并显示其内容并成为其编辑器。
3. Model/View控件概述
下面是Model/View控件及其相应的标准控件的概述。
控件外观标准部件(基于Item的便利类)Model/View view 类(用于外部数据)QListWidgetQListViewQTableWidgetQTableViewQTreeWidgetQTreeView很少用QColumnView将树显示为列表格的层次结构QComboBox既可以作为 view 类也可以作为传统的控件
4. 在表格单和 model 之间使用适配器 Adapters
在表格单和 model 之间使用适配器可以派上用场。
我们可以直接从表格本身编辑存储在表格中的数据但是在文本字段中编辑数据要舒服得多。对于操作一个值(QLineEdit, QCheckBox…)而不是数据集的控件没有直接的Model/View对应物来分离数据和 view 因此我们需要一个适配器来将表格单连接到数据源。
QDataWidgetMapper是一个很好的解决方案因为它将表格单控件映射到表格行并使为数据库表格构建表格单变得非常容易。这个就相当于我们把修改的数据用QDataWidgetMapper 通过 model/view的方式给同步到表格里面去了。
适配器的另一个例子是QCompleter。Qt有QCompleter用于在Qt控件中提供自动补全功能如QComboBox和QLineEdit如下所示。QCompleter使用一个 model 作为它的数据源。 就像下面的简单代码就可以做到这个效果
QStringList wordList;
wordList alpha omega omicron zetaEmanuelEmersonEmilioEmmaEmmily;
QLineEdit *lineEdit new QLineEdit(this);QCompleter *completer new QCompleter(wordList, this);
completer-setCaseSensitivity(Qt::CaseInsensitive);
lineEdit-setCompleter(completer);三、 简单的 model / view 应用程序示例
如果你想开发一个 model / view 应用程序应该从哪里开始Qt官方建议从一个简单的示例开始并逐步扩展它。这使得理解架构变得容易得多。对许多开发人员来说在调用IDE之前尝试详细理解 model/view 架构是困难的。但是从简单 model/view 应用程序实践来看要更容易。
下面是7个非常简单和独立的应用程序展示了 model/view 编程的不同方面
1. 一个只读表
我们从一个使用QTableView显示数据的应用程序开始。稍后我们将添加编辑功能。
// main.cpp
#include QApplication
#include QTableView
#include“mymodel.h”int main(Int argc, char *argv[])
{QApplication a(argc, argv);QTableView tableView;MyModel MyModel;tableView.setModel (myModel);tableView.show ();return a.exec ();
}我们有常用的main()函数 我们创建一个MyModel的实例并使用tableView.setModel(myModel); 传递它的指针给tableView。
tableView会调用它接收到的指针的方法来找出两件事:
应该显示多少行和多少列。每个单元格应该打印什么内容。
model 需要一些代码来响应此请求。我们有一个表数据集所以让我们从QAbstractTableModel开始因为它比更通用的qabstractemmodel更容易使用。
// mymodel.h
#include QAbstractTableModelclass MyModel : public QAbstractTableModel
{Q_OBJECTpublic:MyModel(QObject *parent nullptr);int rowCount(const QModelIndex parent QModelIndex()) const override;int columnCount(const QModelIndex parent QModelIndex()) const override;QVariant data(const QModelIndex index, int role Qt::DisplayRole) const override;
};qabstractablemodel需要实现三个抽象方法。 // mymodel.cpp#include mymodel.hMyModel::MyModel(QObject *parent): QAbstractTableModel(parent){}int MyModel::rowCount(const QModelIndex /*parent*/) const{return 2;}int MyModel::columnCount(const QModelIndex /*parent*/) const{return 3;}QVariant MyModel::data(const QModelIndex index, int role) const{if (role Qt::DisplayRole)return QString(Row%1, Column%2).arg(index.row() 1).arg(index.column() 1);return QVariant();}行数和列数由 MyModel::rowCount() 和 MyModel::columnCount() 提供。当 view 需要知道单元格的文本是什么时它会调用方法MyModel::data()。行和列信息通过index参数指定role 设置为Qt::DisplayRole。下一节将介绍其他role。
在我们的例子中应该显示的数据已经生成。在实际的应用程序中MyModel会有一个名为MyData的成员它充当所有读写操作的目标。
这个小示例演示了 model 的被动性。** model 不知道何时会使用它或需要哪些数据。它只是在每次 view 请求时提供数据。**
当 model 的数据需要更改时会发生什么 view 如何意识到数据已经更改需要再次读取 model 必须发出一个信号表明哪些范围的单元格发生了变化。这将在 3 节中进行演示。
2. 使用role扩展只读示例
除了控制 view 显示的文本 model 还控制文本的外观。对 model 稍作修改后得到如下结果:
实际上除了data()方法之外我们不需要修改任何内容以设置字体、背景颜色、对齐方式和复选框。下面是产生上面显示结果的data()方法。不同的是这次我们使用形参int role根据其值返回不同的信息。 // mymodel.cppQVariant MyModel::data(const QModelIndex index, int role) const{int row index.row();int col index.column();// generate a log message when this method gets calledqDebug() QString(row %1, col%2, role %3).arg(row).arg(col).arg(role);switch (role) {case Qt::DisplayRole:if (row 0 col 1) return QString(--left);if (row 1 col 1) return QString(right--);return QString(Row%1, Column%2).arg(row 1).arg(col 1);case Qt::FontRole:if (row 0 col 0) { //change font only for cell(0,0)QFont boldFont;boldFont.setBold(true);return boldFont;}break;case Qt::BackgroundRole:if (row 1 col 2) //change background only for cell(1,2)return QBrush(Qt::red);break;case Qt::TextAlignmentRole:if (row 1 col 1) //change text alignment only for cell(1,1)return Qt::AlignRight Qt::AlignVCenter;break;case Qt::CheckStateRole:if (row 1 col 0) //add a checkbox to cell(1,0)return Qt::Checked;break;}return QVariant();}每个格式化属性都是通过单独调用data()方法从模型中请求的。role参数用来让模型知道请求的是哪个属性:
enum Qt::ItemDataRoleMeaningTypeQt::DisplayRole文本QStringQt::FontRole字体QFontBackgroundRole单元格的背景刷QBrushQt::TextAlignmentRole文字对齐enum Qt::AlignmentFlagQt::CheckStateRole支持CheckBox with QVariant(), sets checkboxes with Qt::Checked or Qt::Uncheckedenum Qt::ItemDataRoleQt::DecorationRole以图标形式呈现的装饰数据QColor, QIcon or QPixmap
Qt::ForegroundRole |用于使用默认Delegates渲染的项目的前景画笔(通常为文本颜色 |QBrush
请参阅Qt命名空间文档了解有关Qt::ItemDataRole枚举功能的更多信息。
现在我们需要确定使用分离的模型如何影响应用程序的性能因此让我们跟踪视图调用data()方法的频率。为了跟踪视图调用模型的频率我们在data()方法中添加了调试语句它会记录到错误输出流中。
在上面的小示例中data()将被调用42次。每次将光标悬停在字段上时data()将再次被调用——每个单元格调用7次。
分别执行的是 就是上面的 7个 Role 对应的 case 下面的语句。
这就是为什么在调用 data() 并缓存昂贵的查找操作时确保数据可用是很重要的。
3. 表格单元中的时钟 我们仍然有一个只读表但这一次内容每秒都在变化因为我们显示的是当前时间。 QVariant MyModel::data(const QModelIndex index, int role) const{int row index.row();int col index.column();if (role Qt::DisplayRole row 0 col 0)return QTime::currentTime().toString();return QVariant();}让时钟滴答作响的东西少了一些。我们需要每秒都告诉视图时间发生了变化需要重新读取。我们用计时器来做到这一点。在构造函数中我们将其间隔设置为1秒并连接其超时信号。 MyModel::MyModel(QObject *parent): QAbstractTableModel(parent), timer(new QTimer(this)){timer-setInterval(1000);connect(timer, QTimer::timeout , this, MyModel::timerHit);timer-start();}下面是对应的槽: void MyModel::timerHit(){//we identify the top left cellQModelIndex topLeft createIndex(0,0);//emit a signal to make the view reread identified dataemit dataChanged(topLeft, topLeft, {Qt::DisplayRole});}
我们 调用dataChanged()函数让视图再次读取左上角单元格中的数据。注意我们没有显式地将dataChanged()信号连接到视图。这在我们调用setModel()时自动发生。
4. 为列和行设置标题
标题可以通过视图方法隐藏
tableView-verticalHeader()-hide();然而头部内容是通过模型设置的因此我们重新实现了 headerData() 方法: QVariant MyModel::headerData(int section, Qt::Orientation orientation, int role) const{if (role Qt::DisplayRole orientation Qt::Horizontal) {switch (section) {case 0:return QString(first);case 1:return QString(second);case 2:return QString(third);}}return QVariant();}注意headerData()方法也有一个 role与MyModel::data()中role一样的含义。
5. 最小编辑示例
在本例中我们将构建一个应用程序通过重复在表格单元格中输入的值自动用内容填充窗口标题。为了能够轻松地访问窗口标题我们将 QTableView 放在 QMainWindow 中。
该模型决定是否提供编辑功能。我们只需要修改模型以便启用可用的编辑功能。这是通过重新实现下列虚拟方法来实现的setData()和flags()。 // mymodel.h#include QAbstractTableModel#include QStringconst int COLS 3;const int ROWS 2;class MyModel : public QAbstractTableModel{Q_OBJECTpublic:MyModel(QObject *parent nullptr);int rowCount(const QModelIndex parent QModelIndex()) const override;int columnCount(const QModelIndex parent QModelIndex()) const override;QVariant data(const QModelIndex index, int role Qt::DisplayRole) const override;bool setData(const QModelIndex index, const QVariant value, int role Qt::EditRole) override;Qt::ItemFlags flags(const QModelIndex index) const override;private:QString m_gridData[ROWS][COLS]; //holds text entered into QTableViewsignals:void editCompleted(const QString );};我们使用二维数组QString m_gridData来存储数据。这使得m_gridData成为MyModel的核心。MyModel的其余部分就像一个包装器并将m_gridData适应于QAbstractItemModel接口。我们还引入了editCompleted()信号它可以将修改后的文本传输到窗口标题中。 bool MyModel::setData(const QModelIndex index, const QVariant value, int role){if (role Qt::EditRole) {if (!checkIndex(index))return false;//save value from editor to member m_gridDatam_gridData[index.row()][index.column()] value.toString();//for presentation purposes only: build and emit a joined stringQString result;for (int row 0; row ROWS; row) {for (int col 0; col COLS; col)result m_gridData[row][col] ;}emit editCompleted(result);return true;}return false;}每当用户编辑一个单元格时都会调用setData()。index参数告诉我们哪个字段被编辑了value参数提供了编辑过程的结果。Role将始终设置为Qt::EditRole因为单元格只包含文本。如果存在一个复选框并且用户权限被设置为允许选中该复选框那么调用也会将角色设置为Qt::CheckStateRole。 Qt::ItemFlags MyModel::flags(const QModelIndex index) const{return Qt::ItemIsEditable | QAbstractTableModel::flags(index);}可以使用flags()来调整单元格的各种属性。
返回Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled足以显示编辑器可以选择一个单元格。
如果编辑一个单元格修改的数据比该特定单元格中的数据更多模型必须发出dataChanged()信号以便读取已经更改的数据。就像 3 中的写法自己去手动 emit dataChanged()信号
四、中间的话题
1. TreeView
您可以将上面的示例转换为具有树视图的应用程序。只需将QTableView替换为QTreeView就可以得到一个读/写树。不需要对模型进行任何更改。树不会有任何层次结构因为模型本身没有任何层次结构。 QListView、QTableView和QTreeView都使用了一个模型抽象它是一个合并的列表、表和树。这使得在同一个模型中使用多个不同类型的视图类成为可能。 到目前为止我们的示例模型看起来是这样的: 我们想要呈现一棵真正的树。为了建立模型我们将数据包装在上面的示例中。这次我们使用QStandardItemModel它是一个层次化数据的容器也实现了QAbstractItemModel。为了显示树QStandardItemModel必须用QStandardItems填充它能够保存项目的所有标准属性如文本、字体、复选框或笔刷。 // modelview.cpp#include mainwindow.h#include QTreeView#include QStandardItemModel#include QStandardItemMainWindow::MainWindow(QWidget *parent): QMainWindow(parent), treeView(new QTreeView(this)), standardModel(new QStandardItemModel(this)){setCentralWidget(treeView);QListQStandardItem * preparedRow prepareRow(first, second, third);QStandardItem *item standardModel-invisibleRootItem();// adding a row to the invisible root item produces a root elementitem-appendRow(preparedRow);QListQStandardItem * secondRow prepareRow(111, 222, 333);// adding a row to an item starts a subtreepreparedRow.first()-appendRow(secondRow);treeView-setModel(standardModel);treeView-expandAll();}QListQStandardItem * MainWindow::prepareRow(const QString first,const QString second,const QString third) const{return {new QStandardItem(first),new QStandardItem(second),new QStandardItem(third)};}我们只需实例化一个QStandardItemModel并在构造函数中添加一些QStandardItems。然后我们可以建立一个分层的数据结构因为一个QStandardItem可以容纳其他QStandardItem。节点在视图中被折叠和展开。
2. 使用选择器
我们希望访问所选项目的内容以便将其与层次结构一起输出到窗口标题中。
下面来创建几个条目: #include mainwindow.h#include QTreeView#include QStandardItemModel#include QItemSelectionModelMainWindow::MainWindow(QWidget *parent): QMainWindow(parent), treeView(new QTreeView(this)), standardModel(new QStandardItemModel(this)){setCentralWidget(treeView);QStandardItem *rootNode standardModel-invisibleRootItem();//defining a couple of itemsQStandardItem *americaItem new QStandardItem(America);QStandardItem *mexicoItem new QStandardItem(Canada);QStandardItem *usaItem new QStandardItem(USA);QStandardItem *bostonItem new QStandardItem(Boston);QStandardItem *europeItem new QStandardItem(Europe);QStandardItem *italyItem new QStandardItem(Italy);QStandardItem *romeItem new QStandardItem(Rome);QStandardItem *veronaItem new QStandardItem(Verona);//building up the hierarchyrootNode- appendRow(americaItem);rootNode- appendRow(europeItem);americaItem- appendRow(mexicoItem);americaItem- appendRow(usaItem);usaItem- appendRow(bostonItem);europeItem- appendRow(italyItem);italyItem- appendRow(romeItem);italyItem- appendRow(veronaItem);//register the modeltreeView-setModel(standardModel);treeView-expandAll();//selection changes shall trigger a slotQItemSelectionModel *selectionModel treeView-selectionModel();connect(selectionModel, QItemSelectionModel::selectionChanged,this, MainWindow::selectionChangedSlot);}视图在一个单独的选择模型中管理选择这个模型可以用selectionModel()方法取得。我们获取选择模型以便将槽连接到它的selectionChanged()信号。
void MainWindow::selectionChangedSlot(const QItemSelection /*newSelection*/, const QItemSelection /*oldSelection*/)
{//get the text of the selected itemconst QModelIndex index treeView-selectionModel()-currentIndex();QString selectedText index.data(Qt::DisplayRole).toString();//find out the hierarchy level of the selected itemint hierarchyLevel 1;QModelIndex seekRoot index;while (seekRoot.parent() ! QModelIndex()) {seekRoot seekRoot.parent();hierarchyLevel;}QString showString QString(%1, Level %2).arg(selectedText).arg(hierarchyLevel);setWindowTitle(showString);
}我们通过调用treeView-selectionModel()-currentIndex()来获取与选区对应的模型索引并通过模型索引获取字段的字符串。然后我们只需要计算物品的层次结构。顶级元素没有父元素parent()方法将返回一个默认构造的QModelIndex()。
这就是为什么我们使用parent()方法迭代到顶层同时计算迭代过程中执行的步数。
选择模型(如上所示)可以被检索但它也可以被QAbstractItemView::setSelectionModel设置。这就是为什么可以有3个视图类同步选择因为只有一个选择模型的实例被使用。要在3个视图之间共享选择模型请使用selectionModel()并将结果分配给第二个和第三个视图类与setSelectionModel()。
3. 预定义模型
使用model/view的典型方法是包装特定的数据使其可以在视图类中使用。然而Qt也为常见的底层数据结构提供了预定义的模型。如果有一种可用的数据结构适合您的应用程序那么预定义的模型可能是一个很好的选择。
模型名称含义QStringListModel存储一个字符串列表QStandardItemModel存储任意的分层项QFileSystemModel封装的本地的文件系统QSqlQueryModel封装一个SQL结果集QSqlTableModel封装一个SQL表QSqlRelationalTableModel用外键封装一个SQL表QSortFilterProxyModel对另一个模型进行排序和/或过滤
4. Delegates
到目前为止在所有的例子中数据都是以文本或复选框的形式出现在单元格中并以文本或复选框的形式进行编辑。提供这些表示和编辑服务的组件称为Delegates。我们才刚刚开始使用Delegates因为视图使用了默认Delegates。但是假设我们想要有一个不同的编辑器(例如滑块或下拉列表)或者想要以图形的形式显示数据。让我们看一个名为Star Delegate的例子其中使用星号表示评级:
这个视图有一个setItemDelegate()方法用来替换默认的Delegates并安装一个自定义的Delegates。新的Delegates可以通过创建一个继承自QStyledItemDelegate的类来编写。为了编写一个显示星号并且没有输入功能的Delegates我们只需要覆盖两个方法。 class StarDelegate : public QStyledItemDelegate{Q_OBJECTpublic:StarDelegate(QWidget *parent 0);void paint(QPainter *painter, const QStyleOptionViewItem option,const QModelIndex index) const;QSize sizeHint(const QStyleOptionViewItem option,const QModelIndex index) const;};Paint()根据底层数据的内容绘制星号。可以通过调用index.data()来查找数据。
Delegates的sizeHint()方法用于获取每个星号的尺寸以便单元格提供足够的高度和宽度来容纳这些星号。
如果你想在视图类的网格中使用自定义图形表示来显示数据编写自定义Delegates是正确的选择。如果你想离开网格你不会使用自定义Delegates而是使用自定义视图类。
Qt文档中对Delegates的其他引用:
Spin BoxDelegates示例 QAbstractItemDelegate类的引用 QSqlRelationalDelegate类的引用 QStyledItemDelegate类引用 QItemDelegate类引用