MFC 基础
windows桌面应用分为两种类型 基于文档视图类型 和 基于对话框类型。 通常具有复杂交互控件的程序(比如有按钮输入框下拉框等)即为基于对话框类型相对而言比较复杂而基于文档视图类的应用一般是通过菜单栏工具栏来交互形式比较单一相对简单。下面给出基于mfc框架的最基本的桌面程序示例// 1. 创建控制台空项目 // 2. 项目属性-- 高级--MFC的应用[使用标准 Windows 库] 改成 [在共享 DLL 中使用 MFC] // 3. 项目属性--链接器--系统--子系统[控制台 (/SUBSYSTEM:CONSOLE)] 改成 [窗口 (/SUBSYSTEM:WINDOWS)] #include afxwin.h class BaseMainFrame:public CFrameWnd { DECLARE_MESSAGE_MAP() public: BaseMainFrame(); protected: afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); afx_msg void OnClose(void); afx_msg void OnSysCommand(UINT msgID, LPARAM lParam); private: FILE* m_pConsole; }; BaseMainFrame::BaseMainFrame() {} afx_msg void BaseMainFrame::OnClose() { fclose(m_pConsole); FreeConsole();//关闭控制台 CFrameWnd::OnClose(); return; } afx_msg void BaseMainFrame::OnSysCommand(UINT msgID, LPARAM lParam) { DWORD cmdType msgID 0xFFF0; switch (cmdType) { case SC_CLOSE: //OnClose(); //break; default: CFrameWnd::OnSysCommand(msgID, lParam); break; } } afx_msg int BaseMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) { AllocConsole();//打开控制台打日志用 freopen_s(m_pConsole, CONOUT$, w, stdout); return CFrameWnd::OnCreate(lpCreateStruct); } BEGIN_MESSAGE_MAP(BaseMainFrame, CFrameWnd) ON_WM_SYSCOMMAND() ON_WM_CREATE() ON_WM_CLOSE() END_MESSAGE_MAP() class BaseWinApp:public CWinApp { public: BaseWinApp(); virtual BOOL InitInstance() override; virtual int ExitInstance() override; }; BaseWinApp::BaseWinApp() {} BOOL BaseWinApp::InitInstance() { BaseMainFrame* pFrame new BaseMainFrame(); pFrame-Create(NULL, BaseFrame);//将产生BaseMainFrame的WM_CREATE事件 m_pMainWnd pFrame; pFrame-ShowWindow(SW_SHOW); pFrame-UpdateWindow(); return TRUE; } int BaseWinApp::ExitInstance() { return CWinApp::ExitInstance(); } BaseWinApp theApp;//全局唯一 // 主线程启动顺序(最终进入一个事件循环) // WinMain: appmodul.cpp // AfxWinMain: winmain.cpp // (CWinApp*)(theApp)-InitApplication 应用资源初始化是虚函数可以在子类中重写做一些特殊资源的初始化 // (CWinThread*)(theApp)-InitInstance 用户重写的InitInstance // (CWinThread*)(theApp)-Run: appcore.cpp // (CWinThread*)(theApp)-CWinThread::Run(): thrdcore.cpp 进入事件循环,处理消息 // 当消息产生后会在消息循环中侦测到并在DispatchMessage内部调用对应窗口的处理回调(如AfxWndProc: wincore.cpp)进行消息处理 // MFC 中创建线程的两种方法 // 1. CWinThread* AFXAPI AfxBeginThread(AFX_THREADPROC pfnThreadProc, ...); 调用该接口传入线程函数生成一个CWinThread线程管理对象 // 2. CWinThread* AFXAPI AfxBeginThread(CRuntimeClass* pThreadClass,...); 继承CWinThread实现新的Thread类并声明Runtime信息 传入新类的RuntimeClass生成一个线程管理对象。(即CWinApp的做法) // 线程创建之后如果后续该线程中调用了GetMessage/PeekMessage, 则将自动为该线程创建消息队列可用于接收窗口消息也可用于接收非窗口消息。且该消息队队列是系统级别的可以跨进程通信(相比Linux中手动创建消息队列方便许多)。这种队列是系统自带的很通用缺点是不能传递大数据包因此通常还会搭配共享内存使用。基于对话框的应用可以先看win32 框架下的示例。本例展示了窗体之间的消息传递以及消息循环机制mfc框架对这些底层的重要机制进行了封装研究本例程将对理解mfc程序线程模型有重要提示作用#include windows.h #include strsafe.h #include iostream #include cassert #include resource.h//要给项目添加资源文件 //本例需要在资源文件中添加一个Dialog控件(ID为IDD_DIALOG1)和一个Menu控件(ID为IDR_MENU1,MENU中要增加菜单栏和菜单项) #define CONSOLE_ENABLE HINSTANCE g_hInstance NULL; HANDLE h_hThread1 0; HANDLE g_OutputHand NULL; FILE* g_pConsole NULL; WORD wndWidth 0, wndHeight 0; void DebugInfo(const TCHAR* format, ...) { TCHAR buffer[1024] {}; va_list argptr; va_start(argptr, format); HRESULT hr StringCbVPrintfEx(buffer, 1024 * sizeof(TCHAR), NULL, NULL, STRSAFE_NULL_ON_FAILURE, format, argptr); va_end(argptr); if (SUCCEEDED(hr)) { #ifndef CONSOLE_ENABLE OutputDebugString(buffer); #else WriteConsole(g_OutputHand, buffer, wcslen(buffer), NULL, NULL); #endif } } INT_PTR CALLBACK DlgProc(HWND hwndlg, UINT msgID, WPARAM wParam, LPARAM lParam) { INT_PTR iRes FALSE;//让系统执行默认处理过程 switch (msgID) { case WM_SYSCOMMAND://点击窗口的最大化最小化关闭等将产生该消息 if (wParam SC_CLOSE) { DebugInfo(TEXT(SC_CLOSE Event for Handle(%d).\n), hwndlg); HWND pHwnd GetParent(hwndlg); DestroyWindow(hwndlg);//将产生一条WM_DESTROY消息 EnableWindow(pHwnd, TRUE); iRes TRUE; } break; case WM_DESTROY: DebugInfo(TEXT(WM_DESTROY Event for Handle(%d).\n), hwndlg); MessageBox(NULL, TEXT(Dialog Destroyed), TEXT(Notify), MB_OK); break; case WM_COMMAND: DebugInfo(TEXT(WM_COMMAND Event for Handle(%d).\n), hwndlg); if (wParam IDOK) MessageBox(NULL, TEXT(User Confirmed), TEXT(Dialog Result), MB_OK); else if (wParam IDCANCEL) MessageBox(NULL, TEXT(User Denied), TEXT(Dialog Result), MB_OK); break; default: break; } return iRes; } void OnNoModel(HWND hWnd) { HWND pHwnd GetParent(hWnd); EnableWindow(pHwnd, FALSE); //HWND hDlg CreateDialog(g_hInstance, MAKEINTRESOURCE(IDD_DIALOG1),hWnd,DlgProc);// 等效步骤①②③④⑤⑥ //创建对话窗对象并和对话框控件(资源中创建,ID为IDD_DIALOG1)绑定 HRSRC hRs FindResource(g_hInstance, MAKEINTRESOURCE(IDD_DIALOG1), RT_DIALOG);//① HGLOBAL hGl LoadResource(g_hInstance, hRs);//② LPDLGTEMPLATE pTemplate (LPDLGTEMPLATE)LockResource(hGl);//③ HWND hDlg CreateDialogIndirect(g_hInstance, pTemplate, hWnd, DlgProc);//④ UnlockResource(hGl);//⑤ FreeResource(hGl);//⑥ ShowWindow(hDlg, SW_SHOW);//立即返回不阻塞 } void OnCommand(HWND hWnd, WPARAM wParam, LPARAM lParam) { WORD ID LOWORD(wParam);//控件ID WORD Code HIWORD(wParam);//通知类型 switch (ID) { case ID_FFF:// 资源文件中定义菜单栏下拉的菜单选项对应的资源ID点击该菜单项将创建一个对话框出来 DebugInfo(TEXT(Resource Id%d, Code%d.\n), ID, Code); OnNoModel(hWnd); break; case 10: DebugInfo(TEXT(Resource Id%d, Code%d.\n), ID, Code); break; case 11: DebugInfo(TEXT(Resource Id%d, Code%d.\n), ID, Code); break; default: break; } } LRESULT CALLBACK SubWndProc(HWND hWnd, UINT msgID, WPARAM wParam, LPARAM lParam) { LRESULT lRet 0; BOOL bMsgSpread TRUE; DWORD processId, threadId; threadId GetWindowThreadProcessId(hWnd, processId); //DebugInfo(TEXT(SubWndProc Entered: process%lld, thread%lld, msgID%#010x.\n), processId, threadId, msgID); switch (msgID) { case WM_CREATE: { CREATESTRUCT* pCs (CREATESTRUCT*)lParam; DebugInfo(TEXT(Create SubWindow Success, WindowName%s, ClassName%s.\n), pCs-lpszName, pCs-lpszClass); WPARAM Param MAKEWPARAM(GetWindowLongPtr(hWnd, GWLP_ID), 0);//获取窗口id SendMessage(GetParent(hWnd), WM_COMMAND, Param, (LPARAM)hWnd); //PostMessage(GetParent(hWnd), WM_COMMAND, Param, (LPARAM)hWnd); break; } case WM_SIZE: { WORD width LOWORD(lParam), height HIWORD(lParam); DebugInfo(TEXT(SubWindow Size Changed, Height%d, Width%d.\n), height, width); PostMessage(GetParent(hWnd), WM_USER, WM_SIZE, (LPARAM)hWnd); break; } case WM_LBUTTONUP: DebugInfo(TEXT(SubWindow Clicked.\n)); PostMessage(GetParent(hWnd), WM_USER, WM_LBUTTONUP, (LPARAM)hWnd);//通知父窗体子窗口被点击了 break; //case WM_ERASEBKGND: // lRet TRUE; // bMsgSpread FALSE; // break; default: bMsgSpread TRUE; } if (bMsgSpread msgID 0x03FF msgID 0) lRet DefWindowProc(hWnd, msgID, wParam, lParam); return lRet; } LRESULT CALLBACK WndProc(HWND hWnd, UINT msgID, WPARAM wParam, LPARAM lParam) { LRESULT lRet 0; BOOL bMsgSpread TRUE; //系统消息范围 0 msgID 0x03FF //用户自定义消息范围 0x0400(WM_USER) msgID 0x7FFF DWORD processId, threadId; threadId GetWindowThreadProcessId(hWnd, processId); //DebugInfo(TEXT(WndProc Entered: process%lld, thread%lld, msgID%#010X.\n), processId, threadId, msgID); switch (msgID) { case WM_CREATE://CreateWindow执行后ShowWindow执行之前产生该消息 { CREATESTRUCT* pCs (CREATESTRUCT*)lParam; DebugInfo(TEXT(Create Window Success, WindowName%s, ClassName%s.\n), pCs-lpszName, pCs-lpszClass); //可在此处创建子窗口(该子窗口的消息也要在当前线程中Get) /*LPSTR messageBuffer nullptr; HWND subHWnd CreateWindowEx(0,TEXT(SubMain),TEXT(Caption), WS_CHILD | WS_VISIBLE| WS_BORDER, 50, 50, 200, 100, hWnd, (HMENU)10, g_hInstance, NULL); if (subHWnd NULL) { DWORD err GetLastError(); messageBuffer nullptr; size_t size FormatMessageA( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)messageBuffer, 0, NULL); DebugInfo(TEXT(Create SubWnd Failed, Err%s.\n), messageBuffer); }*/ break; } case WM_COMMAND: OnCommand(hWnd, wParam, lParam); //bMsgSpread FALSE; break; case WM_SIZE://窗口大小一旦发生变化将产生此消息(窗口创建时也有一次该消息) { WORD prevWidth wndWidth, prevHeight wndHeight; wndWidth LOWORD(lParam), wndHeight HIWORD(lParam); DebugInfo(TEXT(Window Size Changed, Height%d-%d, Width%d-%d.\n), prevHeight, wndHeight, prevWidth, wndWidth); RedrawWindow(hWnd, NULL, NULL, RDW_INVALIDATE | RDW_ERASE | RDW_NOCHILDREN); if (h_hThread1 ! 0) { static BOOL CreateSubWndNotified FALSE; DWORD tarThrTid GetThreadId(h_hThread1); DWORD curThrTid GetCurrentThreadId(); DWORD hRatio 10000, wRatio 10000; if (prevWidth 0) wRatio wRatio * wndWidth / prevWidth; if (prevHeight 0) hRatio hRatio * wndHeight / prevHeight; WPARAM wpar MAKEWPARAM(wRatio, hRatio); //DebugInfo(TEXT(Send Thread1 Msg From %lld to %lld.\n), curThrTid, tarThrTid); if (!CreateSubWndNotified) { PostThreadMessage(tarThrTid, WM_USER WM_CREATE, wpar, (LPARAM)hWnd);//通知子线程中的窗口按比例修改其窗口大小 CreateSubWndNotified TRUE; } else { PostThreadMessage(tarThrTid, WM_USER WM_SIZE, wpar, (LPARAM)hWnd);//通知子线程中的窗口按比例修改其窗口大小 } } break; } case WM_DESTROY: { DWORD tarThrTid GetThreadId(h_hThread1); PostThreadMessage(tarThrTid, WM_QUIT, wParam, lParam);//让子线程也安全退出 //PostQuitMessage(::GetLastError());//等效下面这句 PostMessage(hWnd, WM_QUIT, ::GetLastError(), NULL); break; } case WM_USER: { if (wParam WM_SIZE) { DebugInfo(TEXT(SubWindow %lld Has Finished Change its Size.\n), lParam); } else if (wParam WM_LBUTTONUP) { DebugInfo(TEXT(SubWindow %lld Has Been Clicked.\n), lParam); } break; } default: bMsgSpread TRUE; break; } if (bMsgSpread msgID 0x03FF msgID 0) lRet DefWindowProc(hWnd, msgID, wParam, lParam); return lRet; } DWORD WINAPI ThreadProc(LPVOID lpParam) { MSG msg {}; DWORD tid GetCurrentThreadId(); //DebugInfo(TEXT(Sub Thread Enter, ThreadId%lld.), tid); HWND subHWnd NULL; int X 250, Y 250, nHeight 100, nWidth 200; while (GetMessage(msg, NULL, 0, 0)) { if (msg.message WM_USER ) { UINT iMsg msg.message - WM_USER; //DebugInfo(TEXT(ThreadId%lld,Received WM_SIZE message! wParam%d, lParam%d.\n),tid, msg.wParam, msg.lParam); HWND hWnd NULL; LPSTR messageBuffer nullptr; WORD width 0, height 0; if (iMsg WM_CREATE) { hWnd (HWND)msg.lParam; subHWnd CreateWindowEx(0, TEXT(SubMain), TEXT(Caption), WS_CLIPSIBLINGS|WS_CHILD | WS_VISIBLE | WS_BORDER, X, Y, nWidth, nHeight, hWnd, (HMENU)11, g_hInstance, NULL);//窗口在哪个线程中创建其窗口消息也需要在这个线程中Get ShowWindow(subHWnd, SW_SHOW); UpdateWindow(subHWnd); } else if (iMsg WM_SIZE) width LOWORD(msg.wParam), height HIWORD(msg.wParam); if (subHWnd NULL) { DWORD err GetLastError(); messageBuffer nullptr; size_t size FormatMessageA( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)messageBuffer, 0, NULL); DebugInfo(TEXT(Create SubWnd From Thread %d Failed, Err%s.\n), tid, messageBuffer); } if (width 0 height 0) { //assert(GetWindowLongPtr(subHWnd, GWLP_ID) 11); WINDOWPLACEMENT windowRectPrev {}; if (GetWindowPlacement(subHWnd, windowRectPrev)) { width 0.5 width * 0.0001 * (windowRectPrev.rcNormalPosition.right - windowRectPrev.rcNormalPosition.left), height 0.5 height * 0.0001 * (windowRectPrev.rcNormalPosition.bottom - windowRectPrev.rcNormalPosition.top); MoveWindow(subHWnd, windowRectPrev.rcNormalPosition.left, windowRectPrev.rcNormalPosition.top, width, height, TRUE);//将产生子窗口的WM_SIZE消息(SendMesage) //DebugInfo(TEXT(Move Sub Window to {%d,%d,%d,%d}.\n),windowRectPrev.rcNormalPosition.left, windowRectPrev.rcNormalPosition.top, width, height); } } continue; } //处理窗口消息 //if(msg.hwnd ! NULL) //{ // WNDPROC wndProc (WNDPROC)GetWindowLongPtr(msg.hwnd, GWLP_WNDPROC); // CallWindowProc(wndProc, msg.hwnd, msg.message, msg.wParam, msg.lParam); //} TranslateMessage(msg); DispatchMessage(msg); } if (subHWnd ! NULL) ::DestroyWindow(subHWnd);//销毁子线程中的窗口 DebugInfo(TEXT(Sub Thread Quit, ThreadId%lld.\n), tid); return 0; } int CALLBACK WinMain(HINSTANCE hIns, HINSTANCE hPreIns, LPSTR lpCmdLine, int nCmdShow) { g_hInstance hIns; #ifdef CONSOLE_ENABLE AllocConsole();//打日志用 g_OutputHand GetStdHandle(STD_OUTPUT_HANDLE);//标准输出 #endif //GetStdHandle(STD_ERROR_HANDLE);//标准错误 //GetStdHandle(STD_INPUT_HANDLE);//标准输入 MessageBox(NULL, TEXT(WinMain Login), TEXT(Dialog Result), MB_OK); h_hThread1 CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL); DWORD masterTid GetCurrentThreadId(); DebugInfo(TEXT(Master Thread Id%lld.\n), masterTid); WNDCLASS wc {}; wc.cbClsExtra 0; wc.cbWndExtra 0; wc.hbrBackground (HBRUSH)(COLOR_WINDOW 1); wc.hCursor NULL; wc.hIcon NULL; wc.hInstance hIns; wc.lpfnWndProc WndProc; wc.lpszClassName TEXT(Main); //wc.lpszMenuName NULL;// IDR_MENU1 在资源文件中添加一个菜单资源资源ID为IDR_MENU1 wc.lpszMenuName MAKEINTRESOURCE(IDR_MENU1); wc.style 0;// CS_HREDRAW | CS_VREDRAW; // 注册主窗体类 ATOM regRes RegisterClass(wc); // 注册子窗口类 if (regRes ! 0) { wc.cbClsExtra 0;//可通过GetWindowLongPtr/SetWindowLongPtr操作窗口额外预留的内存空间 wc.hIcon NULL; wc.lpfnWndProc SubWndProc; wc.lpszClassName TEXT(SubMain); wc.lpszMenuName NULL; regRes RegisterClass(wc); } if (regRes 0) return -1; HWND hParent NULL; HWND hWnd CreateWindowEx(0, TEXT(Main), TEXT(window), WS_CLIPCHILDREN| WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 800, 600, hParent, NULL, hIns, NULL); DWORD processId, threadId; threadId GetWindowThreadProcessId(hWnd, processId); DebugInfo(TEXT(Main window belongs to process%lld, thread%lld.\n), processId, threadId); ShowWindow(hWnd, SW_SHOW); UpdateWindow(hWnd); MSG nMsg {}; while (GetMessage(nMsg, NULL, 0, 0)) { //SendMessage();//阻塞接口消息不进队列内部直接调用窗口的处理函数在当前线程完成消息处理。(WM_CREATE、WM_SIZE等) //PostMessage();//非阻塞接口向系统队列中添加一条消息。(WM_PAINT、WM_QUIT、键盘、鼠标、定时器等) //GetMessage()/PeekMessage(); // 1. 查看当前线程的消息队列中是否存在消息如果有则检查消息是否满足指定条件(WHND,ID范围)如果不满足就不取出消息否则就取出消息并返回 // 2. 如果当前线程的消息队列中没有消息则主动向系统队列获取属于本线程的消息并再次检查从系统队列拉回来的消息是否存在满足指定条件的消息存在的话就取出该消息并返回。 // 3. 如果系统队列也没有属于本线程的消息则检查当前线程的所有窗口看这些窗口是否需要重新绘制如果发现有需要重绘的窗口则产生一条WM_PAINT消息并返回该消息。 // 4. 如果也没有需要重新绘制的窗口则检查定时器是否有到期事件如果有到期事件则产生一条WM_TIMER消息并返回该消息。 // 5. 如果也没有定时器到期事件则整理程序资源内存等然后让出线程执行权限(如果是PeekMessage则直接返回FALSE)。 // 6. 线程被唤醒后继续回到第1步 // 简称有就抓没有就要要不来就造造不了就挂起。 //DebugInfo(TEXT(Main Loop Thread%lld, nMsg Source thrId%lld.\n), GetCurrentThreadId(), threadId); TranslateMessage(nMsg); DispatchMessage(nMsg);//将消息交给窗口处理函数处理 } if (nMsg.message WM_QUIT)//处理Quit消息 { DebugInfo(TEXT(WM_QUIT Event, Quit Error Code%d.\n), nMsg.wParam); } if (g_pConsole ! NULL) fclose(g_pConsole); if(g_OutputHand ! NULL) FreeConsole();//关闭控制台 return 0; } // windows中的消息队列 ① 系统消息队列。所有窗体进程共享。 // ② 应用程序消息队列。由窗体进程自己独占。 // 系统队列是一级队列应用程序队列是二级队列。系统队列会定时将队列中的消息投递到各自所属的应用程序队列中(按照线程派分)。对话框程序示例#include afxwin.h #include afxcmn.h #include resource.h //本例需要在资源文件中添加一个Dialog控件(ID为IDD_DIALOG1) class MainDialog : public CDialog { DECLARE_MESSAGE_MAP() enum { IDD IDD_DIALOG1 }; public: MainDialog():CDialog(IDD){} void OnIdle(){}//可在此处理低优先级任务 protected: BOOL OnInitDialog() override { //CMenu menu; //menu.LoadMenu(IDR_MENU1);//可以给对话框增加菜单栏 //SetMenu(menu); return CDialog::OnInitDialog(); } afx_msg void OnClose(void) { //::DestroyWindow(this-m_hWnd); DestroyWindow();//最后需要销毁窗口 } void AFX_MSG_CALL OnSysCommand(UINT msgID, LPARAM lParam) { DWORD cmdType msgID 0xFFF0; switch (cmdType) { case SC_CLOSE://处理标题栏的关闭事件 //OnClose(); //break; default: CDialog::OnSysCommand(msgID, lParam); break; } } void DoDataExchange(CDataExchange* pDX) override {} }; BEGIN_MESSAGE_MAP(MainDialog, CDialog) ON_WM_SYSCOMMAND() ON_WM_CLOSE() END_MESSAGE_MAP() class BaseWinApp :public CWinApp { public: BaseWinApp(); virtual BOOL InitInstance() override; virtual BOOL OnIdle(LONG lCount) override; virtual int ExitInstance() override; }; BaseWinApp::BaseWinApp() { InitApplication(); } BOOL BaseWinApp::InitInstance() { MainDialog* pFrame new MainDialog(); pFrame-Create(IDD_DIALOG1,NULL); m_pMainWnd pFrame; pFrame-ShowWindow(SW_SHOW); //LONG wProc GetWindowLongPtr(pFrame-m_hWnd, GWLP_WNDPROC);//that should be AfxWndProc //WNDPROC fun AfxWndProc; //BOOL bRet fun (WNDPROC)wProc; //delete pFrame; return TRUE; } BOOL BaseWinApp::ExitInstance() { return CWinApp::ExitInstance(); } BOOL BaseWinApp::OnIdle(LONG lCount) { if (m_pMainWnd ! NULL) ((MainDialog*)m_pMainWnd)-OnIdle(); return CWinThread::OnIdle(lCount); } BaseWinApp theApp;单文档程序示例文档视图类应用程序视图窗口中一般没有复杂的交互控件通常有一个编辑框或者直接是一块画布用作绘图展示。但是也不绝对也可以用对话框作为视图窗口可将复杂控件拖入该对话框实现更灵活的交互功能。#include afxwin.h #include afxext.h #include resource.h //本例需要在资源文件中添加一个Menu控件(ID为IDR_MENU1,至少添加一个菜单栏) 和一个String Table(添加字符串资源IDAFX_IDS_UNTITLED, Value61443,Caption自定义) class CMyDoc :public CDocument { DECLARE_DYNCREATE(CMyDoc) DECLARE_MESSAGE_MAP() }; IMPLEMENT_DYNCREATE(CMyDoc, CDocument) BEGIN_MESSAGE_MAP(CMyDoc, CDocument) END_MESSAGE_MAP() /* class CDialogView :public CFormView // if you want add ui controls to a view window, then you should use CFormView { DECLARE_DYNCREATE(CDialogView) DECLARE_MESSAGE_MAP() public: CDialogView() :CFormView(IDD_DIALOG1) {}//Note: You should add a Dialog resource and set it to be child style. ~CDialogView() {} protected: afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); afx_msg void OnActivateView(BOOL bActivate, CView * pActivateView, CView * pDeactiveView); void DoDataExchange(CDataExchange* pDx) override; void OnInitialUpdate() override; private: BOOL m_bViewInitialized FALSE; }; int CDialogView::OnCreate(LPCREATESTRUCT lpCreateStruct) { CFrameWnd* pParentFrame GetParentFrame(); int iRet CFormView::OnCreate(lpCreateStruct); if (iRet 0 pParentFrame ! NULL)//ok { pParentFrame-SetActiveView(this, FALSE); } return iRet; } void CDialogView::DoDataExchange(CDataExchange* pDx) { CFormView::DoDataExchange(pDx); } void CDialogView::OnInitialUpdate() { CFormView::OnInitialUpdate(); //you can do initializing for sub controls here. m_bViewInitialized TRUE; } void CDialogView::OnActivateView(BOOL bActivate, CView * pActivateView, CView * pDeactiveView) { CFormView::OnActivateView(bActivate, pActivateView, pDeactiveView); if(!m_bViewInitialized) this-SendMessage(0x0364);//WM_INITIALUPDATE } IMPLEMENT_DYNCREATE(CDialogView, CFormView) BEGIN_MESSAGE_MAP(CDialogView, CFormView) ON_WM_CREATE() END_MESSAGE_MAP() */ class CMyView :public CView { DECLARE_DYNCREATE(CMyView) DECLARE_MESSAGE_MAP() public: virtual void OnDraw(CDC* pDC); protected: afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); }; void CMyView::OnDraw(CDC* pDC) { pDC-TextOut(100, 100, _TEXT(this is a view wnd)); } int CMyView::OnCreate(LPCREATESTRUCT lpCreateStruct) { CFrameWnd* pParentFrame GetParentFrame(); int iRet CView::OnCreate(lpCreateStruct); if (iRet 0 pParentFrame ! NULL)//ok { pParentFrame-SetActiveView(this, FALSE); } return iRet; } IMPLEMENT_DYNCREATE(CMyView, CView) BEGIN_MESSAGE_MAP(CMyView, CView) ON_WM_CREATE() END_MESSAGE_MAP() class CMyFrameWnd : public CFrameWnd { DECLARE_DYNCREATE(CMyFrameWnd) DECLARE_MESSAGE_MAP() protected: BOOL OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext) override; afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); private: DWORD m_viewCnt 1; CSplitterWnd m_wndSpliter; CSplitterWnd m_wndSubSpliter; }; BOOL CMyFrameWnd::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext) { if (pContext-m_pCurrentDoc NULL) pContext-m_pCurrentDoc new CMyDoc; if (m_viewCnt 1) { BOOL bRet CFrameWnd::OnCreateClient(lpcs, pContext);// if only one view needed, just use the default view. CWnd* wnd GetDescendantWindow(AFX_IDW_PANE_FIRST, TRUE); CView* pDefaultView DYNAMIC_DOWNCAST(CView, wnd); return bRet; } BOOL bRet FALSE; bRet m_wndSpliter.CreateStatic(this, 1, 2);// use wnd spliter to divide the client area into different sub blocks. Optionally you can choose CDockablePane to realize the same result, which is more flexible for page layout. //bRet m_wndSpliter.CreateView(0, 0, pContext-m_pNewViewClass, SIZE{ 600, 400 }, pContext); m_wndSpliter.SetColumnInfo(0,300,100); bRet m_wndSubSpliter.CreateStatic(m_wndSpliter, 2, 1, WS_CHILD | WS_VISIBLE, m_wndSpliter.IdFromRowCol(0, 0)); bRet m_wndSubSpliter.CreateView(0, 0, pContext-m_pNewViewClass, SIZE{ 0, 300 }, pContext); bRet m_wndSubSpliter.CreateView(1, 0, pContext-m_pNewViewClass, SIZE{ 0, 0 }, pContext); //pContext-m_pNewViewClass RUNTIME_CLASS(CDialogView); bRet m_wndSpliter.CreateView(0, 1, pContext-m_pNewViewClass, SIZE{0, 0}, pContext); m_pViewActive (CView*)m_wndSubSpliter.GetPane(0, 0); return TRUE; } int CMyFrameWnd::OnCreate(LPCREATESTRUCT lpCreateStruct) { return CFrameWnd::OnCreate(lpCreateStruct); } IMPLEMENT_DYNCREATE(CMyFrameWnd, CFrameWnd) BEGIN_MESSAGE_MAP(CMyFrameWnd, CFrameWnd) ON_WM_CREATE() END_MESSAGE_MAP() class CMyWinApp : public CWinApp { public: virtual BOOL InitInstance(); virtual int ExitInstance(); }; int CMyWinApp::ExitInstance() { return CWinApp::ExitInstance(); } BOOL CMyWinApp::InitInstance() { /************ Solution one Begin************************************/ /*CSingleDocTemplate* pTemplate new CSingleDocTemplate(IDR_MENU1, RUNTIME_CLASS(CMyDoc), RUNTIME_CLASS(CMyFrameWnd), RUNTIME_CLASS(CMyView)); AddDocTemplate(pTemplate); OnFileNew();*/ /************ Solution one End************************************/ /**********************Solution two Begin: **************************/ CMyFrameWnd* pFrame new CMyFrameWnd; CMyDoc* pDoc new CMyDoc; CCreateContext cct; cct.m_pCurrentDoc pDoc; cct.m_pNewViewClass RUNTIME_CLASS(CMyView); pFrame-LoadFrame(IDR_MENU1, WS_OVERLAPPEDWINDOW, NULL, cct); m_pMainWnd pFrame; /**********************Solution two End **************************/ m_pMainWnd-ShowWindow(SW_SHOW); m_pMainWnd-UpdateWindow(); return TRUE; } CMyWinApp theApp;多文档程序示例#include afxwin.h #include afxext.h #include afxole.h #include afxmdiframewndex.h #include afxmdichildwndex.h #include resource.h //本例需要在资源文件中添加两个Menu控件(ID为IDR_MENU1和IDR_MENU2) 和一个String Table(添加字符串资源IDAFX_IDS_UNTITLED, Value61443,Caption自定义) class CMyDoc :public CDocument { DECLARE_DYNCREATE(CMyDoc) public: BOOL OnNewDocument() override { if (!CDocument::OnNewDocument()) return FALSE; static int count 0; WCHAR message[10]; swprintf_s(message,L%s%d, LChdWnd, count);//修改子窗口标题 SetTitle(message); count; return TRUE; } }; IMPLEMENT_DYNCREATE(CMyDoc, CDocument) class CMyView :public CView { DECLARE_DYNCREATE(CMyView) public: virtual void OnDraw(CDC* pDC); }; void CMyView::OnDraw(CDC* pDC) { const wchar_t str[] LI am a view wnd; pDC-TextOut(100, 100, str); } IMPLEMENT_DYNCREATE(CMyView, CView) class CMyChild :public CMDIChildWndEx { DECLARE_DYNCREATE(CMyChild) public: CMyChild():CMDIChildWndEx(){} protected: BOOL PreCreateWindow(CREATESTRUCT cs) override { cs.style ~WS_OVERLAPPEDWINDOW; // 移除子框架窗口的标题栏、边框等独立窗口属性 cs.style | WS_CHILD;// 设置为子窗口样式 return CMDIChildWndEx::PreCreateWindow(cs); } // 多文档视图的子框架窗口中也可以利用CSplitterWnd进行窗口切分 }; IMPLEMENT_DYNCREATE(CMyChild, CMDIChildWndEx) class CMyFrameWnd : public CMDIFrameWndEx { public: CMyFrameWnd() :CMDIFrameWndEx() {} BOOL PreCreateWindow(CREATESTRUCT cs) override { m_strTitle MasterWnd;//修改主窗口标题 return CMDIFrameWndEx::PreCreateWindow(cs); } protected: BOOL OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext) override { if (!CMDIFrameWndEx::OnCreateClient(lpcs, pContext)) return FALSE; CWnd* wnd GetDescendantWindow(AFX_IDW_PANE_FIRST, TRUE);//框架已经创建了一个客户区窗口作为各个子框架窗口的父窗口 ASSERT(wnd m_wndClientArea); return TRUE; } protected: afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CMDIFrameWndEx::OnCreate(lpCreateStruct) -1) return -1; CMDITabInfo mdiTabParams; mdiTabParams.m_style CMFCTabCtrl::STYLE_3D_ONENOTE; // 其他可用样式... mdiTabParams.m_bActiveTabCloseButton TRUE; // 设置为 FALSE 会将关闭按钮放置在选项卡区域的右侧 mdiTabParams.m_bTabIcons FALSE; // 设置为 TRUE 将在 MDI 选项卡上启用文档图标 mdiTabParams.m_bAutoColor TRUE; // 设置为 FALSE 将禁用 MDI 选项卡的自动着色 mdiTabParams.m_bDocumentMenu FALSE; // 在选项卡区域的右边缘启用文档菜单 EnableMDITabbedGroups(TRUE, mdiTabParams); // 可以给主框架窗口创建一些可停靠的子窗口(CDockablePane),实现复杂的页面布局 return 0; } public: DECLARE_MESSAGE_MAP() }; BEGIN_MESSAGE_MAP(CMyFrameWnd, CMDIFrameWndEx) ON_WM_CREATE() END_MESSAGE_MAP() class CMyWinApp : public CWinApp { public: virtual BOOL InitInstance(); virtual int ExitInstance(); }; int CMyWinApp::ExitInstance() { return CWinApp::ExitInstance(); } BOOL CMyWinApp::InitInstance() { CMyFrameWnd* pFrame new CMyFrameWnd(); pFrame-LoadFrame(IDR_MENU1);//MENU1为主框架窗口的菜单(MENU1至少需要两个菜单栏) m_pMainWnd pFrame; //MENU2为子框架窗口菜单, 注意子框架的菜单不会显示在子框架窗口中, 而是显示在主框架窗口中, 根据当前正在活动的子框架窗口类型动态切换(MFC 框架已自动处理) //当没有活动的子框架窗口时才显示主框架窗口自身的菜单, 子框架窗口的菜单仅作为资源被使用。这是MDI多文档模型设计规范, 为的是使界面 //更美观。同样的, 不要给子框架窗口设计单独的工具栏和状态栏, 如果需要给不同类型的子框架窗口提供不同的工具项, 可以在在主框架窗口中添 //加特定工具,并仅在目标类型子窗口活动时显示, 其他情况下隐藏即可。状态信息的变化请给主框架窗口的状态栏发消息进行更新。 CMultiDocTemplate* pTemplate new CMultiDocTemplate(IDR_MENU2, RUNTIME_CLASS(CMyDoc), RUNTIME_CLASS(CMyChild), RUNTIME_CLASS(CMyView)); AddDocTemplate(pTemplate); OnFileNew();//创建三个子窗口 OnFileNew(); OnFileNew(); m_pMainWnd-ShowWindow(SW_SHOW); m_pMainWnd-UpdateWindow(); return TRUE; } CMyWinApp theApp;注1在框架中MFC中当窗口句柄与CWnd对象完成关联后属于该窗口句柄的所有消息都将由CWnd::WindowProc处理该函数进一步调用CCmdTarget::OnCmdMsg。这两个函数都是虚函数窗口消息如何流转取决于有没有重载它们。对于CFrameWnd主框架窗口而言它重载了OnCmdMsg接口从其实现源码中可以看到消息被依次转送到ActiveView(视图对象)-Frame(框架对象)-WinApp(应用程序对象)直到消息被处理。注CDocument文档类虽不是窗口类但是它继承自CCmdTarget所以也能处理消息通常这些消息是与之关联的视图对象转送的。注2MFC中窗口句柄(HWND)和窗口对象(CWnd*)的绑定关系是在_AfxCbtFilterHook钩子函数中执行的该钩子函数在窗口创建出来之后第一条窗口消息产生之前会调用一次因此每一条消息的窗口句柄都能找到对应的窗口对象。存放绑定关系的地方是个全局Map变量在窗口对象销毁的时候(DestroyWindow)会执行解绑操作。注3CWnd创建窗口有CreateEx和CreateDlgIndirect两个方法分别对应着::CreateWindowEx和::CreateDialogIndirect系统API前者用来创建通用类型窗口后者用来创建无模式对话框类型窗口(对话窗需要基于资源创建windows资源编辑器自动生成UI资源模板无需注册)。创建窗口后操作系统先发出WM_NCCREATE让用户初始化非UI数据并检查传入的参数是否合法窗口函数返回TRUE后操作系统创建非客户区元素(如标题栏菜单栏状态栏等)然后发出WM_CREATE让窗口函数对各种UI元素进行资源初始化并创建子窗口(根据窗口风格有的窗口没有这些非客户区元素就不会去创建)。对话框类型窗口的创建过程是在内部完成不会发出WM_NCCREATE和WM_CREATE消息当对话框及其内嵌子控件创建完成后会发送WM_INITDIALOG用户可以在消息处理函数中做一些子控件初始化的动作。注销毁窗口的时候是先发WM_DESTROY再发WM_NCDESTROY。可分别用于销毁窗口创建之后和创建之前用户申请的资源。注4通过资源编辑工具添加的控件当其被载入时该资源的窗口连同其内嵌子控件窗口会一并创建出来无需额外create。当父窗口被销毁时Win32 API内部会遍历该窗口的所有子窗口挨个发送destory消息敦促子窗口自毁。MFC中CWnd类在析构时会Destory自己的窗口句柄(特殊情况除外)。注意内嵌子控件在执行DDX_Control与CWnd对象建立绑定关系之前是没有CWnd对象与之关联的此时如果用CWnd::FromHandlePermanent查询将返回NULL, 如果用CWnd::FromHandle查询内部将创建一个CWnd临时对象返回。注5如果窗口对象没有调用Create成员函数创建窗口则可以人为给窗口对象附加一个窗口句柄也叫子类化SubclassWindow。使用DDX_Control接口绑定控件时内部将自动进行子类化绑定之后无需再调用SubclassWindow。子类化现有的窗口还允许用户修改其默认样式可以在PreSubclassWindow中设置样式。注6Windows中一个窗口的各子窗口(儿子窗口)关联的资源ID应该唯一也即一个资源ID可以被不同的窗口关联但是关联同一个资源ID的窗口最好别共享相同的父窗口避免用GetDlgItem查询资源ID时只能查询到第一个窗口造成误导。

