一、实验目的
设计一款携带多种功能的聊天软件,不仅可以聊天,也能修改字体,计时,绘画等等。
二、实验内容:
1)ui设计(满分15分)
2)画图、timer、多线程(满分20分)
3)数据库操作(满分15分)
4)TCP网络通信(满分40分) (客户端15分,多线程服务器25分)
三、实验环境
Qt Creator 4.2.1 (Community)
头像是在网络上找的,其余图标都是自己绘制的,但由于设计时间过早,在后面完成代码的时候发现没有信息的数据库,所以这里的了解成员要改为查找地址。
图表 2设计界面2
这是设计的第二个界面,但是本来想要管理员链接服务端,但是因为项目的问题一直链接不上,排查问题找了很久都没有找到问题,所以这里设计的按钮其实是无法跳转的,所以就改为第一次的设计。
图表 3绘画界面
图表 4客户端界面
图表 5服务端界面
2)画图、timer、多线程
图表 6多线程聊天
可进行多人聊天,只要打开窗口就能够聊天,但如果其他人要接收的话,也需要打开聊天框,不然就不能够接收到信息。
图表 7保存聊天记录
这里为了合理化也设计了字体样式的更改,保存,清空等等,但是颜色字号因为不太理解所以没有做成功,但加粗等成功了。
图表 8保存的聊天记录
图表 9修改字体样式
图表 10警告
为了不出现重复聊天框的BUG,所以要设置一个警告。
图表 11绘图界面
这里只绘制了一个圈和字体,但也可以实现其他的绘画,比如画直线等等,但需要在代码中加入,这里没有加是因为我觉得这样设计会比较好看。
图表 12绘画界面弹出
只要点击来画画吧,就会弹出绘画的这个窗口,这里本来想设计成后面的那些窗口都消失,但发现这样不太明显,就将那条代码删除了。
图表 13计时器
点击计时器按钮,就会跳转到计时器的这个页面来,点击开始就能够开始计时,停止就停止计时,重置就是重新开始。
图表 14点击重置后
点击重置后,时间就归零了。
3)数据库操作
图表 15数据库登录
这里是老师上课讲解的内容,这里进行了一个结合,只要点击右下方的按钮,就能够弹出这个页面,也就是“小蜜蜂数据”,接着就能够链接数据库查询信息了。
图表 16查询信息页面
因为没有数据库,所以这里用的是老师之前教课时所使用的,自己也尝试建立数据库,发现了很多问题,诸如数据的问题,要创建ID类但是一直无法设置好,导致程序出现故障等等。
图表 17重新建立数据库界面
因为原本数据库不太适合我的聊天软件,于是进行了更改,这样就能够查询好友的信息,比较匹配我的聊天软件。
4)TCP网络通信
图表 18同时打开服务端和客户端
能够运行服务端和客户端,界面上的LOGO是我设计的小蜜蜂。
图表 19多线程链接
两个客户端能够和服务端链接并且发送消息。
图表 20单线程链接
以下是主代码展示:【代码不全】
(1)Widegt.cpp
#include "widget.h"
#include "ui_widget.h"
#include"draw.h"
#include "mytime.h"
#include "mydb.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
Widget::Widget(QWidget *parent ,QString name) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
myname=name;
this->ppage2=new draw;//实例化绘画页面S
connect(ui->drawBtn,&QPushButton::clicked,[=](){
//this->hide();
this->ppage2->show();//显示
});
this->ppage3=new mytime;
connect(ui->timerBtn,&QPushButton::clicked,[=](){
this->ppage3->show();
});
this->ppage4=new mydb;
connect(ui->dataBtn,&QPushButton::clicked,[=](){
this->ppage4->show();
});
this->port=0032;
this->udpSocket=new QUdpSocket(this);
udpSocket->bind(this->port,QUdpSocket::ShareAddress |QUdpSocket::ReuseAddressHint);
connect(udpSocket,&QUdpSocket::readyRead,this,&Widget::ReceiveMessage);
connect(ui->sendBtn,&QPushButton::clicked,[=](){
sndMsg(Msg);
});
}
void Widget::sndMsg(Widget::Msgtype type)
{
QByteArray array;
QDataStream stream(&array,QIODevice::WriteOnly);
stream<<type<<this->getName();
switch(type){
case Msg:
if(ui->msgTxtEdit->toPlainText()=="")
{
QMessageBox::warning(this,"警告","发送的内容不能为空!");
return;
}
stream<<this->getMsg();
break;
case UserEnter:
break;
case UserLeft:
break;
}
udpSocket->writeDatagram(array.data(),array.size(),QHostAddress::Broadcast,this->port);
}
QString Widget::getName()
{
return this->myname;
}
QString Widget::getMsg()
{
QString msg=ui->msgTxtEdit->toHtml();
ui->msgTxtEdit->clear();
ui->msgTxtEdit->setFocus();
return msg;
}
void Widget::ReceiveMessage()
{
qint64 size=udpSocket->pendingDatagramSize();
int mysize=static_cast<int>(size);
QByteArray *array=new QByteArray(mysize,0);
udpSocket->readDatagram((*array).data(),size);
QDataStream stream(array,QIODevice::ReadOnly);
int mytype;
QString name,msg;//聊天内容 用户名
QString time=QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");
stream>>mytype;
switch(mytype){
case Msg:
stream>>name>>msg;
ui->msgBrowser->setTextColor(QColor(Qt::blue));
ui->msgBrowser->setCurrentFont(QFont("Times New Roman",10));
ui->msgBrowser->append(("["+name+"]"+time));
ui->msgBrowser->append(msg);
break;
case UserEnter:
stream>>name;
// userEnter(name);
break;
case UserLeft:
stream>>name;
// userLeft(name,time);
break;
}
//字体改变
connect(ui->fontCbx,&QFontComboBox::currentFontChanged,[=](const QFont &font){
ui->msgTxtEdit->setFontFamily(font.toString());
ui->msgTxtEdit->setFocus();
});
//字体大小
void (QComboBox:: *sizebtn)(const QString &text)=&QComboBox::currentTextChanged;
connect(ui->sizeCbx,sizebtn,[=](const QString &text){
ui->msgTxtEdit->setFontPointSize(text.toDouble());
ui->msgTxtEdit->setFocus();
});
//加粗
connect(ui->boldTBtn,&QToolButton::clicked,this,[=](bool checked){
if(checked)
{
ui->msgTxtEdit->setFontWeight(QFont::Bold);
}
else{
ui->msgTxtEdit->setFontWeight(QFont::Normal);
}
});
//倾斜
connect(ui->italicTbtn,&QToolButton::clicked,this,[=](bool checked){
ui->msgTxtEdit->setFontItalic(checked);
ui->msgTxtEdit->setFocus();
});
//下划线
connect(ui->underlineTBtn,&QToolButton::clicked,this,[=](bool checked){
ui->msgTxtEdit->setFontUnderline(checked);
ui->msgTxtEdit->setFocus();
});
//清除
connect(ui->clearTBtn,&QToolButton::clicked,[=](){
ui->msgBrowser->clear();
});
//颜色
connect(ui->colorTBtn,&QToolButton::clicked,this,[=](){
QColor color=QColorDialog::getColor(color,this);
ui->msgBrowser->clear();
});
//保存
connect(ui->saveTBtn,&QToolButton::clicked,[=](){
if(ui->msgBrowser->document()->isEmpty())
{
QMessageBox::warning(this,"警告","保存内容不能为空!");
return;
}
else{
QString filename=QFileDialog::getSaveFileName(this,"保存聊天记录","聊天记录","(*.txt");
if(!filename.isEmpty())
{
QFile file(filename);
file.open(QIODevice::WriteOnly |QFile::Text);
QTextStream stream(&file);
stream<<ui->msgBrowser->toPlainText();
file.close();
}
}
});
}
void Widget::closeEvent(QCloseEvent *)
{
emit this->closeWidget();
}
Widget::~Widget()
{
delete ui;
}
(2)Wedget.h
#ifndef WIDGET_H
#define WIDGET_H
#include "draw.h"
#include
#include
#include "mytime.h"
#include "mydb.h"
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
enum Msgtype{Msg,UserEnter,UserLeft};
explicit Widget(QWidget *parent, QString name);
void sndMsg(Msgtype type);
QString getName();
QString getMsg();
QString getIcon();
void userEnter(QString username);
void userLeft(QString username,QString time);
void ReceiveMessage();
~Widget();
draw *ppage2=NULL;//用来保存绘画界面的实例化对象地址
mytime *ppage3=NULL;//计时器
mydb *ppage4=NULL;//数据库
signals:
void closeWidget();
void showmain();
private:
Ui::Widget *ui;
quint16 port;
QString myname;
QUdpSocket *udpSocket;
public:
void closeEvent(QCloseEvent *);
};
#endif // WIDGET_H
(3)denglu.cpp
#include "denglu.h"
#include "ui_denglu.h"
#include
#include
#include
#include
#include
#include
denglu::denglu(QWidget *parent) :
QDialog(parent),
ui(new Ui::denglu)
{
ui->setupUi(this);
//图标
this->setWindowIcon(QIcon(":/image/bee.PNG"));
//名称
this->setWindowTitle("小蜜蜂聊天");
QList<QString> namelist;
namelist<<"喝饮料的小熊猫"<<"今天很开心"<<"保佑及格!"<<"天选锦鲤在哪里"<<"好好学习天天向上";
QStringList iconNameList;
iconNameList<<"1"<<"2"<<"3"<<"4"<<"5";
QVector<QToolButton *> vector;
for(int i=0;i<5;i++)
{
QToolButton *btn=new QToolButton(this);
//头像
btn->setIcon(QPixmap(QString(":/image/%1.png").arg(iconNameList[i])));
btn->setIconSize(QPixmap((QString(":/image/%1.png").arg(iconNameList[i]))).size());
btn->setText(QString("%1").arg(namelist[i]));
btn->setAutoRaise(true);
btn->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
ui->vlayout_2->addWidget(btn);
vector.push_back(btn);
IsShow.push_back(false);
}
for(int i=0;i<5;i++)
{
connect(vector[i],&QToolButton::clicked,[=](){
if(IsShow[i])
{
QMessageBox::warning(this,"警告","已有相关聊天框,请勿重复点击!");
return;
}
IsShow[i]=true;
Widget *widget=new Widget(nullptr,vector[i]->text());
widget->setWindowIcon(vector[i]->icon());
widget->setWindowTitle(vector[i]->text());
widget->show();
connect(widget,&Widget::closeWidget,this,[=](){
IsShow[i]=false;
});
});
}
}
denglu::~denglu()
{
delete ui;
}
(4)denglu.h
#ifndef DENGLU_H
#define DENGLU_H
#include
namespace Ui {
class denglu;
}
class denglu : public QDialog
{
Q_OBJECT
public:
explicit denglu(QWidget *parent = 0);
~denglu();
private:
Ui::denglu *ui;
QVector<bool> IsShow;
};
#endif // DENGLU_H
(5)draw.cpp
#include "draw.h"
#include "ui_draw.h"
#include
#include"qpainter.h"
draw::draw(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::draw)
{
ui->setupUi(this);
this->setWindowIcon(QIcon(":/image/bee.PNG"));
this->setWindowTitle("小蜜蜂绘画");
}
draw::~draw()
{
delete ui;
}
void draw::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event);
QPainter painter(this);
// 设置画笔颜色
painter.setPen(QColor(0, 160, 230));
// 设置字体:微软雅黑、点大小50、斜体
QFont font;
font.setFamily("Microsoft YaHei");
font.setPointSize(50);
font.setItalic(true);
painter.setFont(font);
// 绘制文本
painter.drawText(rect(), Qt::AlignCenter, "小蜜蜂");
Q_UNUSED(event);
painter.begin(this);
QRectF rectangle(150.0, 100.0, 280.0, 240.0);//如果是一个正方形那就是一个圆
painter.drawEllipse(rectangle);
painter.end();
}
(6)mytime.cpp
#include "mytime.h"
#include "ui_mytime.h"
#include
#include
#include
mytime::mytime(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::mytime)
{
ui->setupUi(this);
this->setWindowIcon(QIcon(":/image/bee.PNG"));
this->setWindowTitle("小蜜蜂计时器");
time.setHMS(0,0,0,0);//设置初始值
timer = new QTimer(this);//创建一个定时器
connect(timer, SIGNAL(timeout()), this, SLOT(update()));
}
mytime::~mytime()
{
delete ui;
}
void mytime::update()
{
static quint32 time_out=0;
time_out++;
time=time.addSecs(1);
ui->label->setText(time.toString("hh:mm:ss"));
}
void mytime::on_startBtn_clicked()
{
timer->start(1000);
}
void mytime::on_stopBtn_clicked()
{
timer->stop();
}
void mytime::on_reBtn_clicked()
{
timer->stop();
time.setHMS(0,0,0,0);
ui->label->setText(time.toString("hh:mm:ss"));
}
(7)mserver.cpp
#include "mserver.h"
#include "ui_mserver.h"
mServer::mServer(QWidget *parent) :
QWidget(parent),
ui(new Ui::mServer)
{
ui->setupUi(this);
this->setWindowIcon(QIcon(":/image/bee.PNG"));
this->setWindowTitle("小蜜蜂服务端");
list_sock.clear();
mserver=new QTcpServer();
connect(mserver,SIGNAL(newConnection()),this,SLOT(acceptListen()));
ui->tabClient->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
mtype=msize=0;
mdata.clear();
}
mServer::~mServer()
{
delete ui;
}
void mServer::sendData()
{
int rows=ui->tabClient->rowCount();
if(rows<1) return;
int row=ui->tabClient->currentIndex().row();
list_sock[row]->write(mdata);
mdata.clear();
}
void mServer::processData(int id)
{
}
void mServer::acceptListen()
{
QTcpSocket *sock=mserver->nextPendingConnection();
list_sock.append(sock);
connect(sock,SIGNAL(readyRead()),this,SLOT(recvData()));
int rows=ui->tabClient->rowCount();
ui->tabClient->insertRow(rows);
ui->tabClient->setItem(rows,0, new QTableWidgetItem(sock->peerAddress().toString()));
ui->tabClient->setItem(rows,1, new QTableWidgetItem(QString::number(sock->peerPort())));
QString str=QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");
ui->tabClient->setItem(rows,2, new QTableWidgetItem(str));
}
void mServer::recvData()
{
QTcpSocket *sock = (QTcpSocket*)sender();
for(int i=0;i<list_sock.length();i++)
{
if(sock == list_sock[i])
{
QString str=sock->peerAddress().toString()+"---"+QString::number(sock->peerPort())+":";
QString str1 = QString::fromLocal8Bit(sock->readAll());
ui->textEdit->append(str);
ui->textEdit->append(str1);
}
}
}
void mServer::on_btn_listen_clicked()
{
if(!mserver->listen(QHostAddress::Any,22233))
{
qDebug()<<mserver->errorString();//>>log.txt
return;
}
}
void mServer::on_btn_discon_clicked()
{
mserver->close();
for(int i=0;i<list_sock.length();i++)
{
list_sock[i]->abort(); //disconnect
}
list_sock.clear();
exit(0);
}
void mServer::on_btn_sendMsg_clicked()
{
QDataStream out(&mdata,QIODevice::WriteOnly); //memcpy
out.setVersion(QDataStream::Qt_5_8);
mtype=101; //101 102 103 201 202
QString str=ui->text_send->text();
QByteArray ba=str.toLocal8Bit();
msize=ba.length();
out<<mtype; out<<msize;
mdata.append(ba);
ba.clear();
sendData();
}
void mServer::on_btn_sendFile_clicked()
{
QString fpath=QFileDialog::getOpenFileName();
if(fpath.isEmpty()) return;
mtype= (fpath.endsWith("jpg"))?103:102;
QFile mfile(fpath);
QByteArray data;
if(!mfile.isOpen())
{ mfile.open(QIODevice::ReadOnly);
data = mfile.readAll();
}
mfile.close();
msize=data.length();
mdata.clear();
QDataStream out(&mdata,QIODevice::WriteOnly); //memcpy
out.setVersion(QDataStream::Qt_5_8);
out<<mtype; out<<msize;
mdata.append(data);
qDebug()<<mtype<<msize;
sendData();
}
(8)mclient.cpp
#include "mclient.h"
#include "ui_mclient.h"
mclient::mclient(QWidget *parent) :
QWidget(parent),
ui(new Ui::mclient)
{
ui->setupUi(this);
this->setWindowIcon(QIcon(":/image/bee.PNG"));
this->setWindowTitle("小蜜蜂客户端");
sock=new QTcpSocket();
connect(sock,SIGNAL(readyRead()),this,SLOT(recvData()));
size=recvsize=totalsize=mtype=0;
send_data.clear();
devid=locid=0;
list_random.clear();
}
mclient::~mclient()
{
delete ui;
}
void mclient::processData()
{
if(mtype == 101)
{
ui->text_recv->clear();
ui->text_recv->append(QString::fromLocal8Bit(mdata));
}
if(mtype == 102)
{
ui->text_recv->clear();
ui->text_recv->append(QString::fromLocal8Bit(mdata));
}
if(mtype == 103)
{
QBuffer buffer(&mdata);
buffer.open( QIODevice::ReadOnly);
QImageReader reader(&buffer, "jpg");
QImage image = reader.read();
QImage img=image.scaled(ui->labImg->rect().width(),ui->labImg->rect().height(),Qt::IgnoreAspectRatio);
ui->labImg->clear();
ui->labImg->setPixmap(QPixmap::fromImage(img));
}
}
void mclient::recvData()
{
if(sock->bytesAvailable()<8) return; //type size
if(totalsize < 1)
{
QDataStream in(sock);
in.setVersion(QDataStream::Qt_5_8);
in>>mtype; in>>totalsize; mdata.clear(); //cont 25k
qDebug()<<mtype;
}
if(recvsize<totalsize)
{
quint32 len=(quint32)sock->bytesAvailable();
recvsize += len;
mdata.append(sock->readAll());
if(recvsize == totalsize)
{
processData();
}
}
}
void mclient::on_btn_con_clicked()
{
sock->connectToHost(ui->textIP->text(),(quint16)ui->textPort->text().toUInt());
}
void mclient::on_btn_send_clicked()
{
QDataStream out(&send_data,QIODevice::WriteOnly); //memcpy
out.setVersion(QDataStream::Qt_5_8);
// mtype=201; //101 102 103 201 202
QString str=ui->lineEdit->text();
QByteArray ba=str.toLocal8Bit();
// size=ba.length();
// out<
send_data.clear();
send_data.append(ba);
ba.clear();
sock->write(send_data);
send_data.clear();
}
void mclient::on_btn_discon_clicked()
{
sock->abort();
exit(0);
}
void mclient::on_btn_gendata_clicked()
{
devid=(quint8)(ui->text_devid->text().toInt());
locid=(quint8)(ui->text_locid->text().toInt());
float a=1.0; float b=100.0; mtype=size=0;
for(int i=0;i<7;i++)
{
QTime t= QTime::currentTime();
qsrand(t.msec()+t.second()*100);
list_random.append(qrand());
QThread::msleep(30);
}
float v1=(float)((450+list_random[0]%100)*a);
float v2=(float)((20+list_random[1]%50)*a);
float v3=(float)((100+list_random[2]%400)*a);
float v4=(float)((5+list_random[3]%25)*a);
float v5=(float)((5+list_random[4]%20)*a);
float v6=(float)((26+list_random[5]%7)*a);
float v7=(float)((85+list_random[6]%7)/b);
send_data.clear();
mtype=222;
QByteArray ba; ba.clear();
QDataStream out1(&ba,QIODevice::WriteOnly);
out1.setVersion(QDataStream::Qt_5_8);
out1<<devid<<locid<<v1<<v2<<v3<<v4<<v5<<v6<<v7;
size=(quint32)ba.length();
QDataStream out(&send_data,QIODevice::WriteOnly);
out1.setVersion(QDataStream::Qt_5_8);
out<<mtype<<size;
send_data.append(ba);
sock->write(send_data);
// qDebug()<
send_data.clear();
}
五、实验总结
在本次实践学习中,我运用上课学习到的知识进行了自己的设计制作,会发现当跟着老师的时候会觉得很轻松的事情,自己上手了却变得困难了,比如多线程这一块内容,跟着老师走的时候就会知道下一步应该走向哪里,但是自己打开的时候却经常报错,而且因为地址的问题要找很多的资料,反复去回忆老师所讲过的内容,因为要复制一些程序到源地址中,而这几个程序我印象不深,所以也导致了在这个问题上面浪费太多时间,这也让我决定以后类似于这样的知识点应该记录到笔记中去。
还有就是手动建槽的时候也要注意规范,之前因为想测试某个功能,忘记在头文件中加入槽,等反应过来之后再去添加槽,却发现还是运行不了,代码也找了很多参考,但是都没有能够找出问题,所以这一个内容我全部删除重新再打一次代码,而后就运行成功了,所以我也认识到规范性也是很重要的。
在最初的UI设计中我是参考别人的设计,和同学一起讨论设计出来的,但发现功能太少了,远远不符合要求,所以再翻找以前的学习代码去找该怎么实现我想要的功能,然后再进行设计,所以在最初设计的基础上做了很多变动,比如因为没有数据库,所以和我最先设想的查找好友资料也无法做出来,只能参考原来上课时候的学习代码进行制作,还有原本想要管理员按钮连接服务端进行跳转,却发现服务端需要单独开设一个项目,那如何把项目放在另一个项目下面我也是找了很多资料,但也一直无法实现,这也是我的一个遗憾,就是无法在一个项目中同时做到多个功能,只能在聊天的基础上搭载绘画,计时器,天气数据查询等的功能。
最后就是代码,其实我参考学习了很多资料,包括之前上课所讲的代码,我都有做参考,在这个过程中也重新学到了很多知识,也算是查缺补漏了,刚开始觉得这个作业很难,但后来一步一步一个一个功能慢慢实现之后,就会发现其实还是能够完成的,每次遇到难题我都会去请教舍友或者网上搜索资料,一步一步试错也让我曾经很崩溃,有时候一整天都在完成一个功能,虽然最后结果出来之后不是很惊艳,但是我觉得我已经做到了我的最好,也学习到了很多!
能做出这个软件,也很感谢:
B站up主:性感的大飞侠-【QT实现群聊聊天系统-哔哩哔哩】 https://b23.tv/yrhCLWL
唔国旗u【04_QT_页面切换(两个ui界面)-哔哩哔哩】 https://b23.tv/wEWbfqW
以上指导很有用!
实验结果存在较多BUG,很不完善!欢迎讨论!