[WTL/Win32]_[中级]_[MVP架构在实际项目中应用的地方]

场景

  1. 在开发WindowsmacOS的界面软件时,Windows用的是WTL/Win32技术,而macOS用的是Cocoa技术。而两种技术的本地语言一个主打是C++,另一个却是Object-c。界面软件的源码随着项目功能增多而增多,这就会给同步WindowsmacOS的功能造成很大负担。 有大部分的调用底层逻辑,界面数据搜索等代码逻辑会重复写两遍,用C++Object-C写。初步估计这部分代码至少占界面代码的50%。这无疑会影响产品发布速度,影响软件质量(可能有些逻辑两个平台不能完全一样),增大了开发的工作量,这部分时间无疑是浪费了的。有什么办法可以把这部分代码进行重用?

说明

  1. 界面代码往往就是获取数据,显示数据,之后点击按钮处理数据。这就需要获取数据,处理数据需要和平台相关的界面代码剥离,显示数据部分依赖平台的框架进行数据绘制。 按照这种逻辑,最好的办法就是数据获取和处理使用C++语言处理,处理这些和界面无关的逻辑代码。 当然如果有特殊情况也可以用.mm文件(这种是Object-CC++混编的文件后缀)来调用Object-C平台接口处理。比如presenter_mac.mmpresenter_win.cpp

  2. 界面架构MVP架构可以满足这个要求。当然这和Object-cC++可以混编有些关系,如果是Swift语言,不能直接调用C++类,需要通过桥接调用Object-C,再通过Object-C调用C++来处理。 使用C++处理跨平台逻辑,最好使用C++11以上标准,因为这个标准多了很多有用的库和特性节省MVP架构的很多代码,如lambda,thread,functional等。

  3. 看看MVP架构的分层,主要是以下三层。很好理解,Presenter作为ViewModel的通讯层,起到了连接视图和底层模型逻辑处理的作用,也起到了跨平台时处理不同平台界面框架获取数据的本地实现的桥梁。

View <-> Presenter <-> Model
  1. 这里说的Presenter可根据界面语言的实现进行基于本地的实现,比如macOSPresenter层需要处理NSString作为字符串存储的数据,而在Windows下需要处理std::wstring作为字符串处理的数据。这些数据如果传递给Model,那么需要转换为std::stringUTF8编码进行处理。

  2. 在这个三层模型里,依赖关系需要注意设计, 切不可以互相依赖

    • View依赖Presenter接口,View里有Presenter的成员变量,Presenter的实例需要通过方法注入。这样如果View更换不同的Presenter也可以通过注入的方法。 View通过成员变量presenter_调用它的方法。异步处理通过传入std::function绑定的方法给Presenter,当Presenter处理完之后再调用绑定的方法,类似回调函数的处理。
    
    private:
    	shared_ptr<Presenter> presenter_ = nullptr;
    
    ...
    void CView::setPresenter(shared_ptr<Presenter> presenter)
    {
    	presenter_ = presenter;
    }
    
    • Presenter依赖Model接口,注意说的接口是根据实际项目的复杂度来定义虚拟类接口,如果实现只有一个,定义一个普通类就行。Presenter通过成员变量model_调用它的方法。异步处理通过传入std::function绑定的方法给Model,当Model处理完之后再调用绑定的方法,类似回调函数的处理。
    protected:
    	shared_ptr<Model> model_;
    
    ...
    void Presenter::setModel(shared_ptr<Model> model)
    {
    	model_ = model;
    }
    
    

例子

  1. 以下的虚拟listviewMVP架构的实现。

View.h

// View.h : interface of the CView class
//
/

#pragma once

#include <utility>
#include <string>
#include <vector>
#include <memory>
#include <atlmisc.h>
#include <atlctrls.h>
#include <atlctrlx.h>
#include <GdiPlus.h>

using namespace std;

class Presenter;

enum
{
	kMyButtonId = WM_USER+1,
	kMyButtonId2,
	kMyButtonId3,
	kMyListViewId
};

class CView : public CWindowImpl<CView>
{
public:
	DECLARE_WND_CLASS(NULL)

	BOOL PreTranslateMessage(MSG* pMsg);

