Qt 之 基于事件总线的MVC模型

基于事件总线的MVC模型

基于Qt的MVC开源框架

简介

QtMVC

  • 与传统的MVC有区别,V和M不是直接关联的
  • 采用front controller模式,将不同的请求映射成不同的Command, 以便增量式和迭代式开发

模块

FrontController

  • 单例: GitlFrontController
  • 维护 - Table
  • 事件队列(MainThreadEvtQue)
  • 工作线程事件队列(WorkerThreadEvtQue)
  • 事件队列锁(EvtQueMutex)
  • 指令锁(CmdExeMutex), ensure one command execution at one time
/*!
 * \brief The GitlFrontController class
 *  Front controller pattern. It accepts all command requests and invoke corresponding commands
 *  (according to the <command name>-<command class> table)
 */

class GitlFrontController : public GitlModule, public QThread
{
public:
    virtual ~GitlFrontController() {}
    
    /*!
     * \brief detonate Filter out the command request from other request, and then it calls
     *                 onCommandRequestArrive function
     * \param rcEvt
     * \return
     */
    virtual bool detonate(GitlEvent& rcEvt);

    /*!
     * \brief onCommandRequestArrive One command request is captured by front controller.
     *                               By default, it find out the corrensponding command and execute this command immediately.
     *                               You can override this function to to something else.
     *
     * \param rcRequest
     */
    virtual void onCommandRequestArrive(GitlIvkCmdEvt& rcEvt);

    /*!
     * \brief registerCommand register an command with a command name
     * \param cCommandFormat
     * \param pMetaObject
     * \return
     */
    bool registerCommand(const QString cCommandName, const QMetaObject *pMetaObject);

    /*!
     * \brief unregisterAllCommand unregister all command
     */
    void unregisterAllCommand();


    virtual void run();
protected:
    explicit GitlFrontController();

protected:

    /// <command string>-<command class> table
    ADD_CLASS_FIELD( CONCATE(QHash<QString,QMetaObject*>), cCommandTable, getCommandTable, setCommandTable)

    SINGLETON_PATTERN_DECLARE(GitlFrontController)

    ADD_CLASS_FIELD_PRIVATE(QList<GitlEvent*>, pcMainThreadEvtQue)
    ADD_CLASS_FIELD_PRIVATE(QList<GitlEvent*>, pcWorkerThreadEvtQue)
    ADD_CLASS_FIELD_PRIVATE(QMutex, cEvtQueMutex)
    ADD_CLASS_FIELD_PRIVATE(QWaitCondition, cEvtQueNotEmpty)
    ADD_CLASS_FIELD_PRIVATE(QWaitCondition, cEvtQueNotFull)
    ADD_CLASS_FIELD(int, iMaxEvtInQue, setMaxEvtInQue, getMaxEvtInQue)

    ADD_CLASS_FIELD_PRIVATE(QMutex, cCmdExeMutex)   ///< ensure one command execution at one time

};

#endif // GITLFRONTCONTROLLER_H

FrontController内的线程

FrontController 的父类包含QThread, 重写run( )

// 重写run
void GitlFrontController::run()
{
    forever
    {
        /// get one event from the waiting queue
        m_cEvtQueMutex.lock();
        if( m_pcWorkerThreadEvtQue.empty() )
        {
            m_cEvtQueNotEmpty.wait(&m_cEvtQueMutex);
        }
        GitlEvent* pcEvt = m_pcWorkerThreadEvtQue.front();  //It is equivalent to the first(): returns a reference to the first item in the list.
        m_pcWorkerThreadEvtQue.pop_front();                 //It is equivalent to removeFirst():remove the first item in the list.
        m_cEvtQueMutex.unlock();
        m_cEvtQueNotFull.wakeAll();

        /// execute command
        GitlIvkCmdEvt& rcCmdRequestEvt = dynamic_cast<GitlIvkCmdEvt&>(*pcEvt);
        onCommandRequestArrive(rcCmdRequestEvt);
        delete pcEvt;

    }
}

从WorkerThreadEvtQue中获取事件,将事件转换成GitlIvkCmdEvt类型,然后通过onCommandRequestArrive传递至Command层。

AbstractCommand

typedef GitlEventParam GitlCommandParameter;

/*!
 * \brief The GitlAbstractCommand class
 *        All command should inherit this class and implement the execute function
 */
class GitlAbstractCommand : public QObject
{
    Q_OBJECT
public:
    Q_INVOKABLE explicit GitlAbstractCommand(QObject *parent = 0):
        QObject(parent)
    {
        m_bInWorkerThread = false;
    }

    virtual ~GitlAbstractCommand()
    {
    }

