简易聊天室源码分析

client.cpp

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
#include "client.h"
#include "ui_client.h"
#include<QTcpSocket>
#include<QDebug>
#include<QMessageBox>
Client::Client(QWidget *parent) :
QDialog(parent),
ui(new Ui::Client)
{
ui->setupUi(this);
setFixedSize(400,190);
totalBytes=0;
bytesRecieved=0;
fileNameSize=0;
tClnt=new QTcpSocket(this);
tPort=5555;
connect(tClnt,SIGNAL(readyRead()),this,SLOT(readMsg()));
connect(tClnt,SIGNAL(error(QAbstractSocket::SocketError)),this,SLOT(displayErr(QAbstractSocket::SocketError)));
}

Client::~Client()
{
delete ui;
}

//处理错误的槽函数
void Client::displayErr(QAbstractSocket::SocketError sockErr)//这是错误类型
{
switch (sockErr)
{
//如果是远程主机关闭连接
case QAbstractSocket::RemoteHostClosedError:
break;
default:
//否则就输出当前连接对象的错误信息
qDebug()<<tClnt->errorString();
}
}

//用于建立新的连接
void Client::newConn()
{
//接受数据块初始化为0
blockSize=0;
//终止当前连接
tClnt->abort();
//连接到当前主机地址和端口号
tClnt->connectToHost(hostAddr,tPort);
//开始启动计时器
time.start();
}

//处理从服务器接受到的信息
void Client::readMsg()
{
//创建一个对象,与tClnt关联
QDataStream in(tClnt);
//设置版本,保证数据的兼容性
in.setVersion(QDataStream::Qt_5_8);
//计算建立连接到当前经过的事件
float useTime=time.elapsed();
//检查已接受的字节数是否小于等于"qint64"两倍大小
if(bytesRecieved<=sizeof(qint64)*2)
{
//如果套接字中有足够的数据可以读取,并且文件名大小为0,可以读取文件大小和文件名大小
if((tClnt->bytesAvailable()>=sizeof(qint64)*2)&&(fileNameSize==0)){
in>>totalBytes>>fileNameSize;
//读取文件总大小和文件名大小
//更新已经接受的字节数量
bytesRecieved+=sizeof(qint64)*2;
}
//如果文件中有足够的数据可以读取,并且文件名大小不等于0,就可以读取文件名
if((tClnt->bytesAvailable()>=fileNameSize)&&(fileNameSize!=0)){
in>>fileName;
//更新已经接受的字节数
bytesRecieved+=fileNameSize;
//如果文件不可以成功打开,就警告
if(!locFile->open(QFile::WriteOnly)){
QMessageBox::warning(this,tr("应用程序"),tr("无法读取文件%1:\n%2.").arg(fileName).arg(locFile->errorString()));
return;}
}else{
return;
}
}
//如果已经接受的字节数量还是不够,就需要接收
if(bytesRecieved<totalBytes){
//增加可以读取的字节数
bytesRecieved+=tClnt->bytesAvailable();
//读取套接字的所有可用数据,存储在inBlock变量中
inBlock=tClnt->readAll();
//写入本地文件
locFile->write(inBlock);
//设置为0,下次读取使用
inBlock.resize(0);
}
//设置进度条的最大值为文件总大小
ui->progressBar->setMaximum(totalBytes);
//设置当前值
ui->progressBar->setValue(bytesRecieved);
//计算传输速度
double speed=bytesRecieved/useTime;
//更新状态标签,显示已接收的字节数、传输速度、总字节数、已用时间和估计剩余时间。
ui->cStatusLbl->setText(tr("已接收%1MB(%2MB/s)\n共%3MB 已用时:%4秒 \n估计剩余时间:%5秒").arg(bytesRecieved/(1024*1024)).arg(speed*1000/(1024*1024),0,'f',2).arg(totalBytes/(1024*1024)).arg(useTime/1000,0,'f',0).arg(totalBytes/speed/1000-useTime/1000,0,'f',0));;
//如果完全接受就关闭本地文件
if(bytesRecieved==totalBytes)
{
locFile->close();
//关闭套接字
tClnt->close();
//更新状态标签的文本,显示已经接受完毕
ui->cStatusLbl->setText(tr("接收文件%1完毕").arg(fileName));

}
}

//用来处理用户点击取消按钮的事件
void Client::on_cCancleBtn_clicked()
{
//终止当前的网格操作,包括当前的连接和任何正在进行的数据传输
tClnt->abort();
//如果文件还打开,就关闭
if(locFile->isOpen())locFile->close();
}

//处理用户点击关闭按钮的事件
void Client::on_cCloseBtn_clicked()
{
//和上卖弄几乎差不多
tClnt->abort();
if(locFile->isOpen())locFile->close();
//需要关闭客户端界面
close();
}

//另外一个成员函数,重新写了事件处理函数,当窗口被关闭的时候调用
//从而使得模拟了用户点击关闭按钮的操作,触发与关闭按钮执行相同的操作
void Client::closeEvent(QCloseEvent *){

on_cCloseBtn_clicked();
}

//这是一个成员函数用来设置文件名,直接创建一个新的QFile对象,并用指针赋值
void Client::setFileName(QString name){

locFile=new QFile(name);
}

//这是用来设置主机地址,并且建立新的连接
void Client::setHostAddr(QHostAddress addr)
{
//将传入的主机地址赋值给成员变量
hostAddr=addr;
//调用成员函数,用来建立与主机的新连接。
newConn();
}





这段代码实现了一个基于 Qt 的客户端应用程序,用于接收文件。以下是逐行的解析:

  1. #include "client.h"#include "ui_client.h":包含客户端类的头文件和 UI 类的头文件。

  2. #include<QTcpSocket>:包含 QTcpSocket 类的头文件,用于处理 TCP 连接。

  3. #include<QDebug>:包含调试输出的头文件。

  4. #include<QMessageBox>:包含消息框的头文件。

  5. Client::Client(QWidget *parent) : QDialog(parent), ui(new Ui::Client):客户端类的构造函数,初始化客户端窗口界面。

  6. setFixedSize(400,190);:设置窗口大小为固定值。

  7. totalBytes=0; bytesRecieved=0; fileNameSize=0;:初始化接收文件所需的变量。

  8. tClnt=new QTcpSocket(this);:创建一个 QTcpSocket 对象用于与服务器进行通信。

  9. tPort=5555;:设置服务器端口号。

  10. connect(tClnt,SIGNAL(readyRead()),this,SLOT(readMsg()));:连接 QTcpSocket 的 readyRead() 信号到 readMsg() 槽函数,当客户端收到数据时会调用 readMsg() 函数。

  11. connect(tClnt,SIGNAL(error(QAbstractSocket::SocketError)),this,SLOT(displayErr(QAbstractSocket::SocketError)));:连接 QTcpSocket 的 error() 信号到 displayErr() 槽函数,当出现连接错误时会调用 displayErr() 函数。

  12. Client::~Client():客户端类的析构函数,释放内存。

  13. void Client::displayErr(QAbstractSocket::SocketError sockErr):处理连接错误的槽函数,根据错误类型显示消息。

  14. void Client::newConn():建立新连接的函数,初始化一些连接参数并连接到服务器。

  15. void Client::readMsg():处理收到的消息的槽函数,根据消息类型执行相应操作。

  16. void Client::on_cCancleBtn_clicked()void Client::on_cCloseBtn_clicked():处理取消和关闭按钮点击事件的槽函数,分别中止连接和关闭文件。

  17. void Client::closeEvent(QCloseEvent *):处理窗口关闭事件的槽函数,确保在关闭窗口时中止连接和关闭文件。

  18. void Client::setFileName(QString name)void Client::setHostAddr(QHostAddress addr):设置文件名和主机地址的函数,用于在连接到服务器时调用。

这段代码的主要作用是实现客户端接收文件的功能,通过建立 TCP 连接,接收服务器端发送的文件,并在界面上显示接收进度和相关信息。

client.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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#ifndef CLIENT_H
#define CLIENT_H
#include<QHostAddress>
#include <QDialog>
#include<QFile>
#include<QTime>
class QTcpSocket;
namespace Ui {
class Client;
}

class Client : public QDialog
{
Q_OBJECT

public:
explicit Client(QWidget *parent = 0);
~Client();
void setHostAddr(QHostAddress addr);//获取发送端IP地址
void setFileName(QString name);//获取文件保存路径
protected:
void closeEvent(QCloseEvent *);
//重写了closeEvent事件处理函数,用来在关闭对话框的时候执行一些操作,中止连接
private:
Ui::Client *ui;
QTcpSocket *tClnt;
//用来处理与服务器的TCP连接和通信
quint16 blockSize;
//用来存储数据块的大小
QHostAddress hostAddr;
//用来存储服务器的ip地址
qint16 tPort;
//用来存储连接的端口号
qint64 totalBytes;
//存储文件的总大小
qint64 bytesRecieved;
//用来存储已经接收当然文件数据的大小
qint64 fileNameSize;
//用来存储文件名的大小
QString fileName;
//用来存储文件名
QFile *locFile;
//用来操作接受到的文件
QByteArray inBlock;
//这是一个用来存储接受到的数据块
QTime time;
//存储接受文件所用的时间
private slots:
void newConn();//连接到服务器
void readMsg();//读取文件数据
void displayErr(QAbstractSocket::SocketError);//显示错误信息

void on_cCancleBtn_clicked();
//点击取消按钮时候调用,用来终止连接并且关闭文件
void on_cCloseBtn_clicked();
//上面多加上了一个功能,是用来关闭对话框的
};

#endif // CLIENT_H

