Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                

Win APITutorial

Download as pdf or txt
Download as pdf or txt
You are on page 1of 20

Windows API

(Microsoft Visual Studio 2019) - Tutorial

1. Using Visual Studio 2019 for creating Windows application using Win32 API and C++
ˆ Create a new project (select Windows Desktop Wizard)

 In the Windows Desktop Wizard choose from combobox "Desktop/Windows Application (.exe)" and
check option "Empty Project" to generate an empty project:

 Be sure to understand all available application type options (to read more information about all options
available in the Windows Desktop Wizard:

1
* Desktop/Windows application - an application with Windows graphical user interface (GUI)
* Console application - an application without Windows, it can communicate with the user using the
console
* DLL - a dynamic linking library - it is compiled into a .dll le (so it cannot be run directly) to add
for modularity to the project.
* Static library - a library of code that can be linked statically to other modules (executable les or
DLLs)
ˆ Let the Windows Desktop Wizard to generate the sample Win32 application
ˆ Right now our project should be empty, we will now add some code to it to display basic window.
ˆ Before we can start writing our program we have to add few more les to it. First, create framework.h
header le with following code:
1 # pragma once
2
3 # define WIN32_LEAN_AND_MEAN
4
5 # include < windows .h >
6

windows.h includes declarations of Windows API functions. while define WIN32_LEAN_AND_MEAN will
exclude rarely used code from windows.h header le and in turn speed up compilation.
ˆ Create tutorial.h le, it should look like this:
1 # pragma once
2
3 // # include " resource . h "
4

ˆ Add tutorial.cpp le with following code:


1 # include " framework . h "
2 # include " tutorial . h "
3
4 # define MAX_LOADSTRING 100
5

ˆ Now we can start adding code to our program.


 Start by adding global variables (to the tutorial.cpp) for app instance, title bar text and name of a
window class:
1 HINSTANCE hInst ;
2 WCHAR szTitle [ MAX_LOADSTRING ];
3 WCHAR szWindowClass [ MAX_LOADSTRING ];

 Next as we are working inside .cpp le we have to create method signatures. Add following code:
1 ATOM MyRegisterClass ( HINSTANCE hInstance ) ;
2 BOOL InitInstance ( HINSTANCE , int ) ;
3 LRESULT CALLBACK WndProc ( HWND , UINT , WPARAM , LPARAM ) ;
4 INT_PTR CALLBACK About ( HWND , UINT , WPARAM , LPARAM ) ;

* wWinMain function - the entry point of the application, returning from this function means
terminating the application
* MyRegisterClass function - a helpful function which registers the class of the main window of
the application (by calling the RegisterClassEx function)
* InitInstance function - to create and show the main window
* WndProc function - the function responding to all messages sent to the main window
* About function - the message handling function for the About dialog box
 We have to add entry point for our application, create wWinMain function:

2
1 int APIENTRY wWinMain ( _In_ HINSTANCE hInstance ,
2 _In_opt_ HINSTANCE hPrevInstance ,
3 _In_ LPWSTR lpCmdLine ,
4 _In_ int nCmdShow )
5 {
6 UNREFERENCED_PARAMETER ( hPrevInstance ) ;
7 UNREFERENCED_PARAMETER ( lpCmdLine ) ;
8
9 std :: wstring title ( L " tutorial \0 " ) ;
10 title . copy ( szTitle , title . size () ) ;
11
12 std :: wstring className ( L " TUTORIAL \0 " ) ;
13 className . copy ( szWindowClass , className . size () ) ;
14
15 MyRegisterClass ( hInstance ) ;
16
17
18 if (! InitInstance ( hInstance , nCmdShow ) )
19 {
20 return FALSE ;
21 }
22
23 HACCEL hAccelTable = LoadAccelerators ( hInstance , nullptr ) ;
24
25 MSG msg ;
26
27 while ( GetMessage (& msg , nullptr , 0 , 0) )
28 {
29 if (! TranslateAccelerator ( msg . hwnd , hAccelTable , & msg ) )
30 {
31 TranslateMessage (& msg ) ;
32 DispatchMessage (& msg ) ;
33 }
34 }
35
36 return static_cast < int >( msg . wParam ) ;
37 }

This time we added a lot of code, some of it should be even marked as undened. Moving from the
beginning to the end:
* First two lines suppress warning which will be generated due to not using lpCmdLine and nCmdShow
* Next we register class and create instance main window
* LoadAccelerators which will be used to load accelerators (more on that later)
* Next we have application main loop, which will feed WndProc of our window with messages
 Now we will create MyRegisterClass function, add following code:
1 ATOM MyRegisterClass ( HINSTANCE hInstance )
2 {
3 WNDCLASSEXW wcex ;
4
5 wcex . cbSize = sizeof ( WNDCLASSEX ) ;
6
7 wcex . style = CS_HREDRAW | CS_VREDRAW ;
8 wcex . lpfnWndProc = WndProc ;
9 wcex . cbClsExtra = 0;
10 wcex . cbWndExtra = 0;
11 wcex . hInstance = hInstance ;
12 wcex . hIcon = nullptr ;
13 wcex . hCursor = nullptr ;
14 wcex . hbrBackground = reinterpret_cast < HBRUSH >( COLOR_WINDOW +1) ;
15 wcex . lpszMenuName = nullptr ;

3
16 wcex . lpszClassName = szWindowClass ;
17 wcex . hIconSm = nullptr ;
18
19 return RegisterClassExW (& wcex ) ;
20 }

Just like before, most of the code should be self-explanatory. When in doubt please consult documen-
tation.
 Add InitInstance function:
1 BOOL InitInstance ( HINSTANCE hInstance , int nCmdShow )
2 {
3 hInst = hInstance ;
4
5 HWND hWnd = CreateWindowW ( szWindowClass , szTitle ,
WS_OVERLAPPEDWINDOW ,
6 CW_USEDEFAULT , 0 , CW_USEDEFAULT , 0 , nullptr , nullptr , hInstance ,
nullptr ) ;
7
8 if (! hWnd )
9 {
10 return FALSE ;
11 }
12
13 ShowWindow ( hWnd , nCmdShow ) ;
14 UpdateWindow ( hWnd ) ;
15
16 return TRUE ;
17 }

CreateWindowW function is used to create all kinds of windows, here we can dene window's class,
parent, style, position, and more.
 We have functions that create instance and window, now it's time to create WndProc which will
process messages received by the window.
1 LRESULT CALLBACK WndProc ( HWND hWnd , UINT message , WPARAM wParam ,
LPARAM lParam )
2 {
3 switch ( message )
4 {
5 case WM_COMMAND :
6 {
7 int wmId = LOWORD ( wParam ) ;
8 switch ( wmId )
9 {
10 case IDM_ABOUT :
11 // DialogBox ( hInst , MAKEINTRESOURCE ( IDD_ABOUTBOX ) , hWnd
, About ) ;
12 break ;
13 case IDM_EXIT :
14 DestroyWindow ( hWnd ) ;
15 break ;
16 default :
17 return DefWindowProc ( hWnd , message , wParam , lParam ) ;
18 }
19 }
20 break ;
21 case WM_PAINT :
22 {
23 PAINTSTRUCT ps ;
24 HDC hdc = BeginPaint ( hWnd , & ps ) ;
25 EndPaint ( hWnd , & ps ) ;
26 }

4
27 break ;
28 case WM_DESTROY :
29 PostQuitMessage (0) ;
30 break ;
31 default :
32 return DefWindowProc ( hWnd , message , wParam , lParam ) ;
33 }
34 return 0;
35 }

