This enables access to Microsoft Windows specific data types and functions that make life easy when programming with apps for Windows. (MSG
, WNDCLASSEX
, etc.)
#include <windows.h>
This is a Forward Declaration of a Window Procedure. The Window Procedure is crudely simplified, an 'event handler' of sorts. It receives events, which in Windows, are called 'messages'. Every window in Windows has a Window Procedure. This will be covered in depth more when we reach the implementation of this declaration.
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
This is the 'entry' point of the program, specifically, a graphical Windows-based program. Console programs use the simpler main(...)
. The four parameters you see are automatically received from the Microsoft Windows operating system when your application is started. The first parameter(hInstance, meaning Instance Handle) being the most important. It functions as a unique 'identifier'(these are often called handles in Win32 programming) for this particular instance of your program you have started. Every program running on Windows has one and it allows you to create multiple instances of your programs window without having to worry it already existing since they both use their own unique instance 'handles'.
More information about WinMain: Click here
More information about hInstance: Click here
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
This is a data structure that contains message information for example when a user clicks a mouse button on top of your window etc. This is required for a normal Win32 application main loop which you will see later in the code. Its initialized here to conform to my favourite C89 coding standard which requires that all variables are declared at the start of the scope. Remember, C89 is your friend, because you can use all C89 in C++. More information about the MSG data structure by clicking here.
MSG msg = {0};
The Window Class. This is an important concept to grasp in Windows programming.
A window class must be successfully registered to the Windows operating system before a window can be created. We will create our own custom window class and try to register it to the operating system. Trying to register a window class to the operating system is not something that always succeeds, because the operating system simply might not have enough memory available for any new window classes anymore. If this is the case it will return a zero and not a sensible unique identifier for our class, which is why the call should always be error checked.
The minimal member variables of the WNDCLASS
struct that are required for a window are as follows:
lpfnWndProc
Contains a function pointer to which function this windows messages should go to for processing and handling.
hInstance
The unique instance handle we received from the operating system into our first entry point parameter when our program started, will be bound here into the window class data structure.
hbrBackground
Without defining this, we would have no background to our window meaning it the background of the window would become whatever was under the window when it was created, and when moved, it would cause all kinds of graphical errors to appear on your window.
lpszClassName
This is the human readable name of the window class we are about to create. This name works as a reference when telling windows what kind 'style' of a window we want to create and what function receives its window messages (WndProc
in this case). We could then for example create multiple windows using a class named "myredwindowclass
" which all had red backgrounds if the class was already registered to the operating system.
IMPORTANT NOTE: Why do you put an L infront of a normal string? Click here.
WNDCLASS wc = {0};
wc.lpfnWndProc = WndProc;
wc.hInstance = hInstance;
wc.hbrBackground = (HBRUSH)(COLOR_BACKGROUND);
wc.lpszClassName = L"minwindowsapp";
if( FAILED(RegisterClass(&wc)) )
return 1;
After the 'raindance' above is complete, we are finally ready to attempt the creation of a window. If this fails, our program will exit with the return code of 2.
The parameter list is as follows:
lpClassName
Here we need to assign CreateWindow
a valid, already registered window class name. A sweet example of already registered class names are common controls, like the edit controls etc. where you type your text in Windows. As those are usually always registered already, and everything visible in windows is a window, even buttons!, you can try replacing the wc.lpszClassName
with this: L"EDIT"
and you will see how changing the windows class affects the window. You can now select and deselect text and your whole window has turned into a text control! :)
lpWindowName
This is pretty self-explanatory, you can decide yourself what kind of a name you give to your window.(more details)
x
The initial X position of the window, or some would call it the initial horizontal position. CW_USEDEFAULT
could be used here(more details)
y
The initial Y position of the window.(more details)
nWidth
The initial width of the window. (more details)
nHeight
The initial height of the window.(more details)
hWndParent
Here could be a 'handle' to the parent or owner window of this window, this is nearing advanced topics so lets leave this for now.(more details)
hMenu
This is a little bit more advanced tool for this tutorial, leave it to null for now. Its good for you to know that this has a deep and useful connection with those resource files that you might have seen when creating the default template win32 app with visual studio.
hInstance
Nothing new here.
lpParam
This parameter is used in advanced windows programming also, for example: abstracting functionality of WndProc
etc.
if(FAILED(CreateWindow(wc.lpszClassName,
L"Minimal Windows Application",
WS_OVERLAPPEDWINDOW|WS_VISIBLE,
0,
0,
640,
480,
0,
0,
hInstance,
NULL)))
return 2;
The Main Loop, also known as, the Message Loop
The programs in the Microsoft Windows operating system communicate with each other using messages, which in some systems are called 'events'. Something happens somewhere, like a mouse is clicked on a windows top right X icon, a message is sent throughout the operating system that 'hey, that window just died, hide it and start freeing up the memory it was eating, remove its classname from the registered class names list etc.'.
Here we check for any messages for our window, and we send them along until our window receives a posted message. DispatchMessage
does the sending.
This is not the place for reacting to received messages, the WndProc
WindowProcedure is the place where that should happen.
When all messages have been processed and we've received a posted message that is 0 or below 0 by its value (like the PostQuitMessage(0)
command produces), we return 0 for a sign of successfull/natural program termination.
while( GetMessage( &msg, NULL, 0, 0 ) > 0 )
{
DispatchMessage( &msg );
}
return 0;
}
The Window Procedure - also known as: Callback function.
This is the place where the messages from DispatchMessage
end up in, if they were targeted for your window. For example: If the user clicks on your windows top right X, your window procedure would receive the WM_CLOSE
message, which is actually a cleverly disguised number, but more on those later.
You can intercept many kinds of messages here and create functionality and reactions to those messages like key presses and mouse clicks and window resizing etc. All window messages are prefixed with WM_
like WM_CLOSE
. WM_CLOSE
is a default window message just like WM_KEYUP
and WM_RBUTTONUP
. You can have your own custom window messages aswell but that is a more advanced topic (although not really hard, so dont worry).
hWnd is the handle to your window
message is the WM_
window message received which you can choose to capture
wParam and lParam can change based on what the message was that was received and handled by you.See my WM_MOUSEMOVE example below about this.
The DefWindowProc
gets called when we specified no actions for the received message (WM_KEYDOWN
, WM_SIZE
), it then passes the message along to others.
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch(message)
{
case WM_CLOSE:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
Capturing Basic Keyboard Input
Navigate to this row of code:
switch(message)
{
and add the following code:
case WM_KEYUP:
{
switch(wParam)
{
case VK_ESCAPE:
{
PostQuitMessage(0);
}
break;
}
break;
}
break;
and when you recompile and run with F5, you can press Escape and you window will close automatically!
I really recommend using MSDN as a source for the Windows API and reading about the wonderful virtual keys that we can utilize and also about the WM_KEYDOWN
to complement the WM_KEYUP
that we've seen. Heres are example links to MSDN that describe more indepth what virtual keys are about and the keyup window message:
Click here for Virtual-Key Codes on MSDN
Click here for WM_KEYUP Window Message on MSDN
Capturing Basic Mouse Input
The mouse input functions a lot like the keyboard input, but I'll show you a useful trick I've learned along the way to get the useful information out of mouse movement a bit easier than every other tutorial on the block. We'll capture mouse coordinates and print the results into your Visual Studio 'Debug Output' window and show a message saying Hello World when the window is clicked with the left mouse button.
Just add this code where your WndProc
function begins from:
WCHAR mouseCoordinates[4] = {0};
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch(message)
{
case WM_MOUSEMOVE:
wsprintf(mouseCoordinates, L"X:%d Y:%d\n", HIWORD(lParam), LOWORD(lParam));
OutputDebugString(mouseCoordinates);
break;
case WM_LBUTTONUP:
{
MessageBox(0,L"Hello World!",0,0);
}
break;
This modification I leave for you to explore forwards, there is a wondrous amount of useful functionality within the Microsoft Windows operating system and I hope I have sparked an interest into delving deeper into it, successfully and without frustrations :)