最近需要参与一款Qt跨平台的软件开发,在此之前,特把基础信息做学习和梳理,仅供参考。
所使用的技术和版本情况如下:
- 虚拟机:VMware 16.2.5
- 操作系统:ubuntu-20.04.6-desktop-amd64:
- Mysql数据库 8.0.36
- Workbench (mysql-workbench-community_8.0.29-1ubuntu20.04_amd64.deb)
- QT 5.12.12(qt-opensource-linux-x64-5.12.12.run)
ps:有人问为什么不用VirtualBox、GNOME Boxes,或者其他Qt、mysql版本问题,前者是因为个人习惯,后者是因为项目要求。
1、开发环境搭建
参考这篇
2、MVC简介
MVC模式是软件工程中常见的一种软件架构模式,该模式把软件系统(项目)分为三个基本部分:模型(Model)、视图(View)和控制器(Controller)。
使用MVC模式有很多优势,例如:
-
简化后期对项目的修改、扩展等维护操作;
-
使项目的某一部分变得可以重复利用;使项目的结构更加直观。
具体来讲,MVC模式可以将项目划分为模型(M)、视图(V)和控制器(C)三个部分,并赋予各个部分不同的功能,方便开发人员进行分组。
**(1)模型(Model):**模型持有所有的数据、状态和程序逻辑。模型接受视图数据的请求,并返回最终的处理结果。
**(2)视图(View):**负责界面的显示,以及与用户的交互功能,例如表单、网页等。
**(3)控制器(Controller):**可以理解为一个分发器,用来决定对于视图发来的请求,需要用哪一个模型来处理,以及处理完后需要跳回到哪一个视图。即用来连接视图和模型。
实际开发中,通常用控制器对客户端的请求数据进行封装(如将form表单发来的若干个表单字段值,封装到一个实体对象中),然后调用某一个模型来处理此请求,最后再转发请求(或重定向)到视图(或另一个控制器)。
3、代码设计与实现
1、框架构成
一个基于C++和QML的跨平台项目框架通常包含以下几个主要组成部分:后端C++逻辑、QML前端界面、信号与槽机制进行通信,以及可能的数据存储(如数据库)。下面是对这个框架构成的简单描述:
1. 后端C++逻辑
- C++类库:C++代码部分通常包含一系列类,这些类封装了应用程序的核心逻辑。这些类可能包括数据处理、算法实现、网络通信、文件操作等。
- 数据库访问:持久化存储数据,C++代码需包含与数据库交互的逻辑。这通常涉及使用数据库API或ORM(对象关系映射)库来执行查询、插入、更新和删除操作。
- 业务逻辑:C++代码包含应用程序的业务逻辑,即根据用户需求和数据状态执行的操作。
2. QML前端界面
- QML文件:QML是一种用于描述用户界面的声明式语言,它类似于HTML和CSS的结合。QML文件定义了应用程序的外观和布局,包括窗口、按钮、文本框等界面元素。
- 界面元素:QML文件中包含各种界面元素,这些元素通过属性和信号与C++代码进行交互。例如,一个按钮的点击事件可以触发一个信号,该信号被C++代码中的槽函数捕获并处理。
- 样式和主题:QML还支持自定义样式和主题,以便轻松更改应用程序的外观。
3. 信号与槽机制进行通信
- 信号:在Qt框架中,信号是对象在特定事件发生时发出的一种通知。QML界面元素和C++对象都可以发出信号。
- 槽:槽是响应信号的函数或方法。当信号被发出时,与之关联的槽函数将被调用。这种机制允许QML界面与C++后端逻辑进行无缝通信。
- 连接信号与槽:在应用程序中,需要显式地将信号连接到槽。这可以在C++代码中完成,也可以在QML文件中使用Qt的内置函数(如
Connections
元素)完成。
4. 应用程序集成
- 主函数:C++代码中的主函数(通常是
main.cpp
)负责初始化Qt应用程序,加载QML界面,并将它们与C++后端逻辑集成在一起。 - 资源文件:为了管理应用程序中的资源(如QML文件、图像、字体等),你可以使用Qt的资源系统。资源文件(通常是
.qrc
文件)定义了这些资源的路径和属性。 - 编译和部署:使用Qt的构建系统(如qmake或CMake)编译应用程序,并确保在目标平台上部署所有必要的依赖项和运行时库。
通过这种框架,你可以开发出既具有强大功能又具有良好用户体验的跨平台应用程序。C++后端提供了灵活性和性能,而QML前端则提供了直观和易于定制的用户界面。
如果是和我一样的QML小白,推荐看两个视频:
数据库相关操作,包含事务命令:UP:爱编程的大丙
QML教程(P20-23,QML与C++交互):UP:落雨薄青衫
2、代码部分
先贴库:gitee
1、.pro文件
Qt项目的.pro
文件(也称为qmake项目文件)。这个文件用于描述如何构建Qt项目,并包含了编译项目所需的各种设置和指令。下面我将逐一解释这个文件中的各个部分:
这是一个Qt项目的.pro
文件(也称为qmake项目文件)。这个文件用于描述如何构建Qt项目,并包含了编译项目所需的各种设置和指令。主要关注前面:
-
QT += quick qml sql quickcontrols2
指定了项目需要使用的Qt模块。这里指定了
quick
(用于Qt Quick框架),qml
(QML支持),sql
(数据库支持)和quickcontrols2
(Qt Quick Controls 2 UI框架)。 -
CONFIG += c++11
指示qmake使用C++11标准来编译项目。
总的来说,这个.pro
文件为Qt项目提供了构建和安装的详细信息。可以根据项目的具体需求来修改。
2、头文件
DbConnector主要处理数据库:
class DbConnector : public QObject
{Q_OBJECT
public:explicit DbConnector(QObject *parent = nullptr);~DbConnector();static DbConnector * getInstance();void createSql(); //用于初始化数据库(打开,连接),在构造函数内调用void closeSql(); //用于关闭数据库,在析构函数内调用Q_INVOKABLE QSqlDatabase getDb();
private:QSqlDatabase db; //定义一个数据库变量
};
MyListModel主要处理自定义模型的数据:
class MyListModel : public QAbstractListModel
{Q_OBJECT
public:enum MyRoleName{Name = Qt::DisplayRole + 1,Value};explicit MyListModel(QObject *parent = nullptr);static MyListModel * getInstance();Q_INVOKABLE bool select();void refreshData();int rowCount(const QModelIndex &parent = QModelIndex()) const override;QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;QHash<int,QByteArray> roleNames() const override;Q_INVOKABLE void addData(QString s);Q_INVOKABLE void updateData(int id,QString s);Q_INVOKABLE void onEnterPressed(int id,QString s);public slots:void addSlot(QString s);void updateSlot(int id,QString s);void onEnterPressedSlot(int id,QString s);
private:QList<QString> m_data;
};
3、源文件
DbConnector:
数据库连接
void DbConnector::createSql()
{db = QSqlDatabase::addDatabase("QMYSQL");//mysql需要自己编译,没有的话查看我上篇的解决方案db.setHostName("localhost");//自己填db.setUserName("username");//自己填db.setDatabaseName("DatabaseName");//自己填db.setPassword("密码");//自己填db.setPort(3306);//自己查if(!db.open()){qDebug()<<"fail :"<<db.lastError().text();}else{qDebug()<<"open db success";}
}
添加逻辑:
void MyListModel::addSlot(QString s)
{addData(s);refreshData();
}
void MyListModel::addData(QString s)
{QSqlQuery query;QString sql = "INSERT INTO person (name) VALUES (:name)";// 使用prepare()方法来准备SQL语句,并使用bindValue()来绑定参数query.prepare(sql);query.bindValue(":name", s);// 执行SQL语句if (!query.exec()){qDebug() << "Failed to insert name:" << query.lastError().text();}else{qDebug() << "Successfully inserted name:" << s;}
}
void MyListModel::refreshData()
{beginResetModel(); // 视图模型更改if(select()){qDebug()<<"refresh data success";}else{qDebug()<<"refresh data failed";}endResetModel();
}
更新逻辑:
void MyListModel::updateSlot(int id,QString s)
{updateData(id,s);int row = id;// 通知QML该元素已更改emit dataChanged(index(row, 0), index(row, 0));refreshData();
}
void MyListModel::updateData(int id, QString s)
{id++;QSqlQuery query;QString sql = "UPDATE person SET name = :newName WHERE id = :id";// 使用prepare()方法来准备SQL语句,并使用bindValue()来绑定参数query.prepare(sql);query.bindValue(":newName", s);query.bindValue(":id", id);// 执行SQL语句if (!query.exec()){qDebug() << "Failed to update name for id:" << id << "Error:" << query.lastError().text();return;}qDebug() << "Successfully updated name for id:" << id;
}
4、QML文件
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.12Window {id:windowwidth: 640height: 480visible: truetitle: qsTr("Hello World")//信号signal addSig(string s)signal updateSig(int id,string s)signal onEnterPressedSig(int id,string s)ComboBox{id:comboBoxx:10y:10z:1width:150height: 40model:MyListModelcurrentIndex: 0font.pointSize: 12editable: true //允许编辑delegate:ItemDelegate{id:delegatewidth:comboBox.widthheight: comboBox.heightcontentItem: Text{text:nameanchors.fill:parentcolor:"black"font:comboBox.fontelide:Text.ElideRightverticalAlignment: Text.AlignVCenter}highlighted: index === comboBox.highlightedIndex//悬浮}Component.onCompleted:{comboBox.editText = "请选择"addSig.connect(MyListModel.addSlot)updateSig.connect(MyListModel.updateSlot)onEnterPressedSig.connect(MyListModel.onEnterPressedSlot)}Button{x:50y:50id:btntext:"新增"onClicked:{var editText = comboBox.editText;addSig(editText);comboBox.currentIndex = MyListModel.rowCount()-1;console.log("currentIndex is ",comboBox.currentIndex);}}Button{x:50y:100id:btn2text:"修改"onClicked:{var currentIndex = comboBox.currentIndex-1;var editText = comboBox.editText;updateSig(currentIndex,editText);}}}
}
Button实现起来比较简单一点,也更符合正常的逻辑。
4、总结
整体项目不难,但是“五脏俱全”,包含了一个QT跨平台项目最基本的知识点,适合新人练手。
希望大神们多多批评指正,我也是刚上手。