QT-第七节 项目实战
项目简介
- 实现一个简单的聊天工具(群聊)
新建项目
- 打开Qt Creator,点击新建NewProject
- Application –> Qt Widgets Application -> choose
- 创建项目名称例如: MyselfQQ,路径自己选择,注意不要有空格和中文
- 选择套件,点击下一步
- 选择基类QWidget,然后点击下一步
- 然后点击完成,至此项目创建完毕。
创建会话列表
资源导入
- 向项目中导入资源,对应九个按钮需要九张图片作为头像图标使用,搜集九张图片(可用共享的资源或者自己收藏的图片,大小在80*80左右)
- 添加新文件 – Qt – Qt Resource 点击choose 名称 res 下一步,点击完成,生成res.qrc文件
- 右击res.qrc,点击open in Editor
- 添加前缀
/
- 添加文件 – 将准备好的文件选中,点击打开,添加成功
- 添加前缀
自定义控件-会话列表类DialogList
- 右击项目名,在弹出的快捷菜单中选择“添加新文件…”菜单项,在弹出的对话框中选择“Qt”选项。选择Qt设计师界面类,单击“Choose…”按钮;
- 界面模板选择Widget,点击下一步
- 类名填写 DialogList (可以起其他名称)点击下一步
- 在汇总中单击“完成”按钮,系统会为我们添加“dialoglist.h”头文件和“dialoglist.cpp”源文件以及dialoglist.ui设计文件
布局会话框列表UI
- 设置dialoglist.ui窗口的大小为 250*700
- 拖入控件QToolBox,修改该控件的currentItemText为“群成员”,QToolBox默认生成的第二页删除掉,整页水平布局;
- 点击QToolBox,右边的属性可以看出,QToolBox下的每个page都是一个Widget。
- 在QToolBox里,可以先利用一些测试控件放入到其中,然后做垂直布局,然后把测试的控件删除掉,这时再点击QToolBox下的page-Widget中就有了一个layout布局,修改这个layoutName为vLayout
dialoglist.cpp代码实现
//头文件
#ifndef DIALOGLIST_H
#define DIALOGLIST_H
#include <QWidget>
namespace Ui {
class DialogList;
}
class DialogList : public QWidget
{
Q_OBJECT
public:
explicit DialogList(QWidget *parent = nullptr);
~DialogList();
private:
Ui::DialogList *ui;
//记录9个icon是否显示
QVector<bool> isshowv;
};
//cpp文件
#endif // DIALOGLIST_H
#include "dialoglist.h"
#include "ui_dialoglist.h"
#include<QToolButton>
#include"widget.h"
#include<QMessageBox>
DialogList::DialogList(QWidget *parent) :
QWidget(parent),
ui(new Ui::DialogList)
{
ui->setupUi(this);
//设置标题
setWindowTitle("Myself QQ");
//设置图标
setWindowIcon(QPixmap(":/images/qq.png"));
//准备图标
//姓名资源
QList<QString>nameList;
nameList << "水票奇缘" << "忆梦如澜" <<"北京出版人"<<"Cherry"<<"淡然"<<"娇娇girl"<<"落水无痕"<<"青墨暖暖"<<"无语";
//图标资源列表
QStringList iconNameList;
iconNameList << "ftbz"<< "ymrl" <<"qq" <<"Cherry"<< "dr"<<"jj"<<"lswh"<<"qmnn"<<"wy";
QVector<QToolButton *> vToolBtn;
for (int i =0;i<9;i++) {
//设置头像
QToolButton *btn = new QToolButton(this);
//设置文字
btn->setText(nameList[i]);
//设置头像
QString iconstr =QString(":/images/%1.png").arg(iconNameList[i]);
btn->setIcon(QPixmap(iconstr));
//设置头像大小(默认图片大小)
btn->setIconSize(QPixmap(iconstr).size());
//设置按钮风格
btn->setAutoRaise(true);
//文字图片一起显示
btn->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
//添加到垂直布局中
ui->vLayout->addWidget(btn);
//容器保存9个按钮
vToolBtn.push_back(btn);
//9个窗口默认都没有显示
isshowv.push_back(false);
}
//对9个按钮添加信号槽
for(int i= 0;i<9;i++){
connect(vToolBtn[i],&QToolButton::clicked,[=](){
//判断当前窗口是否已经打开
if(isshowv[i]){
QString str = QString("%1的窗口已经被打开了").arg(vToolBtn[i]->text());
QMessageBox :: warning(this,"警告",str);
return ;
}
isshowv[i] = true;
//弹出聊天对话框
//构造聊天窗口
//构造参数第一个为0:以顶层的方式弹出,相当于iOS的window窗口弹出
//参数2:窗口名字
//修改构造函Widget
Widget *wd = new Widget(nullptr,vToolBtn[i]->text());
//设置窗口标题
wd->setWindowTitle(vToolBtn[i]->text());
wd->setWindowIcon(vToolBtn[i]->icon());
wd->show();
//监听窗口关闭信号
connect(wd,&Widget::closeWidget,[=]() {
//一旦点击关闭,要回复显示标识
isshowv[i] = false;
//释放内存
delete wd;
});
});
}
}
DialogList::~DialogList()
{
delete ui;
}
##创建聊天列表
聊天界面的布局
- 直接把项目默认创建的widget作为聊天界面
- 双击widget.ui文件进入设计模式,界面宽度属性分别设置为730和450
- 向界面中拖入部件并且进行设置
- 给每个控件重命名
- 如下图
- 布局小细节
- 如何给空白的Widget添加边框
- 点击控件,右键->变形为->QFrame
- 然后在属性表中找到QFrame->frameshape->box
- 如何删除Widget内部布局子控件的默认内间距?
- 点击Widget,属性中找到Layout,删除内间距9
- 如何设置一个已经布局好了的宽高?
- 属性列表maximumSize,展开,里面可以设置最大宽高
- 如何在UI设置ToolBtn的图片
- 属性中找到icon,点击右边的箭头,选择资源,找到相应的图片
- 该控件设置控件大小:size,设置icon大小:iconsize
- ToolBtn详细设置
- 添加选中效果:其中前三个按钮(boldBtn、italicBtn、underlineBtn) ,选中checkable 属性
- 其中所有的ToolBtn属性中的toolTIp依次更改为 加粗、倾斜、下划线、更改字体颜色、保存聊天记录和 清空聊天记录
- 鼠标放上去,会显示意思
- 字体大小下拉框设置
- 界面上 5号控件设置字体大小,设置区间为8~22(与腾讯QQ软件完全相同),双击该部件,点击 + 号按钮添加新项目如图
- currentIndex属性设置为4,即默认为12号字
- TableWidget设置
- 显示用户列表的TableWidget控件,将selectionModel属性选择为 SingleSelection(带有选中效果),将selectBehavior选择为 SelectRows(选中整行),取消选中的showGrid(表格显示)
- 双击TableWidget部件,添加“用户名”列
- 如何给空白的Widget添加边框
- 小知识
- 给“发送”按钮设置快捷键?
- 在UI中找到“发送”按钮,点击在属性列表中找到shortcut,点击右边设置快捷键即可。比如:ctrl+enter
- 给“发送”按钮设置快捷键?
代码实现
//头文件
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include<QUdpSocket>
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
//修改后的构造函数
explicit Widget(QWidget *parent,QString name);
~Widget();
private:
Ui::Widget *ui;
//定义一些型号,相当于一些通知
signals:
//关闭当前窗口信号
void closeWidget();
public:
//重写并实现系统关闭事件
void closeEvent(QCloseEvent *event);
/*--------------消息发送部分------------*/
//枚举用于定义消息类型
//分别代表 聊天信息、新用户加入、用户退出
enum MsgType {Msg,UsrEnter,UsrLeft};
public:
//发送消息:广播形的,相当于向所有用户发送消息
void sndMsg(MsgType type); //广播UDP消息
//告诉别人,我已经上线了
void usrEnter(QString username);//处理新用户加入
void usrLeft(QString usrname,QString time); //处理用户离开
QString getUsr(); //获取用户名
QString getMsg(); //获取聊天信息
private:
QUdpSocket * udpSocket; //udp套接字
qint16 port; //端口
QString uName; //用户名
//定义槽函数,接收信号
void ReceiveMessage(); //接受UDP消息
};
#endif // WIDGET_H
//cpp文件
#include "widget.h"
#include "ui_widget.h"
#include<QDataStream>
#include<QMessageBox>
#include<QDateTime>
#include<QColorDialog>
#include<QFileDialog>
#include<QFile>
#include<QTextStream>
Widget::Widget(QWidget *parent,QString name) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
//初始化信息
udpSocket = new QUdpSocket(this);
//当前的用户名
uName = name;
//当前用户的端口号
port = 9999;
//绑定端口号、模式:共享地址(因为是同一台电脑)、断线重连
udpSocket->bind(this->port,QUdpSocket::ShareAddress|QUdpSocket::ReuseAddressHint);
//发送新用户进入:通知所有成员,我已经上线了
sndMsg(UsrEnter);
//点击发送按钮,发送消息
connect(ui->sendBtn,&QPushButton::clicked,[=](){
sndMsg(Msg);
});
//监听别人发送的数据
connect(udpSocket,&QUdpSocket::readyRead,this,&Widget::ReceiveMessage);
//点击退出按钮
connect(ui->exitBtn,&QPushButton:: clicked,[=](){
//会自动触发closeevent;
this->close();
});
/*-----------辅助功能设置-------------*/
//字体设置
connect(ui->fontCbx,&QFontComboBox::currentFontChanged,[=](const QFont &font){
ui->msgEdit->setCurrentFont(font);
ui->msgEdit->setFocus();
});
//字号大小
void (QComboBox::* cbxsignal)(const QString &text) = &QComboBox::currentIndexChanged;
connect(ui->sizeCbx,cbxsignal,[=](const QString &text){
ui->msgEdit->setFontPointSize(text.toDouble());
ui->msgEdit->setFocus();
});
//加粗
connect(ui->boldTBtn,&QToolButton:: clicked,[=](bool isselect){
if(isselect){
ui->msgEdit->setFontWeight(QFont::Bold);
}else {
ui->msgEdit->setFontWeight(QFont::Normal);
}
});
//倾斜
connect(ui->italicTbtn,&QToolButton::clicked,[=](bool isselect){
ui->msgEdit->setFontItalic(isselect);
});
//下划线
connect(ui->underlineTBtn,&QToolButton::clicked,[=](bool isselect){
ui->msgEdit->setFontUnderline(isselect);
});
//字体颜色
connect(ui->colorTBtn,&QToolButton::clicked,[=](){
QColor color = QColorDialog::getColor(Qt::red);
ui->msgEdit->setTextColor(color);
});
//清空聊天记录
connect(ui->clearTBtn,&QToolButton::clicked,[=](){
ui->msgBrowser->clear();
});
//保存聊天记录
connect(ui->saveTBtn,&QToolButton::clicked,[=](){
if(ui->msgBrowser->document()->isEmpty()){
QMessageBox::warning(this,"警告","聊天内容不能为空");
return ;
}
QString path = QFileDialog:: getSaveFileName(this,"保存","聊天记录","(*.txt)");
if(path.isEmpty()){
QMessageBox::warning(this,"警告","路径不能为空");
return;
}
QFile file(path);
//打开模式、换行
file.open(QIODevice::WriteOnly|QIODevice::Text);
QTextStream stream(&file);
stream<<ui->msgBrowser->toPlainText();
file.close();
});
}
Widget::~Widget()
{
delete ui;
}
void Widget:: closeEvent(QCloseEvent *event){
//通知其他用户:离开
sndMsg(UsrLeft);
//断开套接字
udpSocket->close();
udpSocket->destroyed();
// Widget::closeEvent(event);
emit this->closeWidget();
}
void Widget:: sndMsg(MsgType type) //广播UDP消息
{
//消息分为三类
//自定义消息协议:第一段类型,第二段姓名,第三段具体内容。
QByteArray array;
QDataStream stream(&array,QIODevice::WriteOnly);
//第一、二段内容写入流
stream<<type <<getUsr();
switch (type) {
case Msg: //普通消息发送
//判断消息是否有内容
if(ui->msgEdit->toPlainText()==""){
QMessageBox::warning(this,"警告","发送消息不能为空");
return;
}
stream<<getMsg();
break;
case UsrEnter: //新用户进入消息
//只需要把类型、用户名就够了
break;
case UsrLeft: //新用户离开消息
break;
default:
break;
}
//发送报文
//Broadcast:广播形式的发送,任何IP地址都能接收到
//指定发送的端口,由于是同一台电脑这里就全部用9999
udpSocket->writeDatagram(array,QHostAddress::Broadcast,port);
}
QString Widget:: getMsg() //获取聊天信息
{
QString str = ui->msgEdit->toHtml();
//清空输入框
ui->msgEdit->clear();
ui->msgEdit->setFocus();
return str;
}
void Widget:: ReceiveMessage() //接受UDP消息
{
//拿到数据报文
//消息大小
qint64 size = udpSocket->pendingDatagramSize();
QByteArray array = QByteArray(size,0);
udpSocket->readDatagram(array.data(),size);
//解析数据
//第一段类型、第二用户名、第三段内容
QDataStream stream(&array,QIODevice::ReadOnly);
//类型
int msgtype;
//用户名
QString usrname;
//消息体
QString msg;
//获取当前时间
QString time = QDateTime :: currentDateTime().toString("yyyy-MM-dd hh:mm:ss");
//拿到消息类型
stream >> msgtype;
switch (msgtype) {
case Msg: //普通消息
//拿到用户名、消息体
stream>>usrname>>msg;
//展示聊天记录
ui->msgBrowser->setTextColor(Qt::blue);
ui->msgBrowser->append("["+usrname+"]"+time);
ui->msgBrowser->append(msg);
break;
case UsrEnter: //有新用户进入了
//1. 更新右侧在线人员列表
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 username)//处理新用户加入
{
//1.更新右侧的usrtablWidget
bool isEmpty = ui->usrtablWidget->findItems(username,Qt::MatchExactly).isEmpty();
if(isEmpty){
QTableWidgetItem *usr = new QTableWidgetItem(username);
//插入一行
ui->usrtablWidget->insertRow(0);
ui->usrtablWidget->setItem(0,0,usr);
//2. 追加聊天记录
ui->msgBrowser->setTextColor(Qt::gray);
ui->msgBrowser->append(QString("%1 上线了").arg(username));
//3. 在线人数更新
ui->usrNumLbl->setText(QString("在线用户,%1人").arg(ui->usrtablWidget->rowCount()));
//4. 把自身信息广播出去
//这个不会死循环,因为上面有个isEmpty判断
sndMsg(UsrEnter);
}
}
void Widget:: usrLeft(QString usrname,QString time) //处理用户离开
{
//1.更新右侧usrtablWidget
bool isEmpty = ui->usrtablWidget->findItems(usrname,Qt::MatchExactly).isEmpty();
if(!isEmpty){
int row = ui->usrtablWidget->findItems(usrname,Qt::MatchExactly).first()->row();
ui->usrtablWidget->removeRow(row);
//2. 追加聊天记录
ui->msgBrowser->setTextColor(Qt::gray);
ui->msgBrowser->append(QString("%1 于 %2离开").arg(usrname,time));
//3. 在线人数修改
ui->usrNumLbl->setText(QString("在线用户,%1人").arg(ui->usrtablWidget->rowCount()));
}
}
UDP的简单实用
- 布局两个窗口
- 代码实现:
-
main函数
#include "widget.h" #include <QApplication> #include"udp2.h" int main(int argc, char *argv[]) { QApplication a(argc, argv); //用户1 Widget w; w.show(); //用户2 UDP2 udp2; udp2.show(); return a.exec(); }
-
用户1
#ifndef WIDGET_H #define WIDGET_H #include <QWidget> //导入头文件前,需要将network模块导入到pro工程文件中,否则无法找到QUdpSocket这个文件 #include<QUdpSocket> namespace Ui { class Widget; } class Widget : public QWidget { Q_OBJECT public: explicit Widget(QWidget *parent = nullptr); ~Widget(); private: Ui::Widget *ui; public: //套接字 QUdpSocket *udp; }; #endif // WIDGET_H #include "widget.h" #include "ui_widget.h" Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget) { ui->setupUi(this); //初始化属性 //当前用户的端口号 ui->myport->setText("8888"); //要发送用户的端口号 ui->toport->setText("9999"); //当前用户的ip地址 ui->ipadr->setText("127.0.0.1"); //创建 udp = new QUdpSocket(this); //绑定自身端口号 udp->bind(ui->myport->text().toInt()); //点击发送按钮发送报文 connect(ui->sendbtn,&QPushButton::clicked,[=](){ udp->writeDatagram(ui->input->toPlainText().toUtf8(),QHostAddress(ui->ipadr->text()),ui->toport->text().toInt()); ui->output->append("my say:"+ui->input->toPlainText()); //清空输入框 ui->input->clear(); }); //接收数据 connect(udp,&QUdpSocket:: readyRead,[=](){ //获取报文长度 qint64 size = udp->pendingDatagramSize(); //读取报文 QByteArray array = QByteArray(size,0); udp->readDatagram(array.data(),size); //将数据同步到聊天框 ui->output->append(array); }); } Widget::~Widget() { delete ui; }
-
用户2 与用户1代码相同,除了端口号互换之外。
-