最近在做一個C++ win32的桌面圖形程序,我不是C++程序員,做這個只是因為最近沒什么java的活。
windows api,之前接觸的時候,還是大學,那時用這個開發打飛機游戲純粹是娛樂。現在基本忘光了。
我要實現的最初的需求也很簡單,就是做一個界面,上面有按鈕,點擊按鈕出現新的窗口。界面是htmlayout來做。所以就是看着代碼,邊學邊做,完全摸着石頭過河,這篇文章也沒啥技術含量。
第一步,使用htmlayout,創建一個簡單窗口,這在我上一篇文章有詳細記述:http://www.cnblogs.com/rixiang/p/6605416.html
要說明一點,項目大的框架,例如如何htmlayoout怎樣引入sdk等等在上述鏈接的文章里說明。本篇文章只具體記錄我解決的問題。就是【win32 htmlayout點擊按鈕創建新窗口,以及按鈕圖片樣式】。
第二步,點擊按鈕打開另一個新的窗口
這里我花了些時間摸索win32窗口的原理,其必要的流程是要先注冊窗口ATOM MyRegisterClass(HINSTANCE hInstance),然后調用CreateWindow這一win32 API,之后
ShowWindow(hWnd, nCmdShow); 以及UpdateWindow(hWnd); main函數的話,接下來就是消息循環。
這里的重點,也就是我之前沒注意到搞錯過的事情,就是創建子窗口的時候,其CreateWindow中父窗口句柄的參數一定要放入父窗口的hWnd。另外就是注冊lpszClassName一定要和創建的lpszClassName一致。
所以就是,觸發點擊按鈕事件時,在該事件函數里調用創建子窗口的方法,接着進行子窗口注冊、創建、顯示、更新、消息循環的過程。
在創建子窗口后,通過EnableWindow(hWndPar,FALSE);函數使父窗口暫時失效,在關閉子窗口的時候,再啟用父窗口。
第三步,窗口樣式
我希望達到的樣式是,界面沒有默認的幫助菜單,以及右上角的X關閉。界面上只有按鈕,關閉按鈕也是自己定義。並且按鈕背景圖片是自己定義的。
實現了第二步的基礎上又用了大概一天時間摸索。最后發現,取消默認的幫助菜單其實是需要在窗口注冊函數里定義:
wcex.lpszMenuName = NULL;//MAKEINTRESOURCE(IDC_WIN32HTML);
之后又困惑於自己CSS里定義的按鈕背景圖片為何沒有生效,最后我試着把html加載路徑由相對路徑改為絕對路徑就OK了。
2017年6月27日補充:要達到界面沒有默認的幫助菜單,以及右上角的X關閉的效果,CreateWindow函數的第三個函數需要為:WS_CLIPCHILDREN|WS_CLIPSIBLINGS|WS_POPUP
hwnd = CreateWindow("66", NULL, WS_CLIPCHILDREN|WS_CLIPSIBLINGS|WS_POPUP, CW_USEDEFAULT, 0, 0, 0, hWndPar, NULL, hInst, NULL);
最后效果:
點擊上面的按鈕則彈出新窗口,點擊下面的按鈕則直接退出。
下面是源代碼,以后擴展下也許會放到github,暫時就直接貼代碼了。作為java程序員,感覺桌面窗口程序開發也是非常常見的需求,以后可以做一個總結,把python、C++等等多種桌面窗口程序demo做個合集,放到github:
Win32Html.cpp,這里定義的是main函數和主窗口:
// Win32Html.cpp : 定義應用程序的入口點 #include "stdafx.h" #include "Win32Html.h" #include "New.h" #include <htmlayout.h> #include "behaviors/notifications.h" #include <htmlayout_behavior.hpp> #pragma comment(lib,"HTMLayout.lib") #define MAX_LOADSTRING 100 // 全局變量: HINSTANCE hInst; // 當前實例 TCHAR szTitle[MAX_LOADSTRING]; // 標題欄文本 TCHAR szWindowClass[MAX_LOADSTRING]; // 主窗口類名 // 此代碼模塊中包含的函數的前向聲明: ATOM MyRegisterClass(HINSTANCE hInstance); BOOL InitInstance(HINSTANCE, int); LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM); // 父窗口句柄 HWND hWnd; #include "New.h" int APIENTRY _tWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPTSTR lpCmdLine, _In_ int nCmdShow) { UNREFERENCED_PARAMETER(hPrevInstance); UNREFERENCED_PARAMETER(lpCmdLine); MSG msg; HACCEL hAccelTable; // 初始化全局字符串 LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING); LoadString(hInstance, IDC_WIN32HTML, szWindowClass, MAX_LOADSTRING); MyRegisterClass(hInstance); // 執行應用程序初始化: if (!InitInstance (hInstance, nCmdShow)) { return FALSE; } hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_WIN32HTML)); // 主消息循環: while (GetMessage(&msg, NULL, 0, 0)) { if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } } return (int) msg.wParam; } // // 函數: MyRegisterClass() // // 目的: 注冊窗口類。 // 作者: sonne // 日期: 2017-03-23 // ATOM MyRegisterClass(HINSTANCE hInstance) { WNDCLASSEX wcex; wcex.cbSize = sizeof(WNDCLASSEX); wcex.style = CS_HREDRAW | CS_VREDRAW; wcex.lpfnWndProc = WndProc; wcex.cbClsExtra = 0; wcex.cbWndExtra = 0; wcex.hInstance = hInstance; wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WIN32HTML)); wcex.hCursor = NULL;//LoadCursor(NULL, IDC_ARROW); wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); wcex.lpszMenuName = NULL;//MAKEINTRESOURCE(IDC_WIN32HTML); wcex.lpszClassName = szWindowClass; wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_ICON)); return RegisterClassEx(&wcex); } // // 函數: InitInstance(HINSTANCE, int) // // 目的: 保存實例句柄並創建主窗口 // 作者: sonne // 日期: 2017-03-23 // 注釋: // // 在此函數中,我們在全局變量中保存實例句柄並 // 創建和顯示主程序窗口。 // BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) { hInst = hInstance; // 將實例句柄存儲在全局變量中 hWnd = CreateWindow(szWindowClass, NULL, WS_CLIPCHILDREN|WS_CLIPSIBLINGS|WS_POPUP/*WS_OVERLAPPED*/, CW_USEDEFAULT, 0, 1000, 1000, NULL, NULL, hInstance, NULL); if (!hWnd) { return FALSE; } ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); return TRUE; } void OnButtonClick(HELEMENT button); struct DOMEventsHandlerType: htmlayout::event_handler { DOMEventsHandlerType(): event_handler(0xFFFFFFFF) {} virtual BOOL handle_event (HELEMENT he, BEHAVIOR_EVENT_PARAMS& params ) { switch( params.cmd ) { case BUTTON_CLICK: OnButtonClick(params.heTarget); break;// click on button case BUTTON_PRESS: break;// mouse down or key down in button case BUTTON_STATE_CHANGED: break; case EDIT_VALUE_CHANGING: break;// before text change case EDIT_VALUE_CHANGED: break;//after text change case SELECT_SELECTION_CHANGED: break;// selection in <select> changed case SELECT_STATE_CHANGED: break;// node in select expanded/collapsed, heTarget is the node case POPUP_REQUEST: break;// request to show popup just received, // here DOM of popup element can be modifed. case POPUP_READY: break;// popup element has been measured and ready to be shown on screen, // here you can use functions like ScrollToView. case POPUP_DISMISSED: break;// popup element is closed, // here DOM of popup element can be modifed again - e.g. some items can be removed // to free memory. case MENU_ITEM_ACTIVE: // menu item activated by mouse hover or by keyboard break; case MENU_ITEM_CLICK: // menu item click break; // "grey" event codes - notfications from behaviors from this SDK case HYPERLINK_CLICK: break;// hyperlink click case TABLE_HEADER_CLICK: break;// click on some cell in table header, // target = the cell, // reason = index of the cell (column number, 0..n) case TABLE_ROW_CLICK: break;// click on data row in the table, target is the row // target = the row, // reason = index of the row (fixed_rows..n) case TABLE_ROW_DBL_CLICK: break;// mouse dbl click on data row in the table, target is the row // target = the row, // reason = index of the row (fixed_rows..n) case ELEMENT_COLLAPSED: break;// element was collapsed, so far only behavior:tabs is sending these two to the panels case ELEMENT_EXPANDED: break;// element was expanded, } return FALSE; } } DOMEventsHandler; LRESULT CALLBACK HTMLayoutNotifyHandler(UINT uMsg, WPARAM wParam, LPARAM lParam, LPVOID vParam) { // all HTMLayout notification are comming here. NMHDR* phdr = (NMHDR*)lParam; switch(phdr->code) { case HLN_CREATE_CONTROL: break; //return OnCreateControl((LPNMHL_CREATE_CONTROL) lParam); case HLN_CONTROL_CREATED: break; //return OnControlCreated((LPNMHL_CREATE_CONTROL) lParam); case HLN_DESTROY_CONTROL: break; //return OnDestroyControl((LPNMHL_DESTROY_CONTROL) lParam); case HLN_LOAD_DATA: break; case HLN_DATA_LOADED: break; //return OnDataLoaded((LPNMHL_DATA_LOADED)lParam); case HLN_DOCUMENT_COMPLETE: break; //return OnDocumentComplete(); case HLN_ATTACH_BEHAVIOR: break; } return 0; } // // 函數: WndProc(HWND, UINT, WPARAM, LPARAM) // // 目的: 處理主窗口的消息。 // 作者: sonne // 日期: 2017-03-23 // WM_COMMAND - 處理應用程序菜單 // WM_PAINT - 繪制主窗口 // WM_DESTROY - 發送退出消息並返回 // // LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { int wmId, wmEvent; PAINTSTRUCT ps; HDC hdc; LRESULT lResult; BOOL bHandled; // HTMLayout + // HTMLayout could be created as separate window // using CreateWindow API. // But in this case we are attaching HTMLayout functionality // to the existing window delegating windows message handling to // HTMLayoutProcND function. lResult = HTMLayoutProcND(hWnd,message,wParam,lParam, &bHandled); if(bHandled) return lResult; // 相對路徑 char* chRelativePath = "htmlayoutDemo.htm"; WCHAR wsRelativePath[256]; switch (message) { case WM_CREATE: { // Normally HTMLayout sends its notifications // to its parent. // In this particular case we are using callback function to receive and // and handle notification. Don't bother the desktop window (parent of this window) // by our notfications. HTMLayoutSetCallback(hWnd,&HTMLayoutNotifyHandler,0); // attach DOM events handler so we will be able to receive DOM events like BUTTON_CLICK, HYPERLINK_CLICK, etc. htmlayout::attach_event_handler(hWnd, &DOMEventsHandler); memset(wsRelativePath,0,sizeof(wsRelativePath)); //char* 轉LPCWSTR MultiByteToWideChar(CP_ACP, 0, chRelativePath, strlen(chRelativePath)+1, wsRelativePath, sizeof(wsRelativePath)/sizeof(wsRelativePath[0])); //使用絕對路徑 WCHAR wsPath[MAX_PATH]; GetCurrentDirectoryW(2048,wsPath); wcscat(wsPath,L"\\"); wcscat(wsPath,wsRelativePath); HTMLayoutLoadFile(hWnd,wsPath); SetWindowText(hWnd,"身份認證系統"); SetForegroundWindow( hWnd ); //窗口初始化 DWORD dWidth = HTMLayoutGetMinWidth(hWnd); DWORD dHeight = HTMLayoutGetMinHeight(hWnd,dWidth); int cx = GetSystemMetrics(SM_CXSCREEN); int cy = GetSystemMetrics(SM_CYSCREEN); SetWindowPos(hWnd,HWND_TOPMOST,cx/2-dWidth/2,cy/2-dHeight/2,dWidth,dHeight,SWP_NOZORDER); HRGN hRgn; RECT rect; GetWindowRect(hWnd,&rect); hRgn = CreateRoundRectRgn(0,0,rect.right-rect.left,rect.bottom-rect.top,20,20); SetWindowRgn(hWnd,hRgn,TRUE); } break; case WM_COMMAND: wmId = LOWORD(wParam); wmEvent = HIWORD(wParam); // 分析菜單選擇: switch (wmId) { case IDM_ABOUT: DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About); break; case IDM_EXIT: DestroyWindow(hWnd); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } break; case WM_PAINT: hdc = BeginPaint(hWnd, &ps); // TODO: 在此添加任意繪圖代碼... EndPaint(hWnd, &ps); break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; } // “關於”框的消息處理程序。 INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) { UNREFERENCED_PARAMETER(lParam); switch (message) { case WM_INITDIALOG: return (INT_PTR)TRUE; case WM_COMMAND: if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL) { EndDialog(hDlg, LOWORD(wParam)); return (INT_PTR)TRUE; } break; } return (INT_PTR)FALSE; } //************************************ // 作 者: sonne // 函 數 名: OnButtonClick // 功 能: 按鈕響應事件 // 完 整 名: OnButtonClick // 訪 問 權: public // 返回值類型: VOID // 方法限定符: //************************************ void OnButtonClick(HELEMENT button) { htmlayout::dom::element cBut = button; if (!wcscmp(cBut.get_attribute("id"),L"button1")) { NEW::InitInstance(hInst, 0, hWnd, szWindowClass); } //關閉按鈕 if (!wcscmp(cBut.get_attribute("id"),L"button2")) { PostMessage(hWnd,WM_DESTROY,NULL,NULL); } }
New.h,這里定義的是點擊按鈕彈出的新窗口的一系列方法:
#pragma once #include <htmlayout.h> #include "behaviors/notifications.h" #include <htmlayout_behavior.hpp> namespace NEW { #define MAX_LOADSTRING 100 // 全局變量: HINSTANCE hInst; // 當前實例 TCHAR szTitle[MAX_LOADSTRING]; // 標題欄文本 TCHAR szWindowClass[MAX_LOADSTRING]; // 主窗口類名 // 此代碼模塊中包含的函數的前向聲明: ATOM MyRegisterClass(HINSTANCE hInstance); BOOL InitInstance(HINSTANCE, int); LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM); // // 函數: WndProc(HWND, UINT, WPARAM, LPARAM) // // 目的: 處理主窗口的消息。 // 作者: sonne // 日期: 2017-03-27 // // WM_COMMAND - 處理應用程序菜單 // WM_PAINT - 繪制主窗口 // WM_DESTROY - 發送退出消息並返回 // // LRESULT CALLBACK NewWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { int wmId, wmEvent; PAINTSTRUCT ps; HDC hdc; LRESULT lResult; BOOL bHandled; lResult = HTMLayoutProcND(hWnd,message,wParam,lParam, &bHandled); if(bHandled) return lResult; char* path = "newPage.htm"; WCHAR wsz[256]; switch (message) { case WM_CREATE: memset(wsz,0,sizeof(wsz)); MultiByteToWideChar(CP_ACP, 0, path, strlen(path)+1, wsz, sizeof(wsz)/sizeof(wsz[0])); HTMLayoutLoadFile(hWnd,wsz); //Hello.htm需要放在和exe同一目錄,記得把dll也copy過去 break; case WM_COMMAND: wmId = LOWORD(wParam); wmEvent = HIWORD(wParam); switch (wmId) { case IDM_ABOUT: DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About); break; case IDM_EXIT: DestroyWindow(hWnd); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } break; case WM_PAINT: hdc = BeginPaint(hWnd, &ps); EndPaint(hWnd, &ps); break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; } // // 函數: MyRegisterClass() // // 目的: 注冊窗口類。 // 作者: sonne // 日期: 2017-03-27 // ATOM NewRegisterClass(HINSTANCE hInstance) { WNDCLASSEX wcex; wcex.cbSize = sizeof(WNDCLASSEX); wcex.style = CS_HREDRAW | CS_VREDRAW; wcex.lpfnWndProc = (WNDPROC)NewWndProc; wcex.cbClsExtra = 0; wcex.cbWndExtra = 0; wcex.hInstance = hInstance; wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WIN32HTML)); wcex.hCursor = LoadCursor(NULL, IDC_ARROW); wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); wcex.lpszMenuName = MAKEINTRESOURCE(IDC_WIN32HTML); wcex.lpszClassName = "hh"; wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL)); return RegisterClassEx(&wcex); } // // 函數: InitInstance(HINSTANCE, int) // // 目的: 保存實例句柄並創建主窗口 // // 注釋: // // 在此函數中,我們在全局變量中保存實例句柄並 // 創建和顯示主程序窗口。 // 作者: sonne // 日期: 2017-03-27 // BOOL InitInstance(HINSTANCE hInstance, int nCmdShow, HWND hWndPar, TCHAR szWindowClass[]) { HWND newHWnd; hInst = hInstance; // 將實例句柄存儲在全局變量中 htmlayout::dom::element root; char szCurrPath[MAX_PATH]; GetCurrentDirectory(MAX_PATH,szCurrPath); NewRegisterClass(hInst); newHWnd = CreateWindow("hh", NULL, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, hWndPar, NULL, hInst, NULL); EnableWindow(hWndPar,FALSE); if (!newHWnd) { return FALSE; } ShowWindow(newHWnd, 5); UpdateWindow(newHWnd); MSG msg; HACCEL hAccelTable; hAccelTable = LoadAccelerators(hInst, (LPCTSTR)IDR_ACCELERATOR); while (GetMessage(&msg, NULL, 0, 0)) { if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } } Sleep(100); EnableWindow(hWndPar,TRUE); SetForegroundWindow( hWndPar ); BringWindowToTop(hWndPar); root = htmlayout::dom::element::root_element(hWndPar); if (root) { root.refresh(); } SetCurrentDirectory(szCurrPath); return TRUE; } // “關於”框的消息處理程序。 INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) { UNREFERENCED_PARAMETER(lParam); switch (message) { case WM_INITDIALOG: return (INT_PTR)TRUE; case WM_COMMAND: if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL) { EndDialog(hDlg, LOWORD(wParam)); return (INT_PTR)TRUE; } break; } return (INT_PTR)FALSE; } }
Resource.h:
//{{NO_DEPENDENCIES}} // Microsoft Visual C++ generated include file. // Used by Win32Html.rc // #define IDS_APP_TITLE 103 #define IDR_MAINFRAME 128 #define IDD_WIN32HTML_DIALOG 102 #define IDD_ABOUTBOX 103 #define IDM_ABOUT 104 #define IDM_EXIT 105 #define IDI_WIN32HTML 107 #define IDI_SMALL 108 #define IDC_WIN32HTML 109 #define IDC_MYICON 2 //{{NO_DEPENDENCIES}} // Microsoft Visual C++ 生成的包含文件。 // 供 Settings.rc 使用 // #define IDI_ICON 101 #define IDR_ACCELERATOR 102 #define IDD_MASK 103 #define IDC_X 1002 #ifndef IDC_STATIC #define IDC_STATIC -1 #endif // 新對象的下一組默認值 // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NO_MFC 130 #define _APS_NEXT_RESOURCE_VALUE 129 #define _APS_NEXT_COMMAND_VALUE 32771 #define _APS_NEXT_CONTROL_VALUE 1000 #define _APS_NEXT_SYMED_VALUE 110 #endif #endif
htmlayoutDemo.htm:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <style type="text/css"> body { font:12px Helvetica,Georgia,Arial,sans-serif; margin:0; padding:0; background:#fff; } ul{ list-style-type: none; margin:0px; } .bStyle1 { background:url(test_button1.png) top left no-repeat; width:180px; height:130px; border:none; cursor:pointer; } .bStyle2 { background:url(test_button2.png) top left no-repeat; width:180px; height:130px; border:none; cursor:pointer; } </style> <title> new page </title> </head> <body> <h2>htmlayout demo.</h2> <ul> <li><input type="button" id="button1" class="bStyle1"/></li> <li><input type="button" id="button2" class="bStyle2"/></li> </ul> </body> </html>
項目結構:
github代碼地址:
https://github.com/SonnAdolf/sonne_desktop_graphical_development