运行效果:
完整代码:https://github.com/lzglocal/UDP_CHAT.git 目录设计:
-
创建项目 -
对话框列表 1. Toolbox 群成员 2. 内部 做出一个垂直布局 3. 添加按钮
- 图标
- 文字
- 图标大小
- 风格
- 文字图标都显示
- 按钮保存到 QVector容器中
-
界面布局 -
点击按钮弹出窗口 1. 点击按钮创建窗口
- Widget * widget = new Widget(0,vToolBtn[i]->text());
- 修改Widgeti聊天窗口 构造函数 为两个参数
- 设置聊天窗口的图标、标题
- show方法显示窗口
-
解决多次点击弹出同个窗口bug
- 加入bool的isShow标示
- 判断标示 如果为true就不要多次打开
- 关闭聊天窗口时发生 自定义关闭信号
- 监听关闭信号 更新isShow标示
-
udp通信
- UdpSocket .pro network
- new 出套接字
- 绑定端口
- 书写报文 writeDatagram
- ReadyRead 监听信号
- 读取报文
- 报文长度 qint64 size = udp->pendingDatagramSize();
- 同步聊天记录
-
核心聊天功能
- 通信套接字
- bind ( 端口, 共享地址 | 断线重连)
- 获取用户名
- sndMsg(枚举(普通聊天,用户进入、离开))
- 发送3段数据 类型 、 用户名、 具体内容
- 书写报文 广播发送
- 接受 利用数据流 做分段
-
新用户进入
- 提供处理新用户进入函数
- 更新 右侧TableWidget
- 更新聊天记录
- 更新在线用户人数
-
用户离开
- 提供用户处理函数
- 更新右侧tableWidget
- 更新聊天记录
- 更新在线用户人数
- 在closeEvent事件中 发送用户离开消息
- 端口套接字
- 离开按钮处理
-
辅助功能
- 字体设置
- 字号设置
- 加粗
- 倾斜
- 下划线
- 颜色
- 清空聊天记录
- 保存聊天记录(QIODevice::Text)
-
快捷键
1. 创建新项目
第一步打开Qt Creator,点击新建NewProject Application –> Qt Widgets Application -> choose 创建项目名称例如: MyselfQQ,路径自己选择,注意不要有空格和中文
选择套件,点击下一步 选择基类QWidget,然后点击下一步 然后点击完成,至此项目创建完毕。 运行测试页面 -空页面
2. 创建对话列表
2.1 添加新文件,对话列表类DialogList
右击项目名,在弹出的快捷菜单中选择“添加新文件…”菜单项,在弹出的对话框中选择“Qt”选项。选择Qt设计师界面类,单击“Choose…”按钮; 界面模板选择Widget,点击下一步 类名填写 DialogList (可以起其他名称)点击下一步 在汇总中单击“完成”按钮,系统会为我们添加“dialoglist.h”头文件和“dialoglist.cpp”源文件以及dialoglist.ui设计文件
2.2 设计对话框列表UI
按照下图所示,设计对话框的UI,该窗口的大小为 250*700,其中的主要控件是QToolBox,修改该控件的currentItemText为“群成员”, QToolBox默认生成的第二页删除掉 在群成员里我们放入一个Widget做布局操作,可以先利用一些测试控件放入到其中,然后做垂直布局,然后把测试的控件删除掉,这时该Widget中就有了一个layout布局
测试,main函数中修改代码
#include "dialoglist.h"
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
DialogList list;
list.show();
return a.exec();
}
2.3 资源导入
向项目中导入资源,对应九个按钮需要九张图片作为头像图标使用,搜集九张图片(可用共享的资源或者自己收藏的图片,大小在80*80左右) 添加新文件 – Qt – Qt Resource 点击choose 名称 res 下一步,点击完成,生成res.qrc文件
右击res.qrc,点击open in Editor ,添加前缀 /
添加文件 – 将准备好的文件选中,点击打开,添加成功
2.4 设置窗体标题和图标
2.4.1设置标题
在Drawer构造函数中加入如下代码 setWindowTitle(“QQ ”);
2.4.2 设置图标
设置主窗体标题图标 setWindowIcon(QPixmap(“:/images/qq.png”));
#include "dialoglist.h"
#include "ui_dialoglist.h"
#include <QToolButton>
DialogList::DialogList(QWidget *parent) :
QWidget(parent),
ui(new Ui::DialogList)
{
ui->setupUi(this);
setWindowTitle("QQ");
setWindowIcon(QPixmap(":/images/qq.png"));
DialogList::~DialogList()
{
delete ui;
}
2.5设置列表中的按钮
2.5.1 创建9个按钮存放到QVector容器中
QVector<QToolButton*> vToolBtn;
for(int i=0;i<9;i++){
QToolButton * btn = new QToolButton;
btn->setText("孙悟空");
btn->setIcon(QPixmap(":/images/ftbz.png"));
btn->setIconSize(QPixmap(str).size());
btn->setAutoRaise(true);
btn->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
ui->vLayout->addWidget(btn);
vToolBtn.push_back(btn);
}
效果全部头像和名称一样
2.5.2 替换图片,改为不同的资源
QList<QString>nameList;
nameList << "水票奇缘" << "忆梦如澜" <<"北京出版人"<<"Cherry"<<"淡然"
<<"娇娇girl"<<"落水无痕"<<"青墨暖暖"<<"无语";
QStringList iconNameList;
iconNameList << "spqy"<< "ymrl" <<"qq" <<"Cherry"<< "dr"
<<"jj"<<"lswh"<<"qmnn"<<"wy";
QVector<QToolButton*> vToolBtn;
for(int i=0;i<9;i++){
QToolButton * btn = new QToolButton;
btn->setText(nameList[i]);
QString str = QString(":/images/%1.png").arg(iconNameList.at(i));
btn->setIcon(QPixmap(str));
btn->setIconSize(QPixmap(str).size());
btn->setAutoRaise(true);
btn->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
ui->vLayout->addWidget(btn);
vToolBtn.push_back(btn);
}
再次运行,效果如图
3. 设计聊天窗口
要求双击widget.ui文件进入设计模式,界面宽度属性分别设置为730和450,向界面中拖入部件并且进行设置, 最终效果如图
3.1 界面
双击widget.ui文件进入设计模式,
-
添加不能写字只能显示的 Text Browser控件 -
添加字体对话框 Font Combo Box -
添加下拉框 Combo Box -
添加 6个 Tool Button -
添加Wigget ,把Text Brower放进去做垂直布局,把像素调为0 -
下面同样也添加 widget框 把 Font Combo Box Combo Box 6个 Tool Button 放到里面做水平布局 -
选中 第二个widget 转型为 Frame ,然后多了一个QFrame,编辑frameshape - 更改为 box -
第三个框,文本编辑框,添加widget和Text Edit ,把TextEdit放到widget里面做水平布局,layout边距设置为0 -
添加widget把三个框做垂直布局,widget设置边距0 -
把widget 转型为Qframe ,设置frameshape 为box -
设置文本编辑框高度为100 -
添加右边好友框,一个widget ,一个table widget ,把table widget加到widget中,做水平布局。设置widget间距0 -
聊天和好友窗口做布局,添加widget ,把聊天和好友窗口拉进去做一个左右布局,设置layout为0 设置好友窗口宽度为180 -
底部发送按钮、退出按钮 、在线人数,添加一个widget、 二个pushbutton和一个label,把二个按钮和label拉倒widget做水平布局 -
更改widget 的layout 间距为 0 -
把widget转型为Qframe。frameshape设置为box -
整体布局,添加widget 。把上面窗口和下面widget做一个垂直布局。设置layout值为0,底部添加弹簧填充 设置界面宽度属性分别设置为730和450
3.2 控件类型和属性设置
3.2.1 各个控件设置
如下图中标注的1~8,下表中为上图的属性设置以及控件类型名称。这一步最后命名
3.2.2 ToolBtn详细设置
其中前三个按钮 ,选中 checkable 属性;
-
其中所有的ToolBtn 属性中的toolTIp依次更改为 加粗、倾斜、下划线、更改字体颜色、保存聊天记录和 清空聊天记录 -
设置图标大小为size 为33*32,icon size为26x26 -
设置加粗、倾斜、下划线有被选中功能,选中 checkable 属性 -
设置 加粗、倾斜、下划线、更改字体颜色、保存聊天记录和 清空聊天记录鼠标在上面时有提示功能按钮名
3.2.3 字体大小下拉框设置
-
界面上 5号控件设置字体大小,设置区间为8~22(与腾讯QQ软件完全相同),双击该部件,点击 + 号按钮添加新项目如图 -
设置默认字体为12号,选择第4
3.2.4 TableWidget设置
显示用户列表的TableWidget控件,将selectionModel属性选择为 SingleSelection(带有选中效果),将selectBehavior选择为 SelectRows (选中整行),取消选中的showGrid(表格显示)
双击TableWidget部件,添加“用户名”列,如图
3.2.5 命名设置
参考表中要求命名 至此,所有弹出的聊天信息窗口全部设计完毕
4. 关联图片按钮与聊天窗口
4.1 添加按钮点击事件
下一步就是要关联起聊天的列表窗口和具体的聊天信息窗口了,也就是点击按钮弹出窗口。 在文件dialoglist.cpp 添加
for(int i=0;i<vToolBtn.size();i++){
connect(vToolBtn[i],&QToolButton::clicked,[=](){
Widget * widget = new Widget(0,vToolBtn[i]->text());
widget->setWindowTitle(vToolBtn[i]->text());
widget->setWindowIcon(vToolBtn[i]->icon());
widget->show();
});
}
4.2 修改弹出框的构造函数
!!! Widget文件中的 构造改为Widget(QWidget *parent ,QString name); 参数2 用于传入用户名,让具体的弹出框知道自己的用户名是什么 文件widget.h 和widget.cpp 修改
4.3 测试
运行代码,点击窗口,弹出响应的对话框
4.4 解决一个窗口多次弹出的bug
-
在dialoglist.h中加入标示 QVectorisShow; //代表是否打开窗口的标识,false未打开,true打开 -
dialoglist.cpp构造中 将9个标志位都置为false,添加QMessageBox模块 修改点击事件,根据打开的标志位来判断
isShow.push_back(false);
connect(vToolBtn[i],&QToolButton::clicked,[=](){
if(isShow[i]){
QString str = QString("%1窗口已经被打开了").arg(vToolBtn[i]->text());
QMessageBox::warning(this,"警告",str);
return;
}
isShow[i] = true;
- 然后在widget函数中声明关闭的信号,和重写关闭方法
然后实现重写的关闭方法,发送关闭信号 在widget.h中添加
signals:
void closeWidget();
protected:
void closeEvent(QCloseEvent *);
在widget.cpp 然后实现重写的关闭方法,发送关闭信号
void Widget::closeEvent(QCloseEvent * e){
emit this->closeWidget();
QWidget::closeEvent(e);
}
回到dialoglist.cpp 中点击事件中再添加一行代码
connect(widget,&Widget::closeWidget,[=](){
isShow[i] = false;
});
测试,一个窗口不能多次弹出,关闭后可以再弹出。
5. 实现基本聊天功能
widget.h 中 定义枚举 enum MsgType {Msg,UsrEnter,UsrLeft} //分别代表 聊天信息、新用户加入、用户退出
5.1 声明聊天的方法
在工程文件Myselfqq.pro中加入网络模块 在widget.h 加入聊天声明
public:
void sndMsg(MsgType type);
void usrEnter(QString username);
void usrLeft(QString usrname,QString time);
QString getUsr();
QString getMsg();
private:
QUdpSocket * udpSocket;
qint16 port;
QString uName;
void ReceiveMessage();
5.2 widget.cpp中实现
- 构造函数中:
this->uName = usrname; //获取用户名 udpSocket = new QUdpSocket(this); port = 23333; //采用ShareAddress模式(即允许其它的服务连接 到相同的地址和端口,特别是用在多客户端监听同一个服务器端口等时特别有效),和ReuseAddressHint模式(重新连接服务器) udpSocket->bind(port,QUdpSocket::ShareAddress |QUdpSocket::ReuseAddressHint); connect(udpSocket,&QUdpSocket::readyRead,this,&Widget::ReceiveMessage); //监听信号 sndMsg(UsrEnter); //有新用户加入 发送按钮和离开按钮信号槽连接
udpSocket = new QUdpSocket(this);
uName = name;
this->port = 9999;
udpSocket->bind(port,QUdpSocket::ShareAddress |QUdpSocket::ReuseAddressHint);
sndMsg(UsrEnter);
connect(ui->sendBtn,&QPushButton::clicked,this,[=](){
sndMsg(Msg);
});
connect(ui->exitBtn,&QPushButton::clicked,this,[=]()
{
this->close();
});
connect(udpSocket,&QUdpSocket::readyRead,this,&Widget::ReceiveMessage);
- 发送消息函数 sndMsg
void Widget::sndMsg(MsgType type)
{
QByteArray array;
QDataStream stream(&array,QIODevice::WriteOnly);
stream<<type<<getUsr();
switch(type){
case Msg:
if(ui->msgTextEdit->toPlainText() == "")
{
QMessageBox::warning(this,"警告","发送内容不能为空");
return ;
}
stream<<getMsg();
break;
case UsrEnter:
break;
case UsrLeft:
break;
default:
break;
}
udpSocket->writeDatagram(array,QHostAddress::Broadcast,port);
}
- 获取聊天信息
QString Widget::getMsg(){
QString msg = ui->msgTextEdit->toHtml();
ui->msgTextEdit->clear();
ui->msgTextEdit->setFocus();
return msg;
}
- 接受消息 ReceiveMessage
void Widget::ReceiveMessage(){
qint64 size = udpSocket->pendingDatagramSize();
QByteArray array = QByteArray(size,0);
udpSocket->readDatagram(array.data(),size);
QDataStream stream (&array,QIODevice::ReadOnly);
int msgType;
stream >> msgType;
QString usrName,msg;
QString time = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");
switch (msgType){
case Msg:
stream >> usrName >>msg;
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:
stream >> usrName ;
usrEnter(usrName);
break;
case UsrLeft:
stream >> usrName;
usrLeft(usrName,time);
break;
default:
break;
}
}
- 获取用户名
QString Widget::getUsr()
{
return this->uName;
}
- 新用户进入
void Widget::usrEnter(QString usrname)
{
bool isEmpty = ui->usrTblWidget->findItems(usrname,Qt::MatchExactly).isEmpty();
if(isEmpty)
{
QTableWidgetItem *usr = new QTableWidgetItem(usrname);
ui->usrTblWidget->insertRow(0);
ui->usrTblWidget->setItem(0,0,usr);
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()));
sndMsg(UsrEnter);
}
}
- 用户离开
void Widget::usrLeft(QString usrname,QString time)
{
bool isEmpty = ui->usrTblWidget->findItems(usrname, Qt::MatchExactly).isEmpty();
if(!isEmpty){
ui->usrTblWidget->removeRow(isEmpty);
ui->msgBrowser->setTextColor(Qt::gray);
ui->msgBrowser->setCurrentFont(QFont("Times New Roman", 10));
ui->msgBrowser->append(QString("%1 于 %2 离开!").arg(usrname).arg(time));
ui->UsrNumLbl->setText(QString("在线人数:%1人").arg(ui->usrTblWidget->rowCount()));
}
}
- 重写离开Event
void closeEvent(QCloseEvent *)
void Widget::closeEvent(QCloseEvent * e)
{
emit this->closeWidget();
sndMsg(UsrLeft);
udpSocket->close();
udpSocket->destroyed();
QWidget::closeEvent(e);
}
- 至此可以测试聊天功能
6. 辅助功能
6.1 字体设置
connect(ui->fontCbx,&QFontComboBox::currentFontChanged,this,[=](const QFont &f){
ui->msgTextEdit->setCurrentFont(f);
ui->msgTextEdit->setFocus();
});
6.2 字号设置
void (QComboBox:: * cbxSingal)(const QString &text) = &QComboBox::currentIndexChanged;
connect(ui->sizeCbx,cbxSingal,this,[=](const QString &text){
ui->msgTextEdit->setFontPointSize(text.toDouble());
ui->msgTextEdit->setFocus();
});
6.3 加粗
connect(ui->boldTBtn,&QToolButton::clicked,this,[=](bool checked){
if(checked){
ui->msgTextEdit->setFontWeight(QFont::Bold);
}else{
ui->msgTextEdit->setFontWeight(QFont::Normal);
}
ui->msgTextEdit->setFocus();
});
6.4 倾斜
connect(ui->italicTBtn,&QToolButton::clicked,this,[=](bool checked){
ui->msgTextEdit->setFontItalic(checked);
ui->msgTextEdit->setFocus();
});
6.5下划线
connect(ui->underlineTBtn,&QToolButton::clicked,this,[=](bool checked){
ui->msgTextEdit->setFontUnderline(checked);
ui->msgTextEdit->setFocus();
});
6.6 设置文本颜色
- 在widget.h 中定义私有成员 color
- 在widget.cpp中实现
connect(ui->colorTBtn,&QToolButton::clicked,this,[=](){
color = QColorDialog::getColor(Qt::green,this);
if(color.isValid())
{
ui->msgTextEdit->setTextColor(color);
ui->msgTextEdit->setFocus();
}
});
- 测试
6.7 保存聊天记录
connect(ui->saveTBtn,&QToolButton::clicked,this,[=](){
if(ui->msgBrowser->document()->isEmpty()){
QMessageBox::warning(this,"警告","聊天记录为空,无法保存!",QMessageBox::Ok);
}
else{
QString fName = QFileDialog::getSaveFileName(this,"保存聊天记录","聊天记录","(*.txt)");
if(!fName.isEmpty()){
QFile file(fName);
file.open(QIODevice::WriteOnly | QFile::Text);
QTextStream stream(&file);
stream << ui->msgBrowser->toPlainText();
file.close();
}
}
});
6.8 清空聊天记录
connect(ui->clearTBtn,&QToolButton::clicked,[=](){
ui->msgBrowser->clear();
});
7. 添加快捷键
- 设置信息发送键,输入完成后按Crtl +回车发送信息
- 退出快捷键
8. 测试
|