drawer.cpp

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
#include "drawer.h"
#include<QGroupBox>
#include<QVBoxLayout>
#include<QString>
#include<QDebug>
#include<QApplication>
#include<QPainter>
Drawer::Drawer(QString username,QWidget *parent,Qt::WindowFlags f) :
QToolBox(parent,f)
{
setWindowTitle(tr("ZIYE's Chat"));
setWindowIcon(QPixmap(":/images/O.png"));
//设置窗口标题和窗口图标

//去掉窗口边框
setWindowFlags(Qt::FramelessWindowHint);

bool is=false;
//判断是否存在特定用户
QString usersnumber=tr(":/images/User10.PNG");
//存储用户头像路径
toolBtn1=new QToolButton;//新建一个QToolButton对象,对应一个按钮
toolBtn1->setText(" "+tr("operator "));//设置名字
toolBtn1->setIcon(QPixmap(":/images/User1.PNG"));//设置按钮的图标
toolBtn1->setIconSize(QPixmap(":/images/User1.PNG").size());//按钮大小与图标大小相同
toolBtn1->setAutoRaise(true);//自动浮起
//鼠标悬停的时候按钮会自动升起来,可以进行学习
toolBtn1->setStyleSheet("color:white;font:14pt");
toolBtn1->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
//设置按钮的文本显示在图标右边
if(username=="operator")
{
is=true;
usersnumber=tr(":/images/User1.PNG");
connect(toolBtn1,SIGNAL(clicked()),this,SLOT(showChatWidget1()));
}
//判断用户名是不是特殊用户,如果是的话就修改路径,并且连接信号与槽函数


toolBtn2=new QToolButton;
toolBtn2->setText(" "+tr("Chen "));
toolBtn2->resize(toolBtn1->size());
toolBtn2->setIcon(QPixmap(":/images/User2.PNG"));
toolBtn2->setIconSize(QPixmap(":/images/User2.PNG").size());
toolBtn2->setAutoRaise(true);//自动浮起
toolBtn2->setStyleSheet("color:white;font:14pt");
toolBtn2->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
if(username=="Chen")
{
is=true;
usersnumber=tr(":/images/User2.PNG");
connect(toolBtn2,SIGNAL(clicked()),this,SLOT(showChatWidget2()));
}
//和前面基本上一模一样

toolBtn3=new QToolButton;
toolBtn3->resize(toolBtn1->size());
toolBtn3->setText(" "+tr("Lin "));
toolBtn3->setIcon(QPixmap(":/images/User3.PNG"));
toolBtn3->setIconSize(QPixmap(":/images/User3.PNG").size());
toolBtn3->setAutoRaise(true);//自动浮起
toolBtn3->setStyleSheet("color:white;font:14pt");
toolBtn3->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
if(username=="Lin")
{
is=true;
usersnumber=tr(":/images/User3.PNG");
connect(toolBtn3,SIGNAL(clicked()),this,SLOT(showChatWidget3()));
}

toolBtn4=new QToolButton;
toolBtn4->resize(toolBtn1->size());
toolBtn4->setText(" "+tr("Lou "));
toolBtn4->setIcon(QPixmap(":/images/User4.png"));
toolBtn4->setIconSize(QPixmap(":/images/User4.png").size());
toolBtn4->setAutoRaise(true);//自动浮起
toolBtn4->setStyleSheet("color:white;font:14pt");
toolBtn4->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
if(username=="Lou")
{
is=true;
usersnumber=tr(":/images/User4.png");
connect(toolBtn4,SIGNAL(clicked()),this,SLOT(showChatWidget4()));
}

toolBtn5=new QToolButton;
toolBtn5->resize(toolBtn1->size());
toolBtn5->setText(" "+tr("Su "));
toolBtn5->setIcon(QPixmap(":/images/User5.png"));
toolBtn5->setIconSize(QPixmap(":/images/User5.png").size());
toolBtn5->setAutoRaise(true);//自动浮起
toolBtn5->setStyleSheet("color:white;font:14pt");
toolBtn5->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
if(username=="Su")
{
is=true;
usersnumber=tr(":/images/User5.png");
connect(toolBtn5,SIGNAL(clicked()),this,SLOT(showChatWidget5()));
}

toolBtn6=new QToolButton;
toolBtn6->resize(toolBtn1->size());
toolBtn6->setText(" "+tr("Mai "));
toolBtn6->setIcon(QPixmap(":/images/User6.png"));
toolBtn6->setIconSize(QPixmap(":/images/User6.png").size());
toolBtn6->setAutoRaise(true);//自动浮起
toolBtn6->setStyleSheet("color:white;font:14pt");
toolBtn6->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
if(username=="Mai")
{
is=true;
usersnumber=tr(":/images/User6.png");
connect(toolBtn6,SIGNAL(clicked()),this,SLOT(showChatWidget6()));
}

toolBtn7=new QToolButton;
toolBtn7->resize(toolBtn1->size());
toolBtn7->setText(" "+tr("Wu "));
toolBtn7->setIcon(QPixmap(":/images/User7.png"));
toolBtn7->setIconSize(QPixmap(":/images/User7.png").size());
toolBtn7->setAutoRaise(true);//自动浮起
toolBtn7->setStyleSheet("color:white;font:14pt");
toolBtn7->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
if(username=="Wu")
{
is=true;
usersnumber=tr(":/images/User7.png");
connect(toolBtn7,SIGNAL(clicked()),this,SLOT(showChatWidget7()));
}

toolBtn8=new QToolButton;
toolBtn8->resize(toolBtn1->size());
toolBtn8->setText(" "+tr("Wang "));
toolBtn8->setIcon(QPixmap(":/images/User8.png"));
toolBtn8->setIconSize(QPixmap(":/images/User8.png").size());
toolBtn8->setAutoRaise(true);//自动浮起
toolBtn8->setStyleSheet("color:white;font:14pt");
toolBtn8->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
if(username=="Wang")
{
is=true;
usersnumber=tr(":/images/User8.png");
connect(toolBtn8,SIGNAL(clicked()),this,SLOT(showChatWidget8()));
}

toolBtn9=new QToolButton;

toolBtn9->setText(" "+tr("Tan "));
toolBtn9->setIcon(QPixmap(":/images/User9.PNG"));
toolBtn9->setIconSize(QPixmap(":/images/User9.PNG").size());
toolBtn9->resize(toolBtn1->size());toolBtn9->setAutoRaise(true);//自动浮起
toolBtn9->setStyleSheet("color:white;font:14pt");
toolBtn9->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
if(username=="Tan")
{
is=true;
usersnumber=tr(":/images/User9.PNG");
connect(toolBtn9,SIGNAL(clicked()),this,SLOT(showChatWidget9()));
}
toolBtn11=new QToolButton;
toolBtn11->setIcon(QPixmap(":/images/ico/close.png"));
toolBtn11->setAutoRaise(true);
connect(toolBtn11,SIGNAL(clicked(bool)),this,SLOT(slot_closeWindow()));
//连接了槽函数
toolBtn12=new QToolButton;
toolBtn12->setIcon(QPixmap(":/images/ico/mini.png"));
toolBtn12->setAutoRaise(true);
connect(toolBtn12,SIGNAL(clicked(bool)),this,SLOT(showMinimized()));
//连接了槽函数,以便于点击按钮的时候最小化窗口
QGroupBox *groupBox=new QGroupBox;
//创建了一个QgroupBox对象
groupBox->setStyleSheet("background-image:url(:/images/top_img0.png);font:11pt;font-family:STXihei");
//设置样式表
QVBoxLayout *layout=new QVBoxLayout(groupBox);
//创建了一个垂直布局
QHBoxLayout *layout1=new QHBoxLayout(groupBox);
//水平
QVBoxLayout *layout2=new QVBoxLayout(groupBox);
//垂直
QHBoxLayout *layout3=new QHBoxLayout(groupBox);
//水平
layout->setMargin(0);
//外边距设置为0,可以使得内部控件更加靠近
layout->setAlignment(Qt::AlignLeft);
//左对齐
QLabel *pLabel=new QLabel();
QLabel *pLabel1=new QLabel();
QLabel *pLabel2=new QLabel();
QLabel *pLabel3=new QLabel();

//四个QLable对象,可以显示文字或图像内容
pLabel->setFixedSize(70,70);

QPixmap px=QPixmap(usersnumber);

pLabel->setPixmap(px);
pLabel->setScaledContents(true);
//设置自动适应控件大小
pLabel->setFrameStyle(QFrame::Panel | QFrame::Sunken); //设置外观为凹陷方式
pLabel->setScaledContents(true);

pLabel->setVisible(true);
//设置为可见

pLabel1->setText("Welcome!");
pLabel1->setStyleSheet("background-color:#e3f9fd;font:14pt;font-family:STXihei");
pLabel2->setText("User: "+username);
pLabel2->setStyleSheet("background-color:#e3f9fd;font:14pt;font-family:STXihei");
layout3->addWidget(pLabel1);
layout3->addWidget(toolBtn12);
layout3->addWidget(toolBtn11);
layout2->addLayout(layout3);
layout2->addWidget(pLabel2);

layout1->addWidget(pLabel);
layout1->addLayout(layout2);

pLabel3->setLayout(layout1);
pLabel3->setStyleSheet("background-color:#e3f9fd;font:13pt;font-family:STXihei");
pLabel3->setFixedSize(290,90);
pLabel3->setStyleSheet("color:white;font:19pt");


layout->addWidget(pLabel3);
layout->addWidget(toolBtn1);
layout->addWidget(toolBtn2);
layout->addWidget(toolBtn3);
layout->addWidget(toolBtn4);
layout->addWidget(toolBtn5);
layout->addWidget(toolBtn6);
layout->addWidget(toolBtn7);
layout->addWidget(toolBtn8);
layout->addWidget(toolBtn9);


//如果说用户名并不是已经知道的话就执行下列草祖
if(!is)
{
toolBtn10=new QToolButton;

toolBtn10->setText(" "+username);
toolBtn10->setIcon(QPixmap(":/images/User10.PNG"));
toolBtn10->setIconSize(QPixmap(":/images/User10.PNG").size());
toolBtn10->setAutoRaise(true);//自动浮起
toolBtn10->setStyleSheet("color:white;font:14pt");
toolBtn10->resize(toolBtn1->size());
toolBtn10->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
connect(toolBtn10,SIGNAL(clicked()),this,SLOT(showChatWidget10()));
layout->addWidget(toolBtn10);

}


layout->addStretch();//插入一个占位符

//也是添加操作
this->addItem((QWidget*)groupBox,tr("ZIYE's Chat"));
}

//点击toolBtn1按钮时候创建出来一个聊天窗口,并且设置标题和图标
void Drawer::showChatWidget1(){
chatWidget1=new Widget(0,toolBtn1->text());
chatWidget1->setWindowTitle(toolBtn1->text());
chatWidget1->setWindowIcon(toolBtn1->icon());
chatWidget1->show();
}
void Drawer::showChatWidget2(){
chatWidget2=new Widget(0,toolBtn2->text());
chatWidget2->setWindowTitle(toolBtn2->text());
chatWidget2->setWindowIcon(toolBtn2->icon());
chatWidget2->show();
}
void Drawer::showChatWidget3(){
chatWidget3=new Widget(0,toolBtn3->text());
chatWidget3->setWindowTitle(toolBtn3->text());
chatWidget3->setWindowIcon(toolBtn3->icon());
chatWidget3->show();
}
void Drawer::showChatWidget4(){
chatWidget4=new Widget(0,toolBtn4->text());
chatWidget4->setWindowTitle(toolBtn4->text());
chatWidget4->setWindowIcon(toolBtn4->icon());
chatWidget4->show();
}
void Drawer::showChatWidget5(){
chatWidget5=new Widget(0,toolBtn5->text());
chatWidget5->setWindowTitle(toolBtn5->text());
chatWidget5->setWindowIcon(toolBtn5->icon());
chatWidget5->show();
}
void Drawer::showChatWidget6(){
chatWidget6=new Widget(0,toolBtn6->text());
chatWidget6->setWindowTitle(toolBtn6->text());
chatWidget6->setWindowIcon(toolBtn6->icon());
chatWidget6->show();
}
void Drawer::showChatWidget7(){
chatWidget7=new Widget(0,toolBtn7->text());
chatWidget7->setWindowTitle(toolBtn7->text());
chatWidget7->setWindowIcon(toolBtn7->icon());
chatWidget7->show();
}
void Drawer::showChatWidget8(){
chatWidget8=new Widget(0,toolBtn8->text());
chatWidget8->setWindowTitle(toolBtn8->text());
chatWidget8->setWindowIcon(toolBtn8->icon());
chatWidget8->show();
}
void Drawer::showChatWidget9(){
chatWidget9=new Widget(0,toolBtn9->text());
chatWidget9->setWindowTitle(toolBtn9->text());
chatWidget9->setWindowIcon(toolBtn9->icon());
chatWidget9->show();
}
void Drawer::showChatWidget10(){
chatWidget10=new Widget(0,toolBtn10->text());
chatWidget10->setWindowTitle(toolBtn10->text());
chatWidget10->setWindowIcon(toolBtn10->icon());
chatWidget10->show();
}




void Drawer::slot_closeWindow()
{
QApplication::exit();
}
//点击某个按钮时候关闭应用程序

这段代码定义了一个名为 Drawer 的类,该类继承自 QToolBox,用于显示用户列表和创建用户聊天窗口。

  1. Drawer::Drawer(QString username, QWidget *parent, Qt::WindowFlags f) : QToolBox(parent, f)

    • 构造函数,接受用户名、父窗口指针和窗口标志作为参数,初始化 Drawer 对象,并设置窗口标题和图标。
  2. setWindowTitle(tr("ZIYE's Chat"));

    • 设置窗口标题为 "ZIYE's Chat"。
  3. setWindowIcon(QPixmap(":/images/O.png"));

    • 设置窗口图标为指定的图片。
  4. setWindowFlags(Qt::FramelessWindowHint);

    • 设置窗口标志为 Qt::FramelessWindowHint,即去除窗口边框。
  5. 创建了10个 QToolButton 对象,分别代表10个用户,设置了对应的文本、图标和样式。

  6. 根据用户名连接了对应的槽函数,点击按钮时会显示与该用户的聊天窗口。

  7. 创建了两个额外的工具按钮 toolBtn11toolBtn12,分别用于关闭窗口和最小化窗口,设置了相应的图标,并连接了相应的槽函数。

  8. 创建了一个 QGroupBox 对象,并设置了其背景图片和字体样式。

  9. 创建了多个布局管理器 QVBoxLayoutQHBoxLayout,用于布局窗口组件。

  10. 创建了多个标签 QLabel,用于显示用户信息和欢迎信息。

  11. 设置了每个 QToolButton 对象的大小、文本、图标和样式,并连接了点击信号与槽函数。

  12. 如果用户数量不足10个,创建了一个额外的 toolBtn10 对象,用于显示剩余的用户名,并连接了对应的槽函数。

  13. 将所有组件添加到布局管理器中,并将 groupBox 添加到 QToolBox 中。

  14. 实现了每个按钮对应的槽函数,用于显示与用户的聊天窗口。

  15. 实现了关闭窗口的槽函数 slot_closeWindow(),用于退出应用程序。

这段代码主要实现了用户列表的显示和与用户聊天窗口的创建。