    /*!
     * \brief Execute All subclass should reimplement this function.
     * \param rcInputArg this parameter is from UI(GitlView)
     * \param rcOutputArg this parameter will be delivered to UI(GitlView)
     * \return
     */
    virtual bool execute(GitlCommandParameter &rcInputArg, GitlCommandParameter &rcOutputArg)
    {
        Q_UNUSED(rcInputArg)
        Q_UNUSED(rcOutputArg)
        qCritical() << "Please reimplement <GitlAbstractCommand::execute>";
        return false;
    }

    /*!
     * \brief Dose this command designed to execute in worker thread.
     * For computational intensive command, it should be set to ture.
     * *****If it involves GUI classes creation, it MUST BE SET TO FALSE*****
     */
    ADD_CLASS_FIELD(bool, bInWorkerThread, getInWorkerThread, setInWorkerThread)

};

#endif // GITLABSTRACTCOMMAND_H
  • execute

1.所有的子类都需要实现这个接口
2.GitlEventParam 类,在EventBus模块中定义, 是用来设置事件参数的接口, 参考下节GitLEventParam
3.入参: rcInputArg, 从UI(GitlView)而来。
4.出参: rcOutputArg, 返回给UI(GitVIew)

GitlEventParam

这个是事件参数,其实在EventBus模块中定义的一个类

  • gitleventparam.h
class GitlEventParam
{
public:
    GitlEventParam();

    /*!
     * \brief hasParameter if this event carries a specific parameter
     * \param strParam parameter name
     * \return
     */
    bool hasParameter(QString strParam) const;

    /*!
     * \brief getParameter get the value of a specific parameter
     * \param strParam parameter name
     * \return parameter value, if it does not exist, return a default-constructed QVariant
     */
    QVariant getParameter(const QString& strParam ) const;

    /*!
     * \brief setParameter set the value of a  specific parameter
     * \param strParam parameter name
     * \param rvValue parameter value
     * \return
     */
    bool setParameter(const QString& strParam, const QVariant& rvValue);


    ADD_CLASS_FIELD_PRIVATE( CONCATE(QMap<QString,QVariant>), cParameters)    ///< parameters name-value pair

};

#endif // GITLEVENTPARAM_H

这里的参数存储在m_cParameters成员中,该成员是一个QMap<QString, QVariant>类型
即:

private:
QMap<QString, QVariant>   m_cParameters;
  • gitleventparam.cpp
GitlEventParam::GitlEventParam()
{
}

bool GitlEventParam::hasParameter(QString strParam) const
{
    return m_cParameters.contains(strParam);
}

QVariant GitlEventParam::getParameter(const QString& strParam ) const
{
    QVariant rvValue;
    if( m_cParameters.contains(strParam) )
    {
        rvValue = m_cParameters[strParam];
    }
    else
    {
        qWarning() << QString("Parameter %1 NOT found.").arg(strParam);
    }
    return rvValue;
}

//设置参数
bool GitlEventParam::setParameter(const QString& strParam, const QVariant& rvValue)
{
    m_cParameters[strParam] = rvValue;
    return true;
}

继承AbstractCommand的实际Command

应用中的Command, 这里以FirParamCommand为例, 操控Model , 并且将结果写入output parameter, 该output parameter会传递到view层。

/// command, it manipulates the model and writes the result to output parameter. The output parameter will
/// be pass to view automatically.
class FirParamCommand : public GitlAbstractCommand
{
    Q_OBJECT
public:
    /// Q_INVOKABLE is necessary for constructor
    Q_INVOKABLE explicit FirParamCommand(QObject *parent = 0):GitlAbstractCommand(parent) 
    {
	
	}
    
    bool execute(GitlCommandParameter &rcInputArg, GitlCommandParameter &rcOutputArg)
    {
        QString strDataToCommand = rcInputArg.getParameter("data_to_command").toString();  //返回data_to_command参数名称的实际内容
        TestModel::getInstance()->setDataInModel(strDataToCommand);     //将该实际值传递给TestModel
        rcOutputArg.setParameter("data_to_view", strDataToCommand);
        return true;
    }
};

#endif // TESTCOMMAND_H

注意到在command的execute中,TestModel是一个实际的Model对象,定义如下:

#ifndef TESTMODEL_H
#define TESTMODEL_H
#include "gitldef.h"
#include "gitlmodel.h"
/// model
class TestModel: public GitlModel<TestModel>
{
    ADD_CLASS_FIELD(QString, strDataInModel, getDataInModel, setDataInModel)

protected:
    TestModel() {}
    friend class GitlModel<TestModel>;
};

#endif // TESTMODEL_H

GitlView

  • 继承GitlModule
    这里的GitlView 也是集成GitlModule, 通过Module, 可以获得ModuleDelegate的能力。这样,继承GitlView的控件, 可以利用subscribeToEvtBNyName() 接口将关注的事件和detonate绑定在一起。

  • listenToParams
    监听参数,将该监听参数和回调函数绑定在一起

  • 回调函数表
    在listenToParams接口内,将rcParams和rcCallback 存储至列表中

