I have a main window which is created with the following styles
WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU | WS_TABSTOP | WS_GROUP | WS_VISIBLE
and with ex-stles
WS_EX_ACCEPTFILES | WS_EX_CONTROLPARENT | WS_EX_LEFT | WS_EX_LTRREADING.
This main window has a child window on it, which is an edit control created with styles
WS_VISIBLE | WS_CHILD | ES_READONLY
and ex-style
WS_EX_CLIENTEDGE.
I am going to use this edit control as a progress-bar control. I don't want to use standard Wind32 progress-bar control (PROGRESS_CLASS), because I want to do a some custom painting on it (e.g.; dynamically changing fill color, displaying text on it, etc).
I can paint any region of the main window by the following code:
// hWnd: Handle of the main window
case WM_PAINT:
hDc = BeginPaint(hWnd, &Ps);
Rect = AFunctionToGetCornerThePointsOfTheEditControl();
Rect.right = Rect.left + 3 * (Rect.right - Rect.left) / 4; // Fill 3/4 (75%) of it
Rect.left -= 10; // Enlarge the paint region a little
Rect.top -= 10; // so that we can see it if it stays
Rect.bottom += 10; // under the edit control.
hBrush = CreateSolidBrush(RGB(50,100,255));
ret = FillRect(hDc, &Rect, hBrush); // ret = 1 always
ler = GetLastError(); // ler = 0
EndPaint(hWnd, &Ps);
break;
It looks like this:
I changed this code a little to paint the child control instead:
// hWndEdit: Handle of the edit control
case WM_PAINT:
hDc = BeginPaint(hWndEdit, &Ps);
Rect = AFunctionToGetCornerThePointsOfTheEditControl();
Rect.right = Rect.left + 3 * (Rect.right - Rect.left) / 4; // Fill 3/4 (75%) of it
Rect.left -= 10;
Rect.top -= 10;
Rect.bottom += 10;
hBrush = CreateSolidBrush(RGB(50,100,255));
ret = FillRect(hDc, &Rect, hBrush); // ret = 0 always
ler = GetLastError(); // ler = 6 (ERROR_INVALID_HANDLE)
EndPaint(hWndEdit, &Ps);
break;
This time it doesn't work. The main windows completely disappears as soon as I drag some part it out of the screen area, and it becomes totally unresponsive. Desktop icons under it are visible, but are not clickable.
So, what do I have to do in order to paint the child window (the edit control)?
This article helped me a lot: Subclassing Controls
First, I create a separate message processing function for processing child messages.
LRESULT CALLBACK MyClass::ChildWindowProc( HWND hWnd,
UINT uMsg,
WPARAM wParam,
LPARAM lParam,
UINT_PTR uIdSubclass,
DWORD_PTR dwRefData)
{
static PAINTSTRUCT Ps;
static RECT Rect;
static HBRUSH hBrush1 = CreateSolidBrush(RGB(50,100,255));
static HBRUSH hBrush2 = CreateSolidBrush(RGB(255,100,50));
HDC hDc;
LRESULT lResult;
switch (uMsg)
{
case WM_PAINT:
switch (uIdSubclass)
{
case 1:
hDc = BeginPaint(hWnd, &Ps);
Rect.left = 0;
Rect.right = (LONG) (((double) ITEM2_WIDTH) * Status::GI()->Get_JobCurPercentage());
Rect.top = 0;
Rect.bottom = ITEM_HEIGHT - 3;
FillRect(hDc, &Rect, hBrush1);
EndPaint(hWnd, &Ps);
break;
case 2:
hDc = BeginPaint(hWnd, &Ps);
Rect.left = 0;
Rect.right = (LONG) (((double) ITEM2_WIDTH) * Status::GI()->Get_JobTotPercentage());
Rect.top = 0;
Rect.bottom = ITEM_HEIGHT - 3;
FillRect(hDc, &Rect, hBrush2);
EndPaint(hWnd, &Ps);
break;
default:
return DefSubclassProc(hWnd, uMsg, wParam, lParam);
}
break;
case WM_NCDESTROY:
//ReleaseDC(hWnd, hDc);
return DefSubclassProc(hWnd, uMsg, wParam, lParam);
break;
default:
return DefSubclassProc(hWnd, uMsg, wParam, lParam);
}
return 0;
}
Next, I bind this function to the controls:
SetWindowSubclass( /*_In_ HWND hWnd*/ ed_cur_Progress.hWnd,
/*_In_ SUBCLASSPROC pfnSubclass*/ ChildWindowProc,
/*_In_ UINT_PTR uIdSubclass*/ 1,
/*_In_ DWORD_PTR dwRefData*/ (DWORD_PTR) NULL);
SetWindowSubclass( /*_In_ HWND hWnd*/ ed_tot_Progress.hWnd,
/*_In_ SUBCLASSPROC pfnSubclass*/ ChildWindowProc,
/*_In_ UINT_PTR uIdSubclass*/ 2,
/*_In_ DWORD_PTR dwRefData*/ (DWORD_PTR) NULL);
And, that's all! The result is amazing.
WM_PAINT you are handling is main window's. you need to draw the editbox in its owner WM_PAINT message. I guess you get the error from "hDc = BeginPaint(hWndEdit, &Ps);", you may check it.
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!
Basically, I'm making something that imitates a screen melting effect, but I can only get it working on my primary monitor. I've looked up as much as I could and there was only one forum on GetDC for all monitors but it was to no use, all it done was make a rectangle from my primary monitor to my secondary monitor with the effect still only working on my primary monitor. This is the thread I read: GetDC(NULL) gets primary monitor or virtual screen?
LRESULT CALLBACK Melter(HWND hWnd, UINT Message, WPARAM wParam, LPARAM lParam) {
switch (Message) {
case WM_CREATE: {
HDC Desktop = GetDC(HWND_DESKTOP);
HDC Window = GetDC(hWnd);
BitBlt(Window, 0, 0, ScreenWidth, ScreenHeight, Desktop, 0, 0, SRCCOPY);
ReleaseDC(hWnd, Window);
ReleaseDC(HWND_DESKTOP, Desktop);
SetTimer(hWnd, 0, Interval, 0);
ShowWindow(hWnd, SW_SHOW);
break;
}
case WM_PAINT: {
ValidateRect(hWnd, 0);
break;
}
case WM_TIMER: {
HDC Window = GetDC(hWnd);
int uX = (rand() % ScreenWidth) - (150 / 2), uY = (rand() % 15), Width = (rand() % 150);
BitBlt(Window, uX, uY, Width, ScreenHeight, Window, uX, 0, SRCCOPY);
ReleaseDC(hWnd, Window);
break;
}
case WM_DESTROY: {
KillTimer(hWnd, 0);
PostQuitMessage(EXIT_SUCCESS);
break;
}
return EXIT_SUCCESS;
}
return DefWindowProc(hWnd, Message, wParam, lParam);
}
The line I changed was HDC Window = GetDC(Window) to HDC Window = GetDC(NULL) and then some other stuff like the RECT. It'd be great if someone could help me, thanks :)
PS, ScreenWidth = 3600, ScreenHeight = 1080 whilst PMScreenWidth = 1920, PMScreenHeight = 1080. PM as in Primary Monitor, so I've got all the stuff in that function set to ScreenWidth/ScreenHeight so it's the width/height of all monitors. Still doesn't work though.
GetDC(HWND_DESKTOP) (same as GetDC(0)) already returns the DC for all monitors. The problem with above code is mainly with the usage of BitBlt and choosing the coordinates. See the MCVE below that addresses the question.
Don't draw in response to WM_CREATE, it's just going to get erased in WM_PAINT or when background is erased.
Don't call ValidateRect in response to WM_PAINT. If you want to erase the window then just use FillRect, or force repaint from a command or another route.
Use GetSystemMetrics(SM_CXVIRTUALSCREEN) and GetSystemMetrics(SM_CYVIRTUALSCREEN) to return width and height for the virtual monitor.
Also be sure the process is DPI aware. For testing you can call SetProcessDPIAware(); at the start of the program. Ideally DPI awareness should be set in manifest file.
int uX = (rand() % ScreenWidth) - (150 / 2);
int uY = (rand() % 15);
int Width = (rand() % 150);
BitBlt(Window, uX, uY, Width, ScreenHeight, Window, uX, 0, SRCCOPY);
Above code is copying bits from client DC the the same client DC, it's not going to do anything. Presumably you want to copy from desktop DC to client DC.
Moreover, the coordinates are basically picked at random. It assumes the primary monitor is on top-left. If uX is greater than window's own width, it will not get copied, unless the window stretches the whole virtual monitor.
LRESULT CALLBACK Melter(HWND hWnd, UINT Message, WPARAM wParam, LPARAM lParam) {
switch(Message)
{
case WM_PAINT:
{
PAINTSTRUCT ps;
auto hdc = BeginPaint(hWnd, &ps);
RECT rc;
GetClientRect(hWnd, &rc);
HDC hdesktop = GetDC(0);
int screenx = GetSystemMetrics(SM_XVIRTUALSCREEN);
int screeny = GetSystemMetrics(SM_YVIRTUALSCREEN);
int screenw = GetSystemMetrics(SM_CXVIRTUALSCREEN);
int screenh = GetSystemMetrics(SM_CYVIRTUALSCREEN);
StretchBlt(hdc, 0, 0, rc.right, rc.bottom,
hdesktop, screenx, screeny, screenw, screenh, SRCCOPY);
ReleaseDC(0, hdesktop);
EndPaint(hWnd, &ps);
break;
}
case WM_DESTROY:
PostQuitMessage(0);
break;
}
return DefWindowProc(hWnd, Message, wParam, lParam);
}
You can use
HDC dc = CreateDC(("DISPLAY"), NULL, NULL, NULL);
Check below links:
Using Multiple Monitors as Independent Displays
CreateDCA function
Multiple Display Monitors Functions
I'm trying to make button change background color of window when clicked. I know that I need to handle this event in WM_COMMAND, where I also check ID of this button, but nothing happens. I tried to debug and my program recognizes ID correctly. The piece of code used for changing color works well when in main loop but it doesn't do anything when in WM_COMMAND. How do I solve this problem? Whole code:
#include <Windows.h>
#define BUTTON_ID 100
struct status_info {
const char* waiting = "Waiting for connection...";
const char* connected = "Connected.\nWaiting for frajer to copy number.";
const char* changed = "Number changed.";
}status_info;
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR pCmdLine, int nCmdShow) {
const wchar_t CLASS_NAME[] = L"Name";
WNDCLASS wc = {};
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.lpszClassName = (LPCSTR)CLASS_NAME;
wc.hbrBackground = (HBRUSH)GetStockObject(GRAY_BRUSH);
RegisterClass(&wc);
//main window
HWND hwnd = CreateWindowEx(0, (LPCSTR)CLASS_NAME, (LPCSTR)"Hacker", WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, 500, 300, NULL, NULL, hInstance, NULL);
//number window
HWND number = CreateWindowEx(WS_EX_WINDOWEDGE, TEXT("Static"), TEXT("Account number:\n00 1234 1234 1234 1234 1234 1234"), WS_CHILD | WS_VISIBLE, 5, 5, 240, 40, hwnd, NULL, NULL, NULL);
//status window
const char* status_message = status_info.waiting;
HWND status = CreateWindowEx(WS_EX_WINDOWEDGE, TEXT("Static"), TEXT(status_message), WS_CHILD | WS_VISIBLE, 5, 55, 240, 40, hwnd, NULL, NULL, NULL);
//button
HWND button = CreateWindowEx(0, "BUTTON", "Nightmode", WS_CHILD | WS_VISIBLE, 100, 100, 150, 30, hwnd, (HMENU)BUTTON_ID, hInstance, NULL);
MSG msg;
WNDCLASS okno;
while (GetMessage(&msg, (HWND)NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
SetWindowText(status, status_message);
}
return msg.wParam;
return 0;
}
LRESULT CALLBACK WindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_CREATE:
//MessageBox(hwnd, L"1", L"1", 0);
return (0);
case WM_DESTROY:
//MessageBox(hwnd, L"2", L"2", 0);
PostQuitMessage(0);
return (0);
case WM_COMMAND: {
if (LOWORD(wParam) == BUTTON_ID) {
PAINTSTRUCT ps;
RECT rc;
HDC hdc = BeginPaint(hwnd, &ps);
GetClientRect(hwnd, &rc);
SetBkColor(hdc, BLACK_BRUSH);
ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &rc, 0, 0, 0);
EndPaint(hwnd, &ps);
}
break;
}
default:
return DefWindowProc(hwnd, message, wParam, lParam);
}
}
BeginPaint/EndPaint should be used in response to WM_PAINT only.
You can use GetDC(hwnd)/ReleaseDC(hwnd, hdc) to obtain hdc for painting on device context outside of WM_PAINT, but this will be temporary. The next refresh message causes the window to be erased and repainted according to what's in WM_PAINT
SetDCBrushColor can be used if the goal is to avoid creating brush handle.
static COLORREF bkcolor = RGB(255,255,255);
switch(message)
{
case WM_COMMAND:
if(LOWORD(wparam) == BUTTON_ID)
{
bkcolor = RGB(255, 0, 0);
InvalidateRect(hwnd, NULL, TRUE);
}
break;
case WM_PAINT:
{
PAINTSTRUCT ps;
RECT rc;
HDC hdc = BeginPaint(hwnd, &ps);
GetClientRect(hwnd, &rc);
SetDCBrushColor(hdc, bkcolor);
FillRect(hdc, &rc, (HBRUSH)GetStockObject(DC_BRUSH));
//or use ps.rcPaint to repaint only the section which requires update
//FillRect(hdc, &ps.rcPaint, (HBRUSH)GetStockObject(DC_BRUSH));
EndPaint(hwnd, &ps);
return 0;
}
case WM_ERASEBKGND:
//return 0 means WM_PAINT handles the background
return 0;
Alternatively, use SetClassLongPtr to replace the background brush:
static HBRUSH bkbrush = NULL;
switch(message)
{
case WM_COMMAND:
if(LOWORD(wparam) == BUTTON_ID)
{
COLORREF bkcolor = RGB(rand() % 256, rand() % 256, rand() % 256);
if(bkbrush)
DeleteObject(bkbrush);
bkbrush = CreateSolidBrush(bkcolor);
SetClassLongPtr(hwnd, GCL_HBRBACKGROUND, (LONG)bkbrush);
InvalidateRect(hwnd, NULL, TRUE);
}
break;
I'm just trying to draw an ellipse:
case WM_PAINT:
hdc = BeginPaint(parentWindow, &ps);
Ellipse(hdc, x, y, width, height);
EndPaint(parentWindow, &ps);
, and then erase it with drawing a new ellipse with some new parameters every second using timer:
case WM_CREATE:
SetTimer(hWnd, 1, 1000, NULL);
break;
case WM_TIMER:
x += 5;
InvalidateRect(hWnd, NULL, TRUE);
break;
But ellipses are not erased and layered:
However, i tried to trace WM_ERASEBKGND and it really is sent every InvalidateRect.
Full code:
#include <Windows.h>
#include <windowsx.h>
#include <tchar.h>
#include <iostream>
TCHAR szWindowClass[] = TEXT("CreateThreadWindow");
TCHAR szAppName[] = TEXT("CreateThreadExample");
BOOL InitWindow(HINSTANCE, int);
ATOM MyRegisterClass(HINSTANCE);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
HWND parentWindow;
MSG msg;
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
MyRegisterClass(hInstance);
if (!InitWindow(hInstance, nCmdShow))
return FALSE;
BOOL bRet;
while ((bRet = GetMessage(&msg, (HWND)NULL, 0, 0)) != 0)
{
if (bRet == -1)
return FALSE;
else
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return (int)msg.wParam;
}
ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASS wndClass;
memset(&wndClass, 0, sizeof(wndClass));
wndClass.lpfnWndProc = WndProc;
wndClass.hInstance = hInstance;
wndClass.lpszMenuName = NULL;
wndClass.lpszClassName = szWindowClass;
return RegisterClass(&wndClass);
}
BOOL InitWindow(HINSTANCE hInstance, int nCmdShow)
{
parentWindow = CreateWindow(szWindowClass, szAppName, WS_OVERLAPPEDWINDOW,
300, 0, 600, 600, NULL, NULL, hInstance, NULL);
ShowWindow(parentWindow, nCmdShow);
return TRUE;
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wparam, LPARAM lparam)
{
PAINTSTRUCT ps;
HDC hdc;
static int x = 0, y = 0, width = 200, height = 100;
switch (message) {
case WM_ERASEBKGND:
_RPT1(0, "%s\n", "erase");
break;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
Ellipse(hdc, x, y, width, height);
EndPaint(hWnd, &ps);
break;
case WM_CREATE:
SetTimer(hWnd, 1, 1000, NULL);
break;
case WM_TIMER:
x += 5;
InvalidateRect(hWnd, NULL, TRUE);
break;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
default:
return DefWindowProc(hWnd, message, wparam, lparam);
}
}
Your code isn't erasing anything. It's just drawing an ellipse at the specified coordinates. The previously-drawn ellipse is still there.
You mention the WM_ERASEBKGND message, but there are two reasons why that isn't working for you:
In your window procedure (WndProc), you handle the WM_ERASEBKGND message explicitly, which means that it doesn't get passed to the default window procedure (DefWindowProc). A better way to write your window procedure would be the following:
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wparam, LPARAM lparam)
{
static int x = 0, y = 0, width = 200, height = 100;
switch (message) {
case WM_ERASEBKGND:
{
_RPT1(0, "%s\n", "erase");
break;
}
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
Ellipse(hdc, x, y, width, height);
EndPaint(hWnd, &ps);
return 0;
}
case WM_CREATE:
{
SetTimer(hWnd, 1, 1000, NULL);
break;
}
case WM_TIMER:
{
x += 5;
InvalidateRect(hWnd, NULL, TRUE);
return 0;
}
case WM_DESTROY:
{
PostQuitMessage(0);
return 0;
}
default:
break;
}
return DefWindowProc(hWnd, message, wparam, lparam);
}
Now, the default window procedure gets called every time, unless you explicitly return from inside of a case label.
When you register your window class (inside of MyRegisterClass), you zero all fields of the WNDCLASS structure and then explicitly initialize a couple of them. You don't explicitly initialize the hbrBackground field, so it is being set to 0. And when hbrBackground is 0,
When this member is NULL, an application must paint its own background whenever it is requested to paint in its client area. To determine whether the background must be painted, an application can either process the WM_ERASEBKGND message or test the fErase member of the PAINTSTRUCT structure filled by the BeginPaint function.
This means that the default window procedure isn't doing anything in response to the WM_ERASEBKGND message because you didn't give your window a background brush.
You will either need to set hbrBackground to something like COLOR_WINDOW + 1, or you will need to add code to your WM_ERASEBKGND message handler to erase the window's background yourself.
Or, perhaps an even better option would be to forget about the WM_ERASEBKGND message altogether, as many Windows programmers do, because this two-step erase-and-paint approach tends to cause flicker. Leave the hbrBackground field set to NULL, don't do anything in response to the WM_ERASEBKGND message, and do your erasing at the top of the WM_PAINT handler:
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
// Erase background of entire client area.
RECT rcClient;
GetClientRect(hWnd, &rcClient);
FillRect(hdc, &rcClient, reinterpret_cast<HBRUSH>(COLOR_WINDOW+1));
// Do normal drawing.
Ellipse(hdc, x, y, width, height);
EndPaint(hWnd, &ps);
return 0;
}
I am attempting to write a slot machine Win32 App that uses images to display the result of the spins. I know how to display an image on a normal LRESULT CALLBACK frame, but i'm lost when it comes to displaying images on a dialog. Could anyone help me by explaining(in-depth) how i would go about displaying images? I really appreciate it.
My current Dialog callback:
BOOL CALLBACK DlgProc(HWND hwnd, UINT message,WPARAM wParam, LPARAM lParam)
{
switch(message)
{
case WM_INITDIALOG: //dialog created
g_hbmObject = LoadBitmap(GetModuleHandle(NULL), MAKEINTRESOURCE(IDB_OBJECT));
//initialize slotmachine class
Machine.Initialize(time(0));
if(g_hbmObject == NULL) //test if object is loaded correctly
std::cerr << "Could not load ball..";
break;
case WM_COMMAND: //switch command
switch(LOWORD(wParam))
{
case IDC_SPIN: //slot machine spins
//put spin function, decide what to display
//do i put the display image command here? or where?
break;
case IDC_EXIT: //exit button
DeleteObject(g_hbmObject);
EndDialog(hwnd, 0);
break;
}
break;
case WM_CLOSE:
case WM_DESTROY: //case program is exited
DeleteObject(g_hbmObject);
PostQuitMessage(0);
break;
default:
return FALSE;
}
return TRUE;
}
The following code registers a "REEL" window class: a control that displays a spinning reel for a slot machine. You can create multiple instances of it on a dialog using resource statements like:
CONTROL "",IDC_REEL1,"REEL",0,14,50,40,40
CONTROL "",IDC_REEL2,"REEL",0,70,50,40,40
The reel spins endlessly, but you can easily add private messages to start and stop it.
As explained in the comments, it expects a bitmap representing a reel with resource ID IDB_REEL.
HBITMAP g_hbmpReel;
LRESULT CALLBACK ReelWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
const int ReelTimerId = 5;
switch (message)
{
case WM_CREATE:
SetTimer(hWnd, ReelTimerId, 10, 0);
break;
case WM_DESTROY:
KillTimer(hWnd, ReelTimerId);
break;
case WM_SIZE:
SetWindowPos(hWnd, 0, 0, 0, 40, 40, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOREDRAW | SWP_NOZORDER);
break;
case WM_TIMER:
if (wParam == ReelTimerId)
{
int offset = GetWindowLong(hWnd, 0);
offset = (offset + 5) % 120;
SetWindowLong(hWnd, 0, offset);
InvalidateRect(hWnd, 0, FALSE);
}
break;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
HDC hdcReel = CreateCompatibleDC(hdc);
HBITMAP hbmpOld = (HBITMAP)SelectObject(hdcReel, g_hbmpReel);
int offset = GetWindowLong(hWnd, 0);
BitBlt(hdc, 0, 0, 40, 40, hdcReel, 0, offset, SRCCOPY);
SelectObject(hdcReel, hbmpOld);
DeleteDC(hdcReel);
EndPaint(hWnd, &ps);
}
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
ATOM RegisterReel(HINSTANCE hInstance)
{
WNDCLASSEX wcex = {0};
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.lpfnWndProc = ReelWndProc;
// Window data used to hold the position of the reel
wcex.cbWndExtra = sizeof(int);
wcex.hInstance = hInstance;
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszClassName = L"REEL";
// IDB_REEL is a 40x160 bitmap representing a reel with THREE 40x40 symbols.
// The bottom 40x40 square should be the same as the topmost.
g_hbmpReel = LoadBitmap(hInstance, MAKEINTRESOURCE(IDB_REEL));
return RegisterClassEx(&wcex);
}