开发者

How would I animate a window with the Win32 Api?

How would you go about painting something onto a window in regular intervals.

I have come up with the this (stripped quite a bit for clarity)

#include <windows.h>

void DrawOntoDC (HDC dc) {

     pen    = CreatePen(...)
     penOld = SelectObject(dc, pen)

     ..... Here is the actual drawing, that
     ..... should be regurarly called, since
     ..... the drawn picture changes as time
     ..... progresses

     SelectObject(dc, pen_old);

     DeleteObject(pen);
}



LRESULT CALLBACK WindowProc(....) {
    switch(Msg) {

    case WM_PAINT: {
         PAINTSTRUCT ps;
           dc = BeginPaint(hWnd, &ps);

         .....   A Memory DC is created
         .....   In order to prevent flickering.

         HBITMAP PersistenceBitmap;
         PersistenceBitmap = CreateCompatibleBitmap(dc, windowHeight, windowHeight);

         HDC     dcMemory =  CreateCompatibleDC(dc);
      开发者_JAVA百科   HBITMAP oldBmp = (HBITMAP) SelectObject(dcMemory, PersistenceBitmap);

         DrawOntoDC(dcMemory);

         ..... "copying" the memory dc in one go unto dhe window dc:

         BitBlt ( dc, 
                  0, 0, windowWidth, windowHeight,
                  dcMemory,
                  0, 0,
                  SRCCOPY
                );

         ..... destroy the allocated bitmap and memory DC
         ..... I have the feeling that this could be implemented
         ..... better, i.e. without allocating and destroying the memroy dc
         ..... and bitmap with each WM_PAINT.

         SelectObject(dcMemory, oldBmp);
         DeleteDC(dcMemory);
         DeleteObject(PersistenceBitmap);

     EndPaint  (hWnd, &ps);
         return 0;
    }
    default:
        return DefWindowProc(hWnd, Msg, wParam, lParam);
    }
}


DWORD WINAPI Timer(LPVOID p) {

  ..... The 'thread' that makes sure that the window
  ..... is regularly painted.

  HWND hWnd = (HWND) *((HWND*) p);

  while (1) {
     Sleep(1000/framesPerSecond);
     InvalidateRect(hWnd, 0, TRUE);
  }
}


int APIENTRY WinMain(...) {

    WNDCLASSEX windowClass;
       windowClass.lpfnWndProc         = WindowProc;
       windowClass.lpszClassName       = className;
       ....

    RegisterClassEx(&windowClass);

    HWND hwnd = CreateWindowEx(
                ....
                 className,
                 ....);


    ShowWindow(hwnd, SW_SHOW);
    UpdateWindow(hwnd);

    DWORD threadId;
    HANDLE hTimer  = CreateThread(
      0, 0,
      Timer,
     (LPVOID) &hwnd,
     0, &threadId );

    while( GetMessage(&Msg, NULL, 0, 0) ) {
       ....
    }

    return Msg.wParam;
}

I guess there's a lot that could be improved and I'd appreciate any pointer to things I have overlooked.


Doing this kind of thing with a worker thread is not optimal. Given that the optimal code path for painting is always via a WM_PAINT that leaves two ways to do this:

  1. Simply create a timer on the GUI thread, post WM_TIMER messages to a timerproc, or the window directly, and invoke the OnTick() part of your engine. IF any sprites move, they invalidate their area using InvalidateRect() and windows follows up by automatically posting a WM_PAINT. This has the advantage of having a very low CPU usage if the game is relatively idle.

  2. Most games want stricter timing that can be achieved using a low priority WM_TIMER based timer. In that case, you implement a game loop something like this:

Message Loop:

while(stillRunning)
{
  DWORD ret = MsgWaitForMultipleObjects(0,NULL,FALSE,frameIntervalMs,QS_ALLEVENTS);
  if(ret == WAIT_OBJECT_0){
    while(PeekMessage(&msg,0,0,0,PM_REMOVE)){
      TranslateMessage(&msg);
      DispatchMessage(&msg);
  }
  if(TickGame()) // if TickGame indicates that enough time passed for stuff to change
    RedrawWindow(hwndGame,...); // Dispatch a WM_PAINT immediately.
}

The danger with this kind of message loop is, if the ... application goes into any kind of modal state :- the user starts to drag the window / a modal dialog box pops up, then messages are being pumped by the modal loop, so the animation stops. As a result you need to have a fallback timer if you need to mix a high performance message loop with modal operations.


WRT your WM_PAINT implementation - its usually better to (re)create your backbuffer in response to WM_SIZE messages. That way its always the right size, and you don't incurr the rather large cost of recreating a large memory buffer many times per second.


Borrowing bits and pieces from various places, I came up with the following approach for my similar problem. The following is a test application to test the concept.

In this test application I am using an MFC static window which I am updating with a text string by periodically calling the ::SetWindowText() function with the MFC static window's handle. This works fine to display a marching right angle bracket to demonstrate that the animation works.

In the future I intend on using a memory resident bitmap image which is modified in the animation loop which is then posted into a bitmap attached to the static text window. This technique allows for an animated bitmap to be presented with a more elegant indication of something in progress.

Testing this concept was done with an MFC dialog application that contains two static windows for the progress indicators and two additional buttons, Start and Stop, to start and stop the progress indicators. The goal is that when the Start button is pressed a series of greater than signs are written across the static window and then cleared and then once more started. So the animation looks like an LED sign that has arrows marching across from left to right in a horizontal marque.

These two buttons do nothing more than set an indicator in the animation object to be either one (on) or zero (off). The animation object thread which does the actual animation just reads from the m_state variable and does not modify it.

The timer delay quantity is hardcoded for the purposes of this test. It could easily be a parameter.

The dialog is still responsive and even as it is updating I can display the default About Box for the dialog application and move the About Box around. I can also drag the dialog application itself around screen (without the About Box displayed as that is a modal dialog) with the static window still updating.

How would I animate a window with the Win32 Api?

The Animation class source

The source code for the animation logic is a simple class that starts a thread which then updates the specified dialog control. While the animation loop is a static method of the class, the data used by the loop is specified in the object itself so multiple animations can be done with different objects using the same static loop.

This approach is fairly straightforward and simple. Rather than using a more sophisticated approach with a thread pool and a timer pool, it dedicates a thread and a timer object to a single animation. It appears obvious that this approach will not scale well however for an application with a couple of animations, it works well enough.

class AnimatedImage
{
    UINT     m_state;          // current on/off state of the animation. if off (0) then the window is not updated
    UINT     m_itemId;         // control identifier of the window that we are going to be updating.
    HWND     m_targetHwnd;     // window handle of the parent dialog of the window we are going to be updating
    UINT     m_i;              // position for the next right angle bracket
    wchar_t  m_buffer[32];     // text buffer containing zero or more angle brackets which we use to update the window
    DWORD    m_lastError;      // result of GetLastError() in case of an error.
    HANDLE   m_hTimer;         // handle for the timer
    HANDLE   m_hThread;        // handle for the thread created.
    LARGE_INTEGER m_liDueTime; // time delay between updates

public:
    AnimatedImage(UINT itemId = 0, HWND hWnd = NULL) : m_state(0), m_itemId(itemId), m_targetHwnd(hWnd), m_i(0), m_lastError(0), m_hTimer(NULL), m_hThread(NULL) { memset(m_buffer, 0, sizeof(m_buffer))             ; }
    ~AnimatedImage() { Kill();  CloseHandle(m_hTimer);  CloseHandle(m_hThread); }    // clean up the timer and thread handle.

    static unsigned __stdcall loop(AnimatedImage *p);    // animation processing loop
    void Run();        // starts the animation thread
    void Start();      // starts the animation
    void Stop();       // stops the animation
    void Kill();       // indicates the thread is to exit.

    // Functions used to get the target animation window handle
    // and to set the parent window handle and the dialog control identifier.
    // This could be simpler by just requiring the target animation window handle
    // and letting the user do the GetDlgItem() function themselves.
    // That approach would make this class more flexible.
    HWND GetImageWindow() { return ::GetDlgItem(m_targetHwnd, m_itemId); }
    void SetImageWindow(UINT itemId, HWND hWnd) { m_itemId = itemId; m_targetHwnd = hWnd; }
};

unsigned __stdcall AnimatedImage::loop(AnimatedImage *p)
{
    p->m_liDueTime.QuadPart = -10000000LL;

    // Create an unnamed waitable timer. We use this approach because
    // it makes for a more dependable timing source than if we used WM_TIMER
    // or other messages. The timer resolution is much better where as with
    // WM_TIMER is may be no better than 50 milliseconds and the reliability
    // of getting the messages regularly can vary since WM_TIMER are lower
    // in priority than other messages such as mouse messages.
    p->m_hTimer = CreateWaitableTimer(NULL, TRUE, NULL);
    if (NULL == p->m_hTimer)
    {
        return 1;
    }

    for (; ; )
    {
        // Set a timer to wait for the specified time period.
        if (!SetWaitableTimer(p->m_hTimer, &p->m_liDueTime, 0, NULL, NULL, 0))
        {
            p->m_lastError = GetLastError();
            return 2;
        }

        // Wait for the timer.
        if (WaitForSingleObject(p->m_hTimer, INFINITE) != WAIT_OBJECT_0) {
            p->m_lastError = GetLastError();
            return 3;
        }
        else {
            if (p->m_state < 1) {
                p->m_i = 0;
                memset(p->m_buffer, 0, sizeof(m_buffer));
                ::SetWindowText(p->GetImageWindow(), p->m_buffer);
            }
            else if (p->m_state < 2) {
                // if we are updating the window then lets add another angle bracket
                // to our text buffer and use SetWindowText() to put it into the
                // window area.
                p->m_buffer[p->m_i++] = L'>';

                ::SetWindowText(p->GetImageWindow(), p->m_buffer);
                p->m_i %= 6;      // for this demo just do a max of 6 brackets before we reset.
                if (p->m_i == 0) {
                    // lets reset our buffer so that the next time through we will start
                    // over in position zero (first position) with our angle bracket.
                    memset(p->m_buffer, 0, sizeof(m_buffer));
                }
            }
            else {
                // we need to exit our thread so break from the loop and return.
                break;
            }
        }
    }

    return 0;
}
void AnimatedImage::Run()
{
    m_hThread = (HANDLE)_beginthreadex(NULL, 0, (_beginthreadex_proc_type)&AnimatedImage::loop, this, 0, NULL);
}

void AnimatedImage::Start()
{
    m_state = 1;
}

void AnimatedImage::Stop()
{
    m_state = 0;
}

void AnimatedImage::Kill()
{
m_state = 3;
}

How the class is used

For this simple test dialog application, we just create a couple of global objects for our two animations.

AnimatedImage xxx;
AnimatedImage xx2;

In the OnInitDialog() method of the dialog application the animations are initialized before returning.

// TODO: Add extra initialization here

xxx.SetImageWindow(IDC_IMAGE1, this->m_hWnd);
xxx.Run();
xx2.SetImageWindow(IDC_IMAGE2, this->m_hWnd);
xx2.Run();
return TRUE;  // return TRUE  unless you set the focus to a control

There are two button click handlers which handle a click on either the Start button or the Stop button.

void CMFCApplication2Dlg::OnBnClickedButton1()
{
    // TODO: Add your control notification handler code here

    xxx.Start();
    xx2.Start();
}


void CMFCApplication2Dlg::OnBnClickedButton2()
{
    // TODO: Add your control notification handler code here

    xxx.Stop();
    xx2.Stop();
}

The dialog application main dialog resource is defined as follows in the resource file.

IDD_MFCAPPLICATION2_DIALOG DIALOGEX 0, 0, 320, 200
STYLE DS_SETFONT | DS_FIXEDSYS | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME
EXSTYLE WS_EX_APPWINDOW
FONT 8, "MS Shell Dlg", 0, 0, 0x1
BEGIN
    DEFPUSHBUTTON   "OK",IDOK,209,179,50,14
    PUSHBUTTON      "Cancel",IDCANCEL,263,179,50,14
    CTEXT           "TODO: Place dialog controls here.",IDC_STATIC,10,96,300,8
    LTEXT           "Static",IDC_IMAGE1,7,7,110,21
    LTEXT           "Static",IDC_IMAGE2,64,43,112,27
    PUSHBUTTON      "Start",IDC_BUTTON1,252,16,50,19
    PUSHBUTTON      "Stop",IDC_BUTTON2,248,50,57,21
END
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