class GitlView : public GitlModule
{
public:
    GitlView(GitlEventBus* pcEventBus = NULL);
    virtual ~GitlView() {}
    bool detonate(GitlEvent& rcEvt);    
    
    bool listenToParams(const QString& rcParams, const UIUpdateCallback& rcCallback);
    bool listenToParams(const QStringList& rcParams, const UIUpdateCallback& rcCallback);
    bool unlistenToParams(const QStringList& rcParams);
    bool isListenToParams(const QStringList& rcParams) const;
    ADD_CLASS_FIELD_PRIVATE( CONCATE(QMap<ParamNameList,UIUpdateCallback>), pafCallbacks )
};

而窗口mainwindow 继承GitlView, 在初始化时,调用listenToParams()

    listenToParams("data_to_view", MAKE_CALLBACK(MainWindow::onUIUpdate));   //将返回的参数名称“data_to_view”和onUIUpdate接口绑定在一起
MainWindow
  • mainwindow.h
class MainWindow : public QMainWindow, public GitlView
{
    Q_OBJECT
    
public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

    /// it receives the result from commands
    void onUIUpdate(GitlUpdateUIEvt& rcEvt);
    
private slots:
    void on_mtTestButton_clicked();

private:
    Ui::MainWindow *ui;
};
  • mainwindow.cpp
MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    listenToParams("data_to_view", MAKE_CALLBACK(MainWindow::onUIUpdate));
}

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

//参数传递到view层后的回调函数
void MainWindow::onUIUpdate(GitlUpdateUIEvt &rcEvt)
{
    QString strDataInView = rcEvt.getParameter("data_to_view").toString();
    ui->mtTestLabel->setText(strDataInView);
    qDebug() << strDataInView;
}

void MainWindow::on_mtTestButton_clicked()
{
    /// event (in real case, this event should be dispatch from user interface, i.e. the views)
    GitlIvkCmdEvt cRequestEvt("show_string_command");              //command_name 参数的value = "show_string_command"
    cRequestEvt.setParameter("data_to_command", "Hello GitlMVC");  //data_to_command 参数的value = "Hello GitlMVC"
    cRequestEvt.dispatch();                                        //将事件传递到GitlBus总线上
}

下图可以看到,在cRequestEvt事件中有两个参数

param nameparam value
command_name“show_string_name”
data_to_command“Hello GitlMVC”

自定义命令事件的结构

GitlIvkCmdEvt

从mainwindow中点击按钮的槽函数可以看出,当点击Button后,发出自定义的Command Event, 该CommandEvent继承GitlEvent, 并且
GitlEvent的类内,有设置参数- 值(params-value)的接口, 此处params 为 command_name , 而value 为该Command的名字

  • GitlIvkCmdEvt.h
class GitlIvkCmdEvt : public GitlEvent
{
    /// virtual copy pattern, please add this macro to all the subclass
    CLONABLE(GitlIvkCmdEvt)

public:
    GitlIvkCmdEvt(const QString& strCommandName);

    QString getCommandName();
    void setCommandName(const QString& strCommandName);
};
  • GitlIvkCmdEvt.cpp
GitlIvkCmdEvt::GitlIvkCmdEvt(const QString& strCommandName) :
    GitlEvent(GITL_EXE_COMMAND_REQUEST_EVENT)
{
    GitlEvent::setParameter("command_name", strCommandName);
}

QString GitlIvkCmdEvt::getCommandName()
{
    return GitlEvent::getParameter("command_name").toString();
}

void GitlIvkCmdEvt::setCommandName(const QString& strCommandName)
{
    GitlEvent::setParameter("command_name", strCommandName);
}

GitlUpdateUIEvt
  • 更新UI 事件消息类

  • 这个类和GitlIvkCmdEvt很相似,一个是命令事件类,一个是更新UI事件类,都继承自GitlEvent

  • updateUIEvt.h

class GitlUpdateUIEvt : public GitlEvent
{
    /// virtual copy pattern, please add this macro to all the subclass
    CLONABLE(GitlUpdateUIEvt)

public:
    GitlUpdateUIEvt();

    QString getCommandName();
    void setCommandName(const QString& strCommandName);
};

#endif // GITLUPDATEUIEVT_H
  • updateUIEvt.cpp
GitlUpdateUIEvt::GitlUpdateUIEvt() :
    GitlEvent(GITL_UPDATE_UI_REQUEST_EVENT)
{
}

QString GitlUpdateUIEvt::getCommandName()
{
    return getParameter("command_name").toString();
}

void GitlUpdateUIEvt::setCommandName(const QString& strCommandName)
{
    setParameter("command_name", strCommandName);
}

UIUpdateEvt事件信息

数据流

数据流

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值