QT-第七节 项目实战

项目简介

  1. 实现一个简单的聊天工具(群聊) 图4

新建项目

  1. 打开Qt Creator,点击新建NewProject
  2. Application –> Qt Widgets Application -> choose
  3. 创建项目名称例如: MyselfQQ,路径自己选择,注意不要有空格和中文
  4. 选择套件,点击下一步
  5. 选择基类QWidget,然后点击下一步
  6. 然后点击完成,至此项目创建完毕。

创建会话列表

资源导入

  1. 向项目中导入资源,对应九个按钮需要九张图片作为头像图标使用,搜集九张图片(可用共享的资源或者自己收藏的图片,大小在80*80左右)
  2. 添加新文件 – Qt – Qt Resource 点击choose 名称 res 下一步,点击完成,生成res.qrc文件
  3. 右击res.qrc,点击open in Editor
    1. 添加前缀 /
    2. 添加文件 – 将准备好的文件选中,点击打开,添加成功

自定义控件-会话列表类DialogList

  1. 右击项目名,在弹出的快捷菜单中选择“添加新文件…”菜单项,在弹出的对话框中选择“Qt”选项。选择Qt设计师界面类,单击“Choose…”按钮;
  2. 界面模板选择Widget,点击下一步
  3. 类名填写 DialogList (可以起其他名称)点击下一步
  4. 在汇总中单击“完成”按钮,系统会为我们添加“dialoglist.h”头文件和“dialoglist.cpp”源文件以及dialoglist.ui设计文件

布局会话框列表UI

  1. 设置dialoglist.ui窗口的大小为 250*700
  2. 拖入控件QToolBox,修改该控件的currentItemText为“群成员”,QToolBox默认生成的第二页删除掉,整页水平布局;
    1. 点击QToolBox,右边的属性可以看出,QToolBox下的每个page都是一个Widget。
  3. 在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;
}

##创建聊天列表

聊天界面的布局

  1. 直接把项目默认创建的widget作为聊天界面
  2. 双击widget.ui文件进入设计模式,界面宽度属性分别设置为730和450
    1. 向界面中拖入部件并且进行设置
    2. 给每个控件重命名
    3. 如下图 图4
  3. 布局小细节
    1. 如何给空白的Widget添加边框
      1. 点击控件,右键->变形为->QFrame
      2. 然后在属性表中找到QFrame->frameshape->box
    2. 如何删除Widget内部布局子控件的默认内间距?
      1. 点击Widget,属性中找到Layout,删除内间距9
    3. 如何设置一个已经布局好了的宽高?
      1. 属性列表maximumSize,展开,里面可以设置最大宽高
    4. 如何在UI设置ToolBtn的图片
      1. 属性中找到icon,点击右边的箭头,选择资源,找到相应的图片
      2. 该控件设置控件大小:size,设置icon大小:iconsize
    5. ToolBtn详细设置
      1. 添加选中效果:其中前三个按钮(boldBtn、italicBtn、underlineBtn) ,选中checkable 属性
      2. 其中所有的ToolBtn属性中的toolTIp依次更改为 加粗、倾斜、下划线、更改字体颜色、保存聊天记录和 清空聊天记录
        1. 鼠标放上去,会显示意思
    6. 字体大小下拉框设置
      1. 界面上 5号控件设置字体大小,设置区间为8~22(与腾讯QQ软件完全相同),双击该部件,点击 + 号按钮添加新项目如图
      2. currentIndex属性设置为4,即默认为12号字
    7. TableWidget设置
      1. 显示用户列表的TableWidget控件,将selectionModel属性选择为 SingleSelection(带有选中效果),将selectBehavior选择为 SelectRows(选中整行),取消选中的showGrid(表格显示)
      2. 双击TableWidget部件,添加“用户名”列
  4. 小知识
    1. 给“发送”按钮设置快捷键?
      1. 在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的简单实用

  1. 布局两个窗口 图4
  2. 代码实现:
    1. 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();
       }
      
    2. 用户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;
       }
      
    3. 用户2 与用户1代码相同,除了端口号互换之外。

Table of Contents