drawer.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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#ifndef DRAWER_H
#define DRAWER_H
#include<QToolButton>
#include <QToolBox>
#include"widget.h"
#include<QLabel>
class Drawer : public QToolBox
{
Q_OBJECT
//支持信号与槽
public:
Drawer(QString username,QWidget *parent = 0,Qt::WindowFlags f=0);


private slots:
void showChatWidget1();
void showChatWidget2();
void showChatWidget3();
void showChatWidget4();
void showChatWidget5();
void showChatWidget6();
void showChatWidget7();
void showChatWidget8();
void showChatWidget9();
void showChatWidget10();//显示各个用户聊天窗口的函数

void slot_closeWindow();
//关闭窗口的槽函数。
private:
QToolButton *toolBtn1;
QToolButton *toolBtn2;
QToolButton *toolBtn3;
QToolButton *toolBtn4;
QToolButton *toolBtn5;
QToolButton *toolBtn6;
QToolButton *toolBtn7;
QToolButton *toolBtn8;
QToolButton *toolBtn9;
QToolButton *toolBtn10;
QToolButton *toolBtn11;
QToolButton *toolBtn12;
//聊天按钮,聊天窗口对象指针
Widget *chatWidget1;
Widget *chatWidget2;
Widget *chatWidget3;
Widget *chatWidget4;
Widget *chatWidget5;
Widget *chatWidget6;
Widget *chatWidget7;
Widget *chatWidget8;
Widget *chatWidget9;
Widget *chatWidget10;//对应聊天窗口对象的指针
QToolButton *minBtn ;
QToolButton *closeBbtn ;
//最小化按钮和关闭按钮


};

#endif // DRAWER_H

login.cpp

登录页面的实现

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
#include "login.h"
#include "ui_login.h"

#include "passwdedit.h"
#include "register.h"
#include "systemtrayicon.h"

#include <QMessageBox>
#include <QUrl>
#include <QDesktopServices>
#include <QDir>
#include <QDebug>
#include <QMenu>
#include<drawer.h>
//登录页面头文件,包括了登录界面的类 login.h、ui_login.h,密码编辑界面 passwdedit.h、注册界面 register.h,系统托盘图标 systemtrayicon.h 以及一些 Qt 提供的常用头文件。
float opacity1 = 0.0, opacity2 = 1.0;
//定义了两个全局变量 opacity1 和 opacity2,分别用于设置窗口的透明度。
Login::Login(QWidget *parent) :
QWidget(parent),
ui(new Ui::Login)
{
ui->setupUi(this);

init();

connect(this,SIGNAL(close()),this,SLOT(close()));
///Login 类的构造函数,初始化了用户界面并连接了关闭信号。

}

Login::~Login()
{
delete ui;
}
//初始化窗口的函数,设置窗口标题、拖动标志等,并连接了定时器的超时信号到相应的槽函数。
void Login::init()//初始化窗口
{
setWindowTitle(tr("登录"));//窗口名字

m_Drag = false;//用于跟踪用户是否拖动窗口

timer1 = new QTimer;//
timer1->start(5);//这里创建了一个名为timer1的QTimer对象,并以5毫秒的间隔启动它。
timer2 = new QTimer;//
connect(timer1, SIGNAL(timeout()), this, SLOT(slot_timer1()));
connect(timer2, SIGNAL(timeout()), this, SLOT(slot_timer2()));
//timer1和timer2都连接到它们各自的超时信号(timeout())到槽(slot_timer1()和slot_timer2())。
configWindow();//UI界面设置 去边框,最小化,最大化button
init_sql();//初始化界面密码,帐号的初值

//init记住密码
ui->checkBox_rPasswd->setChecked(true);
//这一行将复选框 checkBox_rPasswd 的选中状态设置为 true,表示默认情况下“记住密码”被选中。
ui->lineEdit_passwd->setEchoMode(QLineEdit::Password);
//这一行将密码输入框 lineEdit_passwd 的回显模式设置为 QLineEdit::Password,这意味着用户输入的字符将以星号或其他遮蔽字符显示,以保护密码的安全性。
}
//获取用户输入的用户名和密码信息。
void Login::get_user_info()
{
user_info_stu.userName.clear();
// 获取当前用户名输入框中的文本,并存储到用户信息结构体中的用户名字段
user_info_stu.userName = ui->cBox_account->currentText();
user_info_stu.passwd.clear();
// 获取密码输入框中的文本,并存储到用户信息结构体中的密码字段
user_info_stu.passwd = ui->lineEdit_passwd->text();
}
//配置窗口的函数,包括设置背景图片、去除窗口边框、设置顶部和用户图片、设置按钮以及任务栏系统托盘。
void Login::configWindow()
{

// 填充背景图片
QPalette palette;
palette.setBrush(/*QPalette::Background*/this->backgroundRole(),
QBrush(QPixmap(":/images/background.png")));
this->setPalette(palette);


//去掉窗口边框
setWindowFlags(Qt::FramelessWindowHint);

//程序init时,设置top ,user img
set_top_img(true, -1);//设置图片显示为随机显示
set_user_img(true, -1);//设置user图片为随机显示

//设置UI的按钮button
set_button();


//设置任务栏系统托盘 start
QStringList strList;
strList << "Rose" << "Login";//login 为设置trayico的显示提示
//创建一个字符串列表,包含两个字符串 "Rose" 和 "Login"。
QIcon icon(":/images/ico/login_tray.png");
//创建一个系统托盘对象,并设置图标和提示信息。
SystemTrayIcon *trayIcon = new SystemTrayIcon(strList, icon, this);
//连接系统托盘的 signal_lang_refresh() 信号和当前类的 refresh() 槽函数,以便在语言设置发生变化时刷新界面。
connect(trayIcon, SIGNAL(signal_lang_refresh()), this, SLOT(refresh()));//关联语言设置函数,刷新界面
//设置任务栏系统托盘 end

}

//初始化数据库的函数。
void Login::init_sql()
{
db = QSqlDatabase::addDatabase("QSQLITE");
//创建一个 SQLite 数据库连接对象。
db.setDatabaseName("database.db");
//设置数据库的名称为 "database.db",这将在当前目录下创建或连接到名为 "database.db" 的 SQLite 数据库文件。
if (!db.open()){
qDebug() << "database open fail!";
//如果数据库连接打开失败,则执行下面的代码块。
}
else
{
qDebug() << "database open success!";
QSqlQuery q;
//创建一个 QSqlQuery 对象,用于执行 SQL 查询和操作。
//创建一个名为userInfo的表 顺序为: 用户名 密码 email
QString sql_create_table = "CREATE TABLE userInfo (name VARCHAR PRIMARY KEY,passwd VARCHAR, email VARCHAR)";
//创建名为 "userInfo" 的表:该表包含三列,分别是用户名(name)、密码(passwd)和电子邮件(email)。
q.prepare(sql_create_table);

if(!q.exec())
{
qDebug()<<"creater table error";
}
//向 "userInfo" 表中插入一条记录:用户名为 "operator",密码为 "operator",电子邮件为 "help@demo.com"。
q.exec("insert into userInfo values ('operator','operator','help@demo.com')");
q.exec("select * from userInfo");
//从 "userInfo" 表中查询所有记录,并将用户名添加到下拉框 ui->cBox_account 中,并将密码添加到 userPasswd 列表中。
while (q.next())
{
QString userName = q.value(0).toString();
ui->cBox_account->addItem(userName);
QString passwd = q.value(1).toString();
userPasswd.append(passwd);
qDebug() << "userName:::"<< userName << "passwd:::" << passwd;
}
//将下拉框 ui->cBox_account 的当前索引设置为 0,并将密码设置为 userPasswd 列表中第一个元素的值。
ui->cBox_account->setCurrentIndex(0);
ui->lineEdit_passwd->setText(userPasswd.at(0));
}
db.close();
//db.close();:关闭数据库连接。
qDebug()<<"database closed!";
}
//设置顶部图片的函数。
void Login::set_top_img(bool isSandom, int index_img)
{
//该函数接受两个参数:isSandom(布尔值,表示是否随机显示图片)和 index_img(整数,表示要显示的图片索引)。
//427 185
int set_index_img = 1;
if(isSandom == true)//随机显示topimg
{
QTime time_sand;
//创建一个 QTime 对象,用于获取当前时间。
time_sand= QTime::currentTime();//获取当前时间
qsrand(time_sand.msec()+time_sand.second()*1000);


set_index_img = qrand()%5 + 1 ;//在1-5中选出随机数

}
if(isSandom == false) //不随机显示,按index_img显示图片s
{
set_index_img = index_img;
}
//如果不需要随机显示图片,则使用提供的 index_img 参数作为图片索引。

QString top_img_path=":/images/top_img1.png";
//定义一个字符串变量 top_img_path,用于存储图片的路径,默认为第一张图片的路径。
qDebug()<< " [leo]" << top_img_path;
QImage top_img;
//定义一个 QImage 对象,用于加载图片。
top_img_path = ":/images/top_img" + QString::number(set_index_img, 10) + ".png";
//根据随机生成的或传入的图片索引,构建要加载的图片的路径。
qDebug()<< " [leo]" << top_img_path;
top_img.load(top_img_path);
//:加载图片。
QPixmap top_pic=QPixmap::fromImage(top_img.scaled(ui->label_top_img->width(),ui->label_top_img->height()));
//将加载的图片转换为 QPixmap 对象,并根据 ui->label_top_img 控件的大小进行缩放。
ui->label_top_img->setPixmap(top_pic);
//将缩放后的图片设置为 label_top_img 控件的内容,实现显示。
qDebug() << " [leo]top_img width heigh:" << ui->label_top_img->width()
<< " " << ui->label_top_img->height();
//输出调试信息,显示当前 label_top_img 控件的宽度和高度。
}
//设置按钮的函数。
void Login::set_button()
{
//构建最小化、关闭按钮,设置按钮,键盘ico
minBtn = new QToolButton(this);
closeBbtn = new QToolButton(this);
setBtn = new QToolButton(this);
keyBtn = new QToolButton(this);
//创建了四个按钮对象 minBtn、closeBbtn、setBtn 和 keyBtn,它们都是 QToolButton 类的实例,并将它们添加到当前窗口中。
//获取界面的宽度
int width = this->width();
//设置最小化、关闭按钮在界面的位置
minBtn->setGeometry(width-55,5,20,20);
closeBbtn->setGeometry(width-25,5,20,20);
setBtn->setGeometry(width-80,7,15,15);
//设置按钮位置:

//设置键盘ico坐标
int x = ui->lineEdit_passwd->x();
int y = ui->lineEdit_passwd->y();
int widthkey = ui->lineEdit_passwd->width();
keyBtn->setGeometry(x+widthkey-20, y, 20, 20);
//获取密码输入框的位置和宽度,并将虚拟键盘按钮设置在密码输入框的右侧。
qDebug() << "[leo]width:" << width ;
qDebug() << "[leo]minBtn" << minBtn->geometry();
qDebug() << "[leo]closeBbtn" << closeBbtn->geometry();

//设置鼠标移至按钮上的提示信息
minBtn->setToolTip(tr("最小化"));
closeBbtn->setToolTip(tr("关闭"));
setBtn->setToolTip(tr("设置"));
keyBtn->setToolTip(tr("虚拟键盘"));

//设置最小化、关闭按钮的样式图标
//设置按钮样式图标和背景透明:
minBtn->setIcon(QIcon(":/images/ico/mini.png"));
minBtn->setStyleSheet("background-color:transparent;");
closeBbtn->setIcon(QIcon(":/images/ico/close.png"));
closeBbtn->setStyleSheet("background-color:transparent;");
setBtn->setIcon(QIcon(":/images/ico/setting.png"));
setBtn->setStyleSheet("background-color:transparent;");
keyBtn->setIcon(QIcon(":/images/keyBoard.png"));
keyBtn->setStyleSheet("background-color:transparent;");

//关联最小化、关闭按钮的槽函数,键盘exe
connect(minBtn, SIGNAL(clicked()), this, SLOT(slot_minWindow()));
connect(closeBbtn, SIGNAL(clicked()), this, SLOT(slot_closeWindow()));
connect(keyBtn, SIGNAL(clicked()), this, SLOT(slot_getKeyBoard()));
connect(setBtn, SIGNAL(clicked()), this, SLOT(slot_setLanguage()));

create_menuLanguage();
//创建语言菜单
}
//设置用户图片的函数。
void Login::set_user_img(bool isSandom, int index_img)
//一致
{
//40,182 85 85
int set_index_img = 1;
if(isSandom == true)//随机显示userimg
{

QTime time_sand;
time_sand= QTime::currentTime();//获取当前时间
qsrand(time_sand.msec()+time_sand.second()*1000);
set_index_img = qrand()%5 + 1 ;//在1-5中选出随机数

}
if(isSandom == false) //不随机显示,按index_img显示图片s
{
set_index_img = index_img;
}

QString user_img_path=":/images/ico/user1.png";
qDebug()<< " [leo]user" << user_img_path;
QImage user_img;
user_img_path = ":/images/ico/user" + QString::number(set_index_img, 10) + ".png";
qDebug()<< " [leo]user" << user_img_path;
user_img.load(user_img_path);
QPixmap img_pic=QPixmap::fromImage(user_img.scaled(ui->label_user_img->width(),
ui->label_user_img->height()));
ui->label_user_img->setPixmap(img_pic);
qDebug() << " [leo]user_img width heigh:" << ui->label_user_img->width()
<< " " << ui->label_user_img->height();
}
//创建语言菜单的函数。
void Login::create_menuLanguage()
{
//语言
act_chinese = new QAction(tr("简体中文"), this);
act_english = new QAction(tr("English"), this);
//创建了两个动作对象 act_chinese 和 act_english,分别表示简体中文和英文。
menu1 = new QMenu;
menu1->addAction(act_chinese);
menu1->addAction(act_english);
//创建了一个菜单对象 menu1,并向其中添加了简体中文和英文两个菜单项。

//在线状态
act0 = new QAction(tr("在线"), this);
act1 = new QAction(tr("隐身"), this);
act2 = new QAction(tr("离线"), this);
act3 = new QAction(tr("忙碌"), this);
//创建了四个动作对象 act0、act1、act2 和 act3,分别表示在线、隐身、离线和忙碌状态。

actGrp = new QActionGroup(this);
actGrp->addAction(act0);
actGrp->addAction(act1);
actGrp->addAction(act2);
actGrp->addAction(act3);
//创建了一个动作组对象 actGrp,并将四个在线状态的动作对象添加到该组中,这样同一时间只能选择一个在线状态。
connect(actGrp, SIGNAL(triggered(QAction*)), this, SLOT(slot_actGrp(QAction*)));
//当在线状态菜单项被触发时,会调用 slot_actGrp(QAction*) 槽函数。
menu2 = new QMenu;
menu2->addAction(act0);
menu2->addAction(act1);
menu2->addAction(act2);
menu2->addAction(act3);
//创建了一个菜单对象 menu2,并向其中添加了在线状态的四个菜单项。
menu1->addAction(act0);
menu1->addAction(act1);
menu1->addAction(act2);
menu1->addAction(act3);
//将在线状态的四个菜单项也添加到语言菜单中。
}
//设置样式的函数。
void Login::setStyle(const QString &style)
//定义了一个名为 setStyle 的函数,该函数用于设置样式。
{
QFile qss( ":/images/qss/" + style);//black.qss
//创建了一个 QFile 对象 qss,并通过 ":/images/qss/" + style 构建样式表文件的路径。style 是一个传入的参数,用于指定样式表的文件名或路径。
qDebug() << "qss :";
qss.open(QIODevice::ReadOnly | QIODevice::Text);
//以只读文本模式打开样式表文件。
qApp->setStyleSheet(qss.readAll());
//设置应用程序的样式表:
qss.close();
}

