Qt 实现类腾讯聊天软件(一)——客户端(登录模块)

本文介绍了客户端登录模块的效果图,包括界面绘制的标准化、自定义下拉框、系统托盘功能,以及与之相关的登录、注册流程和TCP通讯。展示了如何通过lambda表达式连接信号槽,以及密码验证、自动登录和用户信息管理的详细代码实现。

效果图

效果图

主要功能模块

界面绘制

标准界面

界面主要由设计界面进行设置
登录
确认删除
自动登录
注册
代码:
主要是信号槽的连接,主要使用的lambda表达式
登录界面:

void LoginWindow::initializeLogInWindow()
{

	connect(m_ui->tb_close, &QToolButton::clicked, this, [&]() {
		saveUserInfo();
		close();
	});
	connect(m_ui->tb_minimality, &QToolButton::clicked, [&]() {
		hide();
		systemTray->show();
	});
	//自动登录的取消按钮
	connect(m_ui->pb_cancal, &QPushButton::clicked, [&]() {
		m_ui->sw->setCurrentIndex(0);
	});
	//gif播放
	gifMove = new QMovie(":/resource/background.gif");
	m_ui->l_upGif->setMovie(gifMove);
	gifMove->start();
	connect(m_ui->le_user, &QLineEdit::cursorPositionChanged, [&]() {
		m_ui->l_user->setStyleSheet(QStringLiteral("image: url(:/resource/user.png);"));
	});
	connect(m_ui->le_password, &QLineEdit::cursorPositionChanged, [&]() {
		m_ui->l_password->setStyleSheet(QStringLiteral("image: url(:/resource/password.png);"));
		m_ui->l_keyboard->setStyleSheet(QStringLiteral("image: url(:/resource/keyboard2.png);"));
	});
	connect(m_ui->pb_login, &QPushButton::clicked, [&]() {
		m_ui->pb_login->setText("登录中");
		makeSenderJson();
		m_user->startConnect(sendJson);
	});

}

注册界面:
使用正则表达式对输入进行一定的限制

void LoginWindow::initializeRegister()
{
	connect(m_ui->pb_register, &QPushButton::clicked, [&]() {
		m_ui->sw->setCurrentIndex(3);
		//设置id和密码的正则表达式确保用户输入的符合要求
		QRegExp regxId("[a-zA-Z0-9]{8}");
		QValidator *validatorId = new QRegExpValidator(regxId);
		m_ui->le_registerId->setValidator(validatorId);
		QRegExp regxPwd("[a-zA-Z0-9]{16}");
		QValidator *validatorPwd = new QRegExpValidator(regxPwd);
		m_ui->le_registerPwd->setValidator(validatorPwd);
		m_ui->le_registerPwd2->setValidator(validatorPwd);
	});
	connect(m_ui->pb_registerOk, &QPushButton::clicked, [&]() {
		//对注册信息进行一个初步的判断
		if (m_ui->le_registerId->text().size() < 4 || m_ui->le_registerPwd->text().size() < 8)
		{
			QMessageBox::warning(this, "警告", "你的输入不符合要求!", QMessageBox::Cancel);
			m_ui->le_registerId->clear();
			m_ui->le_registerPwd->clear();
			m_ui->le_registerPwd2->clear();
			return;
		}
		if (m_ui->le_registerPwd->text() != m_ui->le_registerPwd2->text())
		{
			QMessageBox::warning(this, "警告", "请确认两次密码输入相同!", QMessageBox::Ok);
			m_ui->le_registerPwd->clear();
			m_ui->le_registerPwd2->clear();
			return;
		}
		if (!m_ui->le_registerPwd->text().contains(QRegExp("[A-Za-z]")))//查看用户的密码是否为单纯数字
		{
			QMessageBox::warning(this, "警告", "密码过于简单!", QMessageBox::Ok);
			m_ui->le_registerPwd->clear();
			m_ui->le_registerPwd2->clear();
			return;
		}
		makeSenderJson();
		m_user->startConnect(sendJson);
	});
	connect(m_ui->pb_registerCancal, &QPushButton::clicked, [&]() {
		m_ui->sw->setCurrentIndex(0);
		m_ui->le_registerId->clear();
		m_ui->le_registerPwd->clear();
		m_ui->le_registerPwd2->clear();
	});

}

