QT - 实例 - Qt实现局域网聊天工具软件

1.简介

1)基本功能

本次设计参考了《Qt及Qt Quick开发实战精解》一书,并对其进行bug补全修正,添加些许新功能进行二次开发,基本的功能:

  • 使用UDP进行组网聊天
  • 使用TCP进行文件传输
  • 能够更改字体,字体大小,粗体,斜体,下划线和字体颜色
  • 能保存聊天记录,清空聊天记录等
  • 能在同一个局域网下通过不同的ip地址加入组网

2)添加功能

添加功能如下:

  • 修改中文乱码问题
  • 自Qt4转移至Qt5环境
  • 添加重绘事件设置窗口背景
  • 支持Enter键发送 消息
  • 对tablewidget进行优化布局
  • 其他功能修正添加

3)效果

在这里插入图片描述

我这里并没有对其进行多ip组网聊天测试,但是功能正常,清空聊天记录和保存功能也可以正常显示,这里就不再过多的赘述。

2.构建

此局域网聊天工具既要作为服务器端,又要作为客户端,因此以可将其作为端到端的P2P模式。
在这里插入图片描述

3.基本功能框架

  • 构建基本界面 – 设计是模式下构建
  • 实现聊天功能 – 使用UDP来事项用户信息的显示和基本的聊天功能
  • 实现文件传输功能 – 使用TCP实现快速传输
  • 完善其他功能 – 类QQ聊i天(更改字体,保存聊天记录等 )

1)界面构建

界面的话,需要三个界面来实现所有的功能,一个主窗口界面,一个文件发送端,一个文件接收端。构建如下

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eSbkW2EO-1584417405286)(Qt%E5%AE%9E%E7%8E%B0%E8%81%8A%E5%A4%A9%E5%B7%A5%E5%85%B7.assets/%E4%B8%BB%E7%95%8C%E9%9D%A2.png)]

在这里插入图片描述

在这里插入图片描述

2)聊天功能实现

UDP有个最显著的特点就是有着强大的广播功能,聊天功能就使用UDP协议,构建组网,在同一局域网下通信,这里只需要将程序运行在不同的ip环境下即可

3)文件传输功能

使用TCP协议实现文件的快速传输,用TCP协议在Qt中的三次握手应用,调用界面,实现传输功能

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

4)其他功能

其他功能就是对字体的操作了,可以更改字体的颜色和大小,粗细,格式等等,通过调用Qt中font类的字体设置。

在这里插入图片描述
在这里插入图片描述

4.相关源码

wdiget.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QTextCodec>
#include <QTextCharFormat>
#include <QPaintEvent>

class QUdpSocket;

class TcpServer;


namespace Ui {
class Widget;
}

// 枚举变量标志信息的类型,分别为消息,新用户加入,用户退出,文件名,拒绝接受文件
enum MessageType{Message, NewParticipant, ParticipantLeft, FileName, Refuse};


class Widget : public QWidget
{
    Q_OBJECT

public:
    explicit Widget(QWidget *parent = 0);
    ~Widget();
    void init();        //初始化背景色


protected:
    void newParticipant(QString userName,
                        QString localHostName, QString ipAddress);  //处理新用户加入
    void participantLeft(QString userName,
                         QString localHostName, QString time);      //处理用户离开
    void sendMessage(MessageType type, QString serverAddress="");   //使用UDP广播发送信息s

    QString getIP();            //获取ip地址
    QString getUserName();      //获取用户名
    QString getMessage();       //获得要发送的消息


    void hasPendingFile(QString userName, QString serverAddress,
                        QString clientAddress, QString fileName);   //处理是否接收文件

    bool saveFile(const QString& fileName);      //保存聊天记录

    void closeEvent(QCloseEvent *);             //关闭事件

    void paintEvent(QPaintEvent *event);        //重绘事件

    bool eventFilter(QObject *target, QEvent *event); //事件过滤器


private:
    Ui::Widget *ui;
    QUdpSocket *udpSocket;
    qint16 port;            //定义端口

    QString fileName;
    TcpServer *server;      //声明TCP文件传输

    QColor color;           //定义字体颜色

    QTextCodec * unCodec = QTextCodec::codecForName ( "GBK" );      // 用于解码

private slots:
    void processPendingDatagrams();

    void on_sendButton_clicked();

    void getFileName(QString);
    void on_sendToolBtn_clicked();
    void on_fontComboBox_currentFontChanged(QFont f);
    void on_sizeComboBox_currentIndexChanged(QString );
    void on_boldToolBtn_clicked(bool checked);
    void on_italicToolBtn_clicked(bool checked);
    void on_underlineToolBtn_clicked(bool checked);
    void on_colorToolBtn_clicked();