//鼠标按下
void Login::mousePressEvent(QMouseEvent *e)
//定义了 mousePressEvent 函数,它是 Login 类的一个成员函数,用于处理鼠标按下事件。这个函数会在鼠标按下时被调用。
{
if (e->button() == Qt::LeftButton) {
//检查鼠标事件中的按键是否是左键。
m_Drag = true;
//成员变量 m_Drag 置为 true,表示鼠标正在拖动窗口。
m_point = e->globalPos() - this->pos();
//计算鼠标按下时的位置相对于窗口左上角的偏移量,并保存在成员变量 m_point 中。这个偏移量用于在拖动窗口时计算新的窗口位置
e->accept();
//接受鼠标事件,表示事件已经被处理。
// qDebug()<<"leo";
}
}

//鼠标移动事件
void Login::mouseMoveEvent(QMouseEvent *e)
//定义了 mouseMoveEvent 函数,它是 Login 类的一个成员函数,用于处理鼠标移动事件。这个函数会在鼠标在窗口内移动时被调用。
{
if (m_Drag && (e->buttons() && Qt::LeftButton)) {
//检查当前鼠标是否处于拖动状态,并且同时按下了左键。
move(e->globalPos() - m_point);
//根据鼠标当前位置和按下时的偏移量 m_point,移动窗口到新的位置。这行代码将当前窗口的位置设置为鼠标当前位置减去按下时的偏移量。
e->accept();
// qDebug()<<"leomove";
}
}
//鼠标释放事件
void Login::mouseReleaseEvent(QMouseEvent *e)
{
m_Drag = false;
}
//将 m_Drag 设置为 false,表示鼠标已经释放,不再处于拖动状态。


// //登录按钮点击事件
void Login::on_btn_login_clicked()
//定义了 on_btn_login_clicked 槽函数,它是在登录按钮被点击时触发的。这个函数会检查用户输入的用户名和密码是否有效,并执行相应的操作。

{
qDebug() << "login:" << user_info_stu.userName << user_info_stu.passwd;
//打印输出用户输入的用户名和密码,用于调试和确认用户输入的信息
if(ui->cBox_account->currentText().isEmpty() ||
ui->lineEdit_passwd->text().isEmpty()){
QMessageBox::warning(this,tr("警告"),tr("请输入用户名和密码!"));
}
//检查用户名和密码是否为空,如果为空,则弹出警告框提示用户输入用户名和密码。
else
{
int is_use_exist_flag = 0; //判断用户是否存在
int is_use_nampwd_check_flag = 0; //判断用户名和密码是否匹配
//初始化两个标志位,用于判断用户是否存在和用户名密码是否匹配。
get_user_info();
//调用 get_user_info() 函数,获取用户输入的用户名和密码。
if(!db.open())
{
qDebug() << "database open fail login!";
}
//如果数据库打开失败,则输出错误信息。
else
{
QSqlQuery query;
qDebug() << "database open success login!";
query.exec("select * from userInfo");
//执行 SQL 查询语句,从数据库中获取用户信息。
while (query.next())
{
QString userName = query.value(0).toString();
QString passwd = query.value(1).toString();
qDebug() << "login userName:::"<< userName << "passwd:::" << passwd;
//遍历查询结果,获取数据库中的用户名和密码,并打印输出。
if(userName == user_info_stu.userName){
is_use_exist_flag = true; //用户存在
if(passwd == user_info_stu.passwd){
is_use_nampwd_check_flag = true; //用户名和密码匹配
QWidget *parent = 0;Qt::WindowFlags f=0;
drawer=new Drawer(userName,parent,f);
drawer->setFixedSize(280,850);
drawer->setWindowOpacity(0.9);

drawer->show();

emit close();
//如果用户名存在且与输入的用户名匹配,并且密码也匹配,则设置相应的标志位,并执行登录操作。
}
}
}

if(is_use_exist_flag == false)
{
QMessageBox::information(this,tr("提示"),tr("用户不存在!"));
}
else
{
if(is_use_nampwd_check_flag == false)
{
QMessageBox::warning(this,tr("警告"),tr("用户密码错误!"));
}
}
}
//如果用户不存在或者用户名密码不匹配,则弹出相应的提示框。
db.close();
//关闭数据库连接。
}
}

//注册button
//注册按钮点击事件处理函数。
void Login::on_btn_regist_clicked()
//定义了 on_btn_regist_clicked 槽函数,它是在注册按钮被点击时触发的。这个函数会打开注册页面并获取用户输入的注册信息,然后将信息写入数据库。
{
Register r;
r.setParent(this); //设置父对象
r.exec(); //注册页面r,仅仅获取信息.
//创建注册页面对象 r,将当前窗口设置为其父对象,并调用 exec() 方法显示注册页面。这里是阻塞式调用,直到用户关闭注册页面后才会继续执行下面的代码。

if(user_info_stu.userName.isEmpty() || user_info_stu.passwd.isEmpty()){
QMessageBox::information(this,tr("提示"),tr("请输入用户名和密码!"));
}
//检查用户输入的用户名和密码是否为空,如果为空,则弹出提示框要求用户输入用户名和密码。
else
{
bool exitFlag = false; //判断用户是否存在

if(!db.open())
{
qDebug() << "database open fail regist!";
}
else
{
QSqlQuery query;
qDebug() << "database open success regist!";
query.exec("select * from userInfo");
while (query.next())
{
QString userName = query.value(0).toString();
QString passwd = query.value(1).toString();
qDebug() << "regist userName:::"<< userName << "passwd:::" << passwd;

if(userName == user_info_stu.userName){
exitFlag = true; //用户存在
}
}//遍历查询结果,检查用户是否已存在。

// 如果用户不存在,则向数据库中插入新的用户信息。
if(exitFlag == false){
query.exec(QString("insert into userInfo values ('%1','%2','%3')")
.arg(user_info_stu.userName).arg(user_info_stu.passwd)
.arg(user_info_stu.email));
qDebug() << "ddd:" << user_info_stu.userName << user_info_stu.passwd << user_info_stu.email;
qDebug()<<"regist:::"<<query.lastQuery();
//将新用户的用户名添加到登录界面的账号下拉框中,并将密码保存到 userPasswd 列表中。
ui->cBox_account->addItem(user_info_stu.userName);
userPasswd.append(user_info_stu.passwd);
QMessageBox::information(this,tr("提示"),tr("注册成功!"));
//弹出提示框,提示用户注册成功。
query.exec("select * from userInfo");
while (query.next())
{
QString userName = query.value(0).toString();
QString passwd = query.value(1).toString();
qDebug() << "regist userName:::"<< userName << "passwd:::" << passwd;
}
//重新执行查询操作,打印输出注册成功后的用户信息。
}else{
QMessageBox::warning(this,tr("警告"),tr("用户已存在!"));
}
//如果用户已存在,则弹出警告框提示用户。
}
db.close();
//关闭数据库连接。
}
}

//修改密码button
//修改密码按钮点击事件处理函数。
void Login::on_btn_edit_pwd_clicked()
//用于处理修改密码按钮被点击时的事件。
//定义了 on_btn_edit_pwd_clicked 槽函数,它是在修改密码按钮被点击时触发的。这个函数会检查用户输入的用户名和密码是否正确,然后打开修改密码页面。
{
if(ui->cBox_account->currentText().isEmpty() ||
ui->lineEdit_passwd->text().isEmpty()){
QMessageBox::information(this,tr("提示"),tr("请输入用户名和密码!"));
}
//检查用户输入的用户名和密码是否为空,如果为空,则弹出提示框要求用户输入用户名和密码。
else
{
bool is_use_exist_flag = false; //判断用户是否存在
bool is_use_nampwd_check_flag = false; //判断用户名和密码是否匹配
get_user_info();

if(!db.open())
{
qDebug() << "database open fail login!";
}
else
{
QSqlQuery query;
qDebug() << "database open success login!";
query.exec("select * from userInfo");
while (query.next())
{
QString userName = query.value(0).toString();
QString passwd = query.value(1).toString();
qDebug() << "edit userName:::"<< userName << "passwd:::" << passwd;

if(userName == user_info_stu.userName)
{
is_use_exist_flag = true; //用户存在
if(passwd == user_info_stu.passwd)
{
is_use_nampwd_check_flag = true; //用户名和密码匹配
passwdEdit passwd;
//遍历查询结果,检查用户是否存在,并且用户名和密码是否匹配。如果用户存在且用户名和密码匹配,则打开修改密码页面。

passwd.setLogin(this);
//this->hide();
passwd.exec();
}
}
}

if(is_use_exist_flag == false)
{
QMessageBox::information(this,tr("提示"),tr("用户不存在!"));
}
else
{
if(is_use_nampwd_check_flag == 0){
QMessageBox::warning(this,tr("警告"),tr("用户密码错误!"));
//如果用户名和密码不匹配,则弹出警告框提示用户密码错误。
}
}
}
db.close();
}
}//关闭数据库连接。

//最小化button
// //最小化按钮点击事件
void Login::slot_minWindow()
{
this->showMinimized();
}
//slot_minWindow 函数在最小化窗口按钮被点击时触发,它调用 showMinimized() 方法将窗口最小化到任务栏。
////关闭窗口按钮点击事件
void Login::slot_closeWindow()
{
timer2->start(5);
}
//slot_closeWindow 函数在关闭窗口按钮被点击时触发,它启动一个定时器 timer2,定时器用于控制窗口的关闭。