由于是无系统边框还需实现窗口移动,这里用到重写鼠标事件:

//拖拽操作
void LoginWindow::mousePressEvent(QMouseEvent *event)
{
	if (event->button() == Qt::LeftButton)//只相应鼠标左键的按下
	{
		_move = true;
		mouseStartPoint = event->globalPos();//鼠标的初始位置
		windowStartPoint = this->frameGeometry().topLeft();	//窗口的初始位置
	}
}

void LoginWindow::mouseMoveEvent(QMouseEvent *event)
{
	if (_move)
	{
		QPoint mouseMove = event->globalPos() - mouseStartPoint; //获得鼠标移动的距离
		this->move(windowStartPoint + mouseMove);		          //改变窗口的位置
	}
}

void LoginWindow::mouseReleaseEvent(QMouseEvent *event)
{
	if (event->button() == Qt::LeftButton)//判断左键抬起关闭获取鼠标移动
		_move = false;
}

自定义的下拉框

下拉框由QListWidgetItem来实现:
由于下拉框中有删除操作,所以为了方便执行删除,使用QSignalMapper 处理信号,以获取执行删除操作的是哪一个窗口。

void LoginWindow::initializeUserInfo()
{
	//读取文件中的JSON数组
	//打开文件
	QFile file(".\\resource\\info\\userInfo.json");
	file.open(QIODevice::ReadWrite | QIODevice::Text);
	QByteArray userData = file.readAll();//读取文件
	file.close();
	QJsonDocument jsonD = QJsonDocument::fromJson(userData);
	QVariantList jsonList = jsonD.toVariant().toList();//将数组中的每个json转换
	//遍历
	for (size_t i = 0; i < jsonList.count(); i++)
	{
		QVariantMap map = jsonList[i].toMap();
		userId.append(map["id"].toString());
		userPwd.append(decodePwd(map["pwd"].toString()));
		rememberPwd.append(map["remember"].toString());
		if (i == 0)
			if (map["logIn"] == "yes")
				log_in = true;
			else
				log_in = false;
	}
	//创建下拉框
	if (userId.isEmpty())//没有用户不创建下拉框
		return;
	QListWidget *list = new QListWidget(this);
	list->setFocusPolicy(Qt::NoFocus);
	m_ui->cb_user->setModel(list->model());
	m_ui->cb_user->setView(list);
	QSignalMapper *signalMapper = new QSignalMapper(this);
	for (size_t i = 0; i < userId.size(); i++)
	{
		//创建下拉框中的窗口
		QListWidgetItem *listItem = new QListWidgetItem(list);
		QHBoxLayout *layout = new QHBoxLayout();//创建布局
		QPushButton *delButton = new QPushButton(this);//创建下拉框中的删除按钮
		delButton->setIcon(QIcon(":/resource/close2.png"));
		delButton->setStyleSheet("QPushButton::hover{background-color: rgb(255,0, 0);} QPushButton{border:0 px;}");
		QLabel *lable = new QLabel(QString("%1").arg(userId.at(i)), this);//设置用户id
		QLabel *icon = new  QLabel(this);//设置图片(预留功能设置头像)
		icon->setFixedSize(35, 35);
		icon->setStyleSheet(QLatin1String("border-image: url(:/resource/user2.png)"));
		lable->setFixedSize(270, 30);
		layout->addWidget(icon);
		layout->addSpacing(3);
		layout->addWidget(lable);
		layout->addSpacing(1);
		layout->addWidget(delButton);
		QWidget *widget = new QWidget(this);
		widget->setStyleSheet("QWidget::hover{color: #2e9aff;}");
		widget->setLayout(layout);//将布局加入窗口
		list->setItemWidget(listItem, widget);
		//连接删除按钮的信号槽 这里交由QSignalMapper来处理 用QSignalMapper转发窗口的序号用于删除操作
		connect(delButton, &QPushButton::clicked, signalMapper, static_cast<void(QSignalMapper::*)()>(&QSignalMapper::map));
		signalMapper->setMapping(delButton, i);//设置转发消息的参数
		index.append(i);//加入对应值处理删除工作
	}
	//连接删除按钮的信号槽
	connect(signalMapper, static_cast<void(QSignalMapper::*)(int)>(&QSignalMapper::mapped), [&](int i) {
		m_ui->sw->setCurrentIndex(1);
		delUserPos = index.indexOf(i);
	});
	m_ui->le_user->setText(m_ui->cb_user->currentText());//初始化将第一个设为显示

	//此信号会发送更换后的文本信息
	connect(m_ui->cb_user, static_cast<void(QComboBox::*)(const QString &)>(&QComboBox::currentIndexChanged), [&](const QString &text) {
		if (judge)return;//信号已交由按钮处理
		m_ui->le_user->setText(text);
		m_ui->cb_voluntarily->setChecked(false);//非0的所以下拉框成员都不是自动登录
		int currentIndex = m_ui->cb_user->currentIndex();
		m_ui->le_user->setText(userId.at(currentIndex));
		if (userPwd.at(userId.indexOf(userId.at(currentIndex))) != "null")
		{
			m_ui->le_password->setText(userPwd.at(userId.indexOf(userId.at(currentIndex))));
			if (rememberPwd.at(userId.indexOf(userId.at(currentIndex))) == "yes")
				m_ui->cb_remember->setChecked(true);
		}
		else
		{
			m_ui->cb_remember->setChecked(false);
			m_ui->le_password->clear();
		}
	});
}