Windows may receive many kind of messages during their lifetime. You will explore more about
messages in the following pages, but for now note that we have simple messages like WM_Destroy and
more complex like WM_COMMAND.
 Now it's time to add resources to our application. You can think of resources as data which packed
inside application during compilation. In WinAPI resource could be a le like icons that we will
create later or accelerator which will dene keyboard shortcuts or a menu bar. In this tutorial we will
add accelerators, menu, icons and string table. To begin we have to create resource le. Right-click
on Resource Files and add new resource le with extension .rc, project should now also contain
Resource.h header le - this is an auto generated le which will contain resource identiers. Right
click on resource le and open it in resources editor. Remember to uncomment appropriate include in
tutorial.h. After each step in this part please compile and run application.
* Right click on tree root and click Add resource, add Accelerator. Click on accelerator and change
it's name to IDC_TUTORIAL Double click on accelerator and add two elements with Id IDM_ABOUT
and ASCII type. This shortcuts will be used to open About window. Now we can pass Id of the
accelerator to LoadAccelerators using MAKEINTRESOURCE(IDC_TUTORIAL).
* Right click on tree root and add two icons with Ids IDI_SMALL and IDI_TUTORIAL. This will
be icons used by our program. After creating them we can use them at the window creation.
Use LoadIcon(hInstance, MAKEINTRESOURCE(ICON_ID) to assign icons to hIcon and hIconSm in
MyRegisterClass.
* Add menu with Id IDC_TUTORIAL to our resources, in menu add following options:
· File with item Exit with Id IDM_EXIT
· Help with item About with Id IDM_ABOUT
Same as above use MAKEINTRESOURCEW(IDC_TUTORIAL) to assign lpszClassName in MyRegister-
Class.
* Finally we will create string table, inside which we will add two strings:
· IDC_TUTORIAL with value TUTORIAL - which is the name of a window class
· IDS_APP_TITLE with value tutorial - title of a window
Now instead of using hardcoded wstrings we can use LoadStringW(hInstance, IDS_APP_TITLE,
szTitle, MAX_LOADSTRING) to load assign value of szTitle and szWindowClass in wWinMain.
ˆ If you were to compare our app with the basic application (without Empty project options) from Visual
Studio you'd see that we are still missing a modal popup window. Let's create one:
 First uncomment line in tutorial.cpp which creates modal window, in response to the IDM_ABOUT
WM_COMMAND.
 Next we will add new resource of type Dialog to our application. Dialog window editor should open
after creating one. Change it's Id to IDD_ABOUTBOX. Using Toolbox editor on the left side of the editor
window add simple text to the modal window.
 Last one thing we have to do is to add code which will handle modal window logic, just like with main
window.
1 INT_PTR CALLBACK About ( HWND hDlg , UINT message , WPARAM wParam , LPARAM
lParam )
2 {
3 UNREFERENCED_PARAMETER ( lParam ) ;
4 switch ( message )
5 {
6 case WM_INITDIALOG :
7 return static_cast < INT_PTR >( TRUE ) ;
8
9 case WM_COMMAND :
10 if ( LOWORD ( wParam ) == IDOK || LOWORD ( wParam ) == IDCANCEL )

5
11 {
12 EndDialog ( hDlg , LOWORD ( wParam ) ) ;
13 return static_cast < INT_PTR >( TRUE ) ;
14 }
15 break ;
16 }
17 return static_cast < INT_PTR >( FALSE ) ;
18 }

 Compile and run the application. It should behave just like a sample applications.
 As an exercise try modifying modal window to look just like the one from a sample program.

Figure 1: Application window

* Note all standard features of a Windows application:


· The main window: resizing, minimizing, maximizing, the icon, system menu in the icon, menu
in the window
· About - the modal dialog box: it must be closed to allow the user to do something with the main
window, it is not resizeable

Figure 2: Solution Explorer

 Project les rehearsal:


* <project_name>.h - use it as the main header le in your application
* Resource.h - contains identiers of resources, it is better to use the Resource View window in
Visual Studio rather than modify it manually
* Resource les - all les used in the application's resources: two icons and the .rc le
* <project_name>.cpp - main le with source code of the application

6
Figure 3: Resource View

 Resources rehearsal (use the Resource View window, if it is not visible, show it using the menu option:
View / Other Windows / Resource View):
* Accelerator - the table with keyboard shortcuts used in the application. To add a new accelerator
just click below the list of used shortcuts, set the identier of the menu item for which the keyboard
shortcut is to create, right click on the new item, choose the 'Next Key Typed' option and just
press the desired combination of keys
* Dialog - dialog boxes, unlike normal windows, can be designed visually. The Toolbox window (View
/ Toolbox) can be used to add new so-called controls and the Properties window (View / Properties
Window) can be used to alter some of their default settings (unfortunately, unlike Windows Forms,
in Win32 applications some of obvious visual features can be changed only from the source code)
* Icon - when editing an icon, be sure to modify all types of the icon (see the menu option: Image /
Current Icon Image Types) - which type of the icon is used depends on the version of the system
and on the context (e.g. the 16x16 type will be used for the window's icon, the 32x32 type in the
Alt-Tab window, etc.)
* Menu - use very functional editor built in Visual Studio and remember about the Properties window
* String Table - if there is a need to display a text to the user, this text should be loaded from the
string table (using the LoadString function) to allow to easily develop multilingual versions of
the application and to set the user interface's language by the system, according to the system's
settings
 Analyse the source code in the main .cpp le of the application:
* wWinMain function - the entry point of the application, returning from this function means termi-
nating the application (the while (GetMessage(...)x bold) loop ends when the PostQuitMes-
sage function is called)
* MyRegisterClass function - a helpful function which registers the class of the main window of
the application (by calling the RegisterClassEx function)
* InitInstance function - to create and show the main window
* WndProc function - the function responding to all messages sent to the main window
* About function - the message handling function for the About dialog box
ˆ In the next sections of the tutorial you may need to include tchar.h header le.
ˆ Getting help in Visual Studio:
 To get information about a Win32 function, macro, or type, just set the cursor on the name and press
F1 (test it for the WNDCLASSEX, MAKEINTRESOURCE, and LoadIcon)
 Sometimes after pressing the F1 key, other information than expected are presented (e.g. for the
ShowWindow function instead of information about the Win32 function, documentation of the
method of one of Excel Object Model from Tools for Oce is displayed)
* To nd desired information use the Index tab (menu: Help / Index) and type the name. Among
all available options choose something with 'Platform SDK', 'Windows Management' or 'GDI' (e.g.
try to nd help about the GetLastError function). If something about a method of a class is
displayed, you are lost, because there are no object oriented programming in Win32 API.

7
 To nd denition of a constant value (for example to nd similar constants), set the cursor on the
name and press F12 (test it for the COLOR_WINDOW, WS_OVERLAPPEDWINDOW, and
WM_PAINT)
ˆ Simple exercises modifying the source code generated by the wizard:
 Modify the name of the window (hints: the string table or CreateWindow or SetWindowText)
 Modify the background colour of the window to the colour of the active window's caption (hints:
WNDCLASSEX::hbrBackground or WM_ERASEBKGND)
 Set the default size of the window to 200 x 50 pixels (hints: CreateWindow or MoveWindow)
 Add new menu item to the window which shows a message box (i.e. a simple window with information)