// //虚拟键盘按钮点击事件
void Login::slot_getKeyBoard()
{
qDebug() << "key!";


QString curPath = QApplication::applicationDirPath();
curPath.append("/osk.exe");
qDebug() << "curPath:" << curPath;

QDesktopServices::openUrl(QUrl(curPath, QUrl::TolerantMode));
}
//当虚拟键盘按钮被点击时,slot_getKeyBoard 函数被调用。首先,它输出调试信息 "key!",然后获取当前应用程序的目录路径,并将其与虚拟键盘程序的文件名 "osk.exe" 连接起来。接着,它使用 QDesktopServices::openUrl 函数打开虚拟键盘程序。


//void Login::slot_setLanguage()
void Login::slot_setLanguage()
{
menu1->exec(QCursor::pos());
}
//当设置按钮被点击时,slot_setLanguage 函数被调用。它执行了 menu1->exec(QCursor::pos()),这会在鼠标光标的位置显示 menu1 对象,也就是一个包含语言选项的菜单。
// //语言动作组槽函数
void Login::slot_actGrp(QAction *act)
{
if (act == act0) {
qDebug() << "act0";
} else if (act == act1) {
qDebug() << "act1";
} else if (act == act2) {
qDebug() << "act2";
} else if (act == act3) {
qDebug() << "act3";
}
}
//当语言动作组中的某个动作被触发时,slot_actGrp 函数被调用,并且传递了被触发的动作指针 act。然后,根据不同的动作指针,会输出相应的调试信息,以便在控制台中查看哪个动作被触发了。
////定时器1槽函数
void Login::slot_timer1()
//这段代码是一个槽函数 slot_timer1(),它是由定时器 timer1 的超时信号触发的。

//在这个函数中,有一个透明度变量 opacity1,初始值为0。每当定时器超时时,透明度 opacity1 会逐渐增加,直到达到1.0。
{
if (opacity1 >= 1.0) {
timer1->stop();
}else{
opacity1 += 0.01;
}
setWindowOpacity(opacity1);//设置窗口透明度
//最后,通过 setWindowOpacity() 函数将当前的透明度应用于窗口,使窗口逐渐变得更加不透明。
}


////定时器2槽函数
void Login::slot_timer2()
{
if (opacity2 <= 0.0) {
timer2->stop();

this->close();
}else{
opacity2 -= 0.01;
}
setWindowOpacity(opacity2);//设置窗口透明度
}
// //账号下拉框激活事件处理函数
void Login::on_cBox_account_activated(int index)
{
ui->lineEdit_passwd->setText(userPasswd.at(index));
qDebug() << "change cBox:" << ui->cBox_account->currentText()
<< userPasswd.at(index);
}
//这行代码将下拉框中选定项对应的密码设置到密码输入框 lineEdit_passwd 中。密码从 userPasswd 列表中取得,索引为 index,即当前激活项的索引。


//下拉框选里面的项时,会切换top_img的图片和头像图片
////账号下拉框当前索引改变事件处理函数
void Login::on_cBox_account_currentIndexChanged(int index)
//这段代码是一个槽函数 on_cBox_account_currentIndexChanged(int index),它在下拉框 cBox_account 中的当前项发生改变时触发。
{
set_top_img(true,index);
//这行代码调用了 set_top_img 函数,并传递了两个参数 true 和 index。该函数根据参数的设置,可能会设置顶部图片,其中 index 是当前选定项的索引,用于确定要显示的图片。
set_user_img(true,index);
//这行代码调用了 set_user_img 函数,并传递了两个参数 true 和 index。该函数根据参数的设置,可能会设置用户图片,其中 index 是当前选定项的索引,用于确定要显示的图片。
}

void Login::refresh()
//刷新login页面当前的字符串。其它页面无需刷新,因为打开时候,会自动刷新相关字符串。而主窗口不会。
{
qDebug() << "xxxxxxxxxxxxxxxxxxx";
//这行代码使用 qDebug() 函数输出一条调试信息,以便在控制台中查看。这条信息用于在程序运行时跟踪代码执行流程或检查特定变量的值等。
ui->btn_login->setText(tr("登录"));
ui->btn_edit_pwd->setText(tr("找回密码"));
ui->btn_regist->setText(tr("注册用户"));
//这几行代码分别设置了三个按钮(登录按钮、找回密码按钮、注册用户按钮)的文本内容。tr() 函数用于进行文本国际化处理,使得程序能够根据当前的语言环境来显示相应的文本内容。

//ui->checkBox_autoLogin->setText(tr("自动登录"));
ui->label->setText(tr("记住密码"));
ui->label_2->setText(tr("自动登录"));
//这两行代码分别设置了两个标签(记住密码标签、自动登录标签)的文本内容。同样地,使用 tr() 函数进行文本国际化处理。
}

login.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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
#ifndef LOGIN_H
#define LOGIN_H
#include<drawer.h>
#include <QWidget>
#include <QSqlDatabase>
#include <QSqlQuery>
#include <QMouseEvent>
#include <QTimer>
#include <QToolButton>
#include <QMenu>
#include <QAction>
#include <QActionGroup>

struct UserInfoStu
{
QString userName;
QString passwd;
QString email;
};

namespace Ui {
class Login;
}

class Login : public QWidget
{
Q_OBJECT

public:
explicit Login(QWidget *parent = 0);
~Login();

void init();//初始化函数
void get_user_info();//初始化
void configWindow();
//配置窗口,可能包括设置窗口样式等。
void init_sql();//数据库
void set_top_img(bool isSandom, int index_img);//isSandom is true,set img show by sandom
void set_button();//设置UI上的按钮
void set_user_img(bool isSandom, int index_img);//设置UI上用户头像

void create_menuLanguage(); //设置语言菜单

void setStyle(const QString &style);//设置style


protected:
void mousePressEvent(QMouseEvent *e);

void mouseMoveEvent(QMouseEvent *e);

void mouseReleaseEvent(QMouseEvent *e);
//鼠标事件处理函数,实现窗口拖拽功能。
signals:
void close();

private slots:
void on_btn_login_clicked();

void on_btn_regist_clicked();

void slot_minWindow();

void slot_closeWindow();

void slot_getKeyBoard();

void slot_setLanguage(); //设置语言

void slot_actGrp(QAction* act);

void slot_timer1();

void slot_timer2();

void on_cBox_account_activated(int index);

void on_btn_edit_pwd_clicked();

void on_cBox_account_currentIndexChanged(int index);

void refresh();//刷新login界面字符串
//各种槽函数,处理按钮点击、定时器触发、下拉框选项改变等事件。
private:
Ui::Login *ui;

bool m_Drag;
//用于标记鼠标左键是否按下,用于实现窗口的拖拽功能。
QPoint m_point;
//记录鼠标相对于窗口的位置,用于窗口拖拽时计算窗口移动的偏移量。
QTimer *timer1, *timer2;
//用于定时器,可能用于界面动画效果等。
QStringList userPasswd; //用户密码

QToolButton *minBtn;
QToolButton *closeBbtn;
QToolButton *setBtn;
QToolButton *keyBtn;
//最小化按钮、关闭按钮、设置按钮、虚拟键盘按钮、在线状态按钮等工具按钮。
QToolButton *status_tBtn; //在线状态

QMenu *menu1; //语言菜单
QAction *act_chinese;
QAction *act_english;
QMenu *menu2; //在线状态
QAction *act0; //在线
QAction *act1; //隐身
QAction *act2; //离线
QAction *act3; //忙碌
QActionGroup *actGrp;
//在线状态动作组。
Drawer *drawer;
//:用于显示抽屉窗口的指针。
public:
UserInfoStu user_info_stu;
//用户信息结构体,包括用户名、密码和邮箱。
QSqlDatabase db;
};

#endif // LOGIN_H

main.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
   #include <QApplication>
#include <QtWidgets>
#include "login.h"

#include <QTextCodec>
//文本解码类头文件

int main(int argc, char *argv[])
{
QApplication a(argc, argv);

QTextCodec::setCodecForLocale(QTextCodec::codecForLocale());
//本地编码格式
Login w;
w.show();
//创建登录页面
return a.exec();
}

passwdedit.cpp

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
#include "passwdedit.h"
#include "ui_passwdedit.h"
#include <QMessageBox>
#include <QDebug>

passwdEdit::passwdEdit(QWidget *parent) :
QDialog(parent),
ui(new Ui::passwdEdit)
{
ui->setupUi(this);

init();
}

passwdEdit::~passwdEdit()
{
delete ui;
}

//设置标题,连接信号槽
void passwdEdit::init()
{
setWindowTitle(tr("修改密码"));

connect(this,SIGNAL(passwdEditOk()),this,SLOT(close()));
connect(ui->btn_cancel,SIGNAL(clicked()),this,SLOT(close()));
}
//设置登录页面指针
void passwdEdit::setLogin(Login *m)
{
if(m != NULL)
{
login = m;
}
}
//确定按钮的事件逻辑
void passwdEdit::on_btn_ok_clicked()

//旧密码,新密码,确认密码
QString oldPasswd = ui->lineEdit_passwd->text();
QString newPasswd = ui->lineEdit_newPasswd->text();
QString passwdOk = ui->lineEdit_passwdOk->text();
//未输入
if(oldPasswd.isEmpty() || newPasswd.isEmpty() || passwdOk.isEmpty())
{
QMessageBox::warning(this,tr("警告"),tr("密码为空!"));
}
else
{
if(oldPasswd == login->user_info_stu.passwd)
{
if(oldPasswd == newPasswd)
{
QMessageBox::warning(this,tr("警告"),tr("新密码与旧密码一样!"));
return;
}
//一致密码比较
else
{
if(newPasswd != passwdOk)
{
QMessageBox::information(this,tr("提示"),tr("密码修改失败!"));
}
//新密码和确认密码不一致
else
{
QSqlQuery query;
query.exec(QString ("update userInfo set passwd = '%1' where name = '%2'")
.arg(newPasswd).arg(login->user_info_stu.userName));
//存入数据
QMessageBox::information(this,tr("提示"),tr("密码修改成功!"));

emit passwdEditOk();
//发射信号,表示密码修改完成
}
}
}
else
{
QMessageBox::warning(this,tr("警告"),tr("旧密码输入错误!"));
}
}
}

passwdedit.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
#ifndef PASSWDEDIT_H
#define PASSWDEDIT_H

#include <QDialog>

#include "login.h"

namespace Ui {
class passwdEdit;
}

class passwdEdit : public QDialog
{
Q_OBJECT

public:
explicit passwdEdit(QWidget *parent = 0);
~passwdEdit();

void init();
//初始化和设置登录指针
void setLogin(Login *m);

signals:
//信号,密码修改成功
void passwdEditOk();

private slots:
//处理:点击确定按钮的事件
void on_btn_ok_clicked();

private:
Ui::passwdEdit *ui;

Login *login;
};

#endif // PASSWDEDIT_H

register.cpp

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
#include "register.h"
#include "ui_register.h"
//#include "login.h"

Register::Register(QWidget *parent) :
QDialog(parent),
ui(new Ui::Register)
{
ui->setupUi(this);

setWindowTitle(tr("注册"));
}

Register::~Register()
{
delete ui
}

//设父对象指针
void Register::setParent(Login *dialog)
{
if(dialog != NULL)
{
pWidget = dialog;
}
}

//获取用户名,密码,邮箱,并关闭对话框
//注册函数.仅仅获取相关值,不做检测和数据库操作
void Register::on_registerCheckButton_clicked()
{
pWidget->user_info_stu.userName = ui->lineEditName->text();
pWidget->user_info_stu.passwd = ui->lineEditPassword->text();
pWidget->user_info_stu.email = ui->lineEditEmail->text();
qDebug() << "333:" << pWidget->user_info_stu.userName << pWidget->user_info_stu.passwd
<< pWidget->user_info_stu.email;
this->close();

}

register.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
40
#ifndef REGISTER_H
#define REGISTER_H

#include <QDialog>
#include <QtSql> //数据库头文件
#include <QMessageBox>
#include <QString>
#include <QSqlDatabase>
#include <QSqlQuery>
#include "login.h"

namespace Ui {
class Register;
}

class Register : public QDialog
{
Q_OBJECT

public:
explicit Register(QWidget *parent = 0);
~Register();

void setParent(Login *dialog);

QSqlDatabase database;//database为注册的数据库名称
bool tableFlag;
int sql_max_id;

private slots:
void on_registerCheckButton_clicked();
//注册按钮点击事件处理
private:
Ui::Register *ui;

Login *pWidget; //父对象

};

