Search code examples
c++winapigdi

Animation freezes when click


I am trying to remove freezing from my stars animation. The animation animates smoothly but it freezes for a tiny second when I click on the animation area. border(), lines(), box(), SetColor() are my own functions, they do not freeze.

This is a WS_POPUP window. I want it to move. I tried handling WM_NCHITTEST. My window moves when I drag it but the animation freezes for a tiny moment and then continue. I dont know how to move further to avoid freezing.

#include <Windows.h>
#include <iostream>
#include "util.h"

#define IDB_GEN 101
#define IDB_EXIT 102

const int numStars = 50;
const int starSpeed = -11;

struct Star {
    int x, y;
    int speed;
};

const int NUM_STARS = 100;
Star stars[NUM_STARS];

PAINTSTRUCT ps;
HDC hdc;
RECT starfield_rc;

int WIDTH = 350;
int HEIGHT = 400;

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_TOPMOST,appname,  // window name
                        appname,  // window text
                        WS_POPUP | WS_VISIBLE, // 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;
}

// 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;

    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);
            gen_button = CreateWindow(TEXT("BUTTON"), NULL, WS_CHILD | WS_VISIBLE | BS_BITMAP,
                                      30, 360, 70, 30, hwnd, (HMENU)200, hIns, 0);
            lic = CreateWindow(TEXT("EDIT"), TEXT("LiE"), WS_CHILD | WS_VISIBLE | WS_BORDER | ES_CENTER, 10, 270, 330, 20, hwnd, (HMENU)300, hIns, 0);
            
            HBITMAP genImg = (HBITMAP)LoadImageA(GetModuleHandleA(nullptr), (LPCSTR)MAKEINTRESOURCE(IDB_GEN), IMAGE_BITMAP, 0, 0, NULL);
        HBITMAP exitImg = (HBITMAP)LoadImageA(GetModuleHandleA(nullptr), (LPCSTR)MAKEINTRESOURCE(IDB_EXIT), IMAGE_BITMAP, 0, 0, NULL);
            
            title = CreateWindow(TEXT("STATIC"), TEXT("Iamwho"), WS_CHILD | WS_VISIBLE, 150, 5, 120, 20, hwnd, 0, hIns, 0);
            
            SendMessageW(exit_button, BM_SETIMAGE, IMAGE_BITMAP, (LPARAM)exitImg);
            SendMessageW(gen_button, BM_SETIMAGE, IMAGE_BITMAP, (LPARAM)genImg);

        }

        break;

    case WM_PAINT:
    {
        PAINTSTRUCT ps;
        hdc = BeginPaint(hwnd, &ps);
        // border(hdc, 0, 20, 200, 300, 1, SetColor(hdc, RGB(255, 255, 255), RGB(255,255,255)));
        lines(hdc, 5, 30, 5, 250, 2);
        lines(hdc, 345, 30, 345, 250, 2);
        starfield(hdc, 6, 31, &starfield_rc);
        box(hdc, 5, 260, 340, 90, 1, (HBRUSH)SetColor(hdc, RGB(255, 255, 255), RGB(255, 255, 255)));
        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)SetColor(hdc, RGB(255, 255, 255), RGB(0, 0, 0)); // black and white

        break;
    }

    case WM_CTLCOLOREDIT:

    {
        hdc = (HDC)wParam;

        return (LRESULT)SetColor(hdc, RGB(255, 255, 255), RGB(0, 0, 0)); // black and white

        break;
    }

   case WM_NCHITTEST:
        RECT rc;
        POINT pt;

        GetCursorPos(&pt);

        GetWindowRect(hwnd, &rc);
        rc.bottom = rc.bottom - 200;

        // if cursor position is within top layered drawn rectangle then
        // set move to HTCAPTION for moving the window from its client
        if (pt.x <= rc.right && pt.x >= rc.left && pt.y <= rc.bottom && pt.y >= rc.top)
        {
            move = DefWindowProc(hwnd, message, wParam, lParam);
            if (move == HTCLIENT)
            {
                move = HTCAPTION;
            }
        }
        
        return move;

        break;


    case WM_DESTROY:
        PostQuitMessage(EXIT_SUCCESS);
        return 0;
    }

    return DefWindowProc(hwnd, message, wParam, lParam);
}

Solution

  • Dragging a window runs a separate modal message loop until the drag is complete (you will receive WM_ENTERSIZEMOVE and WM_EXITSIZEMOVE messages when this happens). By simulating mouse activity on the "caption" to let the OS drag your window, you are letting the drag's internal message loop block your message loop, so you won't get normal WM_PAINT messages in your message queue until the drag is finished.

    Handling WM_NCHITTEST the way you are is not the best way to allow your caption-less window to be dragged! For one thing, you are ignoring the mouse coordinates provided by WM_NCHITTEST. For another, you are returning HTNOWHERE for every coordinate outside of your animation area, instead of returning what DefWindowProc() would normally report. Instead, you should handle the WM_LBUTTONDOWN message and have it call ReleaseCapture() and then send your window a WM_SYSCOMAND(SC_DRAGMOVE) message (where SC_DRAGMOVE=$F012).

    Since you already have a timer (which will continue running normally in the modal message loop), you can have it call UpdateWindow() after InvalidateRect() when in the drag state to force an immediate non-queued WM_PAINT message, eg:

    ...
    
    bool isSizingOrMoving = false;
    
    LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
    {
        ...
        switch (message)
        {
            ...
    
            case WM_ENTERSIZEMOVE:
                isSizingOrMoving = true;
                break;
     
            case WM_EXITSIZEMOVE:
                isSizingOrMoving = false;
                break;
    
            case WM_TIMER:
                InvalidateRect(hwnd, &starfield_rc, FALSE);
                if (isSizingOrMoving)
                    UpdateWindow(hwnd);
                break;
    
            case WM_NCHITTEST: {
                POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
    
                RECT rc;
                GetWindowRect(hwnd, &rc);
                rc.bottom -= 200;
    
                // if cursor position is within top layered drawn rectangle then
                // set move to HTCAPTION for moving the window from its client
                if (PtInRect(&rc, pt))
                {
                    LRESULT move = DefWindowProc(hwnd, message, wParam, lParam);
                    return (move == HTCLIENT) ? HTCAPTION : move;
                }        
    
                break;
            }
    
            /*
            case WM_LBUTTONDOWN: {
                POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
    
                RECT rc;
                GetClientRect(hwnd, &rc);
                rc.bottom -= 200;
    
                if (PtInRect(&rc, pt))
                {
                    ReleaseCapture();
                    const WPARAM SC_DRAGMOVE = $F012;
                    SendMessage(hwnd, WM_SYSCOMAND, SC_DRAGMOVE, 0);
                    return 0;
                }        
    
                break;
            }
            */
    
            ...
        }
    
        return DefWindowProc(hwnd, message, wParam, lParam);
    }
    

    If you find the animation is still too choppy while dragging, you can have WM_ENTERSIZEMOVE start a second timer to update the window more often, and have WM_EXITSIZEMOVE stop that timer.