Qt 学习
C++ 模版
QObject 提供一个十分有用的 api,T findChild(QString, Qt::FindChildOptions)
,这个函数接收一个模版参数,返回模版参数的类型(如果子对象可以造型成 T ),也就是说返回值已经做了 cast
造型处理,这样就可以直接用特定的子类指针接收,使用起来非常方便。可以在对象的子类中寻找特定名称(objectName)的对象,而 Qt::FindChildOptions
参数用于设置搜索范围,Qt::FindDirectChildrenOnly
可以设置搜索范围仅限于当前对象的 children,并且不迭代 children 搜索,默认选项 Qt::FindChildrenRecursively
将迭代搜索所有子对象。
以下内容摘自官方示例:
// 返回最匹配的对象指针,该对象指针可以造型成 QPushButton 指针,并且该对象的 objectName 为 button1
QPushButton *button = parentWidget->findChild<QPushButton *>("button1");
// 返回最匹配的对象指针,该对象指针可以造型成 QListWidget 指针
QPushButton *list = parentWidget->findChild<QListWidget *>();
// 返回最匹配的对象指针,仅在 parentWidget 对象的直接子对象中查找,
// 该对象指针可以造型成 QPushButton 指针,并且该对象的 objectName 为 button1
QPushButton *button = parentWidget->findChild<QPushButton *>("button1", Qt::FindDirectChildrenOnly);
// 返回最匹配的对象指针,仅在 parentWidget 对象的直接子对象中查找,
// 该对象指针可以造型成 QPushButton 指针
QListWidget *list = parentWidget->findChild<QListWidget *>(QString(), Qt::FindDirectChildrenOnly);
需要注意的是最后一种用法,这里想要在 parentWidget 中找到可造型成 QListWidget 的直接子对象,但是不需要指定对象名,第一个参数需要用 QString()
而不能用空字符串 ""
,否则便没有符合要求的子类 list,此时 list 的值为 0x0,结果一直找不到子对象。
以下代码为 Qt5.7 版本源代码:
// QObject.h
template<typename T>
inline T findChild(const QString &aName = QString(), Qt::FindChildOptions options = Qt::FindChildrenRecursively) const
{
typedef typename QtPrivate::remove_cv<typename QtPrivate::remove_pointer<T>::type>::type ObjType;
return static_cast<T>(qt_qFindChild_helper(this, aName, ObjType::staticMetaObject, options));
}
// QObject.cpp
QObject *qt_qFindChild_helper(const QObject *parent, const QString &name, const QMetaObject &mo, Qt::FindChildOptions options)
{
if (!parent)
return 0;
const QObjectList &children = parent->children();
QObject *obj;
int i;
for (i = 0; i < children.size(); ++i) {
obj = children.at(i);
if (mo.cast(obj) && (name.isNull() || obj->objectName() == name))
return obj;
}
if (options & Qt::FindChildrenRecursively) {
for (i = 0; i < children.size(); ++i) {
obj = qt_qFindChild_helper(children.at(i), name, mo, options);
if (obj)
return obj;
}
}
return 0;
}
需要注意,类模版中成员函数的定义和声明都要放在 .h
文件中,防止编译器报错,参考上面的 findChild
模版成员函数,这个函数用到模版,声明和定义都放在 QObject.h 中。
该函数的实现是通过 qt_qFindChild_helper 助手函数实现的。
在 QObject.cpp 源代码中找到的两个函数定义:
void qt_qFindChildren_helper(const QObject *parent, const QRegularExpression &re,const QMetaObject &mo, QList<void*> *list, Qt::FindChildOptions options);
QObject *qt_qFindChild_helper(const QObject *parent, const QString &name, const QMetaObject &mo, Qt::FindChildOptions options);
在源代码实现中,判断能否返回对象指针的判断条件是 mo.cast(obj) && (name.isNull() || obj->objectName() == name)
如果传入的名称参数是空字符串 ""
那么 name.isNull()
返回的是 false,
obj->objectName() == name
返回的是 false,整个判断返回 false,也就无法找到符合要求的子对象了。
Model/View 架构
关于 Qt 的 Model/View 架构,在 官方文档 中已经给出了详细的介绍。
实际项目中使用到的模型是 Table 模型,在子类化 QAbstractTableModel 时,重写其中一些函数用于显示数据。
比如重写 QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
用于显示标题数据。标题数据有纵向和横向两种选择,Qt::Orientation
用于设置方向。如果不指定方向,对所有 role 值为 Qt::DisplayRole
的数据进行绘制,将会看到横向的第一个标题头似乎总是不显示或者显示的是错误的数据。原因在于 TableModel 还会显示纵向的标题头,导致两个方向的标题头重叠。
role 值有 Qt 提供的一些值如 Qt::DisplayRole
,Qt::BackgroundRole
,前者用于显示文本或图片,或者可以用来设置背景。
最开始接触 Qt 的 Model/View 架构时感觉它有些复杂,除了 Model 外,在重写函数时经常看到对 ModelIndex 的使用,ModelIndex 就是索引,包含一些数据,但是 QModelIndex 隐藏了它的构造函数(它的构造函数是用 private 修饰的),无法通过构造函数直接构造一个 QModelIndex 对象(需要通过 createIndex 方法构造):
以下为 Qt5.7 的部分源代码
// qabstractitemmodel.h
friend class QAbstractItemModel;
...
private:
inline QModelIndex(int arow, int acolumn, void *ptr, const QAbstractItemModel *amodel) Q_DECL_NOTHROW
: r(arow), c(acolumn), i(reinterpret_cast<quintptr>(ptr)), m(amodel) {}
编写自己的 model 类时感觉无从下手,好在官方的示例很多,介绍的也很详细。而在重写 TableModel 时,其实也没有怎么使用成员函数中 QModelIndex 参数,就是直接跳过这个参数,对外提供数据。
模型比较简单,目前仅在刷新数据时用到 QModelIndex 。