I'm writing a GDI application (black background and starfield animation). I need help in the window drag code. It drags perfectly but dragging it by selecting the upper part of the window ( title ) and the area near it, the window gets lost. I don't know what I'm doing wrong. I tried doing other things but the animation stops for a second and then continues, I don't want that.
#include <Windows.h>
#include <iostream>
#include <windowsx.h>
#define IDB_EXIT 102
PAINTSTRUCT ps;
HDC hdc;
RECT starfield_rc;
int WIDTH = 350;
int HEIGHT = 400;
const int numStars = 50;
const int starSpeed = 1;
struct Star
{
int x, y;
int speed;
};
const int NUM_STARS = 100;
Star stars[NUM_STARS];
HDC hdcMem;
HBITMAP hbmMem;
HBITMAP hbmOld;
void starfield(HDC hdc, int x, int y, RECT *rc)
{
int width = 335;
rc->left = x;
rc->top = y;
rc->right = width;
rc->bottom = 249;
HDC hdcMem = CreateCompatibleDC(hdc);
HBITMAP hbmMem = CreateCompatibleBitmap(hdc, width, 215);
HBITMAP hbmOld = (HBITMAP)SelectObject(hdcMem, hbmMem);
HBRUSH brush = CreateSolidBrush(RGB(0, 0, 0));
FillRect(hdcMem, rc, brush);
DeleteObject(brush);
for (int i = 0; i < NUM_STARS; i++)
{
SetPixel(hdcMem, stars[i].x, stars[i].y, RGB(255, 255, 255));
stars[i].x = (stars[i].x + stars[i].speed) % width;
}
BitBlt(hdc, x, y, width, 215, hdcMem, 0, 0, SRCCOPY);
SelectObject(hdcMem, hbmOld);
DeleteObject(hbmMem);
DeleteDC(hdcMem);
}
// main function
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lparam);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int nCmdShow)
{
for (int i = 0; i < NUM_STARS; i++)
{
stars[i].x = rand() % 340;
stars[i].y = rand() % 249;
stars[i].speed = rand() % 3 + 1;
}
TCHAR appname[] = TEXT("Template");
WNDCLASS wndclass;
MSG msg;
HWND hwnd;
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wndclass.hInstance = hInstance;
wndclass.lpfnWndProc = WndProc;
wndclass.lpszClassName = appname;
wndclass.lpszMenuName = NULL;
wndclass.style = CS_HREDRAW | CS_VREDRAW;
// check this window class is registered or not
if (!RegisterClass(&wndclass))
{
MessageBox(NULL, TEXT("Window class is not registered"), TEXT("Error...."), MB_ICONERROR);
return 0;
}
hwnd = CreateWindowEx(WS_EX_LAYERED | WS_EX_COMPOSITED,
appname, // window name
appname, // window text
WS_VISIBLE | WS_SYSMENU | WS_POPUP, // set POPUP window style for no border & controls
100, // window position x
100, // window position y
WIDTH, // width
HEIGHT, // height
NULL,
NULL,
hInstance,
NULL);
// show & update created window
SetTimer(hwnd, 1, 10, NULL); // Set a timer to trigger every 100ms
ShowWindow(hwnd, nCmdShow);
// get message from queue
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
bool isSizingOrMoving = false;
// WndProc function
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HINSTANCE hIns;
HWND exit_button, gen_button, title, license;
LRESULT move = NULL;
static int xClick;
static int yClick;
switch (message)
{
case WM_CREATE:
{
exit_button = CreateWindow(TEXT("BUTTON"), NULL, WS_CHILD | WS_VISIBLE | BS_BITMAP,
250, 360, 70, 30, hwnd, (HMENU)100, hIns, 0);
HBITMAP exitImg = (HBITMAP)LoadImageA(GetModuleHandleA(nullptr), (LPCSTR)MAKEINTRESOURCE(IDB_EXIT), IMAGE_BITMAP, 0, 0, NULL);
title = CreateWindow(TEXT("STATIC"), TEXT("Hello"), WS_CHILD | WS_VISIBLE, 150, 5, 120, 20, hwnd, 0, hIns, 0);
SendMessageW(exit_button, BM_SETIMAGE, IMAGE_BITMAP, (LPARAM)exitImg);
}
break;
case WM_PAINT:
{
PAINTSTRUCT ps;
hdc = BeginPaint(hwnd, &ps);
starfield(hdc, 6, 31, &starfield_rc);
EndPaint(hwnd, &ps);
break;
}
case WM_TIMER:
InvalidateRect(hwnd, &starfield_rc, FALSE);
break;
case WM_COMMAND:
switch (wParam)
{
case 100:
PostQuitMessage(EXIT_SUCCESS);
return 0;
}
break;
case WM_CTLCOLORSTATIC:
{
hdc = (HDC)wParam;
return (LRESULT)CreateSolidBrush(RGB(0, 0, 0)); // black and white
break;
}
case WM_CTLCOLOREDIT:
{
hdc = (HDC)wParam;
return (LRESULT)CreateSolidBrush(RGB(0, 0, 0)); // black and white
break;
}
case WM_LBUTTONDOWN:
// Get the click position
xClick = LOWORD(lParam);
yClick = HIWORD(lParam);
if (xClick < 0 && yClick < 0)
{
return 1;
}
// Restrict mouse input to current window
SetCapture(hwnd);
return 1;
case WM_LBUTTONUP:
{
// Window no longer requires all mouse input
ReleaseCapture();
return 1;
}
case WM_MOUSEMOVE:
{
if (GetCapture() == hwnd) // Check if this window has mouse input
{
// Get the window's screen coordinates
RECT rcWindow;
GetWindowRect(hwnd, &rcWindow);
// Get the current mouse coordinates
int xMouse = LOWORD(lParam);
int yMouse = HIWORD(lParam);
// Calculate the new window coordinates
int xWindow = rcWindow.left + xMouse - xClick;
int yWindow = rcWindow.top + yMouse - yClick;
// Set the window's new screen position (don't resize or change z-order)
SetWindowPos(hwnd, NULL, xWindow, yWindow, 0, 0,
SWP_NOSIZE | SWP_NOZORDER);
}
break;
}
case WM_DESTROY:
PostQuitMessage(EXIT_SUCCESS);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
One of many problems here, which may lead to crash or incorrect functioning (use of un-initialized variable hInst, edge cases, etc) is that you don't return 0 on success of WM_MOUSEMOVE
. Also SetWindowPos
sometimes can fail especially if you don't sanitize it input. Some (e.g. you moved it some that there is taskbar between window andclient area) may result in window's collapse because of how you track mouse position and that DefaultWndProc
takes control afterwards.
Initialize used variable. In debug mode they are 0 by Microsofot's odd policy, in release they may be random, unknown values, which causes crash:
HINSTANCE hIns = NULL; // you don't need this, generally.
Extract values with proper macro:
// Get the current mouse coordinates
int xMouse = GET_X_LPARAM(lParam);
int yMouse = GET_Y_LPARAM(lParam);
Do not use the LOWORD or HIWORD macros to extract the x- and y- coordinates of the cursor position because these macros return incorrect results on systems with multiple monitors. Systems with multiple monitors can have negative x- and y- coordinates, and LOWORD and HIWORD treat the coordinates as unsigned quantities.
Funny, these are from windowsx.h
header and are the reason why you would need to include it, but you don't use any of message unpackers.
Modern Windows with virtual desktop are kind of multiple-screens as wel, so you may get issue of window "jumping" out of screen if you try "cross" screen border near the task bar.
Also, at least check if it was successful (you don't need pass control to DefaultWndProc
):
if (SetWindowPos(hwnd, NULL, xWindow, yWindow, 0, 0,
SWP_NOSIZE | SWP_NOZORDER)) {
return 0; // MUST return zero of WM_MOUSEMOVE is processed
}
Another issue is that borders of your window are inactive and transparent to mouse hits, i.e. window is draw as if it is slighly larger than area that receives mouse events. It's just a visual issue typical for borderless, bevel-less windows.