C++ GDI动画不起作用

3

我正在尝试使用GDI创建球体动画,但无法使其正常工作。 我使用以下代码创建了一个球:

   Graphics graphics(hdc);

Pen pen(Color(255, 0, 0, 255)); graphics.DrawEllipse(&pen, sf , 0, 10, 10); 我有一个while循环,它循环并将1添加到sf值,就像这样sf++; 然后我尝试重新绘制窗口(它不起作用),所以我最终会得到多个圆圈;/ 这是循环(循环为int WM_PAINT)

while(sd==1)//sd equals 1
    {
        sf++;
        onPaint(hdc);
        InvalidateRect (hWnd, NULL, TRUE);// this should repaint the window but it doesn't
        UpdateWindow(hWnd);
    }

感谢您的预先帮助。 Rami
4个回答

5

为了实现动画效果,我建议您使用计时器。例如:

int OnCreate(HWND window, WPARAM wParam, LPARAM lParam)
{
   SetTimer(window, TIMER_ID, 1000, 0);
   return 0;
}

现在窗口将每秒(1000毫秒)接收到WM_TIMER消息。您应该处理它们:

int OnTimer(HWND window, WPARAM wParam, LPARAM lParam)
{
   if(TIMER_ID == wParam)
   {
      /*do stuff*/
      InvalidateRect(window, NULL, TRUE);//force window to repaint
   }
   return 0;
}

那么您需要处理WM_PAINT消息来进行绘图。

int OnPaint(HWND window, WPARAM wParam, LPARAM lParam)
{
   PAINTSTRUCT ps;
   HDC dc = BeginPaint(&ps);
   Graphics graphics(hdc);
   graphics.Draw...
   EndPaint(&ps);
   return 0;
}

嗯...它只画了一个圆圈,就这样了。 你可以在这里看到完整的代码 http://pastesite.com/18049 - Ramilol
@Ramiz,你应该在WM_CREATE消息上调用OnCreate,在WndProc中:--编辑:抱歉,请忽略我的评论,我没有注意到你已经调用了OnCreate。 - Tassos
你应该先清除背景,即:graphics.Clear(Color::White)。 - Tassos
你能帮我处理双缓冲吗? - Ramilol
创建一个与窗口大小相同的全局变量Bitmap。 在OnTimer中绘制该位图。 在OnPaint中调用graphics.DrawImage(bitmap, client_rect)。 为了避免闪烁,您还应该在wcex.hbrBackground中传递NULL_BRUSH和/或使用FALSE调用InvalidateRect。 - Tassos
显示剩余2条评论

2
你应该意识到,你在一个带有(sd == 1)条件的循环中递增sf变量,对吗?这将导致无限循环或者永远不会进入循环,因为sd的值没有任何改变。你是否使用过调试器?你为什么需要这样的循环呢?你不应该在循环中调用OnPaint。
如果你想要多个圆形,只需在函数返回之前绘制它们全部。维护一个数据集合,在OnPaint处理程序中用于绘制圆形。

我知道它会一直循环...这只是为了测试...我想做的就是每次while循环时画一个圆,圆的位置会改变,但实际上它并没有改变,它创建了多个具有不同位置的圆。例如,我创建了一个圆,x值为1,当while循环时,圆的x值现在为2,但它创建了一个新的x值为2的圆。 - Ramilol
我不想要多个圆,我只想要一个圆,它会改变位置,就像我用x = 5px,y = 10创建的圆一样。然后它将循环,然后将x更改为x = 6px,旧圆应该消失,新圆与x = 6应该被创建。 - Ramilol

0

我在 MSDN 上找到了一个示例,展示了如何使用纯 Win32 绘制东西。

在 WM_PAINT 中不应调用 Invalidate 或 Updatewindow,因为 UpdateWindow 会发送新的 WM_PAINT 事件,并使无效变得积累,直到下一个 wm_paint 事件。

您应该将代码分成两个函数,一个执行移动操作,另一个在当前位置绘制圆形。

您的 Mover 函数可以从任何地方调用(也许是定时器处理程序函数?)并应以此结束

InvalidateRect (hWnd, NULL, TRUE);
UpdateWindow(hWnd);

为了标记您的客户区域以进行重绘并通知窗口重新绘制。
您的Draw()函数应读取使用移动函数设置的位置,并在该位置周围绘制一个圆圈。
(附注:如果您想最小化闪烁并获得流畅的动画,请在基本动画运行后查看双缓冲)
更新
您的Update函数中缺少UpdateWindow命令。 仅当应用程序接收到WM_PAINT消息时,才会调用您的OnPaint函数,因此您需要发送这些消息。 UpdateWindow可以实现此目的。
VOID update(HDC hdc,HWND hWnd) 
{ 
    sf++; 
    FillRect(hdc,rect,(HBRUSH)(COLOR_WINDOW+1)); 
    InvalidateRect (hWnd, NULL, TRUE); 
    UpdateWindow(hWND);//<- This Line sends a wm_paint-message to your window in order to make it redraw itself
} 
//i didn't do any changes to the onPaint functon but here is the code for it 
VOID onPaint(HDC hdc) 
{ 
    Graphics graphics(hdc); 
    Pen pen(Color(255, 0, 0, 255)); 
    graphics.DrawEllipse(&pen, sf , 0, 10, 10); 
} 

//here is the while loop 
while(sd==1) 
{   onPaint(hdc); 
    update(hdc,hWnd); 
} 

我创建了一个新函数VOID update(HDC hdc,HWND hWnd) { sf ++; FillRect(hdc,rect,(HBRUSH)(COLOR_WINDOW + 1)); InvalidateRect(hWnd,NULL,TRUE); }我没有对onPaint函数做任何更改,但下面是它的代码VOID onPaint(HDC hdc) { Graphics graphics(hdc); Pen pen(Color(255,0,0,255)); graphics.DrawEllipse(&pen,sf,0,10,10); }这是while循环while(sd == 1) { onPaint(hdc); update(hdc,hWnd); }现在我只创造了一个圆圈,有什么想法吗? - Ramilol

0

InvalidateRect会发送WM_ERASEBKGND消息,如果在创建窗口时没有定义WNDCLASS结构的hbrBackground成员(用于重新绘制背景的画刷),它就不会重绘背景,除非你自己处理WM_ERASEBKGND消息。

如果这不是问题的原因,那么可能是因为你直接调用UpdateWindow而不是轮询和处理消息,导致WM_ERASEBKGND消息无法被处理。尝试在绘制新圆之前用背景颜色覆盖先前的圆。

或者使用SendMessage发送WM_ERASEBKGRND消息。


我已经定义了这个画笔: hbrBackground = (HBRUSH)(COLOR_WINDOW+1); 即使我删除更新也没有帮助。 - Ramilol
删除UpdateWindow不会有任何作用,因为你在一个紧密的循环中,它显示的唯一原因是你调用了UpdateWindow,它直接调用了你的消息处理程序。正如我所说,使用WM_ERASEBKGND作为消息调用SendMessage(也直接调用您的消息处理程序)。InvalidateRect只会发送消息,并期望您轮询消息队列以处理它们。 - Dominique McDonnell
好的,我会有 WM_ERASEBKGND,但是里面应该放什么呢? case WM_ERASEBKGND: //这里应该放什么 break; 我需要在循环中更改任何内容吗? 以下是完整代码: http://pastesite.com/18047 - Ramilol

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接