	BEGIN_MSG_MAP_EX(CView)
		MSG_WM_CREATE(OnCreate)
		MESSAGE_HANDLER(WM_PAINT, OnPaint)
		NOTIFY_HANDLER(kMyListViewId,NM_CLICK,OnNMClickListResult)
		NOTIFY_HANDLER(kMyListViewId,LVN_GETDISPINFO,OnGetListViewData)
		NOTIFY_HANDLER(kMyListViewId,LVN_ODCACHEHINT,OnPrepareListViewData)
		NOTIFY_HANDLER(kMyListViewId,LVN_ODFINDITEM,OnFindListViewData)
		COMMAND_RANGE_HANDLER_EX(kMyButtonId,kMyButtonId3,OnCommandIDHandlerEX)
		REFLECT_NOTIFICATIONS()
	END_MSG_MAP()

	void setPresenter(shared_ptr<Presenter> presenter);

protected:
// Handler prototypes (uncomment arguments if needed):
//	LRESULT MessageHandler(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
//	LRESULT CommandHandler(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
//	LRESULT NotifyHandler(int /*idCtrl*/, LPNMHDR /*pnmh*/, BOOL& /*bHandled*/)
	int OnCreate(LPCREATESTRUCT lpCreateStruct);
	LRESULT OnPaint(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/);
	void UpdateLayout();
	LRESULT OnNMClickListResult(int idCtrl,LPNMHDR pnmh,BOOL &bHandled);
	LRESULT OnGetListViewData(int idCtrl,LPNMHDR pnmh,BOOL &bHandled);
	LRESULT OnPrepareListViewData(int idCtrl,LPNMHDR pnmh,BOOL &bHandled);
	LRESULT OnFindListViewData(int idCtrl,LPNMHDR pnmh,BOOL &bHandled);
	void OnCommandIDHandlerEX(UINT uNotifyCode, int nID, CWindow wndCtl);

	void ReloadMockData();
	void ReloadListView();

private:
	std::wstring GetControlText(HWND hwnd,wchar_t* buf = NULL);
	CListViewCtrl listview_;

	CFont font_normal_;
	CFont font_bold_;

	CBrushHandle brush_white_;
	CBrushHandle brush_hollow_;
	CBrush brush_red_;

	CButton buttonReloadMockData_;
	CButton buttonReloadListView_;
	CButton buttonDeleteListViewOneRow_;

private:
	shared_ptr<Presenter> presenter_ = nullptr;
};

View.cpp

// View.cpp : implementation of the CView class
//
/

#include "stdafx.h"
#include "resource.h"
#include <utility>
#include <sstream>
#include <stdint.h>
#include <assert.h>
#include <Strsafe.h>

#include "View.h"
#include <CommCtrl.h>
#include <string>
#include <regex>
#include "Presenter.h"
#include "Photo.h"


using namespace std;

BOOL CView::PreTranslateMessage(MSG* pMsg)
{
	return FALSE;
}

LRESULT CView::OnPaint(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
	CPaintDC dc(m_hWnd);
	CMemoryDC mdc(dc,dc.m_ps.rcPaint);

	CRect rect_client;
	GetClientRect(&rect_client);
	mdc.FillSolidRect(rect_client,RGB(255,255,255));
	//TODO: Add your drawing code here

	return 0;
}

static HFONT GetFont(int pixel,bool bold,const wchar_t* font_name)
{
	LOGFONT lf; 
	memset(&lf, 0, sizeof(LOGFONT)); // zero out structure 
	lf.lfHeight = pixel; // request a 8-pixel-height font
	if(bold)
	{
		lf.lfWeight = FW_BOLD;  
	}
	lstrcpy(lf.lfFaceName, font_name); // request a face name "Arial"
	
	HFONT font = ::CreateFontIndirect(&lf);
	return font;
}


std::wstring CView::GetControlText(HWND hwnd,wchar_t* buf)
{
	auto length = ::GetWindowTextLength(hwnd);
	bool bufNull = false;
	if(!buf){
		buf = new wchar_t[length+1]();
		bufNull = true;
	}
	
	::GetWindowText(hwnd,buf,length+1);
	std::wstring str(buf);

	if(bufNull)
		delete []buf;

	return str;
}