#endif // REGISTER_H

server.cpp

服务器端的代码实现,部分按钮乃至对话框输入框均是采用UI制作

逻辑

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
#include "server.h"
#include "ui_server.h"
#include<QFile>
#include<QTcpServer>
#include<QTcpSocket>
#include<QMessageBox>
#include<QFileDialog>
#include<QDebug>

Server::Server(QWidget *parent) :
QDialog(parent),
ui(new Ui::Server)
{
ui->setupUi(this);
setFixedSize(456,224);
tPort=5555;
//设置端口号
tSrv=new QTcpServer(this);
//TCP服务器对象
connect(tSrv,SIGNAL(newConnection()),this,SLOT(sndMsg()));
//有新的连接就触发发送消息函数
initSrv();
}

Server::~Server()
{
delete ui;
}

void Server::initSrv(){
payloadSize=64*1024;
//设置数据块大小
totalBytes=0;
//总子节
bytesWritten=0;
//已经写入子节
bytesTobeWrite=0;
//待写入子节
ui->sStatusLbl->setText(tr("请选择要传输的文件"));
//设置状态标签文本
ui->progressBar->reset();
//重置进度条
ui->sOpenBtn->setEnabled(true);
//设置打开按钮不可用
ui->sSendBtn->setEnabled(false);
//设置发送按钮不可用
tSrv->close();

//关闭服务器


}//服务器初始状态


void Server::sndMsg(){
ui->sSendBtn->setEnabled(false);
//发送按钮不可用
clntConn=tSrv->nextPendingConnection();
//获取新的客户端连接
connect(clntConn,SIGNAL(bytesWritten(qint64)),this,SLOT(updClntProgress(qint64)));
//// 连接字节写入信号到更新客户端进度槽函数
ui->sStatusLbl->setText(tr("开始传送文件%1").arg(theFileName));
///状态文本
locFile=new QFile(fileName);
if(!locFile->open((QFile::ReadOnly)))
{
QMessageBox::warning(this,tr("应用程序"),tr("无法读取文件%1:\n%2").arg(fileName).arg(locFile->errorString()));
return;
}
//以只读方式打开选中的文件
totalBytes=locFile->size();//通过size函数获取待发送文件的大小
QDataStream sendOut(&outBlock,QIODevice::WriteOnly);
//将发送缓冲区的outBlock封装在一个QDataStream类型的变量中以方便的通过运算符重载<<填写文件头结构
sendOut.setVersion(QDataStream::Qt_5_8);
time.start();//计时
QString curFile=fileName.right(fileName.size()-fileName.lastIndexOf('/')-1);//保留文件名部分
sendOut<<qint64(0)<<qint64(0)<<curFile;//构造一个临时的文件头将该值追加到totalBytes字段从而完成实际需要发送字节数的记录
totalBytes+=outBlock.size();
sendOut.device()->seek(0);//将读写操作设置为从头开始
sendOut<<totalBytes<<qint64((outBlock.size()-sizeof(qint64)*2));
bytesTobeWrite=totalBytes-clntConn->write(outBlock);//将该文件头发出同时修改待发送字节数bytesTObyWrite
outBlock.resize(0);//清空缓冲区
}




void Server::updClntProgress(qint64 numBytes){

qApp->processEvents();//避免传输大文件时冻结
bytesWritten+=(int)numBytes;//更新已经写入的子节
if(bytesTobeWrite>0){
//还有待写入的
outBlock=locFile->read(qMin(bytesTobeWrite,payloadSize));
//读取文件
bytesTobeWrite-=(int)clntConn->write(outBlock);
//减去
outBlock.resize(0);
//清空缓存区
}else{
locFile->close();
//没有要读入的,直接关闭
}
ui->progressBar->setMaximum(totalBytes);
//设置进度条最大值
ui->progressBar->setValue(bytesWritten);
//
float useTime=time.elapsed();
double speed=bytesWritten/useTime;
ui->sStatusLbl->setText(tr("已发送%1MB(%2MB/s)\n共%3MB 已用时:%4s\n估计剩余时间:%5s").arg(bytesWritten/(1024*1024)).arg(speed*1000/(1024*1024),0,'f',2).arg(totalBytes/(1024*1024)).arg(useTime/1000,0,'f',0).arg(totalBytes/speed/1000-useTime/1000,0,'f',0));
//时间,速度,
//已经发送的子节树等于总子节数
if(bytesWritten==totalBytes){
locFile->close();
tSrv->close();
ui->sStatusLbl->setText(tr("传送文件%1成功").arg(theFileName));
//关闭+成功
}
}

void Server::on_sOpenBtn_clicked()
{
fileName=QFileDialog::getOpenFileName(this);
//获取要传输的文件名
if(!fileName.isEmpty()){
theFileName=fileName.right(fileName.size()-fileName.lastIndexOf('/')-1);
//获取文件名
ui->sStatusLbl->setText(tr("要传送的文件为:%1").arg(theFileName));
ui->sSendBtn->setEnabled(true);
ui->sOpenBtn->setEnabled(false);
//发送按钮可用
//打开按钮不可用
}
}

void Server::on_sSendBtn_clicked()
{
if(!tSrv->listen(QHostAddress::Any,tPort)){
qDebug()<<tSrv->errorString();
close();
return;
//不能监听
}
ui->sStatusLbl->setText(tr("等待对方接收... ..."));
emit sndFileName(theFileName);
//发送文件名信号
}

void Server::on_sCloseBtn_clicked()
{
//如果正在监听,关闭,都是关闭+终止
if(tSrv->isListening()){
tSrv->close();
if(locFile->isOpen())locFile->close();
clntConn->abort();
}
close();
}

//重写关闭按钮点击事件
void Server::closeEvent(QCloseEvent *){

on_sCloseBtn_clicked();
}

//显示对方拒绝接收消息
void Server::refused()
{
tSrv->close();
ui->sStatusLbl->setText(tr("对方拒绝接收!"));

}
  • #include:引入需要的头文件。
  • Server::Server(QWidget *parent):构造函数,初始化服务器对象。
  • ~Server():析构函数,释放内存。
  • initSrv():初始化服务器的状态,包括重置进度条、设置状态标签等。
  • sndMsg():处理发送消息,包括准备要发送的文件、计算待发送字节数等。
  • updClntProgress(qint64 numBytes):更新客户端传输进度,计算传输速度、剩余时间等。
  • on_sOpenBtn_clicked():打开文件按钮点击事件处理函数,获取要传输的文件名。
  • on_sSendBtn_clicked():发送文件按钮点击事件处理函数,开始监听并发送文件名。
  • on_sCloseBtn_clicked():关闭按钮点击事件处理函数,关闭服务器、文件和连接。
  • closeEvent(QCloseEvent *):窗口关闭事件处理函数,执行关闭按钮点击事件。
  • refused():对方拒绝接收处理函数,关闭服务器并显示消息。

server.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
40
41
42
43
44
45
46
47
48
49
50
51
52
#ifndef SERVER_H
#define SERVER_H
#include<QDialog>
#include <QTime>
class QFile;
class QTcpServer;//通过新创建QTcpServer对象实现创建一个发送对话框以供用户选择文件发送
class QTcpSocket;
namespace Ui {
class Server;
}

class Server : public QDialog
{
Q_OBJECT

public:
explicit Server(QWidget *parent = 0);
~Server();
void initSrv();//初始化服务器
void refused();//关闭服务器
protected:
void closeEvent(QCloseEvent *);
private:
Ui::Server *ui;
qint16 tPort;
QTcpServer *tSrv;
QString fileName;
QString theFileName;
QFile *locFile;//待发送的文件
qint64 totalBytes;//总共需要发送的字节数
qint64 bytesWritten;//已发送字节数
qint64 bytesTobeWrite;//待发送字节数
qint64 payloadSize;//被初始化为一个常量
QByteArray outBlock;//缓存一次发送的数据
QTcpSocket *clntConn;//客户端发送的套接字
QTime time;
private slots:
void sndMsg();//发送数据
void updClntProgress(qint64 numBytes);//更新进度条
//处理按钮点击事件函数
void on_sOpenBtn_clicked();

void on_sSendBtn_clicked();

void on_sCloseBtn_clicked();

signals:
void sndFileName(QString fileName);
//发送文件名信号
};

#endif // SERVER_H

systemtrayicon.cpp

创建系统托盘图标以及菜单和对应操作

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
#include "systemtrayicon.h"

#include <QApplication>
#include <QDebug>

SystemTrayIcon::SystemTrayIcon(QStringList strList, QIcon icon, QWidget *parent) : QWidget(parent)
{
pWidget = parent;//父窗口指针
m_strList = strList;//字符串列表
m_icon = icon;//图标
//总体就是构造函数的参数保存到类的成员函数
create_sysTrayMenuAct();//1.创建点击托盘菜单项的act行为
create_sysTrayMenu();//2.创建托盘菜单
create_sysTrayIcon();//3.创建托盘,init
//注意初始化顺序:1.init托盘菜单包含的项 2.init托盘菜单 3.init托盘Button
}

//创建点击托盘菜单项的act行为
void SystemTrayIcon::create_sysTrayMenuAct()
{
//创建各个菜单项的行为和连接到对应槽函数
act_sys_tray_min = new QAction(tr("最小化(&M)"),this);
connect(act_sys_tray_min,SIGNAL(triggered()),pWidget,SLOT( hide() ));

act_sys_tray_normal = new QAction(tr("还 原(&R)"),this);
connect(act_sys_tray_normal,SIGNAL(triggered()),pWidget,SLOT( showNormal()) );

act_sys_tray_exit = new QAction(tr("退出(&Q)"),this);
connect(act_sys_tray_exit,SIGNAL(triggered()),qApp,SLOT( quit()));

act_sys_tray_lang_ch = new QAction(tr("简体中文(&C)"),this);
// connect(act_sys_tray_lang_ch,SIGNAL(triggered()),this,SLOT(set_lang()));

act_sys_tray_lang_en = new QAction(tr("英语(&E)"),this);
acrLangGrp = new QActionGroup(this);
acrLangGrp->addAction(act_sys_tray_lang_ch);
acrLangGrp->addAction(act_sys_tray_lang_en);
connect(acrLangGrp,SIGNAL(triggered(QAction*)),this,SLOT( set_lang(QAction*)));

/*
act_sys_tray_min:创建一个动作项用于最小化窗口。当这个动作被触发时,它会调用父窗口的 hide() 槽函数,将窗口最小化。
act_sys_tray_normal:创建一个动作项用于还原窗口。当这个动作被触发时,它会调用父窗口的 showNormal() 槽函数,将窗口还原到正常状态。
act_sys_tray_exit:创建一个动作项用于退出应用程序。当这个动作被触发时,它会调用 qApp 的 quit() 槽函数,退出应用程序。
act_sys_tray_lang_ch 和 act_sys_tray_lang_en:分别创建两个动作项,用于切换语言设置。这两个动作项被添加到一个动作组 acrLangGrp 中,以便于同一时间只能选择其中的一个。连接的槽函数是 set_lang(QAction*),该槽函数根据用户选择的语言项进行相应的处理。
*/
}


//创建托盘菜单
void SystemTrayIcon::create_sysTrayMenu()
{
mSysTrayMenu = new QMenu(this);
//mSysTrayMenu = new QMenu((QWidget*)QApplication::desktop());

mSysTrayMenuLangSetting = new QMenu(tr("语言设置"), this);
mSysTrayMenuLangSetting->addAction(act_sys_tray_lang_ch);
mSysTrayMenuLangSetting->addAction(act_sys_tray_lang_en);
//添加语言动作项
mSysTrayMenu->addMenu(mSysTrayMenuLangSetting);

//新增菜单项---显示主界面
mSysTrayMenu->addAction(act_sys_tray_normal);
//新增菜单项---最小化
mSysTrayMenu->addAction(act_sys_tray_min);

//增加分隔符
mSysTrayMenu->addSeparator();

//新增菜单项---退出程序
mSysTrayMenu->addAction(act_sys_tray_exit);
//完成了系统托盘菜单的创建,并添加了显示主界面、最小化、退出程序等操作项,以及语言设置子菜单。


}


