0.注册界面完善
1. 密码加密
cpp
#ifndef GLOBAL_H
#define GLOBAL_H
#include <QWidget>
#include <functional>
#include <QRegularExpression>
#include "QStyle"
#include <memory>
#include <iostream>
#include <mutex>
#include <QString>
#include <QSettings>
using namespace std;
// extern 声明此变量是在其他文件中定义的全局变量
extern function<void(QWidget*)> repolish;
extern function<QString(QString)> xorString; // 密码加密
extern QString ConfigPath; // 配置文件路径
extern QSettings* ConfigSettings; // 配置文件对象
#endif // GLOBAL_H
cpp
#include "Global.h"
#include <QDir>
// 初始化声明的全局变量
function<void(QWidget*)> repolish = [](QWidget* Widget)
{
Widget->style()->unpolish(Widget);
Widget->style()->polish(Widget);
};
/* 密码加密 */
function<QString(QString)> xorString = [](QString input)
{
QString result = input;
int length = input.length();
length %= 255;
for (int i = 0; i < length; ++i)
{
result[i] = QChar(static_cast<ushort>(input[i].unicode() ^ static_cast<ushort>(length)));
}
return result;
};
// 配置文件路径
QString ConfigPath = QDir::currentPath() + "/Config/Config.ini";
// 管理配置文件对象
QSettings* ConfigSettings = new QSettings(ConfigPath, QSettings::IniFormat);


