Note: 此项目原博客地址
https://llfc.club/category?catid=225RaiVNI8pFDD5L4m807g7ZwmF#!aid/2eIZbBf2pkVGQG1oPdRLDtTDLo0
1、创建项目
首先使用QT软件创建Qt Widgets Application,生成的基类使用QMainWindow

2、添加资源文件并更换图标
首先将准备的图片文件添加到res文件夹下,然后将res文件夹放下项目根目录下;然后在Qt软件中点中项目名称,选择添加新文件,
选择Qt Resource Files,添加Qt的资源文件,名字设为rc,添加成功后右键rc.qrc选择添加现有文件,然后选中res文件夹下的所有图片,
这样res文件夹下的所有图片资源都导入到项目里面了。
然后将QMainWindow的界面长宽改为300x500

之后修改项目左上角的图标,在QMainWindow的界面找到windowIcon,选择添加res文件下的ICNO.ico

修改项目左上面的标题,将MainWindow修改为Chat,在QMainWindow的构造函数添加以下代码即可
3、添加登录页面
右键项目名称并选择添加新项目,点击Qt设计师界面类

选择Dialog Without Buttons

将名字起为LoginDialog,创建完成后点击ui界面并将ui界面修改为以下布局:

在QMainWindow的类里添加LoginDialog的指针成员,并在QMainWindow构造函数里面设置为中心部件
1 2 3
| _login_dialog = new LoginInDialog(); setCentralWidget(_login_dialog); _login_dialog->show();
|
4、添加样式表
在项目的根目录下创建一个名为style的文件夹,打开style文件夹建立一个.text文件,将这个.text文件名称改为stylesheet.qss,然后右键点击Qt项目中的rc.qrc,选择添加现有文件,选择刚刚创建的style文件夹即可,这样stylesheet.qss就被导入到项目中了
打开stylesheet.qss并写下如下代码
1 2 3
| QDialog#LoginInDialog{ background-color:rgb(255,255,255) }
|
这样LoginInDialog界面的背景就被美化成白色了
然后在主函数main里面添加以下代码用来启动qss文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| int main(int argc, char *argv[]) { QApplication a(argc, argv);
QFile qss(":/style/stylesheet.qss");
if(qss.open(QFile::ReadOnly)){ qDebug("open success!"); QString style = QLatin1String(qss.readAll()); a.setStyleSheet(style); qss.close(); }else{ qDebug("open failed!"); }
MainWindow w; w.show();
return a.exec(); }
|
5、添加注册页面
跟添加登录页面的方式添加注册页面,将名字设置为RegisterDialog,添加完成之后打开注册页面的ui,将注册页面的ui修改成以下画面

