逐步解說: (C++) 建立傳統Windows傳統型應用程式
本逐步解說示範如何在 Visual Studio 中建立傳統Windows傳統型應用程式。 您將建立的範例應用程式會使用 Windows API,在視窗中顯示 「Hello, Windows desktop!」。 您可以使用在這個逐步解說中開發的程式碼作為模式,來建立其他 Windows 傳統型應用程式。
Windows API (也稱為 WIN32 API、Windows Desktop API,以及 Windows 傳統 API) 是用來建立Windows應用程式的 C 語言架構。 自 1980 年代起就已經存在,且已用來建立數十年Windows應用程式。 更進階且更容易的程式架構已建置在 Windows API 之上。 例如,MFC、ATL、.NET 架構。 即使是以 C++/WinRT 撰寫之 UWP 和市集應用程式的最新Windows 執行階段程式碼,也會使用下方的 Windows API。 如需 Windows API 的詳細資訊,請參閱Windows API 索引。 有許多方式可以建立Windows應用程式,但上述程式是第一個。
重要
為了簡潔起見,文字中會省略某些程式碼語句。 本檔結尾的 [建置程式碼 ] 區段會顯示完整的程式碼。
必要條件
執行 Microsoft Windows 7 或更新版本的電腦。 建議您Windows 10或更新版本,以獲得最佳的開發體驗。
一份 Visual Studio 複本。 如需如何下載並安裝 Visual Studio 的詳細資訊,請參閱安裝 Visual Studio。 當您執行安裝程式時,請確認已選取使用 C++ 的桌面開發工作負載。 如果您在安裝 Visual Studio 時未安裝此工作負載,也不用擔心。 您可以再次執行安裝程式並立即安裝。
了解使用 Visual Studio IDE 的基本概念。 如果您先前使用過 Windows 傳統型應用程式,您應能輕鬆跟上。 如需簡介,請參閱 Visual Studio IDE 功能導覽。
了解足夠的 C++ 語言基本概念。 別擔心,我們不會進行太複雜的操作。
建立Windows桌面專案
請遵循下列步驟來建立您的第一個Windows桌面專案。 一如往常,您將輸入工作Windows傳統型應用程式的程式碼。 若要查看您慣用版本的Visual Studio檔,請使用版本選取器控制項。 其位於此頁面目錄頂端。
在 Visual Studio 中建立Windows桌面專案
從主功能表中,選擇 [檔案>][新增>Project以開啟 [建立新的Project] 對話方塊。
在對話方塊頂端,將[語言] 設定為[C++]、將[平臺] 設定為[Windows],並將[Project類型] 設定為[桌面]。
從篩選的專案類型清單中,選擇[Windows桌面精靈],然後選擇 [下一步]。 在下一頁中,輸入專案的名稱,例如 DesktopApp。
選擇 [建立] 按鈕以建立專案。
[Windows桌面Project] 對話方塊隨即出現。 在 [應用程式類型] 底下,選取 [ 傳統型應用程式] (.exe) 。 在 [其他選項] 下,選取 [空專案] 。 選擇 [確定 ] 以建立專案。
在方案總管中,以滑鼠右鍵按一下DesktopApp專案,選擇 [新增],然後選擇 [新增專案]。
在 [加入新項目] 對話方塊中,選取 [C++ 檔 (.cpp)] 。 在 [ 名稱] 方塊中,輸入檔案的名稱,例如 HelloWindowsDesktop.cpp。 選擇 [新增] 。
您的專案現在已建立,而且您的原始程式檔會在編輯器中開啟。 若要繼續,請直接跳到 建立程式碼。
在 Visual Studio 2017 中建立Windows桌面專案
在 [檔案] 功能表上,選擇 [新增] 然後選擇 [專案]。
在 [新增Project] 對話方塊的左窗格中,展開[已安裝][Visual>C++],然後選取[Windows Desktop]。 在中間窗格中,選取[Windows桌面精靈]。
在 [ 名稱] 方塊中,輸入專案的名稱,例如 DesktopApp。 選擇 [確定]。
在[Windows Desktop Project] 對話方塊的 [應用程式類型] 底下,選取[Windows應用程式] (.exe) 。 在 [其他選項] 下,選取 [空專案] 。 請確定未選取 [先行編譯標頭 ]。 選擇 [確定 ] 以建立專案。
在方案總管中,以滑鼠右鍵按一下DesktopApp專案,選擇 [新增],然後選擇 [新增專案]。
在 [加入新項目] 對話方塊中,選取 [C++ 檔 (.cpp)] 。 在 [ 名稱] 方塊中,輸入檔案的名稱,例如 HelloWindowsDesktop.cpp。 選擇 [新增] 。
您的專案現在已建立,而且您的原始程式檔會在編輯器中開啟。 若要繼續,請直接跳到 建立程式碼。
在 Visual Studio 2015 中建立Windows桌面專案
在 [檔案] 功能表上,選擇 [新增] 然後選擇 [專案]。
在 [新增Project] 對話方塊的左窗格中,展開[InstalledTemplatesVisual>>C++],然後選取[Win32]。 在中間窗格選取 [Win32 專案] 。
在 [ 名稱] 方塊中,輸入專案的名稱,例如 DesktopApp。 選擇 [確定]。
在Win32 應用程式精靈的 [概觀] 頁面上,選擇 [下一步]。
在 [應用程式設定] 頁面上的 [應用程式類型] 底下,選取[Windows應用程式]。 在 [其他選項] 底下,取消核取 [先行編譯頭檔],然後選取 [ 空白專案]。 選擇 [完成 ] 以建立專案。
在方案總管中,以滑鼠右鍵按一下 DesktopApp 專案,選擇 [新增],然後選擇 [新增專案]。
在 [加入新項目] 對話方塊中,選取 [C++ 檔 (.cpp)] 。 在 [ 名稱] 方塊中,輸入檔案的名稱,例如 HelloWindowsDesktop.cpp。 選擇 [新增] 。
您的專案現在已建立,而且您的原始程式檔會在編輯器中開啟。
建立程式碼
接下來,您將瞭解如何在 Visual Studio 中為Windows傳統型應用程式建立程式碼。
啟動 Windows 傳統型應用程式
就像每個 C 應用程式和 C++ 應用程式都必須有函
main
式作為其起點,每個Windows傳統型應用程式都必須有函WinMain
式。WinMain
具有下列語法。int WINAPI WinMain( _In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nCmdShow );
如需此函式的參數和傳回值的相關資訊,請參閱 WinMain 進入點。
注意
這些額外的單字是什麼,例如
WINAPI
、 或CALLBACK
、 或HINSTANCE
或_In_
? 傳統的Windows API 使用 typedefs 和預處理器宏,廣泛地抽象化一些類型和平臺特定程式碼的詳細資料,例如呼叫慣例、__declspec
宣告和編譯器 pragmas。 在Visual Studio中,您可以使用 IntelliSense快速諮詢功能來查看這些 typedefs 和宏的定義。 將滑鼠停留在感興趣的單字上,或選取滑鼠,然後按CtrlK、CtrlI++ 作為包含定義的小型快顯視窗。 如需詳細資訊,請參閱使用 IntelliSense。 參數和傳回類型通常會使用 SAL 注釋 來協助您攔截程式設計錯誤。 如需詳細資訊,請參閱 使用 SAL 注釋減少 C/C++ 程式碼瑕疵。Windows桌面程式需要
<windows.h>
。<tchar.h>
TCHAR
會定義宏,這會wchar_t
在專案中定義 UNICODE 符號時,最終解析為 ,否則會解析為char
。 如果您一律使用啟用 UNICODE 進行建置,則不需要 TCHAR,而且可以直接使用wchar_t
。#include <windows.h> #include <tchar.h>
除了 函
WinMain
式之外,每個Windows桌面應用程式也必須具有視窗程式函式。 此函式通常命名WndProc
為 ,但您可以將其命名為您想要的任何名稱。WndProc
具有下列語法。LRESULT CALLBACK WndProc( _In_ HWND hWnd, _In_ UINT message, _In_ WPARAM wParam, _In_ LPARAM lParam );
在此函式中,您會撰寫程式碼來處理應用程式在事件發生時從Windows接收的訊息。 例如,如果使用者在應用程式中選擇 [確定] 按鈕,Windows會將訊息傳送給您,而且您可以在函式中
WndProc
撰寫程式碼,以執行任何適當的工作。 它稱為 處理 事件。 您只會處理與應用程式相關的事件。如需詳細資訊,請參閱 Window 程序。
將功能加入 WinMain 函式中
在 函式中
WinMain
,您會填入 WNDCLASSEX類型的結構。 結構包含視窗的相關資訊:應用程式圖示、視窗的背景色彩、要顯示在標題列中的名稱等等。 重要的是,它包含視窗程式的函式指標。 下列範例會顯示一個典型的WNDCLASSEX
結構。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(wcex.hInstance, IDI_APPLICATION); wcex.hCursor = LoadCursor(NULL, IDC_ARROW); wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); wcex.lpszMenuName = NULL; wcex.lpszClassName = szWindowClass; wcex.hIconSm = LoadIcon(wcex.hInstance, IDI_APPLICATION);
如需上述結構欄位的相關資訊,請參閱 WNDCLASSEX。
WNDCLASSEX
使用 Windows 註冊 ,讓它知道您的視窗以及如何將訊息傳送給視窗。 請使用 RegisterClassEx 函式,並將視窗類別結構當做引數傳遞。 因為_T
我們使用 類型,所以TCHAR
會使用 宏。if (!RegisterClassEx(&wcex)) { MessageBox(NULL, _T("Call to RegisterClassEx failed!"), _T("Windows Desktop Guided Tour"), NULL); return 1; }
現在您可以建立視窗。 使用 CreateWindowEx 函式。
static TCHAR szWindowClass[] = _T("DesktopApp"); static TCHAR szTitle[] = _T("Windows Desktop Guided Tour Application"); // The parameters to CreateWindowEx explained: // WS_EX_OVERLAPPEDWINDOW : An optional extended window style. // szWindowClass: the name of the application // szTitle: the text that appears in the title bar // WS_OVERLAPPEDWINDOW: the type of window to create // CW_USEDEFAULT, CW_USEDEFAULT: initial position (x, y) // 500, 100: initial size (width, length) // NULL: the parent of this window // NULL: this application does not have a menu bar // hInstance: the first parameter from WinMain // NULL: not used in this application HWND hWnd = CreateWindowEx( WS_EX_OVERLAPPEDWINDOW, szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 500, 100, NULL, NULL, hInstance, NULL ); if (!hWnd) { MessageBox(NULL, _T("Call to CreateWindowEx failed!"), _T("Windows Desktop Guided Tour"), NULL); return 1; }
此函式會傳
HWND
回 ,這是視窗的控制碼。 控制碼有點像指標,Windows用來追蹤開啟的視窗。 如需詳細資訊,請參閱 Windows 資料類型。此時,已建立視窗,但我們仍然需要告訴Windows讓它可見。 這就是此程式碼的功能:
// The parameters to ShowWindow explained: // hWnd: the value returned from CreateWindow // nCmdShow: the fourth parameter from WinMain ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd);
顯示視窗沒有太多內容,因為您尚未實作函式
WndProc
。 換句話說,應用程式尚未處理Windows正在傳送的訊息。為了處理訊息,我們會先新增訊息迴圈,以接聽Windows傳送的訊息。 當應用程式收到訊息時,此迴圈會將它分派給
WndProc
要處理的函式。 此訊息迴圈會類似下列程式碼。MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return (int) msg.wParam;
如需訊息迴圈中之結構和函式的詳細資訊,請參閱 MSG、 GetMessage、 TranslateMessage和 DispatchMessage。
此時的
WinMain
函式應該類似下列程式碼。int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { 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(wcex.hInstance, IDI_APPLICATION); wcex.hCursor = LoadCursor(NULL, IDC_ARROW); wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); wcex.lpszMenuName = NULL; wcex.lpszClassName = szWindowClass; wcex.hIconSm = LoadIcon(wcex.hInstance, IDI_APPLICATION); if (!RegisterClassEx(&wcex)) { MessageBox(NULL, _T("Call to RegisterClassEx failed!"), _T("Windows Desktop Guided Tour"), NULL); return 1; } // Store instance handle in our global variable hInst = hInstance; // The parameters to CreateWindowEx explained: // WS_EX_OVERLAPPEDWINDOW : An optional extended window style. // szWindowClass: the name of the application // szTitle: the text that appears in the title bar // WS_OVERLAPPEDWINDOW: the type of window to create // CW_USEDEFAULT, CW_USEDEFAULT: initial position (x, y) // 500, 100: initial size (width, length) // NULL: the parent of this window // NULL: this application dows not have a menu bar // hInstance: the first parameter from WinMain // NULL: not used in this application HWND hWnd = CreateWindowEx( WS_EX_OVERLAPPEDWINDOW, szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 500, 100, NULL, NULL, hInstance, NULL ); if (!hWnd) { MessageBox(NULL, _T("Call to CreateWindow failed!"), _T("Windows Desktop Guided Tour"), NULL); return 1; } // The parameters to ShowWindow explained: // hWnd: the value returned from CreateWindow // nCmdShow: the fourth parameter from WinMain ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); // Main message loop: MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return (int) msg.wParam; }
將功能加入 WndProc 函式中
若要讓
WndProc
函式處理應用程式所接收的訊息,請實作 switch 陳述式。要處理的一個重要訊息是 WM_PAINT 訊息。 應用程式必須在顯示視窗的一部分更新時收到
WM_PAINT
訊息。 當使用者在視窗前面移動視窗時,就會發生此事件,然後再次移動它。 您的應用程式不知道何時發生這些事件。 只有Windows知道,所以它會以WM_PAINT
訊息通知您的應用程式。 第一次顯示視窗時,必須更新所有視窗。若要處理
WM_PAINT
訊息,請先呼叫 BeginPaint,然後處理用以配置視窗中文字、按鈕和其他控制項的所有邏輯,再呼叫 EndPaint。 針對應用程式,開始呼叫與結束呼叫之間的邏輯會在視窗中顯示字串 「Hello, Windows desktop!」 。 在下列程式碼中, TextOut 函式是用來顯示字串。PAINTSTRUCT ps; HDC hdc; TCHAR greeting[] = _T("Hello, Windows desktop!"); switch (message) { case WM_PAINT: hdc = BeginPaint(hWnd, &ps); // Here your application is laid out. // For this introduction, we just print out "Hello, Windows desktop!" // in the top left corner. TextOut(hdc, 5, 5, greeting, _tcslen(greeting)); // End application-specific layout section. EndPaint(hWnd, &ps); break; }
HDC
在程式碼中是裝置內容的控制碼,用來在視窗的工作區中繪製。BeginPaint
使用 和EndPaint
函式來準備並完成工作區中的繪圖。BeginPaint
會傳回用於在工作區中繪製之顯示裝置內容的控制碼;EndPaint
結束繪製要求並釋放裝置內容。應用程式通常會處理許多其他訊息。 例如,第一次建立視窗時 WM_CREATE ,並在視窗關閉時 WM_DESTROY 。 下列程式碼會顯示基本但完整的
WndProc
函式。LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { PAINTSTRUCT ps; HDC hdc; TCHAR greeting[] = _T("Hello, Windows desktop!"); switch (message) { case WM_PAINT: hdc = BeginPaint(hWnd, &ps); // Here your application is laid out. // For this introduction, we just print out "Hello, Windows desktop!" // in the top left corner. TextOut(hdc, 5, 5, greeting, _tcslen(greeting)); // End application specific layout section. EndPaint(hWnd, &ps); break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wParam, lParam); break; } return 0; }
建置程式碼
如承諾,以下是工作應用程式的完整程式碼。
建立這個範例
刪除您在編輯器中 HelloWindowsDesktop.cpp 中輸入的任何程式碼。 複製此範例程式碼,然後將它貼到 HelloWindowsDesktop.cpp中:
// HelloWindowsDesktop.cpp // compile with: /D_UNICODE /DUNICODE /DWIN32 /D_WINDOWS /c #include <windows.h> #include <stdlib.h> #include <string.h> #include <tchar.h> // Global variables // The main window class name. static TCHAR szWindowClass[] = _T("DesktopApp"); // The string that appears in the application's title bar. static TCHAR szTitle[] = _T("Windows Desktop Guided Tour Application"); // Stored instance handle for use in Win32 API calls such as FindResource HINSTANCE hInst; // Forward declarations of functions included in this code module: LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); int WINAPI WinMain( _In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nCmdShow ) { 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(wcex.hInstance, IDI_APPLICATION); wcex.hCursor = LoadCursor(NULL, IDC_ARROW); wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); wcex.lpszMenuName = NULL; wcex.lpszClassName = szWindowClass; wcex.hIconSm = LoadIcon(wcex.hInstance, IDI_APPLICATION); if (!RegisterClassEx(&wcex)) { MessageBox(NULL, _T("Call to RegisterClassEx failed!"), _T("Windows Desktop Guided Tour"), NULL); return 1; } // Store instance handle in our global variable hInst = hInstance; // The parameters to CreateWindowEx explained: // WS_EX_OVERLAPPEDWINDOW : An optional extended window style. // szWindowClass: the name of the application // szTitle: the text that appears in the title bar // WS_OVERLAPPEDWINDOW: the type of window to create // CW_USEDEFAULT, CW_USEDEFAULT: initial position (x, y) // 500, 100: initial size (width, length) // NULL: the parent of this window // NULL: this application does not have a menu bar // hInstance: the first parameter from WinMain // NULL: not used in this application HWND hWnd = CreateWindowEx( WS_EX_OVERLAPPEDWINDOW, szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 500, 100, NULL, NULL, hInstance, NULL ); if (!hWnd) { MessageBox(NULL, _T("Call to CreateWindow failed!"), _T("Windows Desktop Guided Tour"), NULL); return 1; } // The parameters to ShowWindow explained: // hWnd: the value returned from CreateWindow // nCmdShow: the fourth parameter from WinMain ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); // Main message loop: MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return (int) msg.wParam; } // FUNCTION: WndProc(HWND, UINT, WPARAM, LPARAM) // // PURPOSE: Processes messages for the main window. // // WM_PAINT - Paint the main window // WM_DESTROY - post a quit message and return LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { PAINTSTRUCT ps; HDC hdc; TCHAR greeting[] = _T("Hello, Windows desktop!"); switch (message) { case WM_PAINT: hdc = BeginPaint(hWnd, &ps); // Here your application is laid out. // For this introduction, we just print out "Hello, Windows desktop!" // in the top left corner. TextOut(hdc, 5, 5, greeting, _tcslen(greeting)); // End application-specific layout section. EndPaint(hWnd, &ps); break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wParam, lParam); break; } return 0; }
在 [ 建置 ] 功能表上,選擇 [ 建置方案]。 編譯的結果應該會出現在 [輸出] 視窗中,Visual Studio。
若要執行應用程式,請按 F5。 包含 「Hello, Windows desktop!」 文字的視窗應該會出現在顯示器的左上角。
恭喜! 您已完成本逐步解說,並建置傳統Windows傳統型應用程式。