2. 添加定时按钮
点击获取验证码后需要让按钮显示倒计时,然后倒计时结束后才能再次点击
添加TimerBtn类
.h
cpp
#ifndef TIMERBTN_H
#define TIMERBTN_H
#include <QPushButton>
#include <QTimer>
class TimerBtn : public QPushButton
{
Q_OBJECT
public:
TimerBtn(QWidget *parent = 0);
~TimerBtn();
public:
virtual void mouseReleaseEvent(QMouseEvent *event) override;
private:
QTimer *_timer = nullptr;
int _counter = 0;
};
#endif // TIMERBTN_H
.cpp文件
cpp
#include "TimerBtn.h"
#include <QMouseEvent>
#include <QDebug>
TimerBtn::TimerBtn(QWidget*parent)
: QPushButton(parent),_counter(10)
{
_timer = new QTimer(this);
connect(_timer, &QTimer::timeout, [this]()
{
_counter--;
if (_counter <= 0)
{
_timer->stop();
_counter = 10;
setText(QString::fromLocal8Bit("获取"));
setEnabled(true); // 设置为可用
return;
}
setText(QString::number(_counter));
});
}
TimerBtn::~TimerBtn()
{
_timer->stop();
}
void TimerBtn::mouseReleaseEvent(QMouseEvent * event)
{
if (event->button() == Qt::LeftButton)
{
qDebug() << "TimerBtn::mouseReleaseEvent";
setEnabled(false); // 设置为不可用
_timer->start(1000); // 启动定时器
emit clicked();
}
QPushButton::mouseReleaseEvent(event);
}
将获取按钮提升为TimerBtm
3. 检测输入是否合理
3.1 枚举错误类型
在Enum.h中添加下列枚举
cpp
/* 标签错误类型 */
enum TipErr {
TIP_SUCCESS = 0,
TIP_EMAIL_ERR = 1,
TIP_PWD_ERR = 2,
TIP_CONFIRM_ERR = 3,
TIP_PWD_CONFIRM = 4,
TIP_VERIFY_ERR = 5,
TIP_USER_ERR = 6
};
3.2 添加错误提示的接口
cpp
QMap<TipErr, QString> _TipErrs; // 错误提示
void AddTipErr(TipErr err, const QString& tips); // 添加错误提示
void DelTipErr(TipErr err); // 清除错误提示
/* 添加错误提示 */
void RegisterWidget::AddTipErr(TipErr err, const QString & tips)
{
_TipErrs[err] = tips;
ShowTipLabel(tips, "error");
}
/* 清空错误提示 */
void RegisterWidget::DelTipErr(TipErr err)
{
_TipErrs.remove(err);
if (_TipErrs.isEmpty())
{
ui.Tip_Label->clear();
return;
}
ShowTipLabel(_TipErrs.first(), "error");
}
3.3 绑定文本输入的回调检查
cpp
/* 检查输入是否合法 */
connect(ui.User_Edit, &QLineEdit::editingFinished, this, &RegisterWidget::CheckUserValid);
connect(ui.Email_Edit, &QLineEdit::editingFinished, this, &RegisterWidget::CheckEmailValid);
connect(ui.PassWord_Edit, &QLineEdit::editingFinished, this, &RegisterWidget::CheckPasswordValid);
connect(ui.Enter_Edit, &QLineEdit::editingFinished, this, &RegisterWidget::CheckEnsurePasswordValid);
connect(ui.Verify_Edit, &QLineEdit::editingFinished, this, &RegisterWidget::CheckVerifyCodeValid);
3.4 回调的实现
cpp
/* 检查用户名是否合法 */
bool RegisterWidget::CheckUserValid()
{
if (ui.User_Edit->text().isEmpty())
{
AddTipErr(TipErr::TIP_USER_ERR, QString::fromLocal8Bit("用户名不能为空"));
return false;
}
/* 一切正常则删除错误提示 */
DelTipErr(TipErr::TIP_USER_ERR);
return true;
}
/* 检查邮箱是否合法 */
bool RegisterWidget::CheckEmailValid()
{
QString emailText = ui.Email_Edit->text();
// 邮箱地址的正则表达式
QRegularExpression regex(R"((\w+)(\.|_)?(\w*)@(\w+)(\.(\w+))+)");
bool match = regex.match(emailText).hasMatch();
if (!match)
{
AddTipErr(TipErr::TIP_EMAIL_ERR, QString::fromLocal8Bit("邮箱格式不正确"));
return false;
}
/* 一切正常则删除错误提示 */
DelTipErr(TipErr::TIP_EMAIL_ERR);
return true;
}
/* 检查密码是否合法 */
bool RegisterWidget::CheckPasswordValid()
{
QString passText = ui.PassWord_Edit->text();
if (passText.length() < 6 || passText.length() > 15)
{
AddTipErr(TipErr::TIP_PWD_ERR, QString::fromLocal8Bit("密码长度必须在6-15位之间"));
return false;
}
/* 密码长度至少6位 可以是字母、数字、特定的特殊字符 */
QRegularExpression regExp("^[a-zA-Z0-9!@#$%^&*]{6,15}$");
bool match = regExp.match(passText).hasMatch();
if (!match)
{
AddTipErr(TipErr::TIP_PWD_ERR, QString::fromLocal8Bit("不能包含非法字符"));
return false;
}
/* 一切正常则删除错误提示 */
DelTipErr(TipErr::TIP_PWD_ERR);
return true;
}
/* 检查确认密码是否合法 */
bool RegisterWidget::CheckEnsurePasswordValid()
{
QString enterText = ui.Enter_Edit->text();
if (ui.PassWord_Edit->text() != enterText)
{
AddTipErr(TipErr::TIP_CONFIRM_ERR, QString::fromLocal8Bit("两次密码输入不一致"));
return false;
}
/* 一切正常则删除错误提示 */
DelTipErr(TipErr::TIP_CONFIRM_ERR);
return true;
}
/* 检查验证码是否合法 */
bool RegisterWidget::CheckVerifyCodeValid()
{
QString verifyText = ui.Verify_Edit->text();
if (verifyText.isEmpty())
{
AddTipErr(TipErr::TIP_VERIFY_ERR, QString::fromLocal8Bit("验证码不能为空"));
return false;
}
/* 一切正常则删除错误提示 */
DelTipErr(TipErr::TIP_VERIFY_ERR);
return true;
}
当确认按钮刷新时,检查输入是否合法
cpp
/* 确认按钮点击 */
void RegisterWidget::OnConfirmButtonClicked()
{
bool valid = CheckUserValid();
if (!valid)
return;
valid = CheckEmailValid();
if (!valid)
return;
valid = CheckPasswordValid();
if (!valid)
return;
valid = CheckEnsurePasswordValid();
if (!valid)
return;
valid = CheckVerifyCodeValid();
if (!valid)
return;
4. 隐藏和显示密码
4.1 QT样式表
css
#Enter_Visible[state='visible_hover']
{
border-image: url(:/Chat/Images/visible_hover.png);
}


css
#PassWord_Visible[state='unvisible']
{
border-image: url(:/Chat/Images/unvisible.png);
}
#PassWord_Visible[state='unvisible_hover']
{
border-image: url(:/Chat/Images/unvisible_hover.png);
}
#PassWord_Visible[state='visible']
{
border-image: url(:/Chat/Images/visible.png);
}
#PassWord_Visible[state='visible_hover']
{
border-image: url(:/Chat/Images/visible_hover.png);
}
#Enter_Visible[state='unvisible']
{
border-image: url(:/Chat/Images/unvisible.png);
}
#Enter_Visible[state='unvisible_hover']
{
border-image: url(:/Chat/Images/unvisible_hover.png);
}
#Enter_Visible[state='visible']
{
border-image: url(:/Chat/Images/visible.png);
}
#Enter_Visible[state='visible_hover']
{
border-image: url(:/Chat/Images/visible_hover.png);
}
4.2 重写ClickedLabel
一个Label有六种状态,普通状态,普通的悬浮状态,普通的点击状态,选中状态,选中的悬浮状态,选中的点击状态
当Label处于普通状态时,被点击后,切换为选中状态,再次点击又切换为普通状态
定义ClickLableState,包括两种状态,一个是普通状态,一个是选中状态。Label的六种状态都是基于这两种状态实现的
通过样式表来灵活切换标签的UI,只需要通过setProperty("state", str);设置对应的状态即可
然后自定义的控件只需要继承即可实现UI随着鼠标的进入离开以及点击时的切换
.h
cpp
#ifndef CLICKEDLABEL_H
#define CLICKEDLABEL_H
#include <QLabel>
#include <QString>
#include "Enum.h"
class ClickedLabel : public QLabel
{
Q_OBJECT
public:
ClickedLabel(QWidget *parent = 0);
~ClickedLabel();
public:
virtual void mousePressEvent(QMouseEvent *event) override;
virtual void enterEvent(QEvent *event) override;
virtual void leaveEvent(QEvent *event) override;
ClickLabelState GetState() const;
signals:
void clicked(void);
private:
void UpdateStyleSheet(QString str); // 刷新样式
private:
QString _Normal; // 未选中
QString _NormalHover;
QString _NormalPress;
QString _Selected; // 选中
QString _SelectedHover;
QString _SelectedPress;
ClickLabelState _CurrState; // 当前标签状态
};
#endif // CLICKEDLABEL_H
.cpp
cpp
#include "ClickedLabel.h"
#include <QMouseEvent>
#include <QDebug>
#include "Global.h"
ClickedLabel::ClickedLabel(QWidget *parent)
: QLabel(parent), _CurrState(ClickLabelState::Normal)
{
_Normal = "unvisible";
_NormalHover = "unvisible_hover";
_NormalPress = "";
_Selected = "visible";
_SelectedHover = "visible_hover";
_SelectedPress = "";
UpdateStyleSheet(_Normal);
}
ClickedLabel::~ClickedLabel()
{}
/* 刷新样式 */
void ClickedLabel::UpdateStyleSheet(QString str)
{
setProperty("state", str);
repolish(this);
update();
}
/* 鼠标按下事件 */
void ClickedLabel::mousePressEvent(QMouseEvent * event)
{
if (event->button() == Qt::LeftButton)
{
if (_CurrState == ClickLabelState::Normal)
{
qDebug() << "clicked , change to selected hover" << _SelectedHover;
_CurrState = ClickLabelState::Selected;
UpdateStyleSheet(_SelectedHover);
}
else
{
qDebug() << "clicked , change to normal hover" << _NormalHover;
_CurrState = ClickLabelState::Normal;
UpdateStyleSheet(_NormalHover);
}
emit clicked();
}
QLabel::mousePressEvent(event);
}
/* 鼠标悬停进入事件 */
void ClickedLabel::enterEvent(QEvent* event)
{
if (_CurrState == ClickLabelState::Normal)
{
qDebug() << "enter , change to hover" << _NormalHover;
UpdateStyleSheet(_NormalHover);
}
else
{
qDebug() << "enter , change to selected hover" << _SelectedHover;
UpdateStyleSheet(_SelectedHover);
}
QLabel::enterEvent(event);
}
/* 鼠标离开事件 */
void ClickedLabel::leaveEvent(QEvent* event)
{
if (_CurrState == ClickLabelState::Normal)
{
qDebug() << "leave , change to normal" << _Normal;
UpdateStyleSheet(_Normal);
}
else
{
qDebug() << "leave , change to selected" << _Selected;
UpdateStyleSheet(_Selected);
}
QLabel::leaveEvent(event);
}
ClickLabelState ClickedLabel::GetState() const
{
return _CurrState;
}
4.3 创建可视化控件

将该标签提升为自定义的控件


4.4 在注册窗口中绑定可视化控件的信号槽
cpp
/* 初始化可视化控件 */
ui.PassWord_Visible->setCursor(Qt::PointingHandCursor);
ui.Enter_Visible->setCursor(Qt::PointingHandCursor);
BindSlotsFromClickedLabel();
cpp
/* 绑定信号槽从ClickedLabel控件 */
void RegisterWidget::BindSlotsFromClickedLabel()
{
/* 密码可见 */
connect(ui.PassWord_Visible, &ClickedLabel::clicked, this, [this]()
{
auto state = ui.PassWord_Visible->GetState();
if (state == ClickLabelState::Normal)
{
ui.PassWord_Edit->setEchoMode(QLineEdit::Password);
}
else if (state == ClickLabelState::Selected)
{
ui.PassWord_Edit->setEchoMode(QLineEdit::Normal);
}
});
connect(ui.Enter_Visible, &ClickedLabel::clicked, this, [this]()
{
auto state = ui.Enter_Visible->GetState();
if (state == ClickLabelState::Normal)
{
ui.Enter_Edit->setEchoMode(QLineEdit::Password);
}
else if (state == ClickLabelState::Selected)
{
ui.Enter_Edit->setEchoMode(QLineEdit::Normal);
}
});
}
4.5 注册成功的提示界面
首先添加两个标签,然后调整整体窗口布局为垂直布局,让两个标签水平居中,添加返回按钮并设置按钮最大最小高度为25,将按钮放置到窗口内,并设置窗口布局为水平


4.6 注册成功的界面切换和自动跳转登陆界面


4.7 绑定返回按钮和取消按钮的槽函数

4.8 在主窗口绑定登录界面和注册界面的切换
切换到注册界面时,先创建注册界面的对象,然后绑定信号方便后续切换回登陆界面

同理,切换回登录界面时,先创建登录界面的对象,然后绑定信号方便切换回注册界面

为什么不再构造函数中创建是因为,调用setCentralWidget会销毁别的闲置窗口,从注册界面再切回登录界面时,此时登陆界面就被销毁了,所以调用已销毁对象的槽函数,程序会崩溃
4.9 测试注册成功的逻辑
流程已跑通