    void currentFormatChanged(const QTextCharFormat &format);
    void on_saveToolBtn_clicked();
    void on_clearToolBtn_clicked();
    void on_exitButton_clicked();
};

#endif // WIDGET_H

widget.cpp

#include "widget.h"
#include "ui_widget.h"
#include <QUdpSocket>
#include <QHostInfo>
#include <QMessageBox>
#include <QScrollBar>
#include <QDateTime>
#include <QNetworkInterface>
#include <QProcess>

#include "tcpserver.h"
#include "tcpclient.h"
#include <QFileDialog>

#include <QColorDialog>
#include <QPainter>
#include <QPixmap>
#include <QKeyEvent>

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

    udpSocket = new QUdpSocket(this);
    port = 45454;
    udpSocket->bind(port, QUdpSocket::ShareAddress | QUdpSocket::ReuseAddressHint);
    connect(udpSocket, SIGNAL(readyRead()), this, SLOT(processPendingDatagrams()));
    sendMessage(NewParticipant);

    server = new TcpServer(this);
    connect(server, SIGNAL(sendFileName(QString)), this, SLOT(getFileName(QString)));

    connect(ui->messageTextEdit, SIGNAL(currentCharFormatChanged(QTextCharFormat)),
            this, SLOT(currentFormatChanged(const QTextCharFormat)));

    init();     //初始化背景

}

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

// 初始化背景
void Widget::init()
{
    ui->messageBrowser->setStyleSheet("background-color:transparent;");
    ui->messageTextEdit->setStyleSheet("background-color:transparent;");
    ui->userTableWidget->setStyleSheet("background-color:transparent;");
    ui->userTableWidget->horizontalHeader()->setStyleSheet("QHeaderView::section{background-color:transparent;font:13pt '宋体';color: white;}");  //row
    ui->userTableWidget->verticalHeader()->setVisible(false);
    //ui->userTableWidget->verticalHeader()->setStyleSheet("QHeaderView::section{background-color:transparent;}");    //col
    //ui->userTableWidget->setStyleSheet("QTableCornerButton::section{background-color:transparent;}");      //左上角交会处

    //设置键盘事件
    ui->sendButton->setFocus();
    ui->sendButton->setDefault(true);
    ui->messageTextEdit->installEventFilter(this);//设置完后自动调用其eventFilter函数


}

// 使用UDP广播发送信息
void Widget::sendMessage(MessageType type, QString serverAddress)
{
    QByteArray data;
    QDataStream out(&data, QIODevice::WriteOnly);
    QString localHostName = QHostInfo::localHostName();
    QString address = getIP();
    out << type << getUserName() << localHostName;

    switch(type)
    {
    case Message :
        if (ui->messageTextEdit->toPlainText() == "") {
            QMessageBox::warning(0,unCodec->toUnicode("警告"),unCodec->toUnicode("输入内容不能为空!"),QMessageBox::Ok);
            return;
        }
        out << address << getMessage();
        ui->messageBrowser->verticalScrollBar()
                ->setValue(ui->messageBrowser->verticalScrollBar()->maximum());
        break;

    case NewParticipant :
        out << address;
        break;

    case ParticipantLeft :
        break;

    case FileName : {
        int row = ui->userTableWidget->currentRow();
        QString clientAddress = ui->userTableWidget->item(row, 2)->text();
        out << address << clientAddress << fileName;
        break;
    }

    case Refuse :
        out << serverAddress;
        break;
    }
    udpSocket->writeDatagram(data,data.length(),QHostAddress::Broadcast, port);
}

// 接收UDP信息
void Widget::processPendingDatagrams()
{
    while(udpSocket->hasPendingDatagrams())
    {
        QByteArray datagram;
        datagram.resize(udpSocket->pendingDatagramSize());
        udpSocket->readDatagram(datagram.data(), datagram.size());
        QDataStream in(&datagram, QIODevice::ReadOnly);
        int messageType;
        in >> messageType;
        QString userName,localHostName,ipAddress,message;
        QString time = QDateTime::currentDateTime()
                .toString("yyyy-MM-dd hh:mm:ss");

        switch(messageType)
        {
        case Message:
            in >> userName >> localHostName >> ipAddress >> message;
            ui->messageBrowser->setTextColor(Qt::blue);
            ui->messageBrowser->setCurrentFont(QFont("Times New Roman",12));
            ui->messageBrowser->append("[ " +userName+" ] "+ time);
            ui->messageBrowser->append(message);
            break;

        case NewParticipant:
            in >>userName >>localHostName >>ipAddress;
            newParticipant(userName,localHostName,ipAddress);
            break;

        case ParticipantLeft:
            in >>userName >>localHostName;
            participantLeft(userName,localHostName,time);
            break;

        case FileName: {
            in >> userName >> localHostName >> ipAddress;
            QString clientAddress, fileName;
            in >> clientAddress >> fileName;
            hasPendingFile(userName, ipAddress, clientAddress, fileName);
            break;
        }

        case Refuse: {
            in >> userName >> localHostName;
            QString serverAddress;
            in >> serverAddress;
            QString ipAddress = getIP();

            if(ipAddress == serverAddress)
            {
                server->refused();
            }
            break;
        }
        }
    }
}