相关新闻

VueCircleMenu:打造惊艳圆形菜单的终极Vue.js组件详解

VueCircleMenu:打造惊艳圆形菜单的终极Vue.js组件详解

VueCircleMenu:打造惊艳圆形菜单的终极Vue.js组件详解 【免费下载链接】VueCircleMenu :rabbit:A beautiful circle menu powered by Vue.js 项目地址: https://gitcode.com/gh_mirrors/vu/VueCircleMenu VueCircleMenu是一款基于Vue.js构建的精美圆形菜单组…

2026/7/2 23:32:24 阅读更多 →
为什么选择JSXGraph?探索这款跨浏览器可视化库的7大优势

为什么选择JSXGraph?探索这款跨浏览器可视化库的7大优势

为什么选择JSXGraph?探索这款跨浏览器可视化库的7大优势 【免费下载链接】jsxgraph JSXGraph is a cross-browser library for interactive geometry, function plotting, charting, and data visualization in a web browser. 项目地址: https://gitcode.com/gh_…

2026/5/17 4:24:46 阅读更多 →
Nut源码解析:深入理解NS游戏文件加密与解密机制

Nut源码解析:深入理解NS游戏文件加密与解密机制

Nut源码解析:深入理解NS游戏文件加密与解密机制 【免费下载链接】nut 项目地址: https://gitcode.com/gh_mirrors/nut/nut Nut是一款功能强大的开源工具,专注于NS游戏文件的处理,尤其在NCA、NSP和XCI等文件格式的加密与解密方面表现出…

