c++windowswinapigdi

Window gets lost after dragging it from top


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);
}

Solution

  • 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.