(hints: resources, menu, case WM_COMMAND and MessageBox)
 Add a keyboard shortcut for the new menu item
ˆ More dicult exercises:
 Move the window to the right in response for clicking on any menu item
1 case WM_COMMAND :
2 {
3 RECT rc ;
4 GetWindowRect ( hWnd , & rc ) ;
5 OffsetRect (& rc , 20 , 0) ;
6 MoveWindow ( hWnd , rc . left , rc . top ,
7 rc . right - rc . left , rc . bottom - rc . top , TRUE ) ;
8 }
9 break ;
10

 Make the window semitransparent


1 // Set WS_EX_LAYERED on this window
2 SetWindowLong ( hWnd , GWL_EXSTYLE ,
3 GetWindowLong ( hWnd , GWL_EXSTYLE ) | WS_EX_LAYERED ) ;
4 // Make this window 50% alpha
5 SetLayeredWindowAttributes ( hWnd , 0 , (255 * 50) / 100 , LWA_ALPHA ) ;
6 // Show this window
7 ShowWindow ( hWnd , nCmdShow ) ;
8 UpdateWindow ( hWnd ) ;
9

 Create 9 identical windows positioned in 3 rows and 3 columns


1 int size = 150;
2 for ( int i = 0; i < 3; i ++) {
3 for ( int j = 0; j < 3; j ++) {
4 hWnd = CreateWindow ( szWindowClass , szTitle ,
5 WS_OVERLAPPEDWINDOW | WS_VISIBLE ,
6 i * 150 , j * 150 , 150 , 150 ,
7 NULL , NULL , hInstance , NULL ) ;
8 }
9 }
10

2. Examples of handling messages


ˆ Getting notication about changing the size of the window
 Add the following code to the switch (message) statement of the WndProc function:
1 case WM_SIZE :
2 {
3 // get the size of the client area
4 int clientWidth = LOWORD ( lParam ) ;
5 int clientHeight = HIWORD ( lParam ) ;
6 // get the size of the window

8
7 RECT rc ;
8 GetWindowRect ( hWnd , & rc ) ;
9 // modify the caption of the window
10 TCHAR s [256];
11 _stprintf_s (s , 256 ,
12 _T ( " Window 's size : % d x % d Client area 's size : % d x % d " ) ,
13 rc . right - rc . left , rc . bottom - rc . top ,
14 clientWidth , clientHeight ) ;
15 SetWindowText ( hWnd , s ) ;
16 }
17 break ;
18

 Explanations:
* Both the width and height of the window's client area are passed in one parameter of the message
(LPARAM), the LOWORD and HIWORD macros can be used to retrieve single values
* The rectangle passed to the GetWindowRect function will be lled with values of the current
position and size of the window
* TCHAR, _stprintf_s, and _T can be used to write the source code independent on the setting
of using Unicode in the application
· To use Unicode and wide character explicitly, wchar_t, swprintf_s, and L"" can be used:
1 wchar_t s [256];
2 swprintf_s (s , 256 ,
3 L " Window 's size : % d x % d Client area 's size : % d x % d " ,
4 rc . right - rc . left , rc . bottom - rc . top ,
5 clientWidth , clientHeight ) ;
6 SetWindowText ( hWnd , s ) ;
7

· Unicode is supported at the system level since Windows 2000 and it is really good idea to use it
(it is much easier to create multilingual applications)!
· Setting the maximum and minimum possible size of the window
 Add the following code to the switch (message) statement of the WndProc function:
1 case WM_GETMINMAXINFO :
2 {
3 MINMAXINFO * minMaxInfo = ( MINMAXINFO *) lParam ;
4 minMaxInfo - > ptMaxSize . x = minMaxInfo - > ptMaxTrackSize . x = 500;
5 minMaxInfo - > ptMaxSize . y = minMaxInfo - > ptMaxTrackSize . y = 200;
6 }
7 break ;
8

 Explanation: the LPARAM parameter of the WM_GETMINMAXINFO has a pointer to the


MINMAXINFO structure, which members can be changed to set special values
 Forcing the window to be square
 Add the following code to the switch (message) statement of the WndProc function:
1 case WM_SIZING :
2 {
3 RECT * rc = ( RECT *) lParam ;
4 if ( wParam == WMSZ_BOTTOM
5 || wParam == WMSZ_BOTTOMLEFT
6 || wParam == WMSZ_BOTTOMRIGHT
7 || wParam == WMSZ_TOP
8 || wParam == WMSZ_TOPLEFT
9 || wParam == WMSZ_TOPRIGHT )
10 {
11 rc - > right = rc - > left + rc - > bottom - rc - > top ;
12 } else {
13 rc - > bottom = rc - > top + rc - > right - rc - > left ;

9
14 }
15 }
16 break ;
17

 Explanation: the similar idea to the WM_GETMINMAXINFO message


 Using the timer
 Create a timer:
1 case WM_CREATE :
2 SetTimer ( hWnd , 7 , 250 , NULL ) ;
3 break ;
4

* Explanation: the WM_CREATE message is sent to the window procedure only once (when the
CreateWindow or CreateWindowEx function is called); it is the rst message when the handle
of the window (HWND) is available and it is great place to do some initialization of the window
(the window is already created but not visible)
* Do something in response for the WM_TIMER message, for example:

1 case WM_TIMER :
2 {
3 if ( wParam == 7) // check timer id
4 {
5 RECT rc ;
6 // get the center of the work area of the system
7 SystemParametersInfo ( SPI_GETWORKAREA , 0 , & rc , 0) ;
8 int centerX = ( rc . left + rc . right + 1) / 2;
9 int centerY = ( rc . top + rc . bottom + 1) / 2;
10 // get current size of the window
11 GetWindowRect ( hWnd , & rc ) ;
12 int currentSize = max ( rc . right - rc . left , rc . bottom - rc . top ) ;
13 // modify size of the window
14 currentSize += stepSize ;
15 if ( currentSize >= maxSize ) {
16 stepSize = - abs ( stepSize ) ;
17 } else if ( currentSize <= minSize ) {
18 stepSize = abs ( stepSize ) ;
19 }
20 MoveWindow ( hWnd , centerX - currentSize / 2 ,
21 centerY - currentSize / 2 ,
22 currentSize , currentSize , TRUE ) ;
23 }
24 }
25 break ;
26

* The above code needs some declarations:


1 const int minSize = 200;
2 const int maxSize = 400;
3 static int stepSize = 10;
4

* (be sure to understand the importance of the static keyword)


* Explanations:
· The SystemParametersInfo function is very useful when there is a need to get or set values
of some system parameters
· There are many functions for changing window's size and/or position, MoveWindow is the
simplest one
3. Examples of working with the mouse and keyboard

10
ˆ Using mouse messages
 Add the following function:
1 void GetTextInfoForMouseMsg ( WPARAM wParam , LPARAM lParam ,
2 const TCHAR * msgName , TCHAR * buf , int bufSize )
3 {
4 short x = ( short ) LOWORD ( lParam ) ;
5 short y = ( short ) HIWORD ( lParam ) ;
6 _stprintf_s ( buf , bufSize , _T ( " % s x : %d , y : %d , vk : " ) ,
7 msgName , x , y ) ;
8 if (( wParam == MK_LBUTTON ) != 0) {
9 _tcscat_s ( buf , bufSize , _T ( " LEFT " ) ) ;
10 }
11 if (( wParam == MK_MBUTTON ) != 0) {
12 _tcscat_s ( buf , bufSize , _T ( " MIDDLE " ) ) ;
13 }
14 if (( wParam == MK_RBUTTON ) != 0) {
15 _tcscat_s ( buf , bufSize , _T ( " RIGHT " ) ) ;
16 }
17 }
18

 Add the following code to the WndProc function:


1 case WM_LBUTTONDOWN :
2 GetTextInfoForMouseMsg ( wParam , lParam , _T ( " LBUTTONDOWN " ) ,
3 buf , bufSize ) ;
4 SetWindowText ( hWnd , buf ) ;
5 break ;
6 case WM_LBUTTONUP :
7 GetTextInfoForMouseMsg ( wParam , lParam , _T ( " LBUTTONUP " ) ,
8 buf , bufSize ) ;
9 SetWindowText ( hWnd , buf ) ;
10 break ;
11 case WM_RBUTTONDOWN :
12 GetTextInfoForMouseMsg ( wParam , lParam , _T ( " RBUTTONDOWN " ) ,
13 buf , bufSize ) ;
14 SetWindowText ( hWnd , buf ) ;
15 break ;
16 case WM_RBUTTONUP :
17 GetTextInfoForMouseMsg ( wParam , lParam , _T ( " RBUTTONUP " ) ,
18 buf , bufSize ) ;
19 SetWindowText ( hWnd , buf ) ;
20 break ;
21 case WM_LBUTTONDBLCLK :
22 GetTextInfoForMouseMsg ( wParam , lParam , _T ( " LBUTTONDBLCLK " ) ,
23 buf , bufSize ) ;
24 SetWindowText ( hWnd , buf ) ;
25 break ;
26 case WM_MBUTTONDBLCLK :
27 GetTextInfoForMouseMsg ( wParam , lParam , _T ( " MBUTTONDBLCLK " ) ,
28 buf , bufSize ) ;
29 SetWindowText ( hWnd , buf ) ;
30 break ;
31 case WM_RBUTTONDBLCLK :
32 GetTextInfoForMouseMsg ( wParam , lParam , _T ( " RBUTTONDBLCLK " ) ,
33 buf , bufSize ) ;
34 SetWindowText ( hWnd , buf ) ;
35 break ;
36

 Be sure to add declarations of variables in the WndProc function:

11
1 const int bufSize = 256;
2 TCHAR buf [ bufSize ];
3

 Run the application and test changing the caption of the window when left, middle, or right mouse
button is pressed or released
 Explanations:
* WM_L... messages are for the left mouse button, WM_M... for middle, and WM_R... for
the right one
* Notications about pressing (WM_xBUTTONDOWN) and releasing (WM_xBUTTONUP)
are sent
* The WM_xBUTTONDBLCLK message is sent when the user use the double click. To get the
notication, the CS_DBLCLKS style must be applied to WNDCLASSEX::style in calling the
RegisterClassEx function:
1 wcex . style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS ;
2

When the CS_DBLCLKS style is applied, the following sequence of messages is sent when the
user use the double click:
· WM_xBUTTONDOWN
· WM_xBUTTONUP
· WM_xBUTTONDBLCLK
· WM_xBUTTONUP
When there is no this style, the WM_xBUTTONDBLCLK message is not sent:
· WM_xBUTTONDOWN
· WM_xBUTTONUP
· WM_xBUTTONDOWN
· WM_xBUTTONUP
ˆ Client and screen coordinations
 Note, that the above example works only when the user uses the mouse in the client area of the window.