2026/5/17 3:20:30 阅读更多 →

最新新闻

大负载六自由度平台:重型工况多自由度姿态模拟的工业级解决方案

大负载六自由度平台:重型工况多自由度姿态模拟的工业级解决方案

大负载六自由度平台:重型工况多自由度姿态模拟的工业级解决方案 随着高端装备制造、试验验证领域的技术升级,重型车辆、航海船舶、航空航天等行业对大负载工况下的多自由度姿态模拟、动力学测试、环境复现需求持续提升。在重型构件、整车级设备、大型工业装置的研发与测试环…

2026/7/3 13:46:36 阅读更多 →
Gazelle源码解析:lstack核心模块设计与关键函数实现

Gazelle源码解析:lstack核心模块设计与关键函数实现

Gazelle源码解析:lstack核心模块设计与关键函数实现 【免费下载链接】gazelle A high performance user-mode stack, which powered by dpdk and lwip 项目地址: https://gitcode.com/openeuler/gazelle 前往项目官网免费下载:https://ar.openeul…

2026/7/3 13:44:36 阅读更多 →
如何免费永久保存微信聊天记录:WeChatMsg完整备份与导出终极指南

如何免费永久保存微信聊天记录:WeChatMsg完整备份与导出终极指南

如何免费永久保存微信聊天记录:WeChatMsg完整备份与导出终极指南 【免费下载链接】WeChatMsg 提取微信聊天记录,将其导出成HTML、Word、CSV文档永久保存,对聊天记录进行分析生成年度聊天报告 项目地址: https://gitcode.com/GitHub_Trendin…