// 处理新用户加入
void Widget::newParticipant(QString userName, QString localHostName, QString ipAddress)
{
    bool isEmpty = ui->userTableWidget->findItems(localHostName, Qt::MatchExactly).isEmpty();
    if (isEmpty) {
        QTableWidgetItem *user = new QTableWidgetItem(userName);
        QTableWidgetItem *host = new QTableWidgetItem(localHostName);
        QTableWidgetItem *ip = new QTableWidgetItem(ipAddress);

        ui->userTableWidget->insertRow(0);
        ui->userTableWidget->setItem(0,0,user);
        ui->userTableWidget->setItem(0,1,host);
        ui->userTableWidget->setItem(0,2,ip);
        ui->messageBrowser->setTextColor(Qt::gray);
        ui->messageBrowser->setCurrentFont(QFont("Times New Roman",10));
        ui->messageBrowser->append(unCodec->toUnicode("%1 在线").arg(userName));
        ui->userNumLabel->setText(unCodec->toUnicode("在线人数:%1").arg(ui->userTableWidget->rowCount()));

        sendMessage(NewParticipant);
    }
}

// 处理用户离开
void Widget::participantLeft(QString userName, QString localHostName, QString time)
{
    int rowNum = ui->userTableWidget->findItems(localHostName, Qt::MatchExactly).first()->row();
    ui->userTableWidget->removeRow(rowNum);
    ui->messageBrowser->setTextColor(Qt::gray);
    ui->messageBrowser->setCurrentFont(QFont("Times New Roman", 10));
    ui->messageBrowser->append(unCodec->toUnicode("%1 在 %2 离开!").arg(userName).arg(time));
    ui->userNumLabel->setText(unCodec->toUnicode("在线人数:%1").arg(ui->userTableWidget->rowCount()));
}

// 获取ip地址
QString Widget::getIP()
{
    QList<QHostAddress> list = QNetworkInterface::allAddresses();
    foreach (QHostAddress address, list) {
        if(address.protocol() == QAbstractSocket::IPv4Protocol)
            return address.toString();
    }
    return 0;
}

// 获取用户名
QString Widget::getUserName()
{
    QStringList envVariables;
    envVariables << "USERNAME.*" << "USER.*" << "USERDOMAIN.*"
                 << "HOSTNAME.*" << "DOMAINNAME.*";
    QStringList environment = QProcess::systemEnvironment();
    foreach (QString string, envVariables) {
        int index = environment.indexOf(QRegExp(string));
        if (index != -1) {
            QStringList stringList = environment.at(index).split('=');
            if (stringList.size() == 2) {
                return stringList.at(1);
                break;
            }
        }
    }
    return "unknown";
}

// 获得要发送的消息
QString Widget::getMessage()
{
    QString msg = ui->messageTextEdit->toHtml();

    ui->messageTextEdit->clear();
    ui->messageTextEdit->setFocus();
    return msg;
}


// 发送消息
void Widget::on_sendButton_clicked()
{
    sendMessage(Message);
}




// 获取要发送的文件名
void Widget::getFileName(QString name)
{
    fileName = name;
    sendMessage(FileName);
}

// 传输文件按钮
void Widget::on_sendToolBtn_clicked()
{
    if(ui->userTableWidget->selectedItems().isEmpty())
    {
        QMessageBox::warning(0, unCodec->toUnicode("选择用户"),
                             unCodec->toUnicode("请先从用户列表选择要传送的用户!")/**/, QMessageBox::Ok);
        return;
    }
    server->show();
    server->initServer();
}

// 处理是否接收文件
void Widget::hasPendingFile(QString userName, QString serverAddress,
                            QString clientAddress, QString fileName)
{
    QString ipAddress = getIP();
    if(ipAddress == clientAddress)
    {
        int btn = QMessageBox::information(this,unCodec->toUnicode("接受文件"),
                                           unCodec->toUnicode("文件来自于%1(%2):%3,是否接受?")
                                           .arg(userName).arg(serverAddress).arg(fileName),
                                           QMessageBox::Yes,QMessageBox::No);
        if (btn == QMessageBox::Yes) {
            QString name = QFileDialog::getSaveFileName(0,unCodec->toUnicode("保存文件"),fileName);
            if(!name.isEmpty())
            {
                TcpClient *client = new TcpClient(this);
                client->setFileName(name);
                client->setHostAddress(QHostAddress(serverAddress));
                client->show();
            }
        } else {
            sendMessage(Refuse, serverAddress);
        }
    }
}



