標籤:
前面我們所舉的例子中都是單一文件介面架構,也就是說這個視窗裡面的客戶區就是一個文檔介面,可以編寫程式在裡面輸入或者繪製文本和圖形輸出,但是不能有出現多個文檔的情況。比如下面的UltraEdit就是一個典型的多重文件介面,他可以同時編輯多個文檔,每個文檔還可以最大化,最小化等等,我們今天就來看看多文檔的基本架構是怎麼實現的。
多重文件介面的架構建立需要幾下幾步。
主架構窗的建立跟普通的視窗沒有什麼區別,就是自己註冊一個類並用該類建立一個重疊的視窗,這個可以用CreateWindow/CreateWindowEx函數完成,主架構視窗可以用自己的菜單和狀態列。
客戶區建立的建立同樣用你CreateWindow,但需要指定類為“MDICLIENT”,用這個類會建立多文檔的客戶區視窗。或者採用CreateWindowEx函數,指定擴充風格為WS_EX_MDICHILD。客戶區不需要菜單和狀態列。
建立工作或者視圖視窗作為實際文件視窗,這個也是需要自己註冊類並建立自己需要的視圖視窗。視圖視窗可以有自己的菜單,一般不需要狀態列。當視圖視窗啟用時,主視窗的菜單需要替換成視圖視窗的菜單,因為這個時候往往是對視圖視窗進行操作,菜單替換也是理所當然的。菜單設定通過WM_MDISETMENU訊息完成。該訊息定義如下:
SendMessage(hWndControl, WM_MDISETMENU, wParam, lParam)wParam表示新架構視窗菜單,lParam表示要設定的菜單控制代碼
OK,基本的API就這些,下面我們直接上demo程式來示範,由於視圖一般是一個多文檔的執行個體,所以demo代碼中用類來儲存視圖,每一個視圖就是一個類的執行個體:
主檔案,該檔案包括主架構視窗、客戶視窗已經視圖視窗調用:
#include <windows.h>#define CLIENT_WND_BGROUND#define IDM_FIRSTCHILD 1001#define IDM_FILE_NEW 1002HINSTANCE g_hInst;TCHAR szMDIMainFrame[]= TEXT("MDI Main Frame");TCHAR gszAppName[] = TEXT("MDI Demo");HWND ghWndMDIClient;HWND ghWndMainFrame;HMENU ghMainFrameMenu;void CreateChildWndMenu(void);HWND CreateDocView(HWND hParentWnd, HINSTANCE hInstance);static LRESULT CALLBACK MainFrameProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);#ifdef CLIENT_WND_BGROUNDstatic WNDPROC pfOrigClientWndProc;LRESULT CALLBACK ClientWndSubClassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam){ switch(uMsg) { case WM_ERASEBKGND: { RECT rect; HDC hDC = (HDC)wParam; GetClientRect(hwnd, &rect); HBRUSH hbr = CreateSolidBrush(RGB(125, 150, 125)); HBRUSH holdbr = (HBRUSH)SelectObject(hDC, hbr); FillRect(hDC, &rect, hbr); SelectObject(hDC, holdbr); DeleteObject(hbr); } return TRUE; default: break; } return CallWindowProc(pfOrigClientWndProc, hwnd, uMsg, wParam, lParam); }#endifHMENU CreateMainFrameMenu(void){ //主架構菜單 HMENU hMenu = CreateMenu(); //檔案菜單 HMENU hFileMenu = CreateMenu(); AppendMenu(hFileMenu, MF_STRING, IDM_FILE_NEW, TEXT("&New")); AppendMenu(hMenu, MF_POPUP, (UINT_PTR)hFileMenu, TEXT("&File")); return hMenu;}int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd){ MSG msg; WNDCLASSEX wndclass = {0}; wndclass.cbSize= sizeof(WNDCLASSEX); wndclass.style= CS_HREDRAW | CS_VREDRAW; wndclass.lpfnWndProc= MainFrameProc; wndclass.hInstance= hInstance; wndclass.hIcon= NULL; wndclass.hCursor= LoadCursor(NULL, IDC_ARROW); wndclass.hbrBackground= (HBRUSH)GetStockObject(WHITE_BRUSH); wndclass.lpszClassName= szMDIMainFrame; if(!RegisterClassEx(&wndclass)) { MessageBox(NULL, TEXT("This program requires Windows NT!"), gszAppName, MB_ICONERROR); return 0; } g_hInst = hInstance; ghMainFrameMenu = CreateMainFrameMenu(); CreateChildWndMenu(); //Create the main MDI frame window ghWndMainFrame = CreateWindow(szMDIMainFrame, gszAppName, WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN, CW_USEDEFAULT,// allows system choose an x position CW_USEDEFAULT,// allows system choose a y position 400, 300, NULL,// handle to parent window ghMainFrameMenu,// handle to menu hInstance,// handle to the instance of module NULL);// Long pointer to a value to be passed to the window through the ShowWindow(ghWndMainFrame, SW_SHOW); UpdateWindow(ghWndMainFrame); while(GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg) ; DispatchMessage(&msg) ; } return msg.wParam ;}LRESULT CALLBACK MainFrameProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam){ switch(message) { case WM_CREATE: { CLIENTCREATESTRUCT ccs; ccs.hWindowMenu = NULL; ccs.idFirstChild = IDM_FIRSTCHILD; ghWndMDIClient = CreateWindow(TEXT("MDICLIENT"), NULL, WS_CHILD | WS_CLIPCHILDREN | WS_VISIBLE, 0, 0, 0, 0, hWnd, NULL, g_hInst, (void *)&ccs);#ifdef CLIENT_WND_BGROUND pfOrigClientWndProc = (WNDPROC)SetWindowLong(ghWndMDIClient, GWL_WNDPROC, (LONG)ClientWndSubClassProc);#endif } return 0; case WM_COMMAND: switch(LOWORD(wParam)) { case IDM_FILE_NEW: CreateDocView(ghWndMDIClient, g_hInst); return 0; } break; case WM_DESTROY: PostQuitMessage(0); return 0; } return DefFrameProc(hWnd, ghWndMDIClient, message, wParam, lParam);}
下面是視圖視窗,每個視圖視窗由一個類來描述,本文僅僅是一個demo,只有執行個體化一個視圖視窗作為示範:
#include <windows.h>#define IDM_EDIT_COPY 1003#define IDM_EDIT_PASTE 1004class CViewInfo{public: CViewInfo(HINSTANCE hInst); ~CViewInfo(); HWND CreateViewWindow(HWND hParentWnd);private: HINSTANCE m_hInst; HWND m_hWndView;};static TCHAR szViewWndName[] = TEXT("View Window");static CViewInfo *g_pSystemInfo;static HMENU ghChildWndMenu;extern HMENU ghMainFrameMenu;CViewInfo::CViewInfo(HINSTANCE hInst){ m_hWndView = NULL; m_hInst = hInst;}CViewInfo::~CViewInfo(){ m_hWndView = NULL;}LRESULT CALLBACK ViewWindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam){ switch(message) { case WM_MDIACTIVATE: { HWND hWndClient = GetParent(hWnd); HWND hWndFrame = GetParent(hWndClient); HMENU hMenu = GetSubMenu(ghChildWndMenu, 0); if (lParam == (LPARAM)hWnd) { SendMessage(hWndClient, WM_MDISETMENU, (WPARAM)ghChildWndMenu, (LPARAM)hMenu); EnableMenuItem(ghChildWndMenu, IDM_EDIT_COPY, MF_GRAYED); } // Set the framewindow menu if losing focus if (lParam != (LPARAM)hWnd) { SendMessage(hWndClient, WM_MDISETMENU, (WPARAM)ghMainFrameMenu, (LPARAM)NULL); } // call DrawMenuBar after the menu items are set DrawMenuBar(hWndFrame); return 0 ; } case WM_CLOSE: delete g_pSystemInfo; break; case WM_DESTROY: return 0; } return DefMDIChildProc(hWnd, message, wParam, lParam); //Frame window calls DefFrameProc rather than DefWindowProc}HWND CViewInfo::CreateViewWindow(HWND hParentWnd){ WNDCLASSEX wcDoc; wcDoc.cbSize= sizeof(WNDCLASSEX); wcDoc.style= CS_HREDRAW | CS_VREDRAW; wcDoc.lpfnWndProc= ViewWindowProc; wcDoc.cbClsExtra= 0; wcDoc.cbWndExtra= 0; wcDoc.hInstance= m_hInst; wcDoc.hIcon= NULL; wcDoc.hCursor= LoadCursor(NULL, IDC_ARROW); wcDoc.hbrBackground= (HBRUSH) GetStockObject(WHITE_BRUSH); wcDoc.lpszMenuName= NULL; wcDoc.lpszClassName= szViewWndName; wcDoc.hIconSm= NULL; if(!RegisterClassEx(&wcDoc)) { DWORD dw_LastError = GetLastError(); if(ERROR_CLASS_ALREADY_EXISTS != dw_LastError) { return 0; } } m_hWndView = CreateWindowEx(WS_EX_MDICHILD, szViewWndName, TEXT("New view"), 0, CW_USEDEFAULT, CW_USEDEFAULT, 300, 200, hParentWnd, NULL, m_hInst, NULL); return m_hWndView;}HWND CreateDocView(HWND hParentWnd, HINSTANCE hInstance){ HWND hView; g_pSystemInfo = new CViewInfo(hInstance); hView = g_pSystemInfo->CreateViewWindow(hParentWnd); if (hView == NULL) { delete g_pSystemInfo; g_pSystemInfo = NULL; } return hView;}void CreateChildWndMenu(void){ //View菜單 ghChildWndMenu = CreateMenu(); //編輯菜單 HMENU hEditMenu = CreateMenu(); AppendMenu(hEditMenu, MF_STRING, IDM_EDIT_COPY, TEXT("&Copy")); AppendMenu(hEditMenu, MF_STRING, IDM_EDIT_PASTE, TEXT("&Paste")); AppendMenu(ghChildWndMenu, MF_POPUP, (UINT_PTR)hEditMenu, TEXT("&Edit"));}
本執行個體運行後,介面如下:
選擇File->New建立一個視圖後demo程式如下,可以看到菜單編程視圖的菜單:
最大化後可以看到視圖視窗和填滿客戶視窗:
雖然本程式只實現了一個視圖的執行個體,但是再增加一個是很容易的,只要想辦法在菜單中調用CreateDocView函數,並且正確處理對象指標即可。執行個體並沒有增加狀態列,因為這個對多文檔並不是必須的,要增加的讀者可以參考前面的建立Toolbar和Statusbar一文。
本執行個體實現了一個基本的多文件視窗架構,讀者朋友可以在此基礎上加上工具列、狀態列、視圖視窗建立對類的處理,多執行個體以及具體的需求,完成實用化的多重文件介面。
更多經驗交流可以加入Windows編程討論QQ群:454398517。
關注公眾平台:程式員互動聯盟(coder_online),你可以第一時間擷取原創技術文章,和(Java/C/C++/Android/Windows/Linux)技術大牛做朋友,線上交流編程經驗,擷取編程基礎知識,解決編程問題。程式員互動聯盟,開發人員自己的家。
轉載請註明出處http://www.coderonline.net/?p=2052,謝謝合作!
【Windows編程】系列第十一篇:多重文件介面架構