基于MFC的MVC框架的编程实践

1. UI框架

Windows桌面应用的开发,C++语言依然占据着主流。用C++开发桌面应用,面临UI框架的选择,主流的C++ UI框架有MFC,DUI,Qt,cef。它们各有各的优势。MFC,制作简单的UI非常容易,因为MFC并不完全开源,所以想深度美化MFC UI,难度非常大,多用于一些对UI效果要求不高的工业控制软件。DUI(Direct UI),即直接绘制的UI,所有的控制部件都是通过GDI/GDI+绘制的,因为其开源,如果想自定义一些控件,也会更容易一点。像360,钉钉,微信,有道等桌面应用程序都是基于DUI开发的UI。Qt功能非常强大,其支持不同的UI开发模式,有传统的Widget,还有基于前端技术的UML,另外,还能跨平台。但是Qt因为其功能强大,所以其附带的库非常多,开发框架比较重,所以一些对UI要求不是太高的软件,并且不追求跨平台的桌面软件,可能不会选择Qt。WPS是Qt开发的,并且做到跨平台。cef,就是Chrome浏览器的显示内核库。如果UI相使用Web开发技术,适量和底层通信,就可以使用cef框架。

2. MVC

MVC 设计模式一般指 MVC 框架,M(Model)指数据模型层,V(View)指视图层,C(Controller)指控制层。使用MVC的目的是将UI,数据,业务逻辑分开,以解耦这三个模块的关系。这样就可以做到在保证业务逻辑不变的前提下,可以自由更换UI和数据模式,提升代码的可维护性。
基于MFC的MVC框架的编程实践_第1张图片
上图是MVC各模块的关系图,但是这种引用的方式并不够灵活,在某些框架下,适用性是够好。但是在MFC框架下,其实并不完善。如Model中的数据有更新,那么此时怎么办呢?难度View定时去查询数据,这么做的效率又太低了。
为了解决上述问题,可以将MVC稍微变化一下,如下图的MVP。数据的什么时候响应由Presenter来主要完成,而不是由View去不停地询问再显示,这样可以提高效率。在MFC中,可以通过消息机制,将要显示的内容通知到UI。
基于MFC的MVC框架的编程实践_第2张图片
上图的Presenter模块功能太多了,使用起来可能不够方便。如:view上有编辑框,编译框每输入一个符号,是不是就必须立即通过Presenter响应并存放在Model中呢?那么编辑框连续输入多个字符,那是不是必须响应多次呢?使用起来就不方便了。使用MVC的首要目的是将代码分开,至于各模块之间的交互,选择一个更适合自己项目的数据流方式。

3. MFC

微软基础类库(英语:Microsoft Foundation Classes,简称MFC)是微软公司提供的一个类库(class libraries),以C++类的形式封装了Windows API,并且包含一个应用程序框架,以减少应用程序开发人员的工作量。MFC的UI开发框架,主要包括:基于对话框、基于单文档和基于多文档。
单文档和多文档模式,其实已经是利用了MVC框架。Document、View、Frame,正好对应MVC。一些对UI要求较低的工具,常使用基于对话框的开发模板。经常看到一些开源代码,其所有的功能基于都写在了CXXXDlg.cpp文件中,这样导致业务逻辑、数据和UI完全耦合在一起,再加上多线程,动不动还会出现主线程死锁的情况。这样的项目,越维护到后面,累积的问题越大,越让人痛苦。

4. MFC实践MVC

下面我们实现一个最简单的加法计算的基于Dialog的工程,用以实践MVC框架。

4.1. View

View如下图,代码在CCalcDlg.h/CCalcDlg.cpp中。
基于MFC的MVC框架的编程实践_第3张图片
其头文件中引入两个接口类,用来设置"+"左右的数据和获取结果。Run Action直接转发到Controller模块,然后建立一个消息响应来更新数据。其代码基本设计如下:

CCalcDlg::CCalcDlg(ICalcModel* pModel, ICtrl* pCtrl) : m_pModel(pModel), m_pCtrl(pCtrl)
{
    ......
}

void CCalcDlg::OnCompute()
{	
	UpdateData(TRUE);
	m_pModel->SetLeft(m_nLeft);
	m_pModel->SetRight(m_nRight);
    // 直接转发Action
	m_pCtrl->OnCompute();
}

// 建立消息响应更新结果
ON_MESSAGE(WM_UPDATE_RUSULT, &CCalcDlg::OnUpdateResult)

LRESULT CCalcDlg::OnUpdateResult(WPARAM wParam, LPARAM lParam)
{
	int nRes = *(int*)(wParam);
	m_nRus = nRes;
	UpdateData(FALSE);

	return 1;
}

4.2. Model

通过View,我们可以看到其中有3个数据,数据都以接口的形式提供,"+“左右的数据需要提供设置的接口,”="右边的数据则需要读的接口。设计一个给View模块使用的接口类如下:

class ICalcModel
{
public:
    virtual void SetLeftValue(int val) = 0;
    virtual void SetRightValue(int val) = 0;
    virtual int GetResult() = 0;
};

另外还需要设计一个接口类给Controller模块使用,"+“左右的数据需要提供读取的接口,”="右边的数据则需要设置的接口。

class ICtlModel
{
public:
    virtual int GetLeftValue() = 0;
    virtual int GetRightValue() = 0;
    virtual void SetResult(int val) = 0;
};

真实的数据模块实现上述接口,并且完成数据的存储。

class ArithmeticModel : public CCalcModel, public ICtlModel
{
public:
    virtual void SetLeftValue(int val) override
    {
        m_left = val;
    }
    virtual void SetRightValue(int val) override
    {
        m_left = val;
    }
    virtual int GetResult() override
    {
        return m_result;
    }
    virtual int GetLeftValue() override
    {
        return m_left;
    }
    virtual int GetRightValue() override
    {
        return m_right;
    }
    virtual void SetResult(int val) override
    {
        m_result = val;
    }

private:
    int m_left = 0;
    int m_right = 0;
    int m_result = 0;
};

4.3. Controller

控制模块主要接口View的消息响应,并通知View更新结果,然后从Model模块获取数据,完成计算并设置结果。为了更好的解耦,所有的编程都是基于接口实现的。Cotroller接口:

class ICtrl
{
public:
    virtual void Init(HWND hWnd) = 0;
    virtual void OnCompute() = 0;
};

具体的业务逻辑代码:

class CArithmetic : public ICtrl
{
public:
    CArithmetic(ICtlModel* pModel) : m_pModel(pModel){}
    virtual void Init(HWND hWnd) override
    {
        m_hWnd = hWnd;
    }

    virtual void OnCompute() override
    {
        int result = m_pModel->GetLeft() + m_pModel->GetRight();
        m_pModel->SetResult(result);
        // 通知View更新结果
        ::SendMessage(m_hWnd, WM_UPDATE_RESULT, 0, 0);
    }
};

4.4. CXXApp模块

CXXApp模块负责总的调用,其主要代码如下:

ArithmeticModel model;
CArithmetic arith(&model);
CCalcDlg dlg(&model, &arith);
dlg.DoModel();

4.5. 下载

示例代码下载

你可能感兴趣的:(VC++,Windows编程,mfc,mvc,c++)