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);

}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章