2026/7/3 13:42:35 阅读更多 →
LV3296与TM4C129ENCZAD在工业数据采集中的应用

LV3296与TM4C129ENCZAD在工业数据采集中的应用

1. 项目概述:LV3296与TM4C129ENCZAD的协同工作场景在工业自动化和物联网边缘计算领域,数据采集与处理的实时性、可靠性一直是工程师面临的挑战。LV3296作为一款高性能信号调理芯片,配合TI的TM4C129ENCZAD微控制器,构成了一个典型的…

2026/7/3 13:42:35 阅读更多 →
OpenClaw安装教程详细步骤,图文并茂轻松跟做

OpenClaw安装教程详细步骤,图文并茂轻松跟做

这篇是写给喜欢"图文并茂"风格的朋友的。我会把OpenClaw安装过程中的每个关键步骤都详细描述,并标注你应该在屏幕上看到的界面元素。如果你之前看纯文字教程容易跟丢,这篇会适合你。 OpenClaw最新版本一键部署包下载地址:https://t…

2026/7/3 13:38:33 阅读更多 →
TPAFE0808与PIC32MZ多通道信号采集系统设计

TPAFE0808与PIC32MZ多通道信号采集系统设计

1. 项目背景与核心需求解析 在工业自动化和嵌入式系统开发领域,多通道信号采集与实时控制一直是关键需求。TPAFE0808作为一款8通道模拟前端芯片,配合PIC32MZ2048EFH144这款高性能32位微控制器,能够构建出强大的信号处理与系统监测平台。这种组…