然后在注册页面的构造函数里面将code_lineedit和confirm_lineedit置为密码模式
1 2
| ui->code_lineedit->setEchoMode(QLineEdit::Password); ui->confirm_lineedit->setEchoMode(QLineEdit::Password);
|
我们在qss里面添加tips的样式,正确状态下tips里面的文字显示为绿色,错误状态下tips的文字显示为红色
1 2 3 4 5 6 7
| #tips[state = 'normal']{ color: green; }
#tips[state = 'error']{ color: red; }
|
然后我们来实现tips的刷新功能,这个刷新功能函数repolish打算做成全局函数来实现,因此我们添加global.cpp和global.h文件
global.h文件的声明:
1 2 3 4 5 6 7 8 9 10
| #ifndef GLOBAL_H #define GLOBAL_H
#include <QWidget> #include <functional> #include "QStyle"
extern std::function<void(QWidget*)> repolish;
#endif
|
global.cpp文件里repolish函数的实现:
1 2 3 4
| std::function<void(QWidget*)> repolish=[](QWidget* w){ w->style()->unpolish(w); w->style()->polish(w); };
|
在RegisterDialog的构造函数里面添加tips的样式设置
1 2
| ui->tips->setProperty("state", "normal"); repolish(ui->tips);
|
接下来实现获取验证码的逻辑,在RegisterDialog.ui里面右键点击获取按钮,选择转到槽,在槽函数里面写下如下代码
1 2 3 4 5 6 7 8 9 10 11 12 13
| auto email=ui->email_lineedit->text();
QRegularExpression regex(R"((\w+)(\.|_)?(\w*)@(\w+)(\.(\w+))+)"); bool match = regex.match(email).hasMatch(); if(match){ ShowTips("邮箱地址正确", true);
} else{ ShowTips("邮箱地址不正确", false); }
|
下面实现ShowTips函数,这个函数用来显示tips的文字和状态
1 2 3 4 5 6 7 8 9 10
| void RegisterDialog::ShowTips(QString str, bool b_ok){ if(b_ok){ ui->tips->setProperty("state", "normal"); } else{ ui->tips->setProperty("state", "error"); } ui->tips->setText(str); repolish(ui->tips); }
|
6、添加单例类
我们编写网络通讯需要确保一个类只生成一个实例,所以我们编写一个单例模板类,之后的网络通讯类都继承这个单例类
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
| #include <mutex> #include <memory> #include <iostream> #include "QDebug"
template <typename T> class SingleTon{ protected: SingleTon() = default; SingleTon(const SingleTon<T>&) = delete; SingleTon& operator = (const SingleTon<T>&) = delete;
static std::shared_ptr<T> _instance;
public: static std::shared_ptr<T> GetInstance(){ static std::once_flag _flag; std::call_once(_flag,[&](){ _instance = std::shared_ptr<T>(new T); }); return _instance; } void PrintAdress(){ std::cout<< _instance.get() <<std::endl; }
};
template <typename T> std::shared_ptr<T> SingleTon<T>::_instance = nullptr;
|
7、添加Http管理类
Http管理类主要用于管理HTTP接收发送等请求,首先我们需要在pro文件里面添加网络库
在global.h里面添加一些Http管理类用到的头文件
1 2 3 4 5 6 7 8 9 10 11 12
| #include <QByteArray> #include <QNetworkRequest> #include <QNetworkReply> #include <qDebug> #include <QString> #include <QUrl> #include <QObject> #include <QNetworkAccessManager> #include <memory> #include <QJsonObject> #include <QJsonDocument> #include <QMap>
|
添加httpMgr.cpp和httpMgr.h,httpMgr.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
| #ifndef HTTPMGR_H #define HTTPMGR_H
#include "singleton.h" #include "global.h"
class HttpMgr: public QObject,public SingleTon<HttpMgr>, public std::enable_shared_from_this<HttpMgr> { Q_OBJECT friend class SingleTon<HttpMgr>; public: ~HttpMgr(); void PostHttpRequest(ReqID id, Modules modules, QUrl url, QJsonObject json);
signals: void SignalHttpFinished(ReqID id, Modules modules, ErrorCode ec_code, QString res); void SignalRegisterFinished(ReqID id, ErrorCode ec_code, QString res);
private slots: void SlotHttpFished(ReqID id, Modules modules, ErrorCode ec_code, QString res);
private: HttpMgr();
QNetworkAccessManager _manager; };
#endif
|
PostHttpRequest函数用来接收HTTP发送过来的消息,参数id和modules用来区分HTTP信息的类型,url是请求的地址,json是请求的数据
我们在global.h里面定义ReqID枚举类型
1 2 3 4
| enum ReqID{ ID_GET_VARIFY_CODE = 1001, ID_REGISTER_USER = 1002 }
|
ReqID有两个作用,一个作用是定位接收的是哪一部份数据;第二个作用是存储数据的key值使用,ReqID=1001时,说明此时处于注册状态,我们需要将ReqID和验证码存入到map里,ReqID就作为验证码的key值使用;同理,当ReqID=1002时,ReqID就作为密码的key值所使用
在global.h里面定义ErrorCode
1 2 3 4 5
| enum ErrorCode{ SUCESS = 0, ERROR_JSON = 1, ERROR_NETWORK = 2 };
|
ErrorCode是错误的类型,用于帮助定位项目运行过程中在哪一部分出现问题
在global.h里面定义Modules
1 2 3 4
| enum Modules{ REGISTER_MOD = 0, };
|
Modules也是用来定位接收的是哪一部分数据,用来说明项目此时处于的状态,目前只将注册状态设为0,但之后还会有登录状态和忘记密码的状态
在PostHttpRequest函数里面首先发送Post请求然后收到回复并解析,无论解析的数据如何都会发送SignalHttpFinished信号,然后数据传输到槽函数SlotHttpFished里面,槽函数SlotHttpFished再次发送信号SignalRegisterFinished,接收信号的槽函数SlotRegisterFinished被定义于RegisterDialog类里面
简单来讲,RegisterDialog类里面的函数调用HttpMgr类的函数发送HTTP信息,HttpMgr类的函数将接收的信息传送回RegisterDialog类里面的函数,数据的解析是在RegisterDialog类里进行,HttpMgr类只负责数据的发送和接收
PostHttpRequest函数的实现如下
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
| void HttpMgr::PostHttpRequest(ReqID id, Modules modules, QUrl url, QJsonObject json) { QByteArray data = QJsonDocument(json).toJson(); QNetworkRequest request(url); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); request.setHeader(QNetworkRequest::ContentLengthHeader,QByteArray::number(data.length()));
auto self = shared_from_this(); QNetworkReply* reply = _manager.post(request, data); QObject::connect(reply, &QNetworkReply::finished, [reply, self, id, modules](){ if(reply->error() != QNetworkReply::NoError){ qDebug()<<reply->errorString(); emit self->SignalHttpFinished(id, modules, ErrorCode::ERROR_NETWORK, ""); reply->deleteLater(); return; }
QString res = reply->readAll(); emit self->SignalHttpFinished(id, modules, ErrorCode::SUCESS, res); reply->deleteLater(); return; }); }
|
槽函数SlotHttpFished的实现
1 2 3 4 5 6
| void HttpMgr::SlotHttpFished(ReqID id, Modules modules, ErrorCode ec_code, QString res) { if(modules == Modules::REGISTER_MOD){ emit SignalRegisterFinished(id, ec_code, res); } }
|
在HttpMgr的构造函数里面将信号SignalHttpFinished与槽函数SlotHttpFished连接
1
| connect(this, &HttpMgr::SignalHttpFinished, this, &HttpMgr::SlotHttpFished);
|
在RegisterDialog的构造函数里面将信号SignalRegisterFinished和槽函数SlotRegisterFinished连接
1
| connect(HttpMgr::GetInstance().get(), &HttpMgr::SignalRegisterFinished, this, &RegisterDialog::SlotRegisterFinished);
|
然后实现槽函数SlotRegisterFinished
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
| void RegisterDialog::SlotRegisterFinished(ReqID id, ErrorCode ec_code, QString res) { if(ec_code != ErrorCode::SUCESS){ ShowTips("网络请求错误", false); return; } else{ QJsonDocument jsonDoc = QJsonDocument::fromJson(res.toUtf8());
if(jsonDoc.isNull()){ ShowTips("JSON解析错误", false); return; }
if(jsonDoc.isObject()){ ShowTips("JSON解析错误", false); return; }
_handlers[id](jsonDoc.object()); return; } }
|
8、添加注册信息处理
我们需要对注册的信息进行处理,在RegisterDialog类的私有成员进行声明
1 2
| QMap<ReqID, std::function<void(const QJsonObject&)>> _handlers;
|
对于注册信息的定义与声明
1 2 3 4 5 6 7 8 9 10 11 12 13
| void RegisterDialog::InitHttpHandlers() { _handlers.insert(ReqID::ID_GET_VARIFY_CODE, [this](QJsonObject jsonobj){ int error = jsonobj["error"].toInt(); if(error != ErrorCode::SUCESS){ ShowTips("参数错误", false); true; }
auto email = jsonobj["email"].toString(); ShowTips("验证码已经发送到邮箱,请注意查收", true); }); }
|
在槽函数SlotRegisterFinished里面添加根据id调用函数处理对应逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| void RegisterDialog::SlotRegisterFinished(ReqID id, ErrorCode ec_code, QString res) { if(ec_code != ErrorCode::SUCESS){ ShowTips("网络请求错误", false); return; } else{ _handlers[id](jsonDoc.object()); return; } }
|