static std::wstring GetProductBinDir()
{
	static wchar_t szbuf[MAX_PATH];  
	GetModuleFileName(NULL,szbuf,MAX_PATH);  
    PathRemoveFileSpec(szbuf);
	int length = lstrlen(szbuf);
	szbuf[length] = L'\\';
	szbuf[length+1] = 0;
	return std::wstring(szbuf);
}

LRESULT CView::OnGetListViewData(int idCtrl,LPNMHDR pnmh,BOOL &bHandled)
{
	NMLVDISPINFO* plvdi = (NMLVDISPINFO*) pnmh;
	auto iItem = plvdi->item.iItem;
	if (-1 == iItem)
		return 0;
	
	auto count = presenter_->getPhotoCount();
	if(count <= iItem)
		return 0;

	auto photo = presenter_->getPhoto(iItem);
	if(plvdi->item.mask & LVIF_TEXT){
		switch(plvdi->item.iSubItem)
		{
		case 0:
			StringCchCopy(plvdi->item.pszText, plvdi->item.cchTextMax, to_wstring((int64_t)iItem+1).c_str());
			break;
		case 1:
			StringCchCopy(plvdi->item.pszText, plvdi->item.cchTextMax, photo->name.c_str());
			break;
		case 2:
			StringCchCopy(plvdi->item.pszText, plvdi->item.cchTextMax, photo->format.c_str());
			break;
		case 3:
			StringCchCopy(plvdi->item.pszText, plvdi->item.cchTextMax, photo->createDate.c_str());
			break;
		}
	}
	
	return 0;
}

LRESULT CView::OnPrepareListViewData(int idCtrl,LPNMHDR pnmh,BOOL &bHandled)
{
	return 0;
}

LRESULT CView::OnFindListViewData(int idCtrl,LPNMHDR pnmh,BOOL &bHandled)
{	
	return 0;
}

LRESULT CView::OnNMClickListResult(int idCtrl,LPNMHDR pnmh,BOOL &bHandled)
{
	return 0;
}

void CView::OnCommandIDHandlerEX(UINT uNotifyCode, int nID, CWindow wndCtl)
{
	switch(nID)
	{
	case kMyButtonId:
		{
			ReloadMockData();
			MessageBox(L"刷新模拟数据完成");
			break;
		}
	case kMyButtonId2:
		{
			ReloadListView();
			MessageBox(L"重新加载表格数据完成");
			break;
		}
	case kMyButtonId3:
		{
			int iItem = -1;
			while((iItem = listview_.GetNextItem(iItem,LVNI_SELECTED)) != -1){
				presenter_->removePhoto(iItem);
				listview_.DeleteItem(iItem);
				iItem--;
			}

			MessageBox(L"已删除");
			break;
		}
	}
}

void CView::ReloadListView()
{
	listview_.SetItemCount(0);
	presenter_->clearPhotos();
}

void CView::ReloadMockData()
{
	presenter_->loadPhotos([this]() {
		listview_.SetItemCount(presenter_->getPhotoCount());
	});
	
}

int CView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
	font_normal_ = ::GetFont(16,false,L"Arial");
	font_bold_ = ::GetFont(16,true,L"Arial");
	
	brush_hollow_ = AtlGetStockBrush(HOLLOW_BRUSH);
	brush_white_ = AtlGetStockBrush(WHITE_BRUSH);
	brush_red_.CreateSolidBrush(RGB(255,0,0));

	// 1.创建CListViewCtrl
	listview_.Create(m_hWnd,0,NULL,WS_CHILD | WS_TABSTOP |WS_VISIBLE
		|LVS_ALIGNLEFT|LVS_REPORT|LVS_SHOWSELALWAYS|WS_BORDER|LVS_OWNERDATA,0,kMyListViewId);
	listview_.SetExtendedListViewStyle(LVS_EX_FULLROWSELECT|LVS_EX_GRIDLINES|LVS_EX_DOUBLEBUFFER);
	listview_.SetFont(font_normal_);
	auto header = listview_.GetHeader();
	header.SetFont(font_bold_);
	listview_.SetBkColor(RGB(255,255,255));

	listview_.InsertColumn(0,L"No.",LVCFMT_LEFT,40);
	listview_.InsertColumn(1,L"Name",LVCFMT_LEFT,100);
	listview_.InsertColumn(2,L"Format",LVCFMT_LEFT,100);
	listview_.InsertColumn(3,L"Create Date",LVCFMT_LEFT,100);

	// 2.创建按钮
	buttonReloadMockData_.Create(m_hWnd,0,L"加载新数据",WS_CHILD|WS_VISIBLE,0,kMyButtonId);
	buttonReloadMockData_.SetFont(font_normal_);

	buttonReloadListView_.Create(m_hWnd,0,L"刷新表格",WS_CHILD|WS_VISIBLE,0,kMyButtonId2);
	buttonReloadListView_.SetFont(font_normal_);

	buttonDeleteListViewOneRow_.Create(m_hWnd,0,L"删除选中行",WS_CHILD|WS_VISIBLE,0,kMyButtonId3);
	buttonDeleteListViewOneRow_.SetFont(font_normal_);
	

	UpdateLayout();

	return 0;
}