//创建系统托盘按钮
void SystemTrayIcon::create_sysTrayIcon()
{
if (!QSystemTrayIcon::isSystemTrayAvailable()) //判断系统是否支持系统托盘图标
{
return;
}

mSysTrayIcon = new QSystemTrayIcon(pWidget);
//系统托盘图标对象
mSysTrayIcon->setIcon(m_icon); //设置图标图片
mSysTrayIcon->setToolTip(m_strList.at(1)); //托盘时,鼠标放上去的提示信息
mSysTrayIcon->showMessage(m_strList.at(0), m_strList.at(1), QSystemTrayIcon::Information, 10000);
//显示消息


mSysTrayIcon->setContextMenu(mSysTrayMenu);//设置托盘上下文菜单

mSysTrayIcon->show();//在系统托盘显示此对象

//给QSystemTrayIcon对象mSysTrayIcon添加信号为activated(QSystemTrayIcon::ActivationReason)的槽函数
connect(mSysTrayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
this, SLOT(slot_sys_tray_iconActivated(QSystemTrayIcon::ActivationReason)));

}





//系统托盘触发槽函数
/*********************
enum QSystemTrayIcon::ActivationReason 表述托盘上图标的触发缘由
常量 值 描述
QSystemTrayIcon::Unknown 0 未知原因
QSystemTrayIcon::Context 1 请求系统托盘的上下文菜单
QSystemTrayIcon::DoubleClick 2 鼠标双击
QSystemTrayIcon::Trigger 3 鼠标单击
QSystemTrayIcon::MiddleClick 4 鼠标中间按键
**********************/

//不同的激活原因执行不同的操作
void SystemTrayIcon::slot_sys_tray_iconActivated(QSystemTrayIcon::ActivationReason reason)
{
switch (reason)
{
case QSystemTrayIcon::Unknown: //未知原因
break;
case QSystemTrayIcon::Context: //请求系统托盘的上下文菜单
break;
case QSystemTrayIcon::DoubleClick: //鼠标双击
pWidget->setWindowState(Qt::WindowActive);
pWidget->show();
//父窗口活动并显示
break;
case QSystemTrayIcon::Trigger: //鼠标单击

break;
case QSystemTrayIcon::MiddleClick: //鼠标中间按键
break;
default:
break;
}

}

//设置语言
void SystemTrayIcon::set_lang(QAction *act)
{
qDebug() << act->text();
//输选择动作项的文本
//根据语言选择对应的翻译文件
if(act == act_sys_tray_lang_ch)
{
qDebug() << "china ";
translator.load(":/cn.qm");
qApp->installTranslator(&translator);

}
if(act == act_sys_tray_lang_en)
{
qDebug() << "english ";
translator.load(":/en.qm");
qApp->installTranslator( &translator );
}
//上述就是
this->refresh();//刷新托盘相关文字
emit signal_lang_refresh();//发送刷新页面文字的信号
}

//各个菜单和子菜单的文字
void SystemTrayIcon::refresh()
{
this->act_sys_tray_exit->setText(tr("退出(&Q)"));
this->act_sys_tray_min->setText(tr("最小化(&M)"));
this->act_sys_tray_lang_ch->setText(tr("简体中文(&C)"));
this->act_sys_tray_lang_en->setText(tr("英语(&E)"));
this->act_sys_tray_normal->setText(tr("还 原(&R)"));
this->mSysTrayMenuLangSetting->setWindowTitle(tr("语言设置"));
qDebug() << "lllllllllllll" << this->mSysTrayMenuLangSetting->windowTitle();

}

systemtrayicon.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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#ifndef SYSTEMTRAYICON_H
#define SYSTEMTRAYICON_H

#include <QWidget>
#include <QSystemTrayIcon>
#include <QMenu>
#include <QAction>
#include <QTranslator>
#include <QActionGroup>

class SystemTrayIcon : public QWidget
{
Q_OBJECT
public:
//explicit SystemTrayIcon(QWidget *parent = 0);
explicit SystemTrayIcon(QStringList strList, QIcon icon, QWidget *parent = 0);

void create_sysTrayMenu(); //创建托盘菜单
void create_sysTrayIcon(); //创建托盘菜单的图标
void create_sysTrayMenuAct(); //创建点击托盘菜单项的act行为
void create_sysTrayMenuLangeSettingAct(); //创建点击托盘菜单项的act行为


signals:
void signal_lang_refresh();
//语言更改信号
public slots:

void slot_sys_tray_iconActivated(QSystemTrayIcon::ActivationReason reason);//系统托盘触发槽函数
void set_lang(QAction *act);
//设置应用程序语言槽函数

public:
QAction *actFixed; //固定位置
QActionGroup *acrLangGrp;
void refresh();
//接受语言更改信号并进行更改

private:

QWidget *pWidget; //父对象

QSystemTrayIcon *mSysTrayIcon; //系统托盘
QMenu *mSysTrayMenu; //系统托盘显示菜单
QMenu *mSysTrayMenuLangSetting; //系统托盘语言设置
QAction *act_sys_tray_min; //最小化
QAction *act_sys_tray_normal; //正常
QAction *act_sys_tray_exit; //退出
QAction *act_sys_tray_lang_ch; //china
QAction *act_sys_tray_lang_en; //english
QStringList m_strList; //托盘信息
QIcon m_icon; //托盘图标
QTranslator translator;
//翻译应用程序文本

};

#endif // SYSTEMTRAYICON_H

widget.cpp

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
#include "widget.h"
#include "ui_widget.h"
#include<QUdpSocket>
#include<QHostInfo>
#include<QMessageBox>
#include<QScrollBar>
#include<QDateTime>
#include<QNetworkInterface>
#include<QProcess>
#include"server.h"
#include"client.h"
//服务端和客户端
#include<QFileDialog>
//其他都是互相控制
Widget::Widget(QWidget *parent,QString usrname) :
QWidget(parent),
ui(new Ui::Widget)
{

ui->setupUi(this);
// this->setFixedSize(925,555);
//this->setStyleSheet("background-image: url(:/images/Color.png)");
ui->sendBtn->setShortcut(tr("Alt+S"));
//设置发送按钮快捷键(好东西)
uName=usrname;
//用户名
udpSocket=new QUdpSocket(this);
//绑定UDP套接字
port=23232;
//绑定到指定端口
udpSocket->bind(port,QUdpSocket::ShareAddress|QUdpSocket::ReuseAddressHint);
//将 UDP 套接字绑定到指定端口,并设置了选项 ShareAddress 和 ReuseAddressHint,表示允许地址共享和重用地址。
connect(udpSocket,SIGNAL(readyRead()),this,SLOT(processPendingDatagrams()));
//连接了 UDP 套接字的 readyRead() 信号到槽函数 processPendingDatagrams(),表示当有数据可读时会触发该槽函数。
//连接槽
sndMsg(UsrEnter);
srv=new Server(this);
//创建服务器
connect(srv,SIGNAL(sndFileName(QString)),this,SLOT(getFileName(QString)));
///连接了 srv 对象的 sndFileName() 信号到槽函数 getFileName(),用于接收到文件名时进行处理。
connect(ui->msgTxtEdit,SIGNAL(currentCharFormatChanged(QTextCharFormat)),this,SLOT(curFmtChanged(QTextCharFormat)));
//连接了消息编辑框的 currentCharFormatChanged() 信号到槽函数 curFmtChanged(),用于处理消息格式的改变。
}

Widget::~Widget()
{
delete ui;
}
//发送消息的函数,根据消息类型不同执行不同的操作,比如发送消息、用户进入消息等。
void Widget::sndMsg(MsgType type,QString srvaddr)
{
//定义了 sndMsg() 函数,接受两个参数:消息类型 type 和服务器地址 srvaddr。
QByteArray data;
QDataStream out(&data,QIODevice::WriteOnly);
//创建了一个 QByteArray 对象 data 用于存储消息数据,并创建了一个 QDataStream 对象 out 用于向 data 中写入数据。
QString address=getIP();
//调用 getIP() 函数获取本地 IP 地址,并将其保存在 address 变量中
out<<type<<getUsr();
//将消息类型和当前用户发送至数据流 out 中。
switch (type) {
case Msg:
if(ui->msgTxtEdit->toPlainText()=="")
{
QMessageBox::warning(0,tr("Warning"),("The conclusion can't be empty"),QMessageBox::Ok);
return;
}
out<<address<<getMsg();
ui->msgBrowser->verticalScrollBar()->setValue(ui->msgBrowser->verticalScrollBar()->maximum());
break;
case UsrEnter:
out<<address;
break;
case UsrLeft:
break;
case FileName:
{
int row=ui->usrTblWidget->currentRow();
QString clntaddr=ui->usrTblWidget->item(row,1)->text();
out<<address<<clntaddr<<fileName;
break;
}
case Refuse:
{
out<<srvaddr;
break;
}
}
/*
对于 Msg 类型的消息,如果消息编辑框中的内容为空,则弹出警告框提醒用户,并返回;否则将本地 IP 地址和消息内容写入数据流中,并滚动消息浏览器的垂直滚动条至最底部。
对于 UsrEnter 类型的消息,将本地 IP 地址写入数据流中。
对于 FileName 类型的消息,获取当前选中用户的 IP 地址,并将本地 IP 地址、客户端 IP 地址和文件名写入数据流中。
对于 Refuse 类型的消息,将服务器地址写入数据流中。
*/
udpSocket->writeDatagram(data,data.length(),QHostAddress::Broadcast,port);
//使用 UDP 套接字 udpSocket 发送数据流 data 到广播地址,并指定端口号为 port
}


//处理待处理的数据报的槽函数,根据数据报中的消息类型不同执行不同的操作。
//用于处理接收到的 UDP 数据报。
void Widget::processPendingDatagrams(){
//当 UDP 套接字有待处理的数据报时,进入循环。
while(udpSocket->hasPendingDatagrams()){

QByteArray datagram;
datagram.resize(udpSocket->pendingDatagramSize());
udpSocket->readDatagram(datagram.data(),datagram.size())
;
//创建一个 QByteArray 对象 datagram 用于存储接收到的数据报,并调整其大小以适应待处理的数据报,然后通过 readDatagram() 函数读取数据报的内容。
QDataStream in(&datagram,QIODevice::ReadOnly);
//读取数据
int MsgType;
in>>MsgType;
//读取从数据流中读取消息类型。
QString usrName,ipAddr,msg;
QString time=QDateTime::currentDateTime().toString("yyyy-MM-ddhh:mm:ss");
switch (MsgType)
{
//根据消息类型进行不同的处理。
case Msg:in>>usrName>>ipAddr>>msg;
//对于 Msg 类型的消息,从数据流中读取用户名、IP 地址和消息内容,并将用户名和时间戳添加到消息浏览器中,并显示消息内容。
ui->msgBrowser->setTextColor(Qt::blue);
ui->msgBrowser->setCurrentFont(QFont("Times New Roman",12));
ui->msgBrowser->append("["+usrName+"]"+time);
ui->msgBrowser->append(msg);
break;
case UsrEnter:
//对于 UsrEnter 类型的消息,从数据流中读取用户名和 IP 地址,并调用 usrEnter() 函数进行处理。
in>>usrName>>ipAddr;
usrEnter(usrName,ipAddr);
break;
case UsrLeft:
in>>usrName;
usrLeft(usrName,time);
break;
//对于 UsrLeft 类型的消息,从数据流中读取用户名,并调用 usrLeft() 函数进行处理。
case FileName:{
in>>usrName>>ipAddr;
QString clntAddr,fileName;
in>>clntAddr>>fileName;
hasPendingFile(usrName,ipAddr,clntAddr,fileName);
break;}
//对于 FileName 类型的消息,从数据流中读取用户名、IP 地址、客户端地址和文件名,并调用 hasPendingFile() 函数进行处理。
case Refuse:
in>>usrName;
QString srvAddr;
in>>srvAddr;
QString ipAddr=getIP();
if(ipAddr==srvAddr){
srv->refused();
}
break;
///对于 Refuse 类型的消息,从数据流中读取用户名和服务器地址,并检查本地 IP 地址是否与服务器地址匹配,如果匹配,则调用 srv->refused() 函数进行处理。
}

}

}
//处理用户进入消息的函数。
void Widget::usrEnter(QString usrname, QString ipaddr){
bool isEmpty=ui->usrTblWidget->findItems(usrname,Qt::MatchExactly).isEmpty();
//检查用户表格中是否已经存在该用户,使用 findItems() 函数在用户表格中查找指定的用户名称,如果找不到,则返回 true,表示用户表格中不存在该用户。
if(isEmpty){
//不存在
QTableWidgetItem *usr=new QTableWidgetItem(usrname);
QTableWidgetItem *ip=new QTableWidgetItem(ipaddr);
ui->usrTblWidget->insertRow(0);
ui->usrTblWidget->setItem(0,0,usr);
ui->usrTblWidget->setItem(0,1,ip);
ui->msgBrowser->setTextColor(Qt::gray);
ui->msgBrowser->setCurrentFont(QFont("Times New Roman",10));
ui->msgBrowser->append(tr("%1在线!").arg(usrname));
ui->usrNumLbl->setText(tr("在线人数:%1").arg(ui->usrTblWidget->rowCount()));
//创建新的表格项,分别用于存储用户名和 IP 地址,并将它们添加到用户表格中的第一行。
//设置消息浏览器的文本颜色和字体,然后在消息浏览器中添加一条用户上线的提示消息。
//更新在线用户数量标签,显示当前在线用户的数量。
sndMsg(UsrEnter);
//结束 if 条件语句。
}
}

