C++设计模式之观察者者模式:多线程拷贝文件,能够支持多种拷贝文件进度通知(QT平台)

C++设计模式之观察者者模式:多线程拷贝文件,能够支持多种拷贝文件进度(QT平台)

一、观察者模式

最近在学习设计模式,学习到了观察者模式。观察者模式的定义说明,我就不赘述了。网上有很多资料。直接把我的代码展示给大家,让大家了解观察者模式的用法。

二、实际应用
拷贝文件是一个很常见功能,拷贝文件时,一般都会有进度条的。假如客户有一个新的需求,比如拷贝完成以后有一个提示音,类似于迅雷下载文件完成以后的提示音。用观察者模式就可以很好解决这个新的需求,上代码。

三、抽象的通知类(class IProgress)
这个抽象类用来通知拷贝文件的进度
头文件:

#ifndef IPROGRESS_H
#define IPROGRESS_H


class IProgress { public:
    IProgress();
    virtual ~IProgress(){}
    virtual void DoProgress(float value) = 0;

#endif // IPROGRESS_H

源文件:

#include "iprogress.h"

IProgress::IProgress()
{

}`

这个类定义了一个纯虚函数:virtual void DoProgress(float value) = 0来处理拷贝文件进度。
四、拷贝文件类(class CopyFile)
头文件:

#ifndef COPYFILE_H
#define COPYFILE_H
#include <list>
class IProgress;
class CopyFile
{
public:
    CopyFile();
    ~CopyFile();
    void addNotice(IProgress *notice);
    void removeNotice(IProgress *notice);
    void doCopy(const char *srcFileName, const char *descFileName);
protected:
    void onProgress(float value);
private:
   std::list<IProgress*> m_listIProgress;

};

#endif // COPYFILE_H

源文件:

#include "copyfile.h"
#include "iprogress.h"
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
using namespace std;
CopyFile::CopyFile()
{
}

CopyFile::~CopyFile()
{
}

void CopyFile::addNotice(IProgress *notice)
{
    m_listIProgress.push_back(notice);
}

void CopyFile::removeNotice(IProgress *notice)
{
    m_listIProgress.remove(notice);
}

void CopyFile::doCopy(const char *srcFileName, const char *descFileName)
{
    FILE *fp1,*fp2;
    char res[1024] = {0};
    long long len;
    fp1 = fopen(srcFileName,"rb");
    fp2 = fopen(descFileName,"wb");
    fseek(fp1,0,SEEK_END);
    len = _ftelli64(fp1);
    fseek(fp1,0,SEEK_SET);
    //printf("len:%d",len);
    long long count = len /1024;
    long long remain = len%1024;
    for(long long i=0;i<count;i++)
    {
        fread(res, 1, sizeof(res), fp1);
        fwrite(res, 1, sizeof(res), fp2);
        onProgress(((float)((i+1)*sizeof(res)))/(float)len);

    }
    memset(res,0,sizeof(res));
    if(remain)
    {
        fread(res, 1, remain, fp1);
        fwrite(res, 1, remain, fp2);
        onProgress(1);
    }
    //fseek(fp2,0,SEEK_SET);
    fclose(fp1);
    fclose(fp2);
}

void CopyFile::onProgress(float value)
{
    list<IProgress*>::iterator itor = m_listIProgress.begin();
    while(itor != m_listIProgress.end())
    {
        (*itor)->DoProgress(value);
        ++itor;
    }
}

拷贝文件类定义了一个拷贝文件函数,拷贝文件函数我用的是标准文件IO,进行文件复制操作。我这个程序是用QT开发的,为什么我不用QT的接口操作,而用标准文件IO操作,是因为这样设计可以最大程度把代码复用,我这个类可以放在其它C++平台使用编译。然后还定义了一个存放通知者的容器。通过这个通知者容器去调用通知者各自的处理进度的方法。而我这个类不用具体去关心进度如何显示,只需要调用对应的接口就行了,这样就和进度显示的代码解耦合了。
五、普通进度条对话框类(class Dialog_progressBar)
头文件:

#ifndef DIALOG_PROGRESSBAR_H
#define DIALOG_PROGRESSBAR_H

#include "iprogress.h"

#include <QDialog>

namespace Ui {
class Dialog_progressBar;
}

class Dialog_progressBar : public QDialog, public IProgress
{
    Q_OBJECT

public:
    explicit Dialog_progressBar(QWidget *parent = 0);
    ~Dialog_progressBar();
    virtual void DoProgress(float value);
signals:
    void sig_Progress(float value);
private:
    Ui::Dialog_progressBar *ui;
};

#endif // DIALOG_PROGRESSBAR_H
#include "dialog_progressbar.h"
#include "ui_dialog_progressbar.h"
#include <QDebug>
Dialog_progressBar::Dialog_progressBar(QWidget *parent) :
    QDialog(parent),
    ui(new Ui::Dialog_progressBar)
{
    ui->setupUi(this);
    setModal(false);
}

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

void Dialog_progressBar::DoProgress(float value)
{
    //ui->progressBar->setValue(value*100);
    int progressBarValue = value*100;
    QMetaObject::invokeMethod(ui->progressBar, "setValue",
                                Q_ARG(int, progressBarValue));
//    ui->progressBar->setValue(progressBarValue);
    if(!this->isVisible())
    {
        QMetaObject::invokeMethod(this, "show");
    }
    if(value == 1)
    {
        QMetaObject::invokeMethod(this, "hide");
        //this->hide();
    }
}

这里没什么好说的,就是重新实现了void DoProgress(float value)这个虚函数。就是要注意一下,这里由于我用到了多线程去处理拷贝文件,这个void DoProgress(float value)在另外一个线程执行的。而进度条对话框是在主线程创建的,所以不能直接调用界面的槽函数,直接调用程序会崩溃的。只能通过 QMetaObject::invokeMethod这种方式去调用。
六、拷贝完成后音效类(class MusicNotice)
头文件:

#ifndef MUSICNOTICE_H
#define MUSICNOTICE_H

#include "iprogress.h"

class QMediaPlayer;

class MusicNotice : public IProgress
{
public:
    MusicNotice();
    ~MusicNotice();
    virtual void DoProgress(float value);
private:
    QMediaPlayer *m_pPlayer;
};

#endif // MUSICNOTICE_H

源文件:

#include "musicnotice.h"
#include <QMediaPlayer>

MusicNotice::MusicNotice()
{
    m_pPlayer = new QMediaPlayer();
}

MusicNotice::~MusicNotice()
{
    m_pPlayer->deleteLater();
}


void MusicNotice::DoProgress(float value)
{
    if(value == 1)
    {
        m_pPlayer->setMedia(QUrl::fromLocalFile("./sound.mp3"));
        m_pPlayer->setVolume(50);
        m_pPlayer->play();
    }
}

实现virtual void DoProgress(float value);下载完成后播放音乐。
七、拷贝管理类,配合QThread使用(CopyObject)
头文件:

#ifndef COPYOBJECT_H
#define COPYOBJECT_H

#include "copyfile.h"
#include "dialog_progressbar.h"
#include <QObject>

class CopyObject : public QObject
{
    Q_OBJECT
public:
    explicit CopyObject(QObject *parent = 0);
    inline CopyFile *getCopyFileObj(){return &m_copyFile;}
public slots:
    void doCopy(QString srcFileName, QString descFileName);
signals:

public slots:
private:
    CopyFile m_copyFile;
};

#endif // COPYOBJECT_H

源文件:

#include "copyobject.h"
#include "copyfile.h"
CopyObject::CopyObject(QObject *parent) : QObject(parent)
{

}

void CopyObject::doCopy(QString srcFileName, QString descFileName)
{
    m_copyFile.doCopy(srcFileName.toLocal8Bit().data(),
                      descFileName.toLocal8Bit().data());
}

这个主要是配和QT的多线程使用而定义一个继承QObject的类。
八、主界面类
头文件:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include "copyfile.h"
#include "iprogress.h"
#include <QMainWindow>
#include <QThread>

class QProgressBar;
class IProgress;
namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();
signals:
    void sig_copyFile(const QString& srcFileName, const QString& descFileName);
private slots:
    void on_pushButton_import_clicked();

    void on_pushButton_copy_clicked();

private:
    Ui::MainWindow *ui;
    QThread copyThread;
    IProgress *m_pNotice = nullptr;
    IProgress *m_pDialog_progressBar;
};

#endif // MAINWINDOW_H

源文件:

#include "copyobject.h"
#include "mainwindow.h"
#include "musicnotice.h"
#include "ui_mainwindow.h"
#include "dialog_progressbar.h"
#include <QMessageBox>
#include <QFileDialog>
#include <QProgressBar>
MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    m_pNotice = new MusicNotice;
    m_pDialog_progressBar = new Dialog_progressBar(this);
    CopyObject *pCopyObject = new CopyObject;
    if(pCopyObject->getCopyFileObj())
    {
        pCopyObject->getCopyFileObj()->addNotice(m_pDialog_progressBar);
        pCopyObject->getCopyFileObj()->addNotice(m_pNotice);
    }
    pCopyObject->moveToThread(&copyThread);
    connect(&copyThread, &QThread::finished, pCopyObject, &QObject::deleteLater);
    connect(this, &MainWindow::sig_copyFile, pCopyObject, &CopyObject::doCopy);
    copyThread.start();
}

MainWindow::~MainWindow()
{
    delete ui;
    delete m_pNotice;
    copyThread.quit();
    copyThread.wait();
}

void MainWindow::on_pushButton_import_clicked()
{
    QString fileName = QFileDialog::getOpenFileName(this, tr("打开文件"),
                                                     "./",
                                                     tr(" (*.*)"));
    if(!fileName.isEmpty())
    {
        ui->lineEdit_src->setText(fileName);
    }

}

void MainWindow::on_pushButton_copy_clicked()
{
    QString dir = QFileDialog::getExistingDirectory(this, tr("打开文件夹"),
                                                      "./",
                                                      QFileDialog::ShowDirsOnly
                                                      | QFileDialog::DontResolveSymlinks);
    if(!dir.isEmpty())
    {
        ui->lineEdit_des->setText(dir);
        if(ui->lineEdit_src->text().isEmpty())
             QMessageBox::warning(this,tr("拷贝出错"),tr("未选择源文件"));
        else
        {
            QString descFileName = ui->lineEdit_des->text() + "/copyfile";
            emit sig_copyFile(ui->lineEdit_src->text(),descFileName);
        }
    }
    else
    {
        QMessageBox::warning(this,tr("拷贝出错"),tr("未选择目标文件夹"));
    }

}

main.cpp:

#include "mainwindow.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();

    return a.exec();
}

这里讲一下使用多线程的好处,由于拷贝文件是一个耗时操作,尤其是拷贝大文件,很容易堵塞界面,用多线程就能很好的解决这个问题。

八、界面
在这里插入图片描述
在这里插入图片描述

九、实际运行效果
在这里插入图片描述
复制完成的音乐播放没办法在网页展示出来,大家可以把我代码拷贝过去用QT运行一下,看一下实际的运行效果。
十、总结
其实学设计模式主要要学会如何把代码模块块、类化,一个功能一个类,每个类之间不要有很强的联系,不要把所有的功能都集中在一个类,尤其是集中在主界面类。界面就是界面,只是用来和用户进行交互的,不要把逻辑功能都耦合在界面类里面,这样你的代码就很不好复用了。
并且我们看到用到观察模式以后,多个进度如何处理的类可以交给多人去写,并行进行代码编写工作,比如在一个团队里面,程序框架师把这个观察者模式的框架搭起来了,员工甲可以写进度条动画显示,员工乙可以写音效进度条。两个人可以同时开发,互不干扰。极大的节省了开发时间,提高了工作效率。还有就是用户有一个新的进度通知显示需求,对于已经发布的程序来说,不可能重新修改原有的代码,重新编译部署测试,再发布。使用观察者模式就可以只增加一个类,把这个类编译成动态库,再通过修改程序的配置文件来动态加载新增的动态库,这个就极大的扩展了程序,而不需要重新编译主程序界面代码,重新部署编译发布主程序界面,极大的降低了每个功能和主界面程序的耦合性。

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: qt线文件拷贝进度条程序的下载,在这里我将简单介绍一下实现的思路与步骤。 首先,在使用Qt进行多线文件拷贝进度显示之前,我们需要引入Qt的相关头文件,例如QFile、QThread等。 接下来,我们需要创建一个自定义的线程类,继承自QThread。在这个线程类中,我们需要重写run()函数,在run()函数中实现文件拷贝操作。具体的拷贝操作可以使用QFile类中的copy()函数。 为了实现进度条的显示,我们可以通过信号与槽机制来实现。我们可以在自定义的线程类中定义一个信号,用来传递文件拷贝进度。可以使用QFile类的size()函数获取文件的总大小,然后在每次拷贝一个块的时候,发送一个信号,参数为已拷贝的字节数,通过计算已拷贝字节数和文件总大小的比例,来计算拷贝进度。 在主线程或主窗口类中,我们需要创建一个进度条控件用于显示拷贝进度。然后连接自定义的线程中的信号与主线程中的槽函数,以更新进度条的进度。 在点击开始拷贝按钮的时候,我们需要创建一个自定义的线程对象,并调用start()函数启动线程。然后在槽函数中处理接收到的信号,更新进度条的进度。 以上就是实现qt线文件拷贝进度条程序的简要步骤。通过多线程的方式进行文件拷贝,可以使程序在拷贝较大文件时不会出现界面卡顿的情况,同时通过进度条可以直观地显示拷贝进度。 ### 回答2: 对于Qt线文件拷贝进度条程序的下载,我们可以采用如下步骤: 1. 首先,在Qt中创建一个新的工程,添加一个进度条控件和一个按钮控件。 2. 在主线程中点击按钮事件的槽函数中,创建一个新的子线程,并传递需要拷贝文件路径和目标路径作为参数。 3. 在子线程中,使用Qt文件操作类QFile进行文件拷贝。通过打开源文件,创建目标文件并复制源文件内容到目标文件中,实现文件拷贝的功能。 4. 在拷贝文件的过程中,可以在子线程中实时更新进度条的值,并通过信号和槽机制将进度条的值传递给主线程。 5. 在主线程中,接收并处理来自子线程的进度条值变化的信号,实时更新界面上的进度条的显示。 6. 当文件拷贝完成后,子线程发送一个完成信号给主线程,主线程处理该信号,提示文件拷贝已完成。 7. 最后,通过Qt的信号和槽机制,将按钮的点击事件和文件拷贝的相关操作进行连接,使得点击按钮时可以启动文件拷贝进度条程序的下载。 通过以上步骤,我们可以实现一个基于Qt线程、带有进度条的文件拷贝程序。用户可以通过点击界面上的按钮来触发拷贝过程,并实时显示拷贝进度。这样可以提高文件拷贝的效率,并提供更好的用户体验。 ### 回答3: Qt是一种跨平台的C++应用程序开发框架,它提供了丰富的功能库和工具,可以帮助开发者快速构建各种类型的应用程序。在Qt中,我们可以使用多线程来实现文件拷贝功能,并在界面上显示一个进度条,以便用户能够清楚地了解文件拷贝进度。 首先,我们需要创建一个继承自QThread的自定义类,用于执行文件拷贝的操作。在这个类中,我们可以重写run()函数,在函数中执行拷贝文件的逻辑。在拷贝文件的过程中,我们可以通过信号机制来实时发送拷贝进度给主线程。 在主线程中,我们可以创建一个进度条控件,并与自定义的文件拷贝线程进行连接。当文件拷贝线程发送进度信号时,我们可以更新进度条的值,让用户能够实时观察文件拷贝进度。 同时,我们还可以提供一些用户交互的功能,比如添加文件、选择目标文件夹等。这些功能可以通过Qt的信号与槽机制来实现,用户可以触发相应的信号,然后我们在槽函数中执行相应的操作,比如选择文件,选择目标文件夹等。 除此之外,为了防止文件拷贝操作过程中出现的各种异常情况,我们还可以在文件拷贝线程中进行错误处理和异常捕获,并通过信号与槽机制向主线程发送错误信息,以便及时通知用户。 总而言之,通过Qt线程和信号与槽机制,我们可以简单地实现一个多线文件拷贝进度条程序的下载功能。这样用户可以在界面上直观地看到文件拷贝进度,并能够通过界面交互实现文件选择和目标文件夹选择等功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值