void CView::UpdateLayout()
{
	CRect rect;
	GetClientRect(&rect);

	CClientDC dc(m_hWnd);
	dc.SelectFont(font_normal_);

	CSize size_control(500,300);
	CRect rect_control = CRect(CPoint(20,20),size_control);
	listview_.MoveWindow(rect_control);

	CSize size_button;
	buttonReloadMockData_.GetIdealSize(&size_button);
	rect_control = CRect(CPoint(rect_control.left,rect_control.bottom+10),size_button);
	buttonReloadMockData_.MoveWindow(rect_control);

	CSize sizeButton2;
	buttonReloadListView_.GetIdealSize(&sizeButton2);
	rect_control = CRect(CPoint(rect_control.right+10,rect_control.top),sizeButton2);
	buttonReloadListView_.MoveWindow(rect_control);

	CSize sizeButton3;
	buttonDeleteListViewOneRow_.GetIdealSize(&sizeButton3);
	rect_control = CRect(CPoint(rect_control.right+10,rect_control.top),sizeButton3);
	buttonDeleteListViewOneRow_.MoveWindow(rect_control);
}

void CView::setPresenter(shared_ptr<Presenter> presenter)
{
	presenter_ = presenter;
}

Presenter.h


#ifndef PRESENTER_H
#define PRESENTER_H

#include <vector>
#include <memory>
#include <functional>

using namespace std;

class Photo;
class Model;

typedef function<void()> FuncSimple;

class Presenter
{

public:
	shared_ptr<Photo> getPhoto(int i);
	void clearPhotos();
	size_t getPhotoCount();
	void removePhoto(int nItem);
	void loadPhotos(FuncSimple func);
	void setModel(shared_ptr<Model> model);

protected:
	vector<shared_ptr<Photo>> photos_;

protected:
	shared_ptr<Model> model_;
	FuncSimple funcLoadFinish_;
};


#endif // !PRESENTER_H

Presenter.cpp

#include "stdafx.h"
#include "Presenter.h"
#include "Photo.h"
#include "Model.h"
#include "dispatch_queue.h"

shared_ptr<Photo> Presenter::getPhoto(int i)
{
	return (i < photos_.size()) ? photos_[i] : nullptr;
}

void Presenter::clearPhotos()
{
	photos_.clear();
}

size_t Presenter::getPhotoCount()
{
	return photos_.size();
}

void Presenter::removePhoto(int nItem)
{
	if (nItem < getPhotoCount()) {
		auto ite = photos_.begin() + nItem;
		photos_.erase(ite);
	}
}

void Presenter::loadPhotos(FuncSimple func)
{
	funcLoadFinish_ = func;
	model_->loadPhoto([this](vector<shared_ptr<Photo>>* photos) {

		// 需要把数据发送到主线程更新,这样才不会出现多线程访问共享数据冲突。
		DispatchQueue::DispatchAsync(DispatchQueue::DispatchGetMainQueue(), 
			new FuncSimple([this, photos]() {
				photos_.insert(photos_.end(), photos->begin(), photos->end());
				if (funcLoadFinish_)
					funcLoadFinish_();

				delete photos;
		}));

	});
}

void Presenter::setModel(shared_ptr<Model> model)
{
	model_ = model;
}

Model.h


#ifndef MODEL_H
#define MODEL_H