Position passed in the LPARAM parameter is in the client area coordinations, to transform between
the client area and screen coordinations, the ScreenToClient and ClientToScreen function can be
used, e.g.:
1 void GetTextInfoForMouseMsg ( HWND hWnd , WPARAM wParam , LPARAM lParam ,
const TCHAR * msgName , TCHAR * buf , int bufSize )
2 {
3 short x = ( short ) LOWORD ( lParam ) ;
4 short y = ( short ) HIWORD ( lParam ) ;
5 POINT pt = {x , y };
6 ClientToScreen ( hWnd , & pt ) ;
7 _stprintf_s ( buf , bufSize ,
8 _T ( " % s x : %d , y : %d , ( sx : %d , sy : % d ) vk : " ) ,
9 msgName , x , y , pt .x , pt . y ) ;
10

ˆ Using mouse capture


 Note also, that the WM_xBUTTONUP message is sent only if the user released the mouse button
in the client area of the window, to be sure that this message will be sent wherever the button is
released, use the mouse capture:
1 case WM_LBUTTONDOWN :
2 GetTextInfoForMouseMsg ( hWnd , wParam , lParam , _T ( " LBUTTONDOWN " ) ,
3 buf , bufSize ) ;
4 SetWindowText ( hWnd , buf ) ;
5 SetCapture ( hWnd ) ;
6 break ;
7 case WM_LBUTTONUP :
8 ReleaseCapture () ;
9 GetTextInfoForMouseMsg ( hWnd , wParam , lParam , _T ( " LBUTTONUP " ) ,

12
10 buf , bufSize ) ;
11 SetWindowText ( hWnd , buf ) ;
12 break ;
13

 Note, that in this case negative values for mouse coordinations can be sent, so it is very important
to use signed integer type to use this value: x = (short)LOWORD(lParam); (without casting to short
type big positive values would be used)
ˆ Changing mouse cursor
 Add the following code to the WndProc function:
1 case WM_CREATE :
2 cursor = LoadCursor ( NULL , IDC_HAND ) ;
3 break ;
4 case WM_SETCURSOR :
5 SetCursor ( cursor ) ;
6 return TRUE ;
7

 Add also a declaration of the cursor variable (if it is a local variable, the static keyword must be used)
 static HCURSOR cursor = NULL;
 Explanations:
* To change the mouse cursor, the WM_SETCURSOR message and the SetCursor function can
be used
* The SetCursor function needs an HCURSOR parameter which represents a cursor. It can be
loaded from resources or from the system using the LoadCursor function
* The above code changes the cursor for all areas of the window
* It is possible to use simpler code: SetCursor(LoadCursor(NULL, IDC_HAND)); but it would be
less ecient
* The NULL value as the rst parameter of the LoadCursor method means, that the cursor must
be loaded from the system (not from resources of the application)
* To use a cursor from applications resources:
· Add the cursor to resources (using the Resource View window in Visual Studio)
· Pass the hInst variable as the rst parameter for the LoadCursor function, e.g. LoadCursor(hInst,
IDC_MYCURSOR)
ˆ Moving the window by dragging by any point of the window
 Add the following code to the WndProc function:
1 case WM_NCHITTEST :
2 return HTCAPTION ;
3

 Note, that in real applications the code should be more complicated (e.g. to allow the user to close the
window using the close button or just to use the menu). To close the window press Alt+F4.
ˆ Using keyboard messages
 Add the following function:
1 void GetTextInfoForKeyMsg ( WPARAM wParam , const TCHAR * msgName ,
2 TCHAR * buf , int bufSize )
3 {
4 static int counter = 0;
5 counter ++;
6 _stprintf_s ( buf , bufSize , _T ( " % s key : % d ( counter : % d ) " ) , msgName ,
wParam , counter ) ;
7 }
8

 Add the following code to the WndProc function:

13
1 case WM_KEYDOWN :
2 GetTextInfoForKeyMsg ( wParam , _T ( " KEYDOWN " ) , buf , bufSize ) ;
3 SetWindowText ( hWnd , buf ) ;
4 break ;
5 case WM_KEYUP :
6 GetTextInfoForKeyMsg ( wParam , _T ( " KEYUP " ) , buf , bufSize ) ;
7 SetWindowText ( hWnd , buf ) ;
8 break ;
9

 Run the application and test changing the caption of the window when keys are pressed and released
 Note, that because of the autorepeat feature, more than one WM_KEYDOWN message may be
posted before a WM_KEYUP message is posted (the counter added at the end of the text is being
increased when the key is being pressed)
 Virtual key codes are constant values which identify keys on the keyboard
 (see https://docs.microsoft.com/en-us/windows/desktop/inputdev/virtual-key-codes)
 When the character is important instead of the virtual key code, the WM_CHAR message can be
used:
1 case WM_CHAR :
2 _stprintf_s ( buf , bufSize , _T ( " WM_CHAR : % c " ) , ( TCHAR ) wParam ) ;
3 SetWindowText ( hWnd , buf ) ;
4 break ;
5

 The WM_CHAR message automatically recognizes the state of shift and caps lock keys and trans-
forms the character
4. Examples of drawing using GDI
ˆ The code for drawing should be placed in responding for the WM_PAINT message
ˆ Simple drawing:
 Drawing texts
* The simplest way to draw a text:
1 case WM_PAINT :
2 {
3 PAINTSTRUCT ps ;
4 HDC hdc = BeginPaint ( hWnd , & ps ) ;
5 TCHAR s [] = _T ( " Hello world ! " ) ;
6 TextOut ( hdc , 0 , 0 , s , ( int ) _tcslen ( s ) ) ;
7 EndPaint ( hWnd , & ps ) ;
8 }
9 break ;
10

* More options are available when the DrawText function is used:


1 case WM_PAINT :
2 {
3 PAINTSTRUCT ps ;
4 HDC hdc = BeginPaint ( hWnd , & ps ) ;
5 TCHAR s [] = _T ( " Hello world ! " ) ;
6 RECT rc ;
7 GetClientRect ( hWnd , & rc ) ;
8 DrawText ( hdc , s , ( int ) _tcslen ( s ) , & rc ,
9 DT_CENTER | DT_VCENTER | DT_SINGLELINE ) ;
10 EndPaint ( hWnd , & ps ) ;
11 }
12 break ;
13

14
* Explanations:
· The GetClientRect function gets the rectangle of the client area of the window (the left and
top members are always set to 0)
· Using the last parameter of the DrawText function, the alignment of the text can be changed
 Using pens
* The HPEN is a handle to a pen and represents a pen in drawing using GDI
1 case WM_PAINT :
2 {
3 PAINTSTRUCT ps ;
4 HDC hdc = BeginPaint ( hWnd , & ps ) ;
5 HPEN pen = CreatePen ( PS_SOLID , 2 , RGB (255 , 0 , 0) ) ;
6 HPEN oldPen = ( HPEN ) SelectObject ( hdc , pen ) ;
7 MoveToEx ( hdc , 0 , 0 , NULL ) ;
8 LineTo ( hdc , 100 , 100) ;
9 SelectObject ( hdc , oldPen ) ;
10 DeleteObject ( pen ) ;
11 EndPaint ( hWnd , & ps ) ;
12 }
13 break ;
14

* Explanations:
· GDI always uses current objects (one pen, one brush, one font, etc.) to draw, the object can be
selected as current on the device context (HDC) using the SelectObject function.
· The colour contains 3 values: for the red, green, and blue. The RGB macro can be used to set
specic colour, its parameters are integer values from the range [0..255])
· VERY IMPORTANT: All created GDI objects must be destroyed. The pen created using the
CreatePen function must be destroyed using the DeleteObject.
· VERY IMPORTANT: Objects selected as current on the device context cannot be destroyed (so
always remember the old object and restore it before calling the DeleteObject function)
 Using brushes
* Brushes are similar to pens, they are used when something must be lled, e.g. a rectangle:
1 case WM_PAINT :
2 {
3 PAINTSTRUCT ps ;
4 HDC hdc = BeginPaint ( hWnd , & ps ) ;
5 HPEN pen = CreatePen ( PS_DOT , 1 , RGB (255 , 0 , 0) ) ;
6 HPEN oldPen = ( HPEN ) SelectObject ( hdc , pen ) ;
7 HBRUSH brush = CreateSolidBrush ( RGB (0 , 128 , 0) ) ;
8 HBRUSH oldBrush = ( HBRUSH ) SelectObject ( hdc , brush ) ;
9 Rectangle ( hdc , 20 , 20 , 120 , 120) ;
10 SelectObject ( hdc , oldPen ) ;
11 DeleteObject ( pen ) ;
12 SelectObject ( hdc , oldBrush ) ;
13 DeleteObject ( brush ) ;
14 EndPaint ( hWnd , & ps ) ;
15 }
16 break ;
17

 Using fonts
1
2 case WM_PAINT :
3 {
4 PAINTSTRUCT ps ;
5 HDC hdc = BeginPaint ( hWnd , & ps ) ;
6 TCHAR s [] = _T ( " Hello world ! " ) ;
7 HFONT font = CreateFont (
8 - MulDiv (24 , GetDeviceCaps ( hdc , LOGPIXELSY ) , 72) , // Height

15
9 0, // Width
10 0, // Escapement
11 0, // Orientation
12 FW_BOLD , // Weight
13 false , // Italic
14 FALSE , // Underline
15 0, // StrikeOut
16 EASTEUROPE_CHARSET , // CharSet
17 OUT_DEFAULT_PRECIS , // OutPrecision
18 CLIP_DEFAULT_PRECIS , // ClipPrecision
19 DEFAULT_QUALITY , // Quality
20 DEFAULT_PITCH | FF_SWISS , // PitchAndFamily
21 _T ( " Verdana " ) ) ; // Facename
22 HFONT oldFont = ( HFONT ) SelectObject ( hdc , font ) ;
23 RECT rc ;
24 GetClientRect ( hWnd , & rc ) ;
25 DrawText ( hdc , s , ( int ) _tcslen ( s ) , & rc ,
26 DT_CENTER | DT_VCENTER | DT_SINGLELINE ) ;
27 SelectObject ( hdc , oldFont ) ;
28 DeleteObject ( font ) ;
29 EndPaint ( hWnd , & ps ) ;
30 }
31 break ;
32

 Using bitmaps
* Add a bitmap to resources (right click on the project in the Solution Explorer window in Visual
Studio, Add / Resource, and choose Bitmap in the dialog box)
* Draw something on the bitmap (there is a simple image editor built in Visual Studio)
* Save the bitmap (note, that a .bmp le is created)
* Check an identier of the bitmap in the Resource View window (it should be IDB_BITMAP1
by default)
* Copy the following code for the WM_PAINT message
1 case WM_PAINT :
2 {
3 PAINTSTRUCT ps ;
4 HDC hdc = BeginPaint ( hWnd , & ps ) ;
5 TCHAR s [] = _T ( " Hello world ! " ) ;
6 HBITMAP bitmap = LoadBitmap ( hInst ,
7 MAKEINTRESOURCE ( IDB_BITMAP1 ) ) ;
8 HDC memDC = CreateCompatibleDC ( hdc ) ;
9 HBITMAP oldBitmap = ( HBITMAP ) SelectObject ( memDC , bitmap ) ;
10 BitBlt ( hdc , 0 , 0 , 48 , 48 , memDC , 0 , 0 , SRCCOPY ) ;
11 StretchBlt ( hdc , 200 , 100 , -200 , 100 , memDC ,
12 0 , 0 , 48 , 48 , SRCCOPY ) ;
13 SelectObject ( memDC , oldBitmap ) ;
14 DeleteObject ( bitmap ) ;
15 DeleteDC ( memDC ) ;
16 EndPaint ( hWnd , & ps ) ;
17 }
18 break ;
19

* Explanations:
· There is no function for drawing a bitmap on a device context, another device context must be
used
· To create a memory device context use the CreateCompatibleDC function. Each created
device context must be destroyed using the DeleteDC function
· The BitBlt function copies a bitmap from one device context to another one, the StretchBlt
function allows to resize the bitmap

16
ˆ Drawing in response for messages other than WM_PAINT
 Copy the following code for the WM_LBUTTONDOWN message:
1 case WM_LBUTTONDOWN :
2 {
3 HDC hdc = GetDC ( hWnd ) ;
4 HBRUSH brush = CreateSolidBrush ( RGB (128 , 128 , 0) ) ;
5 HBRUSH oldBrush = ( HBRUSH ) SelectObject ( hdc , brush ) ;
6 short x = ( short ) LOWORD ( lParam ) ;
7 short y = ( short ) HIWORD ( lParam ) ;
8 const int rad = 5;
9 Ellipse ( hdc , x - rad , y - rad , x + rad , y + rad ) ;
10 SelectObject ( hdc , oldBrush ) ;
11 DeleteObject ( brush ) ;
12 ReleaseDC ( hWnd , hdc ) ;
13 }
14 break ;
15

 Explanations:
* The BeginPaint and EndPaint functions can be used only in response for the WM_PAINT
message, in all other cases the GetDC and ReleaseDC functions must be used
* When the window is refreshed (e.g. after changing its size), only the code for the WM_PAINT
message is called, this is the reason why the content is cleared
ˆ Invalidating and updating the window
 A better solution of previous example (with storing clicked points in the list):
1 # include < list >
2 using namespace std ;
3 static list < POINT > pointsList ;
4 ...
5 case WM_LBUTTONDOWN :
6 {
7 POINT pt ;
8 pt . x = ( short ) LOWORD ( lParam ) ;
9 pt . y = ( short ) HIWORD ( lParam ) ;
10 pointsList . push_back ( pt ) ;
11 InvalidateRect ( hWnd , NULL , TRUE ) ;
12 }
13 break ;
14 case WM_PAINT :
15 {
16 PAINTSTRUCT ps ;
17 HDC hdc = BeginPaint ( hWnd , & ps ) ;
18 HBRUSH brush = CreateSolidBrush ( RGB (128 , 128 , 0) ) ;
19 HBRUSH oldBrush = ( HBRUSH ) SelectObject ( hdc , & brush ) ;
20 list < POINT >:: const_iterator iter = pointsList . begin () ;
21 while ( iter != pointsList . end () ) {
22 POINT pt = * iter ;
23 const int rad = 5;
24 Ellipse ( hdc , pt . x - rad , pt . y - rad , pt . x + rad , pt . y + rad ) ;
25 iter ++;
26 }
27 SelectObject ( hdc , oldBrush ) ;
28 DeleteObject ( brush ) ;
29 EndPaint ( hWnd , & ps ) ;
30 }
31 break ;
32

 Explanations:

17
* The InvalidateRect function sets the specied rectangle (or full client area when the NULL value
is used) as a region that must be redrawn and inserts the WM_PAINT message to the message
queue
* To force the window to redraw as soon as possible, call the UpdateWindow functions immediately
after calling the InvalidateRect function
ˆ Flicker-free drawing
 Copy the following code for the WM_PAINT message and resize the window - it ickers
1 case WM_PAINT :
2 {
3 PAINTSTRUCT ps ;
4 HDC hdc = BeginPaint ( hWnd , & ps ) ;
5 RECT rc ;
6 GetClientRect ( hWnd , & rc ) ;
7 HBRUSH oldBrush = ( HBRUSH ) SelectObject ( hdc ,
8 ( HBRUSH ) GetStockObject ( GRAY_BRUSH ) ) ;
9 Rectangle ( hdc , 0 , 0 , rc . right , rc . bottom ) ;
10 SelectObject ( hdc , ( HBRUSH ) GetStockObject ( BLACK_BRUSH ) ) ;
11 const int margin = 50;
12 Rectangle ( hdc , margin , margin ,
13 rc . right - margin , rc . bottom - margin ) ;
14 SelectObject ( hdc , oldBrush ) ;
15 EndPaint ( hWnd , & ps ) ;
16 }
17 break ;
18

 There are two reasons of ickering, the rst one is the default background of the window. To disable
drawing the background, set the NULL value for the background brush or use the WM_ERASEBKGND
message:
1 case WM_ERASEBKGND :
2 return 1;
3

 The second reason of ickering is drawing gures one after another (rstly the grey rectangle is drawn
and secondly the black one). For two rectangle it is easy to modify the code to avoid such covering, but
in general the only way to avoid ickering is o-screen drawing (i.e. drawing using a memory bitmap):
1 static HDC offDC = NULL ;
2 static HBITMAP offOldBitmap = NULL ;
3 static HBITMAP offBitmap = NULL ;
4 case WM_CREATE :
5 {
6 HDC hdc = GetDC ( hWnd ) ;
7 offDC = CreateCompatibleDC ( hdc ) ;
8 ReleaseDC ( hWnd , hdc ) ;
9 break ;
10 }
11 case WM_SIZE :
12 {
13 int clientWidth = LOWORD ( lParam ) ;
14 int clientHeight = HIWORD ( lParam ) ;
15 HDC hdc = GetDC ( hWnd ) ;
16 if ( offOldBitmap != NULL ) {
17 SelectObject ( offDC , offOldBitmap ) ;
18 }
19 if ( offBitmap != - NULL ) {
20 DeleteObject ( offBitmap ) ;
21 }
22 offBitmap = CreateCompatibleBitmap ( hdc , clientWidth , clientHeight ) ;
23 offOldBitmap = ( HBITMAP ) SelectObject ( offDC , offBitmap ) ;
24 ReleaseDC ( hWnd , hdc ) ;

18
25 }
26 break ;
27 case WM_PAINT :
28 {
29 PAINTSTRUCT ps ;
30 HDC hdc = BeginPaint ( hWnd , & ps ) ;
31 RECT rc ;
32 GetClientRect ( hWnd , & rc ) ;
33 HBRUSH oldBrush = ( HBRUSH ) SelectObject ( offDC ,
34 ( HBRUSH ) GetStockObject ( GRAY_BRUSH ) ) ;
35 Rectangle ( offDC , 0 , 0 , rc . right , rc . bottom ) ;
36 SelectObject ( offDC , ( HBRUSH ) GetStockObject ( BLACK_BRUSH ) ) ;
37 const int margin = 50;
38 Rectangle ( offDC , margin , margin ,
39 rc . right - margin , rc . bottom - margin ) ;
40 SelectObject ( offDC , oldBrush ) ;
41 BitBlt ( hdc , 0 , 0 , rc . right , rc . bottom , offDC , 0 , 0 , SRCCOPY ) ;
42 EndPaint ( hWnd , & ps ) ;
43 }
44 break ;
45 case WM_ERASEBKGND :
46 return 1;
47 case WM_DESTROY :
48 if ( offOldBitmap != NULL ) {
49 SelectObject ( offDC , offOldBitmap ) ;
50 }
51 if ( offDC != NULL ) {
52 DeleteDC ( offDC ) ;
53 }
54 if ( offBitmap != NULL ) {
55 DeleteObject ( offBitmap ) ;
56 }
57 PostQuitMessage (0) ;
58 break ;
59

* Explanations:
· In response for the WM_CREATE message, a memory device context is created (it has no
dependency on the size of the window, so there is no need to recreate it during responding for
the WM_SIZE message)
· In response for the WM_SIZE message, a memory bitmap is created (so whenever the window
changes its size, the bitmap is recreated - that's why resizing of the window is much slower for
the above code)
(a) If such slow resizing is unacceptable, a bitmap with the biggest possible size (use desktop's
size) should be created in response for the WM_CREATE message
· In the WM_PAINT message everything is painted on the memory device context and at the
end copied to the screen device context using the BitBlt function
* This solution is not perfect - there is no response for changing the image depth of the display (in
bits per pixel). The WM_DISPLAYCHANGE message should be used.
5. Additional task:
ˆ Create window inside other window (check out WS_CHILD ag for CreateWindow function)
ˆ Use timer to change position or/and size of child window

6. More references and useful links:


ˆ https://docs.microsoft.com/en-us/windows/desktop/apiindex/windows-api-list
ˆ http://cpp0x.pl/kursy/Kurs-WinAPI-C++/167
ˆ www.winprog.org/tutorial/
ˆ Debugging tutorials

19
 Short debugging tutorial: http://www.dotnetperls.com/debugging
 More debugging tips & tricks: https://blogs.msdn.microsoft.com/brunoterkaly/2009/09/28/t
he-art-of-debugging-a-developers-best-friend-intro-lesson-1-repost/

20

You might also like