单击或拖动标题栏时如何防止计时器冻结?
How to prevent Timer from freezing when titlebar is clicked or dragging?
我有一个定时器,负责逐帧显示GIF图片。我注意到当我 right-clicked
和 hold
时 titilebar
计时器暂停我认为 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
以停止线程。
我有一个定时器,负责逐帧显示GIF图片。我注意到当我 right-clicked
和 hold
时 titilebar
计时器暂停我认为 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
以停止线程。