#include <functional>
#include <memory>
#include <vector>
#include "Photo.h"

using namespace std;

typedef function<void(vector<shared_ptr<Photo>>*)> FuncLoadPhoto;

class Model
{
public:
	void loadPhoto(FuncLoadPhoto func);

protected:
	FuncLoadPhoto funcLoadPhoto_;
};

#endif

Model.cpp

#include "stdafx.h"
#include "Model.h"
#include <thread>
#include "Photo.h"

using namespace std;

void Model::loadPhoto(FuncLoadPhoto func)
{
	funcLoadPhoto_ = func;

	// 模拟异步加载数据
	thread t1([this]() {
		wchar_t buf[MAX_PATH] = { 0 };
		int index = 0;
		
		auto photos = new vector<shared_ptr<Photo>>();
		for (int i = 0; i < 10000; ++i, ++index) {
			auto photo = new Photo();
			wsprintf(buf, L"Name-%d", index);
			photo->name = buf;

			wsprintf(buf, L"Format-%d", index);
			photo->format = buf;

			wsprintf(buf, L"createDate-%d", index);
			photo->createDate = buf;

			photos->push_back(move(shared_ptr<Photo>(photo)));
		}

		if (funcLoadPhoto_)
			funcLoadPhoto_(photos);
	});

	t1.detach();
}

dispatch_queue.h

#ifndef __DISPATCH_QUEUE_H
#define __DISPATCH_QUEUE_H

#include <Windows.h>
#include <WinUser.h>
#include <functional>

enum
{
    WMC_DISPATCH_MAIN_QUEUE = WM_USER+1000
};

typedef struct DispatchQueueObject1
{
    DWORD threadId;
    HWND m_hwnd;
	UINT msg;
}DispatchQueueObject;

class DispatchQueue
{
public:
	static DWORD GetMainThreadId();
	static bool IsCurrentMainThread();
	static void DispatchQueueInit(HWND hwnd);
	static DispatchQueueObject* DispatchGetMainQueue();
	static void FreeDispatchMainQueue(DispatchQueueObject* dqo);
	static void DispatchAsync(DispatchQueueObject* queue,std::function<void()>* callback);
};



#endif

dispatch_queue.cpp

#include "stdafx.h"

#include "dispatch_queue.h"

static HWND gMainHwnd = NULL;
static DWORD gMainThreadId = 0;

bool DispatchQueue::IsCurrentMainThread()
{
	return GetMainThreadId() == GetCurrentThreadId();
}

DWORD DispatchQueue::GetMainThreadId()
{
	return gMainThreadId;
}

void DispatchQueue::DispatchQueueInit(HWND hwnd)
{
    gMainHwnd = hwnd;
	gMainThreadId = GetCurrentThreadId();
}

void DispatchQueue::DispatchAsync(DispatchQueueObject* queue,std::function<void()>* callback)
{
	if(queue->threadId){
        ::PostThreadMessage(queue->threadId,queue->msg,(WPARAM)callback,0);
    }else{
		::PostMessage(queue->m_hwnd,queue->msg,(WPARAM)callback,0);
    }
	FreeDispatchMainQueue(queue);
}

DispatchQueueObject* DispatchQueue::DispatchGetMainQueue()
{
    DispatchQueueObject* object = (DispatchQueueObject*)malloc(sizeof(DispatchQueueObject));
    memset(object,0,sizeof(DispatchQueueObject));
    object->m_hwnd = gMainHwnd;
	object->msg = WMC_DISPATCH_MAIN_QUEUE;
    return object; 
}

void DispatchQueue::FreeDispatchMainQueue(DispatchQueueObject* dqo)
{
	free(dqo);
}

MainFrm.h

// MainFrm.h : interface of the CMainFrame class
//
/

#pragma once

#include "View.h"
#include "Presenter.h"
#include "Model.h"
#include "dispatch_queue.h"

