I have a custom subclassed button created in the WM_CREATE message of my WindowProc callback. Here are the creation and subclassing instructions, along with a structure used to control the button state:
static button_state btnstateBtnInstall;
hBtnInstall = CreateWindow(WC_BUTTON, L"Button", WS_CHILD | WS_VISIBLE, (window_width / 2) - (btn_install_width / 2), window_height - (window_height / 6) - (btn_install_height / 2), btn_install_width, btn_install_height, hwnd, (HMENU)HMENU_btn_install, NULL, NULL);
SetWindowSubclass(hBtnInstall, BtnInstallProc, 0, (DWORD_PTR)&btnstateBtnInstall);
The struct is defined as follows:
struct button_state
{
bool pushed;
button_state() { pushed = false; }
};
The subclassed procedure is coded as follows:
LRESULT CALLBACK BtnInstallProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubClass, DWORD_PTR dwRefData)
{
button_state* state = (button_state*)dwRefData;
// Omitted part where I create brushes and font to be used for painting
switch (msg)
{
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
RECT rc = ps.rcPaint;
POINT pt;
GetCursorPos(&pt);
ScreenToClient(hwnd, &pt);
BOOL hover = PtInRect(&rc, pt);
if (state->pushed)
{
// Pushed
FillRect(hdc, &rc, hBrPushed);
}
else if (hover)
{
// Mouse over
FillRect(hdc, &rc, hBrHover);
}
else
{
// Normal
FillRect(hdc, &rc, hBrNormal);
}
SetBkMode(hdc, TRANSPARENT);
SetTextColor(hdc, RGB(255, 255, 255));
SelectFont(hdc, SegoeUI);
static LPCWSTR InstallBtnTxt = L"Install";
static int InstallBtnTxtLen = static_cast<int>(wcslen(InstallBtnTxt)); // Should be a safe cast, for small arrays like this one
DrawText(hdc, InstallBtnTxt, InstallBtnTxtLen, &rc, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
EndPaint(hwnd, &ps);
return 0;
}
case WM_LBUTTONDOWN:
{
state->pushed = true;
break;
}
case WM_LBUTTONUP:
{
state->pushed = false;
break;
}
// Omitted part where I handle WM_DESTROY to do cleanup
}
return DefSubclassProc(hwnd, msg, wParam, lParam);
}
To compare the behaviour of my button with a standard one, I created another button without subclassing and using only the standard BUTTON class and WS_VISIBLE | WS_CHILD attributes.
As you can see from the attached gif, when I repeatedly click on a standard class button, every click produces the animation while when hitting multiple times the button I created with this code it seems like it isn't catching every click but (as you can see) something like 50% of them.
What am I missing? Why isn't my button as responsive as the one that comes with the standard class?
TL;DR: when clicking slowly on my subclassed button, the painting is correct. As you can see from my attached image, the difference between mine and a standard button is big when repeating clicks on them. How can I make my control be as responsive as the standard one?
When you are clicking fast some WM_LBUTTONDOWN messages are substituted by the WM_LBUTTONDBLCLK (default double click handling). You need to handle it just like you are handling the WM_LBUTTONDOWN message.
case WM_LBUTTONDOWN:
case WM_LBUTTONDBLCLK: // <-- Added
{
state->pushed = true;
break;
}
Related
I have a Window procedure which I have defined in a class and I need to be able to access the class' members using the this pointer.
The Window procedure:
static LRESULT CALLBACK TextWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
MyCustomClass* self =
reinterpret_cast<MyCustomClass*>(
GetWindowLongPtr(hWnd, GWLP_USERDATA) // Not recieving the pointer!
);
switch(message) {
case WM_DESTROY: {
SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)self->origWndProc); // I am subclassing btw.
break;
}
case WM_PAINT: {
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
HBRUSH hBrush = (HBRUSH)GetStockObject(NULL_BRUSH);
FillRect(hdc, &ps.rcPaint, hBrush);
SetBkMode(hdc, TRANSPARENT);
DrawText(hdc, L"OK", -1, &ps.rcPaint, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
SetBkMode(hdc, OPAQUE);
EndPaint(hWnd, &ps);
DeleteObject(hBrush);
break;
}
case WM_LBUTTONDOWN: {
printf("%d", self->x); // Crashes the program
break;
}
}
return CallWindowProc(self->origWndProc, hWnd, message, wParam, lParam); // Subclassing!
}
Now after creating the window (a text control in my case) I am immediately setting the pointer to the class using SetWindowLongPtr, like this:
// Variable in the class that holds the `HWND` data to the text control.
this->textControl = CreateWindow(
L"STATIC",
L"Hello World!",
WS_CHILD | WS_VISIBLE | SS_NOTIFY,
this->x, this->y, this->width, this->height, // Position and size, members defined in the class
this->parentWindow, // Parent window.
0,
NULL,
NULL
);
SetWindowLongPtr(this->textControl, GWLP_USERDATA, (LONG_PTR)this); // Setting the pointer
this->origWndProc = (WNDPROC)SetWindowLongPtr(this->textControl, GWLP_WNDPROC, (LONG_PTR)this->TextWndProc); // Setting the window procedure for "Subclassing"!
But the problem is I am not receiving the this pointer when I am calling GetWindowLongPtr as seen in the Window procedure.
Am I calling it wrong? I don't know what is wrong.
Any help would be greatly appreciated! Thank you in advance!
I have created a Windows application. The elements that I create are using subclassing as I wanted to handle mouse hover events.
DWORD dwStyleOfIcons = SS_BITMAP | SS_NOTIFY | WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_BORDER;
img1 = CreateWindow(L"STATIC", NULL, dwStyleOfIcons,
posX, posY, imgWt, imgHt,
hWnd, (HMENU)ICON1_CLICKED, hInst, NULL);
SetWindowSubclass(img1, StaticSubClassWndProc, ICON1_CLICKED, 0);
In my StaticSubClassWndProc(), I handle WM_MOUSEMOVE, WM_MOUSELEAVE, and WM_MOUSEHOVER:
LRESULT CALLBACK StaticSubClassWndProc (HWND hwndsubclass, UINT msg, WPARAM wp, LPARAM lp, UINT_PTR uidsubclass , DWORD_PTR dwrefdata)
{
...
switch(Msg)
{
case WM_MOUSEHOVER: {
if(uidsubclass == ICON1_CLICKED){
texture = "texture2.bmp";
modifyImage(texture);
}
break;
}
case WM_MOUSELEAVE: {
if(uidsubclass == ICON1_CLICKED){
texture = "texture.bmp";
modifyImage(texture);
}
break;
}
There are many STATIC items in my application, which all I wanted the behavior of a pop up context menu, like when I hover over the image it changes to a selected image, and when the cursor is out of view the image changes back to normal. I was able to do that.
I was able to do this for images which act as icons, but how do I do it for static text controls? Essentially, in a pop up menu, the selected text is all highlighted:
Is there no simpler way to make my elements in this window behave like a pop up menu? All I want is this custom structure of pop up menu behavior.
I think you did not handle the TrackMouseEvent function correctly, which caused your child window to be unable to process the WM_MOUSEHOVER and WM_MOUSELEAVE messages.
I tested the following code and it worked for me:
HBITMAP hBmp1 = (HBITMAP)LoadImage(NULL, L"test1.bmp", IMAGE_BITMAP, 200, 300, LR_LOADFROMFILE);
HBITMAP hBmp2 = (HBITMAP)LoadImage(NULL, L"test2.bmp", IMAGE_BITMAP, 200, 300, LR_LOADFROMFILE);
HWND img1;
LRESULT CALLBACK StaticSubClassWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, UINT_PTR uidsubclass, DWORD_PTR dwrefdata)
{
switch (msg)
{
case WM_MOUSEMOVE:
{
TRACKMOUSEEVENT lpEventTrack;
lpEventTrack.cbSize = sizeof(TRACKMOUSEEVENT);
lpEventTrack.dwFlags = TME_HOVER | TME_LEAVE;
lpEventTrack.hwndTrack = img1;
lpEventTrack.dwHoverTime = 100;
TrackMouseEvent(&lpEventTrack);
break;
}
case WM_MOUSEHOVER:
{
if (uidsubclass == ICON1_CLICKED) {
SendMessage(hwnd, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)hBmp1);
}
break;
}
case WM_MOUSELEAVE:
{
if (uidsubclass == ICON1_CLICKED) {
SendMessage(hwnd, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)hBmp2);
}
break;
}
default:
return DefSubclassProc(hwnd, msg, wParam, lParam);
}
}
But you need to be aware that WM_MOUSEHOVER and WM_MOUSELEAVE will trigger frequently, so I don't think you should use this method to load pictures when the mouse is hovered or left, which will frequently trigger the loading of pictures.
Is it possible to paint colored text to what I've already done?
I've tried WM_CTLCOLORSTATIC, CreateSolidBrush(), and several other functions.
//-----------------------------------
// Learning the win32 API for C++
//
// Created by: Cosmic Cruizer
//-----------------------------------
#include <windows.h>
#include <tchar.h>
// Function declarations
bool SetUpWindowClass (char*, int, int, int); // Remove window structure from WinMain and put into function
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM) // Pre-declare Windows procedure/function
// Global variables
const char CLASS_NAME[] = "My Window Class Array"; // declared for window class; Can be static or const
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow){
//Step 1: Registering the Window Class
HWND hwnd{}; // This is the handle for our window
MSG msg{}; // Handle for messages to the application
SetUpWindowClass (NULL, NULL, NULL, NULL); // The Window structure - removed from main and made into a function
// Step 2: Creating the Window
hwnd = CreateWindowEx( // returns a handle to the new window, or zero if the function fails
WS_EX_CLIENTEDGE, // Optional window styles. Can be set to 0
CLASS_NAME, // Name of window class, see set 1b. Also set as the constant
"My First C++ Windows App", // Window title text
WS_OVERLAPPEDWINDOW, // Window style, title bar, a border, a system menu, and Minimize and Maximize buttons.
200, 200, 500, 400, // Size and position
NULL, NULL, hInstance, NULL); // Parent window, Menu, Instance handle, Additional app data
// Add an exit button
CreateWindow(
"BUTTON", // Predefined class; Unicode assumed
"EXIT", // Button text
WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON, // Styles
200, 200, 60, 25, // x position, y position, Button width, Button height
hwnd, // Parent window
NULL, // No menu.
(HINSTANCE)GetWindowLongPtr(hwnd, GWLP_HINSTANCE), NULL); // Pointer not needed.
ShowWindow(hwnd, nCmdShow); // Make the window visible on the screen
// Step 3: The Message Loop
while(GetMessage(&msg, NULL, 0, 0) != 0) { // Run the message loop. It will run until GetMessage() returns 0
TranslateMessage(&msg); // Translate virtual-key messages into character messages
DispatchMessage(&msg); // Send message to WindowProcedure
}
return msg.wParam;
}
//---------- Functions ----------//
// Setup the window structure
bool SetUpWindowClass (char *cpTitle, int iR, int iG, int iB) {
//Step 1a: The Window structure
WNDCLASSEX wc{}; // Data structure for the windowclass
wc.cbSize = sizeof(WNDCLASSEX); // Sets the size of the Windows API
wc.style = 0; // define additional elements of the window class
wc.lpfnWndProc = WndProc; // defines most of the behavior of the window. See setp 4 "LRESULT CALLBACK WndProc" function
wc.cbClsExtra = 0; // No extra bytes after the window class
wc.cbWndExtra = 0; // structure for the window instance
wc.hInstance = GetModuleHandle (NULL); // handle to the application instance.
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); // handle to icon class, if NULL, system provides a default icon.
wc.hCursor = LoadCursor(NULL, IDC_ARROW); // handle to cursor class
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+18); // Add color as the background of the window
wc.lpszMenuName = NULL; // No menu
wc.lpszClassName = CLASS_NAME; // string that identifies the window class
wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
//Step 1b: Register the window class, and if it fails quit the program
if (RegisterClassEx (&wc)) return true;
else return false;
}
// Step 4: the Window Procedure in this function
LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam){
switch(uMsg){
case WM_CLOSE:{
DestroyWindow(hwnd);
break;
}
case WM_COMMAND:{ // Close the window when exit is pressed
if (MessageBox(hwnd, "Really quit?", "Exit Warning", MB_OKCANCEL) == IDOK){ // what the hell, just wanted this.
PostQuitMessage(0);
}
break;
}
case WM_DESTROY:{
PostQuitMessage(0);
break;
}
//--- trying to create colored text ---//
case WM_CTLCOLORSTATIC:{
HDC hdcStatic = (HDC) wParam; // handle to display context
hwnd = (HWND) lParam; // handle to control window
SetTextColor(hdcStatic, RGB(100,255,255));
SetBkColor(hdcStatic, RGB(250,250,6));
return (INT_PTR)CreateSolidBrush(RGB(250,250,100));
}
case WM_CTLCOLOREDIT:{
HDC hdcStatic = (HDC) wParam;
SetTextColor(hdcStatic, RGB(0,0,255));
SetBkColor(hdcStatic, RGB(0,230,0));
return (INT_PTR)CreateSolidBrush(RGB(0,230,0));
}
case WM_PAINT:{ // All painting (text) occurs here, between BeginPaint and EndPaint.
PAINTSTRUCT ps; // Holds info about current painting session.
HDC hdc = BeginPaint(hwnd, &ps); // Create the device context (DC)
// Each character is added to the cpaText array. Then the for loop goes through and paints each character
int iY = 7; // Vertical spaces and number of lines for the array
const char *cpaText [iY] = { // changed from char to const char to get rid of warning. and added 1 for each line and a return
"Hello Peoples",
"",
"This is my first attempt to program using the Win32 API.",
"I can only hope it gets better with time.",
"",
"Created by \"The\" Cosmic Cruizer"
};
for (int iLoopCounter = 0; cpaText [iLoopCounter] != 0; iLoopCounter++, iY += 20) {
TextOut (hdc, 5, iY, cpaText [iLoopCounter], strlen (cpaText [iLoopCounter]));
}
EndPaint(hwnd, &ps); // Free up HDC created with BeginPaint
break;
}
default:{
return DefWindowProc(hwnd, uMsg, wParam, lParam); // Return is needed either here or at the end
break;
}
}
return DefWindowProc(hwnd, uMsg, wParam, lParam); // Return is needed either here or in the default case
}
WM_CTLCOLORSTATIC and WM_CTLCOLOREDIT are notification messages used by STATIC/EDIT controls, neither of which you have on your window, so you will never receive those messages and should remove those handlers from your code.
You are trying to draw colored text directly onto your window using TextOutA() in a WM_PAINT handler, which is fine. But per the TextOutA() documentation:
The TextOut function writes a character string at the specified location, using the currently selected font, background color, and text color.
Your WM_PAINT handler is not selecting anything into the HDC that BeginPaint() returns, before trying to draw on it. It simply needs to configure the desired font/color values as desired, eg:
HFONT hFont;
LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam){
switch(uMsg){
...
case WM_CREATE:{
hFont = CreateFont(...); // or CreateFontIndirect()
break;
}
case WM_DESTROY:{
DeleteObject(hFont);
PostQuitMessage(0);
break;
}
case WM_SETTINGCHANGE:
case WM_FONTCHANGE:{
DeleteObject(hFont);
hFont = CreateFont(...); // or CreateFontIndirect()
InvalidateRect(hwnd, NULL, TRUE);
break;
}
case WM_PAINT:{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
HFONT hOldFont = (HFONT) SelectObject(hdc, hFont);
SetTextColor(hdc, ...);
SetBkColor(hdc, ...);
int iY = 7;
const char *cpaText [iY] = {
"Hello Peoples",
"",
"This is my first attempt to program using the Win32 API.",
"I can only hope it gets better with time.",
"",
"Created by \"The\" Cosmic Cruizer"
};
for (int iLoopCounter = 0; cpaText [iLoopCounter] != 0; iLoopCounter++, iY += 20) {
TextOutA (hdc, 5, iY, cpaText [iLoopCounter], strlen (cpaText [iLoopCounter]));
}
SelectObject(hdc, hOldFont);
EndPaint(hwnd, &ps);
return 0;
}
...
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
I've just created multiple edit boxes (11x11 controls) based on this article:
https://msdn.microsoft.com/en-us/library/windows/desktop/hh298433%28v=vs.85%29.aspx
Well, not exactly same, but I used the code in case WM_CREATE: block to create huge number of controls.
I use this dialog process on the parent window:
INT_PTR CALLBACK StartupDialogProc(HWND dialog, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg){
case WM_INITDIALOG:
Init_Startup(dialog);
return 1;
/*
case EN_CHANGE:
case WM_CTLCOLOREDIT:
{
HDC hdC = (HDC)wParam;
COLORREF crColorBackground = RGB(255,0,0);
if (crColorBackground)
SetBkColor(hdC, crColorBackground);
SetTextColor( hdC, RGB(12,112,212) );
SetBkMode( hdC, TRANSPARENT );
RECT rect;
GetClientRect( (HWND)lParam, &rect );
HBRUSH hBrush = CreateSolidBrush( RGB(209,209,209) );
//FrameRect( hdC, &rect, hBrush );
Rectangle( hdC, (int)rect.left, (int)rect.top, (int)rect.right, (int)rect.bottom );
DeleteObject( hBrush );
LOGBRUSH lb;
lb.lbStyle = BS_SOLID;
lb.lbColor = RGB(249,249,249);
lb.lbHatch = 0;
CreateBrushIndirect(&lb); // LRESULT
// GetStockObject(NULL_BRUSH);
return 1;
}
break;
*/
case WM_DESTROY:
setts.options.page = GetDlgItemInt(dialog, IDC_O_STARTUP_PAGE, NULL, FALSE);
setts.options.recent = GetDlgItemInt(dialog, IDC_O_STARTUP_RECENT, NULL, FALSE);
break;
case WM_CLOSE:
EndDialog(dialog, FALSE);
break;
case WM_COMMAND:
if (wParam == IDOK) {
EndDialog(dialog, TRUE);
return 0;
}
}
return 0;
}
There is few things unclear to me:
1) if I would like to change color of border for all edit controls from id 5001 to id 5121, how to do that? To me, the commented code does not work (when would it be uncommented). It looks like I have this in incorrect place.
2) how correctly create the dialog processes to all the controls? Because there is big number and could be yet few times higher, should I just call a loop from 5001 to id 5121 and call the function:
INT_PTR CALLBACK EditDlgProc(HWND dialog, UINT msg, WPARAM wParam, LPARAM lParam) - that won't work, because every function would need to have different name.
To change the border color of edit control, you have to subclass the edit control and override WM_NCPAINT. That's a little advanced, and you don't really need it. You can just use WS_EX_CLIENTEDGE flag:
CreateWindowEx(WS_EX_CLIENTEDGE, L"EDIT" ...
Also make sure project manifest is setup so you get modern window's look.
This would be an error if it had not been commented out:
case EN_CHANGE:
case WM_CTLCOLOREDIT:
Each case should end in break; or return 0;
Moreover, WM_CTLCOLOREDIT should return a brush which was created on heap. It should not return 1. See documentation :
There are also other errors in that section, you should just get rid of that. See this example for painting.
I'm creating a Windows application with WinAPI. I'm using the TextOut() function to display updated text to the user when handling the WM_PAINT message for the window.
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc;
hdc = BeginPaint(hwnd, &ps);
SelectObject(hdc, hfDefault);
// display the user data in the window
TextOut(hdc,10,70, "Points: 0", 9);
TextOut(hdc,10,85, "Level: 0", 8);
// ...
EndPaint(hwnd, &ps);
}
break;
How can I change the text printed by TextOut() after the function is called as well as the last parameter that determines the length of the printed text?
Everything I've found about using TextOut() was about the text font.
Maybe something like this....
// I'll assume hwnd is global
void OnSomeActionToRefreshValues()
{
HDC hdc = ::GetDc(hwnd);
DrawValues(hdc, 88, 99);
ReleaseDC(hdc);
}
void DrawValues(HDC hdc, int points, int level)
{
// Might need a rectangle here to overwrite old text
SelectObject(hdc, hfDefault); // I assume hfDefault is global
TCHAR text[256];
swprintf_s(text, 256, L"Points: %d", points);
TextOut(hdc, 10, 70, text, wcslen(text));
swprintf_s(text, 256, L"Level: %d", level);
TextOut(hdc, 10, 85, text, wcslen(text));
}
And in you win proc:
case WM_PAINT:
PAINTSTRUCT ps;
HDC hdc;
hdc = BeginPaint(hwnd,&ps);
DrawValues(hdc, 88, 99);
EndPaint(hwnd,&ps);
break;
In order to update the displayed text in a window when handling the WM_PAINT message, you will need to have some source for the text string to be displayed.
Since your original post is somewhat old, the Windows API has changed with new versions of Windows, with the current version of Windows 10 and Windows 11 already in beta.
Windows since Windows XP is 16 bit UNICODE for the WinAPI so people mostly use wchar_t text characters. This requires that text character string constants need the L modifier as in L"wchar_t text".
Using Visual Studio 2019, I put together a simple example that runs with Windows 10. This is a simple Windows WinAPI desktop GUI application. I started with a new project in Visual Studio and had the IDE generate the skeleton for a Windows Desktop GUI application with the wWinMain(), MyRegisterClass(), InitInstance(), and WndProc().
Then I modified that generated source to do the following:
present in the main window four buttons to allow data changes
display two text strings which are updated with counts for button clicks
I elected to use the default font so did not do anything to modify the font used to display the text. If you need to modify the font, you will need to add the code to create the font you want, select the new font into the HDC for drawing the text, then use TextOut() to draw the text with the new font. After using the font you would need to swap it back out and then delete it.
The first step was to create a data area for managing the buttons and button clicks. I chose to create the buttons in the InitInstance().
static struct {
const wchar_t* txt; // pointer to text to display on button face
int iCount; // count of number of times button clicked
HWND hwnd; // button window handle which identifies the button
} myButtons[] = {
{L"Points up", 0, 0},
{L"Points dwn", 0, 0},
{L"Level up", 0, 0},
{L"Level dwn", 0, 0}
};
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
hInst = hInstance; // Store instance handle in our global variable
HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);
if (!hWnd)
{
return FALSE;
}
// create the displayed window along with the buttons.
// the buttons are in a single row at the top of the window.
POINT myPoint = { 10, 10 }; // x, y
for (auto &a : myButtons) {
a.hwnd = CreateWindow(
L"BUTTON", // Predefined class; Unicode assumed
a.txt, // Button text
WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON, // Styles
myPoint.x, // x position
myPoint.y, // y position
100, // Button width
50, // Button height
hWnd, // Parent window
NULL, // No menu.
(HINSTANCE)GetWindowLongPtr(hWnd, GWLP_HINSTANCE),
NULL); // Pointer not needed.
myPoint.x += 100 + 20; // button width plus a separation distance
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
return TRUE;
}
The source code for updating the displayed window follows. We have two functions, a button click handler to determine which button was clicked and the WndProc() with the WM_PAINT message handler which modifies the displayed window.
// process a button click event and return an indication
// whether the button handle matches one we are managing (1)
// or not managing (0).
int buttonClick(HWND hWnd, HWND hButton)
{
// look through the list of buttons to see if the window handle
// of the button event matches one of our buttons.
for (auto &a : myButtons) {
if (a.hwnd == hButton) {
// this is one of our buttons so we increment button click count.
// then invalidate the window area and update to trigger WM_PAINT message.
a.iCount++;
InvalidateRect(hWnd, NULL, TRUE);
UpdateWindow(hWnd);
return 1; // indicate we processed this event.
}
}
return 0; // indicate we did not process this event
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_COMMAND:
{
int wmId = LOWORD(wParam);
int wmCode = HIWORD(wParam);
// Parse the menu selections:
switch (wmId)
{
case IDM_ABOUT:
DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
default:
// not a menu event so see if it is a button click or not.
if (wmCode == BN_CLICKED) {
// if we are managing this button then we skip
// the DefWindowProc() otherwise it is called.
if (buttonClick(hWnd, (HWND)lParam))
break;
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
}
break;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
// TODO: Add any drawing code that uses hdc here...
// create the text strings we are going to display/update
wchar_t myText[2][64];
// following swprintf_s() works because template
// generates the proper call with the additional buffer
// size argument.
swprintf_s(myText[0], L"Points: %d", myButtons[0].iCount - myButtons[1].iCount);
swprintf_s(myText[1], L"Level: %d", myButtons[2].iCount - myButtons[3].iCount);
// get the text metrics of the font we are using to draw the text so
// that we can find out how tall the letters are and can adjust the
// distance for each line of text properly.
TEXTMETRIC myTextMetric = { 0 };
GetTextMetrics(hdc , &myTextMetric);
// we will use a POINT struct for maintaining the point at which
// the text output will start. x coordinate is horizontal position
// and y coordinate is the vertical position.
POINT myPoint = { 10, 150 }; // x, y
int myMargin = 5;
// iterate over the list of strings we are displaying and
// display each one on a separate line.
for (auto &a : myText) {
TextOut(hdc, myPoint.x, myPoint.y, a, wcslen(a));
myPoint.y += myTextMetric.tmHeight + myMargin;
}
EndPaint(hWnd, &ps);
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}