单击或拖动标题栏时如何防止计时器冻结?

How to prevent Timer from freezing when titlebar is clicked or dragging?

我有一个定时器,负责逐帧显示GIF图片。我注意到当我 right-clickedholdtitilebar 计时器暂停我认为 1 秒,当我 left-clicked hold 标题栏时,计时器暂停,直到我松开鼠标。

LRESULT CALLBACK GDIHelper::StaticControlProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData) {
    switch(uMsg) {
        case WM_TIMER:
        {
            OnTimer(); // Do something on timer.
            InvalidateRect(staticControl, NULL, FALSE);
            return 0;
        }
        case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hwnd, &ps);
           
            Graphics g(hdc);
            g.DrawImage(m_pImage, 0, 0, width, height);

            EndPaint(hwnd, &ps);
            return TRUE; 
        }
        case WM_DESTROY:
        {
            return 0;
        }
    }
    return DefSubclassProc(hwnd, uMsg, wParam, lParam);
}


LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
    switch(message) {
        case WM_CREATE: {
            staticControl = CreateWindowEx(0, L"STATIC", NULL, WS_CHILD | WS_VISIBLE | SS_OWNERDRAW, xPosition, yPosition, width, height, hWnd, NULL, NULL, NULL); //create the static control.
            SetWindowSubclass(staticControl, &StaticControlProc, unique_id, 0);
            
            gdiHelper.AnimateGIF();
            
            break;
        }
        case WM_PAINT:
        {
            HDC hdc;
            PAINTSTRUCT ps;
            hdc = BeginPaint(hWnd, &ps);

            //Paint other images and text here...
            
            EndPaint(hWnd, &ps);
            break;
        }
        case WM_DESTROY:
        {
            gdiHelper.Destroy();
            PostQuitMessage(0);
            break;
        }
    }
    return DefWindowProc(hWnd, message, wParam, lParam);
}

下面是负责创建计时器的函数。

void GDIHelper::OnTimer() {
    if(isPlayable) {
        GUID Guid = FrameDimensionTime;
        m_pImage->SelectActiveFrame(&Guid, m_iCurrentFrame);        
        m_iCurrentFrame = (++m_iCurrentFrame) % m_FrameCount;
    }
}

void GDIHelper::AnimateGIF() {
    if(m_bIsPlaying == TRUE) {
        return;
    }
    
    m_iCurrentFrame = 0;
    GUID Guid = FrameDimensionTime;
    m_pImage->SelectActiveFrame(&Guid, m_iCurrentFrame);
    SetTimer(staticControl, 120, ((UINT*)m_pItem[0].value)[m_iCurrentFrame] * 10, NULL);
    ++m_iCurrentFrame;
    m_bIsPlaying = TRUE;
}

如何预防?

InvalidateRect() 不会立即重绘您的控件。它将简单地为 window 的特定矩形区域安排未来的重绘。单击或按住标题栏时使动画冻结。 使用 InvalidateRect() 后跟 UpdateWindow(),这将强制立即重绘 window 的指定区域。

但这不仅仅是解决问题。忘记你的计时器,使用 non-blocking 线程代替 InvalidateRect()UpdateWindow().

int animation_duration = 0;

void GDIHelper::TheAnimation() { //This function should be static.
    if(m_bIsPlaying == TRUE) {
        return;
    }
    
    m_iCurrentFrame = 0;
    GUID Guid = FrameDimensionTime;
    m_pImage->SelectActiveFrame(&Guid, m_iCurrentFrame);
    ++m_iCurrentFrame;
    m_bIsPlaying = TRUE;
    animation_duration = ((UINT*)m_pItem[0].value)[m_iCurrentFrame] * 10;
    
    while(isPlayable) { //Make sure to set isPlayable to false on destroy to stop this thread.
        std::this_thread::sleep_for(std::chrono::milliseconds(animation_duration)); //instead of timer, use sleep.
        
        m_pImage->SelectActiveFrame(&Guid, m_iCurrentFrame);
        m_iCurrentFrame = (++m_iCurrentFrame) % m_FrameCount;
        
        InvalidateRect(staticControl, NULL, FALSE);
        UpdateWindow(staticControl); //perform immediate redrawing
    }                                                                                               
}

LRESULT CALLBACK GDIHelper::StaticControlProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData) {
    switch(uMsg) {
        case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hwnd, &ps);
           
            Graphics g(hdc);
            g.DrawImage(m_pImage, 0, 0, width, height); //draw your image.

            EndPaint(hwnd, &ps);
            return TRUE; 
        }
        ....
    }
    return DefSubclassProc(hwnd, uMsg, wParam, lParam);
}

然后启动线程并使用detach() for non-blocking UI.

std::thread t(TheAnimation);
t.detach(); // this will be non-blocking thread.

阅读代码中的注释以获得一些说明,并且不要忘记在销毁时将 isPlayable 设置为 false 以停止线程。