class CMainFrame : 
	public CFrameWindowImpl<CMainFrame>, 
	public CUpdateUI<CMainFrame>,
	public CMessageFilter, public CIdleHandler
{
public:
	DECLARE_FRAME_WND_CLASS(NULL, IDR_MAINFRAME)

	CView m_view;

	virtual BOOL PreTranslateMessage(MSG* pMsg);
	virtual BOOL OnIdle();

	BEGIN_UPDATE_UI_MAP(CMainFrame)
	END_UPDATE_UI_MAP()

	BEGIN_MSG_MAP(CMainFrame)
		MESSAGE_HANDLER(WM_CREATE, OnCreate)
		MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
		COMMAND_ID_HANDLER(ID_APP_EXIT, OnFileExit)
		COMMAND_ID_HANDLER(ID_FILE_NEW, OnFileNew)
		COMMAND_ID_HANDLER(ID_APP_ABOUT, OnAppAbout)
		MESSAGE_HANDLER(WMC_DISPATCH_MAIN_QUEUE, OnDispatchMainQueueEvent)
		CHAIN_MSG_MAP(CUpdateUI<CMainFrame>)
		CHAIN_MSG_MAP(CFrameWindowImpl<CMainFrame>)
	END_MSG_MAP()

// Handler prototypes (uncomment arguments if needed):
//	LRESULT MessageHandler(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
//	LRESULT CommandHandler(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
//	LRESULT NotifyHandler(int /*idCtrl*/, LPNMHDR /*pnmh*/, BOOL& /*bHandled*/)

	LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/);
	LRESULT OnDestroy(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled);
	LRESULT OnFileExit(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/);
	LRESULT OnFileNew(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/);
	LRESULT OnAppAbout(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/);

	LRESULT OnDispatchMainQueueEvent(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
	{
		std::function<void()>* func = (std::function<void()>*)wParam;
		(*func)();
		delete func;
		bHandled = TRUE;
		return 0;
	}
};
	

MainFrm.cpp

// MainFrm.cpp : implmentation of the CMainFrame class
//
/

#include "stdafx.h"
#include "resource.h"

#include "aboutdlg.h"
#include "MainFrm.h"

BOOL CMainFrame::PreTranslateMessage(MSG* pMsg)
{
	if(CFrameWindowImpl<CMainFrame>::PreTranslateMessage(pMsg))
		return TRUE;

	return m_view.PreTranslateMessage(pMsg);
}

BOOL CMainFrame::OnIdle()
{
	return FALSE;
}

LRESULT CMainFrame::OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
	// 注册线程接收异步主线程消息的窗口
	DispatchQueue::DispatchQueueInit(m_hWnd);

	auto presenter = make_shared<Presenter>();
	auto model = make_shared<Model>();

	presenter->setModel(model);
	m_view.setPresenter(presenter);
	m_hWndClient = m_view.Create(m_hWnd, rcDefault, NULL, WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, WS_EX_CLIENTEDGE);

	// register object for message filtering and idle updates
	CMessageLoop* pLoop = _Module.GetMessageLoop();
	ATLASSERT(pLoop != NULL);
	pLoop->AddMessageFilter(this);
	pLoop->AddIdleHandler(this);

	return 0;
}

