如何在静态控制中显示动画 GIF?

问题描述

我正在尝试显示动画 GIF,但我设法让它工作了。但是,我有一个问题,其他绘制的图像和文本受到计时器的影响,它们正在重新绘制。我尝试使用 FillRect() 解决它,但它们在闪烁。

所以我尝试使用这个 Static Control,您可以在其中创建子类并在其上绘画。

WNDPROC StaticWndProc = NULL;
GDIHelper gdiHelper;

LRESULT CALLBACK MyStaticWndProc2(HWND hwnd,UINT Message,WParaM wparam,LParaM lparam) { //Call back for static control.
    switch(Message) {
        case WM_TIMER:{
            gdiHelper.OnTimer(); // Do something on timer.
            return 0;
        }
        case WM_PAINT:{
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hwnd,&ps);
            gdiHelper.DrawItem(hdc,15,95,95); //draw the GIF image.
            EndPaint(hwnd,&ps);
            return TRUE;
        }
        case WM_DESTROY:{
            gdiHelper.Desrtroy();
            return 0;
        }
    }
    return CallWindowProc(StaticWndProc,hwnd,Message,wparam,lparam); //v2
}


LRESULT CALLBACK WndProc(HWND hWnd,UINT message,WParaM wParam,LParaM lParam) {
    switch(message) {
        case WM_CREATE: {
            HWND staticcontrol = CreateWindowEx(0,L"STATIC",NULL,WS_CHILD | WS_VISIBLE,150,200,124,hWnd,NULL); //create the static control.
            StaticWndProc = (WNDPROC)SetwindowLongPtr(staticcontrol,GWLP_WNDPROC,(LParaM)MyStaticWndProc2); //subclass the static control.

            gdiHelper.LoadImageFromFile(hWnd,ID_OF_YOUR_TIMER,"C:\\spinner.gif",L"GIF"); //load the GIF.
            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:{
            PostQuitMessage(0);
            break;
        }
    }
    return DefWindowProc(hWnd,message,wParam,lParam);
}

以下是 GDIHelper.cpp 类负责为 GIF 制作动画的函数

#include "GDIHelper.h"
/**
    GDIHelper.h has the following declarations;
    public:
        GDIHelper();
        void LoadImageFromresource(HWND hWnd,UINT_PTR timer_id,HMODULE hMod,const wchar_t* resid,const wchar_t* restype);
        void LoadImageFromFile(HWND hWnd,string file_location,const wchar_t* restype);
        void OnTimer();
        void Desrtroy();
        void Stop(UINT_PTR timer_id);
        void DrawItem(HDC hdc,int xPosition,int yPosition,int width,int height);
        
    private:
        void InitializeImage();
        bool IsFileExist(string file_name);
        void AnimateGIF();
        HWND hwnd;
        Image* m_pImage;
        GUID* m_pDimensionIDs;
        UINT m_FrameCount;
        PropertyItem* m_pItem;
        UINT m_iCurrentFrame;
        UINT_PTR timer_id;
        BOOL m_bIsPlaying;
        BOOL isPlayable;
**/

/** GDIHelper is a class helper to display images and animated GIF **/
GDIHelper::GDIHelper() {
    timer_id = 0;
    m_FrameCount = 0;
    m_iCurrentFrame = 0;
    m_pImage = NULL;
    m_pDimensionIDs = NULL;
    m_pItem = NULL;
    hwnd = NULL;
    m_bIsPlaying = FALSE;
    isPlayable = FALSE;
}

/** Function to destroy objects and arrays,call this function on WM_DESTROY of WinProc. **/
void GDIHelper::Desrtroy() {
    if(m_pDimensionIDs) {
        delete[] m_pDimensionIDs;
    }

    if(m_pItem) {
        free(m_pItem);
    }

    if(m_pImage) {
        delete m_pImage;
    }
}

/** Functon to load the next frame of GIF,must be call on WM_TIMER. **/
void GDIHelper::OnTimer() {
    if(isPlayable) {
        KillTimer(hwnd,timer_id);
        GUID Guid = FrameDimensionTime;
        m_pImage->SelectActiveFrame(&Guid,m_iCurrentFrame);
        SetTimer(hwnd,120,((UINT*)m_pItem[0].value)[m_iCurrentFrame] * 10,NULL);
        m_iCurrentFrame = (++m_iCurrentFrame) % m_FrameCount;
        InvalidateRect(hwnd,FALSE);
    }
}

/** Private function,call this to animate the GIF image,should be call before drawing the image usually on WM_PAINT. **/
void GDIHelper::AnimateGIF() {
    if(m_bIsPlaying == TRUE) {
        return;
    }
    if(isPlayable) {
        m_iCurrentFrame = 0;
        GUID Guid = FrameDimensionTime;
        m_pImage->SelectActiveFrame(&Guid,NULL);
        ++m_iCurrentFrame;
        InvalidateRect(hwnd,FALSE);
        m_bIsPlaying = TRUE;
    }
}
/** Function to draw the image in Window,must be call on WM_PAINT. **/
void GDIHelper::DrawItem(HDC hdc,int height) {
    AnimateGIF(); //This will only works if the image has more than one frame.
    Graphics g(hdc);
    g.DrawImage(m_pImage,xPosition,yPosition,width,height);
}


/** Private function,accessible only in this class,check if file exist. **/
bool GDIHelper::IsFileExist(string file_name) {
    struct stat buffer;
    return (stat(file_name.c_str(),&buffer) == 0);
}

/** Private function,function to count and get the frame of image. **/
void GDIHelper::InitializeImage() {
    UINT count = m_pImage->GetFrameDimensionsCount();
    m_pDimensionIDs = new GUID[count];
    m_pImage->GetFrameDimensionsList(m_pDimensionIDs,count);

    m_FrameCount = m_pImage->GetFrameCount(&m_pDimensionIDs[0]);

    if(m_FrameCount > 1) { //frame of GIF is more than one,all good,we don't want the error of `Access violation reading location`
        isPlayable = TRUE;
        OutputDebugString(_T("NOTICED: GDIHelper::InitializeImage >> Image file has more than 1 frame,its playable.\n"));
    }

    UINT TotalBuffer = m_pImage->GetPropertyItemSize(PropertyTagFrameDelay);
    m_pItem = (PropertyItem*)malloc(TotalBuffer);
    m_pImage->GetPropertyItem(PropertyTagFrameDelay,TotalBuffer,m_pItem);
}


/** Function to Load Image from Local File. **/
void GDIHelper::LoadImageFromFile(HWND hWnd,UINT_PTR ttimer_id,string file_name,const wchar_t* restype) {
    hwnd = hWnd;
    timer_id = ttimer_id;
    
    if(!IsFileExist(file_name)) {
        OutputDebugString(_T("ERROR: GDIHelper::LoadImageFromFile >> Invalid file or not exist\n"));
        return; 
    }
    std::wstring widestr = std::wstring(file_name.begin(),file_name.end()); // Convert the string file_name to wstring.
    m_pImage = Image::FromFile(widestr.c_str()); //Convert the wtring to wchar and initialize.
    InitializeImage(); //Initialize the image.
}

只绘制了 GIF 的单帧,看起来计时器不起作用(我不确定),因为它没有动画。

解决方法

您将错误的句柄传递给窗口。在您的 WndProc 回调中,您需要传递您创建的 hWnd 而不是 staticcontrol

应该是;

case WM_CREATE: {
    HWND staticcontrol = CreateWindowEx(0,L"STATIC",NULL,WS_CHILD | WS_VISIBLE,150,200,124,hWnd,NULL); //create the static control.
    StaticWndProc = (WNDPROC)SetWindowLongPtr(staticcontrol,GWLP_WNDPROC,(LPARAM)MyStaticWndProc2); //subclass the static control.
    
    /** Instead of `hWnd`,the handle of the parent Window,you need to use the handle window you created for static control which is `staticcontrol` **/
    gdiHelper.LoadImageFromFile(staticcontrol,ID_OF_YOUR_TIMER,"C:\\spinner.gif",L"GIF"); //load the GIF.
    break;
}

除了 WndProc 回调中的 WM_DESTROY 之外,您可能还想这样做;

case WM_DESTROY:{
    SetWindowLong(staticcontrol,GWL_WNDPROC,(LPARAM)MyStaticWndProc2);
    PostQuitMessage(0);
    break;
}