QxORM & QT 使用过程记录
以下内容均基于 Windows
背景知识:只知道 C++ 语法,不知道 C++ 的工程实践
如何编译 QxORM
- 下载源码
- 使用 Qt 打开源码目录下的
*.pro
文件 - 在 Qt 的 项目 → 构建和运行 选择一个编译器,如
Desktop Qt 6.7.0 MinGW 64-bit
- 注意上方的 构建设置 栏目下的 编辑构建设置,其拥有多个构建方法,如
Debug
,Release
- 将 构建目录 调整为 QxORM 的源码根目录 +
/lib
,依次对Debug
、Release
构建方法进行调整 - 在左下方的 💻 那,依次选择
Debug
和Release
点击 🔨 ,一共需要 🔨 两次
完成:lib 目录下出现 libQxOrmd.a
和 libQxOrm.a
文件
如何将 QxORM 加入 Qt 项目中
将 Qt 项目的 *.pro
文件加入以下语句
- 第一行表示在
release
构建的情况下,使用文件名结尾不带d
的 QxORM - 第二行表示在
debug
构建的情况下,使用文件名结尾带d
的 QxORM - 第 4 5 行引入头文件
1 | win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../../Qt/QxOrm/lib/ -lQxOrm |
所有出现的 $$PWD/../../Qt/QxOrm/
都要更改为自己的 QxORM 与 Qt 项目的相对目录,$$PWD
指代 Qt 项目的根路径。
QxORM 需要使用数据库驱动,所以还需要在第一行进行修改(添加 sql)
1 | QT += core gui sql |
甚至看到有教程说要把 QxORM 的源码全部拖到 Qt 项目中的 😲
还好过往的开发经验告诉我,当一个问题的找不到靠谱的解决方案时,往往是这个问题过于简单和常见,搜索 “Qt 添加库”,放弃 QxORM 关键词,即可得到答案。
如何创建一个新的实体
注意:如果实体创建的步骤有遗漏,QxORM 并不会给出非常清晰的提示,很容易摸不着头脑,所以要确定自己没有遗漏任何步骤
直接使用 QtCreator 建立类,下面省略了
#IFDEF
等 QtCreator 会生成的代码
-
头文件内注册
classname.h 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class ClassName {}
// qx::trait::no_base_class_defined 是对于当前实体没有父类的情况
QX_REGISTER_HPP_EXPORT_DLL(ClassName, qx::trait::no_base_class_defined, 1)
// 如果有父类实体,则直接使用父类实体的类名,如 BaseEntity
// QX_REGISTER_HPP_EXPORT_DLL(ClassName, BaseEntity, 1)
// 可选 触发器
namespace qx {
namespace dao {
namespace detail {
template <>
struct QxDao_Trigger<ClassName>
{
static inline void onBeforeInsert(ClassName * t, qx::dao::detail::IxDao_Helper * dao)
{ if (t) { t->onBeforeInsert(dao); } }
static inline void onBeforeUpdate(ClassName * t, qx::dao::detail::IxDao_Helper * dao)
{ if (t) { t->onBeforeUpdate(dao); } }
static inline void onBeforeDelete(ClassName * t, qx::dao::detail::IxDao_Helper * dao)
{ Q_UNUSED(t); Q_UNUSED(dao); }
static inline void onBeforeFetch(ClassName * t, qx::dao::detail::IxDao_Helper * dao)
{ Q_UNUSED(t); Q_UNUSED(dao); }
static inline void onAfterInsert(ClassName * t, qx::dao::detail::IxDao_Helper * dao)
{ Q_UNUSED(t); Q_UNUSED(dao); }
static inline void onAfterUpdate(ClassName * t, qx::dao::detail::IxDao_Helper * dao)
{ Q_UNUSED(t); Q_UNUSED(dao); }
static inline void onAfterDelete(ClassName * t, qx::dao::detail::IxDao_Helper * dao)
{ Q_UNUSED(t); Q_UNUSED(dao); }
static inline void onAfterFetch(ClassName * t, qx::dao::detail::IxDao_Helper * dao)
{ Q_UNUSED(t); Q_UNUSED(dao); }
};
} // namespace detail
} // namespace dao
} // qx -
CPP 文件内注册
classname.cpp 1
2
3
4
5
6
7
8
9
10
11
12// 不要遗漏这一句
QX_REGISTER_CPP_EXPORT_DLL(ClassName)
namespace qx
{
template <> void register_class(QxClass<Category>& t)
{
// 依次声明所有要在数据表中存储的属性
// 如不声明,则不存储
// 如果有父类,无需在此声明父类的属性
}
}
QxORM 自动更新创建时间和更新时间 created_at updated_at
使用 Trigger 触发器实现,使用父类来减少重复代码。
先定义一个 BaseEntity
类,然后依次定义 id
、created_at
,updated_at
字段,再添加触发器:
onBeforeInsert
插入前更新created_at
为QDateTime::currentDateTime()
onBeforeUpdate
更新前更新updated_at
为QDateTime::currentDateTime()
示例代码:
1 |
|
1 |
|
我们真的需要 bi-directional 关系吗?
在 Paper
和 Author
的多对多关系里,惯性的思维是应该同时包含 Paper-->Authors
和 Author-->papers
的双向多对多关系,这样才可以同时互相获得。
然而,在 C++ 中,这将导致循环包含,编译无法通过。
在 Paper
和 Author
中间,我们还需要一个 AuthorToPaper
中间实体,当我们获取需要知道某篇文章的作者时,我们已经知道了该文章的 id
,那我们可以很容易的拿到 AuthorToPaper
实体,反之亦然。
所以,从原先的双向多对多关系,变成了两个单向多对一关系,只是添加了一个需要接触的中间实体。
QxORM Relation
在定义 Relation 时,无需手动定义外联的列,如 category_id
等,只需要在声明实体结构的时候,手动指定这个列
1 | t.relationManyToOne(&Category::parent, "parent_id"); |
这样之后,就会自动生成 parent_id。
在实际操作两个对象让他们关联的时候,直接使用 a->B = b
即可
QxORM 插入
在使用 qx::dao::insert(T &T)
的时候,注意其中是引用传递,所以插入数据库后,更新的 ID 也会同步到变量中。
Qt 插槽与信号
插槽Slots 函数 是用于处理 信号函数 的函数。类似于 监听器 <-> 事件 系统,插槽作为监听器,信号作为事件。
注意 Signal 函数和 Slots 函数相对应的签名必须一致,包括形参的 const
等修饰符。
1 | class Handler : public QObject |
莫名其妙的 fetch_all
不存在问题
本来 QxORM 用的好好的,突然发现编译不通过了,一直报错 fetch all' is not a member 0f qx::dao'; did you mean qx::dao::throwable::fetch all?
差点就准备放弃继续开发了,还好最后看到问题时发生在 QxORM.h
头文件中的,猜想是源码可能被不小心修改了,所以重新下载了一遍源码并重新编译,成功解决了问题。
(其实可能并不需要重新编译,只是因为 QxORM/include
里的文件编译不通过,但编译后使用的应该是已经编译好的动态链接库)
Qt TreeView 鼠标悬停卡死 程序未响应
触发方法:
- 开启有道词典的划词、取词
- 鼠标悬停 TreeView 中的某一项等待两秒
- 程序未响应,可见后台内存逐渐攀升
Qt QTreeWidget控件造成程序不响应,内存泄露_qtreewidgetitem中的子节点点击后程序崩溃-CSDN博客
QAbstractNativeEventFilter
的签名和文中的时候有变动,需要修改
1 |
|
解耦设计思路
在编辑某个实体的信息的时候,是否应该交由对话框类修改和保存实体呢?还是说对话框类仅仅用于返回所需要的数据?
假设:
- 对话框类负责修改和保存实体:那在呼出对话框的时候,就应该将所有所需的信息传入对话框中,在对话框编辑完成后,还需要根据编辑的结果更新主窗口的信息。比如在添加一个类别后,我们如何知道这个类别是归属于哪个父类的,这个信息也应该随着对话框的呼出传入。
- 如果只是返回所需的信息。这个思想很像 MVC 架构,View 层只负责收集数据,一切后台操作都交由 Controller 和 Model 层。但是在桌面应用程序中,这样是否过于麻烦?矫枉过正?
和文心一言讨论后,暂时决定使用 2 来设计,感觉是一个比较稳妥的方法,就是编码比较麻烦。
无法修改 paper->year
奇奇怪怪的坑,本来是在一个函数内调整 p->year
然后外部再用 QSring::number
方法来转为字符串,结果编译时竟然直接跳过了 p->year
这一行,但在 QString::number
中手动指定 base
参数后又可以了。貌似是由于 QtCreator 构建的时候没有清除旧的编译文件导致的。
member access into incomplete type QMimeData
要加入头文件 <QMimeData>