Create a Windows Form
If you use C++ language for application development on Windows platform, there are three basic ways: the first is to use Windows API for development. It is the basic interface for communication between the Windows operating system and applications. The second uses MFC for development. MFC is a C++ class encapsulated by Microsoft for Windows API. The third option is Windows Forms. It is the graphical user interface part of the Microsoft.NET development framework. This course uses the first method, using C++ language to call the Windows API to communicate directly with the operating system, and the graphics library DirectX, which can support graphics card hardware, to achieve the highest overall execution efficiency and, of course, the lowest and most difficult. However, this way we can get a more detailed understanding of the game development process from the bottom level. That is to say, this is the most effective way to understand and master the basics of our games.
Windows API basic data types include: BYTE, CHAR, WORD, SHORT, INT, and so on. All Windows data types are type redefined from C language data types. DWORD is essentially an unsigned long data type with 32-bit unsigned integers. Unsigned types generally start with "U", for example, "INT" is a signed type, and "UINT" is an unsigned type. Pointer types are preceded by LP or P, such as LPDWORD and PDWORD. STR stands for string, C for const, T for wide character. For example, LPSTR stands for character pointer, that is, string variable, LPCSTR for string constant, LPCTSTR for wide string constant. Handle types are usually named by prefixing the object name with "H". HWND represents the handle to the operation window, HICON represents the handle to the icon, and HCURSOR represents the handle to the cursor. All Windows data types are defined in this way in the SDK header file and are derived from the standard C language.
Next, we use VS2019 to create a form. First, create a C++ empty project.
Then fill in the project name "D3D_01_Windows" and the storage path.
Note that this project defaults to the Console program and click Create:
Right-click Source File -> Add (D) -> New Item (W)... to create the main.cpp source file.
Copy the following code into the "main.cpp" file.
#include <windows.h> // Windows Application Entry File int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow) { // Show a message box MessageBox(NULL, L"Hello Windows!", L"Title", 0); return 0; }
You also need to modify how the program runs, right-click the project name "D3D_01_Windows" to select "Properties", and in the System project under Linker, change the value of "Subsystem" from "Console" to "Window".
Click Local Windows Debugger on the toolbar to run the program.
Microsoft provides a wide range of functions for us to develop Windows-based applications. These functions are the Windows APIs, which are declared in the Windows.h header file. This interface is used for all communication between any Windows application and Windows itself. MessageBox is just a function in the Windows API that displays a message box. We certainly won't use MessageBox to develop games. It's just a reminder box, not a window. When using the C++ console program, we know that its entry function is main, while the entry function in the Windows application is wWinMain, as follows:
int WINAPI wWinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow);
The WINAPI after the function returns the type int is a macro, which is u stdcall, which allows the compiler to compile code in compatibility mode. It's okay to remove WINAPI, but generally we can keep it.
The first parameter, hInstance of type HINSTANCE, represents an instance handle to which the program runs. HInstance is actually a number. An application can run multiple instances, each of which is assigned a handle value and passed to the WinMain function through the hInstance parameter. This is a very important parameter that we need to use.
The second parameter, hPrevInstance of type HINSTANCE, represents the handle to the previous instance of the current instance. It has been officially stated that this parameter is meaningless and we will not use it at all.
The third parameter, the PWSTR type pCmdLine, is a string passed to the program as a command line parameter. Normally, we don't need to execute exe files from the command line, so we don't use this parameter at all.
The fourth parameter, int type nCmdShow, is a flag indicating whether the main application window is minimized, maximized, or normally displayed. This parameter is used when displaying windows.
Next, we'll actually create a Windows form. Every Windows application must have at least one form (also called a window) with a title bar, a menu bar, a toolbar, and so on. Almost all the Windows applications we can see will be attached to the window. In Windows applications, windows are marked by window handles (HWND s). Handles are symbols that identify resources in Windows programs. For example, windows, pictures, mice, and so on. When the system creates these resources, it allocates memory for them and returns the symbolic identity of each resource, which is the handle. It is essentially a variable that holds a particular resource. Once we understand these concepts, we begin rewriting main.cpp.
// Introduce header file #include <windows.h> #define WINDOW_LEFT 200 // window position #define WINDOW_TOP 100 // window position #define WINDOW_WIDTH 800 // Window Width #define WINDOW_HEIGHT 600 // Window Height #define WINDOW_TITLE L"D3D Game Development" // Window Title #define CLASS_NAME L"D3D Game Development" // Window Class Name // Declare window procedure functions LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
Let's start by defining some macros that are used to configure the windows that will be generated. It is important to note the declaration of the window procedure function, which is an interactive mechanism for Windows Forms programs. This function must be used when creating windows. We will explain the use of this function later, which can receive input from keyboard and mouse. Of course, the code to create the form must be in the entry function wWinMain. There are 6 steps to create a form.
// The first step is to construct the WNDCLASSEX structure WNDCLASSEX wndClass = { 0 }; wndClass.cbSize = sizeof(WNDCLASSEX); // Set the byte size of the structure wndClass.style = CS_HREDRAW | CS_VREDRAW; // Set the style of the window wndClass.lpfnWndProc = WndProc; // Set pointer to window procedure function wndClass.cbClsExtra = 0; // Set additional memory for window class wndClass.cbWndExtra = 0; // Set additional memory for windows wndClass.hInstance = hInstance; // Set program instance handle wndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION); // Set Icon Handle wndClass.hCursor = LoadCursor(NULL, IDC_ARROW); // Set cursor handle wndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);// Set background brush handle wndClass.lpszMenuName = NULL; // Set Menu Name wndClass.lpszClassName = CLASS_NAME; // Set window class name wndClass.hIconSm = NULL; // Set window small icon handle
The above parameter settings are not really important, just a general understanding.
// Step 2: Register Window if (!RegisterClassEx(&wndClass)) return -1; // Step 3: Create a window HWND hwnd = CreateWindow( CLASS_NAME, // Window Class Name WINDOW_TITLE, // Window Title WS_OVERLAPPEDWINDOW, // Window Style CW_USEDEFAULT, // Initial x-coordinate of window CW_USEDEFAULT, // Initial y-coordinate of window WINDOW_WIDTH, // Initial window width WINDOW_HEIGHT, // Window Initial Height NULL, // hwnd NULL, // window menu handle hInstance, // Program Instance Handle NULL); // Additional parameters // Step 4: Show the window MoveWindow(hwnd, WINDOW_LEFT, WINDOW_TOP, WINDOW_WIDTH, WINDOW_HEIGHT, true); ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd);
Our previous console programs basically run in the order of the code. When the code is executed, the program is finished. The Windows Forms program is an event (message) response mechanism, that is, its logical code is driven by events (messages) from outside users. Events like keyboard input, mouse clicks, and so on. When the form is generated, it will always exist and remain unchanged if nothing happens. Event (message) listening for Windows Forms programs is done by the operating system and handled by the Windows Forms program's window procedure function. All events are out of order because you don't know when the user will click that button event. Therefore, our Windows Forms programs need to use an infinite loop to continuously receive events (messages) from the operating system.
// Step 5: Message Cycle Process MSG msg = { 0 }; // Define a message object (msg) // Use the while loop message queue if the message is not WM_QUIT message, continue loop while (msg.message != WM_QUIT) { // Get the message and give it to the window procedure function if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); } } // Step 6: Logout window UnregisterClass(CLASS_NAME, wndClass.hInstance); return 0;
At this point, the form is created in six steps. Next, we have to complete the window procedure function.
// Define window procedure functions LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_PAINT: ValidateRect(hwnd, NULL); // Window Redraw Message break; case WM_DESTROY: PostQuitMessage(0); // Window Destroy Message break; default: // Call default window procedure return DefWindowProc(hwnd, message, wParam, lParam); } return 0; }
We say that window procedure functions are used to handle events. Different events have different logic to handle them. In the above section, we only deal with two events, one is window redrawing and the other is window destruction. Window redrawing means changing the size of a window, such as maximizing the window. Destroying a window is easy. Clicking X in the upper right corner of the window closes the window. The window closes and the Windows Forms program ends. Click Local Windows Debugger on the toolbar to run the program and you will see the form we created. Game development is about drawing 2D images or 3D models on this form. Although this window program involves a lot of code, it is basically fixed and is not the main content of game development, so you can understand it.
Download addresses for all code cases for this course:
Note: This is the second lesson in our series of game development tutorials, which use C++ language and DirectX to explain some basic theories in game development. The main learning goal is to understand theoretical knowledge. The accompanying C++ code can be understood and run successfully without requiring us to use DirectX to develop games. If there are some mistakes in the course, please leave a message to correct them. Thank you very much!