2026/7/3 13:38:33 阅读更多 →

日新闻

Nginx防御TLS重协商攻击实战:从原理到配置与监控

Nginx防御TLS重协商攻击实战:从原理到配置与监控

1. 项目概述:为什么TLS重协商攻击至今仍需警惕十多年前的CVE-2011-1473,一个关于TLS/SSL协议重协商机制的漏洞,现在提起来还有必要吗?很多运维和开发朋友可能会觉得,这都老掉牙了,现代服务器和客户端不都默…

2026/7/3 0:03:59 阅读更多 →
华为防火墙双通道远程管理实战:Web与SSH配置详解

华为防火墙双通道远程管理实战:Web与SSH配置详解

1. 项目概述:为什么需要双通道远程管理防火墙?在任何一个稍具规模的企业网络里,防火墙都是那个默默守护在边界的关键角色。作为网络工程师,我们不可能每次都跑到机房,插上console线去配置它。远程管理能力,…

2026/7/3 0:03:59 阅读更多 →
AD74413R与PIC18F65K40的高精度工业数据采集方案

AD74413R与PIC18F65K40的高精度工业数据采集方案

1. 项目概述:AD74413R与PIC18F65K40的协同工作在工业自动化和精密测量领域,同时实现高精度模数转换(ADC)和数模转换(DAC)功能是许多复杂系统的核心需求。AD74413R作为一款四通道可配置模拟输入/输出器件,与PIC18F65K40微控制器的组合&#xf…

2026/7/3 0:05:59 阅读更多 →

周新闻

月新闻