LRESULT CMainFrame::OnDestroy(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
{
	// unregister message filtering and idle updates
	CMessageLoop* pLoop = _Module.GetMessageLoop();
	ATLASSERT(pLoop != NULL);
	pLoop->RemoveMessageFilter(this);
	pLoop->RemoveIdleHandler(this);

	bHandled = FALSE;
	return 1;
}

LRESULT CMainFrame::OnFileExit(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
{
	PostMessage(WM_CLOSE);
	return 0;
}

LRESULT CMainFrame::OnFileNew(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
{
	// TODO: add code to initialize document

	return 0;
}

LRESULT CMainFrame::OnAppAbout(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
{
	CAboutDlg dlg;
	dlg.DoModal();
	return 0;
}

项目下载

https://download.csdn.net/download/infoworld/89445554

在这里插入图片描述
注意: 关于WTL的开发学习可以订阅我的课程:
使用WTL进行Windows桌面应用开发-第一部_在线视频教程-CSDN程序员研修院

参考

  1. 观察者模式在项目中实际使用例子

  2. 观察者模式在项目中实际使用例子2

  3. QQ NT全新重构,探寻24岁QQ大重构背后的思考_跨端开发

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/718937.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Aigtek高压放大器在柔性爬行机器人驱动性能研究中的应用

实验名称&#xff1a;柔性爬行机器人的材料测试 研究方向&#xff1a;介电弹性体的最小能量结构是一种利用DE材料的电致变形与柔性框架形变相结合设计的新型柔性驱动器&#xff0c;所谓最小能量是指驱动器在平衡状态时整个系统的能量最小&#xff0c;当系统在外界的电压刺激下就…

开发一个python工具,pdf转图片,并且截成单个图片,然后修整没用的白边

今天推荐一键款本人开发的pdf转单张图片并截取没有用的白边工具 一、开发背景&#xff1a; 业务需要将一个pdf文件展示在前端显示&#xff0c;但是基于各种原因&#xff0c;放弃了h5使用插件展示 原因有多个&#xff0c;文件资源太大加载太慢、pdf展示兼容性问题、pdf展示效果…

应急便携式气象观测站

TH-BQX5自然灾害&#xff0c;如台风、暴雨、洪涝、干旱等&#xff0c;给人们的生命财产安全带来了巨大威胁。在应对这些灾害时&#xff0c;准确的气象观测数据是制定有效应对策略的基础。近年来&#xff0c;应急便携式气象观测站在自然灾害的监测和预警中发挥了越来越重要的作用…

在 Blazor 中在子组件和父组件之间共享数据

介绍 可以在Blazor 中创建一个子组件并在另一个组件中重用它。我们将非常轻松地在这些组件之间共享数据。我们将创建一个自定义文本框作为子组件。此自定义文本框将显示文本框中的当前字符数&#xff0c;并在需要时限制字符总数。我将逐步解释所有操作。 在 Visual Studio 中…

购物App需要进行软件测试吗?包括哪些测试内容?

随着移动互联网的飞速发展&#xff0c;购物App在人们的日常生活中扮演着越来越重要的角色。然而&#xff0c;由于App开发的复杂性和用户对于购物体验的高要求&#xff0c;保证App的质量成为了一项重要的任务。而软件测试作为确保App质量的关键环节&#xff0c;也日益受到重视。…

文件操作(1)(C语言版)

前言&#xff1a; 为什么要学习文件操作&#xff1a; 1、如果大家写过一些代码&#xff0c;当运行结束的时候&#xff0c;这些运行结果将不复存在&#xff0c;除非&#xff0c;再次运行时这些结果才能展现在屏幕上面&#xff0c;就比如之前写过的通讯录。 现实中的通讯录可以保…

智游剪辑手机版发布!

耗时一个多月&#xff0c;手机版终于开发的差不多了&#xff0c;下面带大家一起来看下效果咋样吧&#xff01; 功能介绍 打开应用就可以直接看到我们的所有功能了&#xff0c;支持分类查看和关键词搜索功能&#xff0c;每个功能都可以查看帮助教程和收藏&#xff0c;点击即可进…

Day40

Day40 监听器 概念&#xff1a; 监听器用于监听web应用中某些对象信息的创建、销毁、增加&#xff0c;修改&#xff0c;删除等动作的 发生&#xff0c;然后作出相应的响应处理。当范围对象的状态发生变化的时候&#xff0c;服务器自动调用 监听器对象中的方法。 常用于统计在线…

AWS——01篇(AWS入门 以及 AWS之EC2实例及简单实用)AWS

AWS——01篇&#xff08;AWS入门 以及 AWS之EC2实例及简单实用&#xff09; 1. 前言 2. 创建AWS账户 3. EC2 3.1 启动 EC2 新实例 3.1.1 入口 3.1.2 设置名称 选择服务 3.1.3 创建密钥对 3.1.4 网络设置——安全组 3.1.4.1 初始设置 3.1.4.2 添加安全组规则&#xff08;开放新…

0X0-基于Sklearn的机器学习入门:聚类(上)

本节及后续章节将介绍深度学习中的几种聚类算法&#xff0c;所选方法都在Sklearn库中聚类模块有具体实现。本节为上篇&#xff0c;将介绍几种相对基础的聚类算法&#xff0c;包括K-均值算法和均值漂移算法。 目录 X.1 聚类概述 X.1.1 聚类的种类 X.1.2 Sklearn聚类子模块 …

【JVM结构、JVM参数、JVM垃圾回收】

JVM&#xff1a;Java Virtual Machine java虚拟机 虚拟机&#xff1a;使用软件技术模拟出与具有完整硬件系统功能、运行在一个隔离环境中的计算机系统。 JVM官方文档&#xff1a;https://docs.oracle.com/javase/specs/jvms/se8/html/index.html java 一些命令 javac 将文件编…

【C++入门(3)】函数重载、引用

一、函数重载 1、函数重载概念 函数重载是指在同一作用域中&#xff0c;具有不同形参列表&#xff08;参数的 个数 或 类型 或类型顺序 不同&#xff09;的同名函数。 C语言中不允许同名函数的存在&#xff0c;如果一个程序中有两个函数的函数名完全相同&#xff0c;就会报错…

C#(C Sharp)学习笔记_多态【十九】

前言 个人觉得多态在面向对象编程中还比较重要的&#xff0c;而且不容易理解。也是学了一个下午&#xff0c;才把笔记写得相对比较完善&#xff0c;但仍欠缺一些内容。慢慢来吧…… 什么是多态&#xff1f; 基本概念 在编程语言和类型论中&#xff0c;多态&#xff08;Poly…

2024最新版Node.js下载安装及环境配置教程(非常详细)

一、进入官网地址下载安装包 官网&#xff1a;Node.js — Run JavaScript Everywhere 其他版本下载&#xff1a;Node.js — Download Node.js (nodejs.org) 选择对应你系统的Node.js版本 二、安装程序 &#xff08;1&#xff09;下载完成后&#xff0c;双击安装包&#xf…

OpenGL Super Bible 7th-Primitives, Pipelines, and Pixels图元、渲染管线与像素

简介 本文的原版为《OpenGL Super Bible 7th》,是同事给我的,翻译是原文+译文的形势。文章不属于机器直译,原因在于语言不存在一一对应的关系,我将尽可能的按照中国人看起来舒服的方式来翻译这些段子,如果段子让你感到身心愉悦,那还劳烦点个关注,追个更。如果我没有及时…

从0进入微服务需要了解的基础知识

文章目录 系统架构演化过程为什么要了解系统架构的演化过程技术发展认知技术选型与创新 演变过程单体架构分层-分布式集群微服务 分布式\集群\微服务 微服务中的核心要素-拆分原则项目拆分与复杂度微服务的拆分维度有哪些小结 微服务中的核心要素服务化进行拆分后一定是微服务&…

MFC扩展库BCGControlBar Pro v35.0新版亮点:重新设计的工具栏编辑器等

BCGControlBar库拥有500多个经过全面设计、测试和充分记录的MFC扩展类。 我们的组件可以轻松地集成到您的应用程序中&#xff0c;并为您节省数百个开发和调试时间。 BCGControlBar专业版 v35.0已全新发布了&#xff0c;这个版本改进类Visual Studio 2022的视觉主题、增强对多个…

ChatGPT付费创作系统V3.0.2独立版 WEB+H5+小程序端 (H5端界面美化+Pika视频作品广场+SunoAI 文生歌)系统部署教程

播播资源GPT付费体验系统最新版系统是一款基于ThinkPHP框架开发的AI问答小程序&#xff0c;是基于国外很火的ChatGPT进行开发的Ai智能问答小程序。当前全民热议ChatGPT&#xff0c;流量超级大&#xff0c;引流不要太简单&#xff01;一键下单即可拥有自己的GPT&#xff01;无限…

MinIO Enterprise Cache:实现超性能的分布式 DRAM 缓存

随着计算世界的发展和 DRAM 价格的暴跌&#xff0c;我们发现服务器配置通常配备 500GB 或更多的 DRAM。当您处理大型部署时&#xff0c;即使是那些具有超高密度 NVMe 驱动器的部署&#xff0c;这些服务器上的服务器数量乘以 DRAM 也会迅速增加&#xff0c;通常达到几 TB。该 DR…

【Intel CVPR 2024】通过图像扩散模型生成高质量360度场景,只需要一个语言模型

在当前人工智能取得突破性进展的时代&#xff0c;从单一输入图像生成全景场景仍是一项关键挑战。大多数现有方法都使用基于扩散的迭代或同步多视角内绘。然而&#xff0c;由于缺乏全局场景布局先验&#xff0c;导致输出结果存在重复对象&#xff08;如卧室中的多张床&#xff0…