系统托盘

	//系统托盘
	systemTray = new QSystemTrayIcon(this);
	//设置系统托盘
	systemTray->setIcon(QIcon(":/resource/Icon.png"));
	systemTray->setToolTip("CJJMail");
	setWindowIcon(QIcon(":/resource/Icon.png"));
	connect(systemTray, &QSystemTrayIcon::activated, [&](QSystemTrayIcon::ActivationReason reason) {
		if (reason == QSystemTrayIcon::DoubleClick)
			this->showNormal();
		//恢复窗口
	});
	//为系统托盘设置菜单
	QMenu *systemTrayMenu = new QMenu(this);
	systemTrayMenu->setStyleSheet(QString("background-color: #a7a7a7;"));
	//设置菜单按钮
	QAction *_closeAction = new QAction("退出", this);
	_closeAction->setIcon(QIcon(":/resource/exit.png"));
	systemTrayMenu->addAction(_closeAction);
	systemTray->setContextMenu(systemTrayMenu);
	//连接托盘Action信号槽
	connect(_closeAction, &QAction::triggered, this, &LoginWindow::close);

功能模块

读写用户预留信息

使用JSON保存用户信息:
保存操作:

void LoginWindow::saveUserInfo()
{

	for (size_t i = 1; i < userId.size(); i++)
	{
		if (userId.at(i) == m_ui->le_user->text())
		{
			userId.pop_back();
			userPwd.pop_back();
			rememberPwd.pop_back();
			break;
		}
	}
	QJsonArray jsonArray;//因为有多个用户记录所以生成json数组
	for (size_t i = 0; i < userId.size(); i++)
	{
		QJsonObject jsonObj;
		jsonObj.insert("id", userId.at(i));
		jsonObj.insert("pwd", encryptPwd(userPwd.at(i)));
		jsonObj.insert("remember", rememberPwd.at(i));
		if (i == 0)
			if (m_ui->cb_voluntarily->isChecked())
				jsonObj.insert("logIn", "yes");
			else
				jsonObj.insert("logIn", "no");
		jsonArray.append(jsonObj);
	}
	QJsonDocument jsonD;
	jsonD.setArray(jsonArray);
	QFile file(".\\resource\\info\\userInfo.json");
	file.open(QIODevice::WriteOnly | QIODevice::Text);
	file.write(jsonD.toJson());
	file.close();
}

明文保存会泄露密码所以用ASCII码进行简单的转义:

QString LoginWindow::encryptPwd(QString unencryptPwd)
{
	//用char 的ASCII码进行加密
	//先将字符串转为char 再将char转为int即可
	char*  _pwd;
	QByteArray ba = unencryptPwd.toLatin1(); // must
	_pwd = ba.data();
	QString tmpPwd;
	for (size_t i = 0; i < unencryptPwd.length(); i++)
	{
		int tmp = *_pwd;
		tmpPwd.append(QString::number(tmp));
		if (i + 1 != unencryptPwd.length())
		{
			tmpPwd.append("-");
			++_pwd;
		}
	}
	return tmpPwd;
}

读取:

	//读取文件中的JSON数组
	//打开文件
	QFile file(".\\resource\\info\\userInfo.json");
	file.open(QIODevice::ReadWrite | QIODevice::Text);
	QByteArray userData = file.readAll();//读取文件
	file.close();
	QJsonDocument jsonD = QJsonDocument::fromJson(userData);
	QVariantList jsonList = jsonD.toVariant().toList();//将数组中的每个json转换
	//遍历
	for (size_t i = 0; i < jsonList.count(); i++)
	{
		QVariantMap map = jsonList[i].toMap();
		userId.append(map["id"].toString());
		userPwd.append(decodePwd(map["pwd"].toString()));
		rememberPwd.append(map["remember"].toString());
		if (i == 0)
			if (map["logIn"] == "yes")
				log_in = true;
			else
				log_in = false;
	}

发送消息的格式也为JSON:

void LoginWindow::makeSenderJson()
{
	sendJson.clear();
	if (m_ui->sw->currentIndex() == 0)//判断为登录
	{
		QJsonObject jsonO;
		jsonO.insert("type", "logIn");
		jsonO.insert("id", m_ui->le_user->text());
		jsonO.insert("pwd", m_ui->le_password->text());
		QJsonDocument jsonD;
		jsonD.setObject(jsonO);
		sendJson = jsonD.toJson();
	}
	else if (m_ui->sw->currentIndex() == 2)//自动登录
	{
		QJsonObject jsonO;
		jsonO.insert("type", "logIn");
		jsonO.insert("id", userId.at(0));
		jsonO.insert("pwd", userPwd.at(0));
		QJsonDocument jsonD;
		jsonD.setObject(jsonO);
		sendJson = jsonD.toJson();
	}
	else if (m_ui->sw->currentIndex() == 3)//注册
	{
		QJsonObject jsonO;
		jsonO.insert("type", "register");
		jsonO.insert("id", m_ui->le_registerId->text());
		jsonO.insert("pwd", m_ui->le_registerPwd->text());
		QJsonDocument jsonD;
		jsonD.setObject(jsonO);
		sendJson = jsonD.toJson();
	}
}

解密:

QString LoginWindow::decodePwd(QString encryptPwd)
{
	QStringList tmpList = encryptPwd.split("-");
	QString decode;
	std::for_each(tmpList.begin(), tmpList.end(), [&](QString pwd) {
		int tmp = pwd.toInt();
		char _tmp = tmp;
		decode.append(_tmp);
	});
	return decode;
}

TCP通讯

维护了一个新对象,用于之后的其他功能:

UserManage::UserManage(QObject *parent)
	: QObject(parent)
{
	m_socket = new QTcpSocket(this);
}

UserManage::~UserManage()
{

}

void UserManage::startConnect(QByteArray message)
{
	m_message = message;
	m_socket->connectToHost(QHostAddress("127.0.0.1"), 8000);
	//m_socket->connectToHost(QHostAddress("192.168.8.1"), 8000);
	connect(m_socket, &QTcpSocket::connected, [=]() {
		connectSucceed();
	});
}


void UserManage::connectSucceed()
{
	m_socket->write(m_message);
	connect(m_socket, &QTcpSocket::readyRead, [=]() {
		readMessage();
	});
}

void UserManage::readMessage()
{
	
	QString message= m_socket->readAll();
	emit newData(message);

}

有情链接与致谢

界面设计过程中,参读了大佬们的文章受益匪浅。我只是站在巨人的肩膀上模仿前人的成就。
@花狗Fdog
@我只是一个单纯的2

评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Pointer=NULL

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值