//处理用户离开消息的函数。
void Widget::usrLeft(QString usrname, QString time)
//用户名和时间
{
int rowNum=ui->usrTblWidget->findItems(usrname,Qt::MatchExactly).first()->row();
//获取行号
ui->usrTblWidget->removeRow(rowNum);
//移除
ui->msgBrowser->setTextColor(Qt::gray);
ui->msgBrowser->setCurrentFont(QFont("Times New Roman",10));
ui->msgBrowser->append(tr("%1于%2离开").arg(usrname).arg(time));
ui->usrNumLbl->setText(tr("在线人数:%1").arg(ui->usrTblWidget->rowCount()));
//设置和显示
}

////获取本机ip
QString Widget::getIP(){
QList<QHostAddress>list=QNetworkInterface::allAddresses();
//获取本地主机所有地址列表
foreach (QHostAddress addr, list) {
if(addr.protocol()==QAbstractSocket::IPv4Protocol)
return addr.toString();
//遍历找到IPV4
}
return 0;
}
//用户名
QString Widget::getUsr(){
return uName;
}


//消息内容
QString Widget::getMsg(){
QString msg=ui->msgTxtEdit->toHtml();
ui->msgTxtEdit->clear();
ui->msgTxtEdit->setFocus();
return msg;
/*
从消息编辑框 msgTxtEdit 中获取用户输入的消息,并将其以 HTML 格式保存到 msg 变量中。

cpp
Copy code
ui->msgTxtEdit->clear();
清空消息编辑框,以便用户可以输入下一条消息。

cpp
Copy code
ui->msgTxtEdit->setFocus();
将焦点设置回消息编辑框,以便用户可以立即输入下一条消息。

cpp
Copy code
return msg;
返回获取到的消息。
*/
}

void Widget::on_sendTBtn_triggered(QAction *arg1){

}

//发送按钮时候的槽函数
void Widget::on_sendBtn_clicked()
{
sndMsg(Msg);
}

//获取文件名
void Widget::getFileName(QString name){

fileName=name;
sndMsg(FileName);
}
//



//发送按钮点击
void Widget::on_sendTBtn_clicked()
{
if(ui->usrTblWidget->selectedItems().isEmpty())
{
QMessageBox::warning(0,tr("选择用户"),tr("请先选择目标用户!!"),QMessageBox::Ok);
return;
//检查用户是否已选择了目标用户。如果用户没有选择目标用户,就会显示一个警告对话框,并提示用户选择目标用户。
}
srv->show();
srv->initSrv();
//显示服务器窗口。,初始话
}



//还有待处理文件的槽函数
void Widget::hasPendingFile(QString usrname, QString srvaddr, QString clntaddr, QString filename){
//用于处理接收到待处理文件的情况,参数包括发送文件的用户名称 usrname,服务器地址 srvaddr,客户端地址 clntaddr 和文件名 filename。


QString ipAddr=getIP();
//本机地址
if(ipAddr==clntaddr)
//检查本机的 IP 地址是否与客户端地址相同,即判断是否是本机接收文件。
{
int btn=QMessageBox::information(this,tr("接收文件"),tr("来自%1(%2)的文件:%3,是否接收?").arg(usrname).arg(srvaddr).arg(filename),QMessageBox::Yes,QMessageBox::No);
if(btn==QMessageBox::Yes){
QString name=QFileDialog::getSaveFileName(0,tr("保存文件"),filename);
//打开文件对话框,让用户选择文件保存的位置和文件名,并将结果存储在 name 变量中。
if(!name.isEmpty()){

Client *clnt=new Client(this);
clnt->setFileName(name);
clnt->setHostAddr(QHostAddress(srvaddr));
clnt->show();
//创建一个 Client 对象,用于接收文件。
}
}else{
sndMsg(Refuse,srvaddr);
}
}

}

void Widget::on_fontCbx_currentFontChanged(const QFont &f)
{
ui->msgTxtEdit->setCurrentFont(f);
ui->msgTxtEdit->setFocus();
}
//处理字体
//当字体下拉框的当前字体改变时,会触发这个槽函数。它将当前字体 f 设置为消息编辑框 ui->msgTxtEdit 的字体,并将焦点设置回消息编辑框,以便用户继续输入或编辑。
void Widget::on_sizeCbx_currentIndexChanged(const QString &arg1)
{
ui->msgTxtEdit->setFontPointSize(arg1.toDouble());
ui->msgTxtEdit->setFocus();
}
//当字号下拉框的当前选项改变时,会触发这个槽函数。它将当前选中的字号 arg1(以字符串形式)转换为 double 类型,并将其设置为消息编辑框 ui->msgTxtEdit 的字号。然后,它将焦点设置回消息编辑框,以便用户继续输入或编辑。

//当粗体按钮被点击时,会触发这个槽函数。如果按钮处于选中状态(即 checked 为 true),则将消息编辑框 ui->msgTxtEdit 的字体加粗。否则,将字体设置为普通(即正常)字体。然后,将焦点设置回消息编辑框,以便用户继续输入或编辑。
void Widget::on_boldTBtn_clicked(bool checked)
{
if(checked)ui->msgTxtEdit->setFontWeight(QFont::Bold);
else ui->msgTxtEdit->setFontWeight(QFont::Normal);
ui->msgTxtEdit->setFocus();
}

//当斜体按钮被点击时,会触发这个槽函数。如果按钮处于选中状态(即 checked 为 true),则将消息编辑框 ui->msgTxtEdit 的字体设置为斜体。否则,取消斜体设置。然后,将焦点设置回消息编辑框,以便用户继续输入或编辑。
void Widget::on_italicTBtn_clicked(bool checked)
{
ui->msgTxtEdit->setFontItalic(checked);
ui->msgTxtEdit->setFocus();
}

void Widget::on_underlineTBtn_clicked(bool checked)
{
ui->msgTxtEdit->setFontUnderline(checked);
ui->msgTxtEdit->setFocus();
}
//当下划线按钮被点击时,会触发这个槽函数。如果按钮处于选中状态(即 checked 为 true),则将消息编辑框 ui->msgTxtEdit 的字体设置为带下划线。否则,取消下划线设置。然后,将焦点设置回消息编辑框,以便用户继续输入或编辑。

void Widget::on_colorTBtn_clicked()
{
color=QColorDialog::getColor(color,this);
//这一行打开了颜色对话框,并等待用户选择颜色。选择的颜色会存储在 color 变量中。
if(color.isValid()){
ui->msgTxtEdit->setTextColor(color);
ui->msgTxtEdit->setFocus();
//这里检查用户是否选择了有效的颜色。如果用户确实选择了颜色,则执行以下操作。
//将消息编辑框 ui->msgTxtEdit 的文本颜色设置为用户选择的颜色。
//最后,将焦点设置回消息编辑框,以便用户继续输入或编辑文本。
}
}
//

//当前格式更改时的槽函数,用于更新格式工具栏的状态。
void Widget::curFmtChanged(const QTextCharFormat &fmt)
//这个函数接收一个 QTextCharFormat 类型的参数 fmt,该参数表示当前文本的字符格式。
{
ui->fontCbx->setCurrentFont(fmt.font());
if(fmt.fontPointSize()<8){
ui->sizeCbx->setCurrentIndex(4);

}else{
ui->sizeCbx->setCurrentIndex(ui->sizeCbx->findText(QString::number(fmt.fontPointSize())));
}
ui->boldTBtn->setChecked(fmt.font().bold());
ui->italicTBtn->setChecked(fmt.font().italic());
ui->underlineTBtn->setChecked(fmt.font().underline());
//根据当前格式中的字体是否粗体、斜体和下划线,设置格式工具栏中的粗体、斜体和下划线按钮的选中状态。
color=fmt.foreground().color();
}




//保存按钮点击时的槽函数,用于保存聊天记录。
void Widget::on_saveTBtn_clicked()
{
if (ui->msgBrowser->document()->isEmpty()){
QMessageBox::warning(0,tr("警告"),tr("聊天记录为空,无法保存!"),QMessageBox::Ok);
//如果聊天记录文档为空,就会弹出一个警告对话框,提示用户聊天记录为空,无法保存。
}else {
QString fname=QFileDialog::getSaveFileName(this,tr("保存聊天记录"),tr("聊天记录"),tr("文本(*.txt);;所有文件(*.*)"));
//如果聊天记录不为空,就会弹出一个文件保存对话框,提示用户选择保存的文件名和路径,并指定保存文件的格式。
if(!fname.isEmpty())saveFile(fname);
//如果用户选择了保存的文件名和路径(即 fname 不为空),就会调用 saveFile 函数来保存聊天记录到指定的文件中。
}
}

//保存文件的函数。
bool Widget::saveFile(const QString &filename){
QFile file(filename);
if(!file.open(QFile::WriteOnly|QFile::Text)){
QMessageBox::warning(this,tr("保存文件"),tr("无法保存文件%1:\n%2").arg(filename).arg(file.errorString()));

return false;

}
QTextStream out(&file);
out<<ui->msgBrowser->toPlainText();
//将聊天记录文本框中的内容写入文件。
return true;

}



//窗口关闭事件的处理函数。
void Widget::closeEvent(QCloseEvent *event){
sndMsg(UsrLeft);
QWidget::closeEvent(event);

}
//这个函数重写了 QWidget 类中的 closeEvent 事件处理函数,用于处理窗口关闭事件。
//在窗口关闭之前,通过调用 sndMsg 函数发送一个用户离开的消息,通知其他用户该用户已离线
//调用了 QWidget 类中的 closeEvent 函数,执行默认的窗口关闭事件处理,确保窗口可以正常关闭。





void Widget::on_clearTBtn_clicked()
{
ui->msgBrowser->clear();
}
//清除按钮点击时的槽函数,用于清除聊天记录。
void Widget::on_exitBtn_clicked()
{
close();
}
//,用于关闭窗口。

widget.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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include<QColorDialog>
class QUdpSocket;
class Server;
#include<QTextCharFormat>
namespace Ui {
class Widget;
}
enum MsgType{Msg,UsrEnter,UsrLeft,FileName,Refuse};//使用枚举变量区分不同的广播
class Widget : public QWidget
{
Q_OBJECT

public:
explicit Widget(QWidget *parent,QString usrname);
~Widget();

protected:
void usrEnter(QString usrname,QString ipaddr);//处理新用户加入
void usrLeft(QString usrname,QString time);//处理用户离开
void sndMsg(MsgType type,QString srvaddr="");//广播UDP地址
void hasPendingFile(QString usrname,QString srvaddr,QString clntaddr,QString filename);//用于收到文件名UDP信息时判断是否接收该文件
QString getIP();
QString getUsr();
QString getMsg();
bool saveFile(const QString &filename);
void closeEvent(QCloseEvent *event);
private:
Ui::Widget *ui;
QUdpSocket *udpSocket;
qint16 port;
QString uName;
QString fileName;
Server *srv;
QColor color;
private slots:
void processPendingDatagrams();//接收UDP消息
void on_sendBtn_clicked();
void getFileName(QString);//用来获取服务器类sndFileName()信号发送过来的文件名
void on_sendTBtn_triggered(QAction *arg1);
void on_sendTBtn_clicked();
void on_fontCbx_currentFontChanged(const QFont &f);
void on_sizeCbx_currentIndexChanged(const QString &arg1);
void on_boldTBtn_clicked(bool checked);
void on_italicTBtn_clicked(bool checked);
void on_underlineTBtn_clicked(bool checked);
void on_colorTBtn_clicked();
void curFmtChanged(const QTextCharFormat &fmt);
void on_saveTBtn_clicked();
void on_clearTBtn_clicked();
void on_exitBtn_clicked();
};

#endif // WIDGET_H