// 更改字体族
void Widget::on_fontComboBox_currentFontChanged(QFont f)
{
    ui->messageTextEdit->setCurrentFont(f);
    ui->messageTextEdit->setFocus();
}


// 更改字体大小
void Widget::on_sizeComboBox_currentIndexChanged(QString size)
{
    ui->messageTextEdit->setFontPointSize(size.toDouble());
    ui->messageTextEdit->setFocus();
}

// 加粗
void Widget::on_boldToolBtn_clicked(bool checked)
{
    if(checked)
        ui->messageTextEdit->setFontWeight(QFont::Bold);
    else
        ui->messageTextEdit->setFontWeight(QFont::Normal);
    ui->messageTextEdit->setFocus();
}

// 倾斜
void Widget::on_italicToolBtn_clicked(bool checked)
{
    ui->messageTextEdit->setFontItalic(checked);
    ui->messageTextEdit->setFocus();
}

// 下划线
void Widget::on_underlineToolBtn_clicked(bool checked)
{
    ui->messageTextEdit->setFontUnderline(checked);
    ui->messageTextEdit->setFocus();
}

// 颜色
void Widget::on_colorToolBtn_clicked()
{
    color = QColorDialog::getColor(color, this);
    if (color.isValid()) {
        ui->messageTextEdit->setTextColor(color);
        ui->messageTextEdit->setFocus();
    }
}

void Widget::currentFormatChanged(const QTextCharFormat &format)
{
    ui->fontComboBox->setCurrentFont(format.font());

    // 如果字体大小出错(因为我们最小的字体为12),使用15
    if (format.fontPointSize() < 12) {
        ui->sizeComboBox->setCurrentIndex(3);
    } else {
        ui->sizeComboBox->setCurrentIndex( ui->sizeComboBox
                                          ->findText(QString::number(format.fontPointSize())));
    }
    ui->boldToolBtn->setChecked(format.font().bold());
    ui->italicToolBtn->setChecked(format.font().italic());
    ui->underlineToolBtn->setChecked(format.font().underline());
    color = format.foreground().color();
}

// 保存聊天记录
void Widget::on_saveToolBtn_clicked()
{
    if (ui->messageBrowser->document()->isEmpty()) {
        QMessageBox::warning(0, unCodec->toUnicode("等待"), unCodec->toUnicode("聊天记录为空,无法保存!")/*!*/, QMessageBox::Ok);
    } else {
        QString fileName = QFileDialog::getSaveFileName(this,
                                                        unCodec->toUnicode("保存聊天记录"), unCodec->toUnicode("聊天记录"), unCodec->toUnicode("文本(*.txt);;所有文件(*.*)"));
        if(!fileName.isEmpty())
            saveFile(fileName);
    }
}

// 保存聊天记录
bool Widget::saveFile(const QString &fileName)
{
    QFile file(fileName);
    if (!file.open(QFile::WriteOnly | QFile::Text)) {
        QMessageBox::warning(this, unCodec->toUnicode("保存文件"),
                             unCodec->toUnicode("无法保存文件! %1:\n %2").arg(fileName)
                             .arg(file.errorString()));
        return false;
    }
    QTextStream out(&file);
    out << ui->messageBrowser->toPlainText();

    return true;
}

// 清空聊天记录
void Widget::on_clearToolBtn_clicked()
{
    ui->messageBrowser->clear();
}

// 退出按钮
void Widget::on_exitButton_clicked()
{
    close();
}

// 关闭事件
void Widget::closeEvent(QCloseEvent *e)
{
    sendMessage(ParticipantLeft);
    QWidget::closeEvent(e);
}

//重绘事件
void Widget::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);
    painter.drawPixmap(rect(),QPixmap("://images/moutain.jpg"),QRect());
}

bool Widget::eventFilter(QObject *target, QEvent *event)
{

    if(target == ui->messageTextEdit)		//可替换
            {
                if(event->type() == QEvent::KeyPress)//回车键
                {
                     QKeyEvent *k = static_cast<QKeyEvent *>(event);

                     if(k->key() == Qt::Key_Return)
                     {
                         on_sendButton_clicked();		//替换为需要响应的函数事件,以这里的按钮为例
                         return true;
                     }
                }
            }
            return QWidget::eventFilter(target,event);

}

发布了43 篇原创文章 · 获赞 7 · 访问量 9017

猜你喜欢

转载自blog.csdn.net/qq_41488943/article/details/104918643