【问题标题】:How to display animated GIF in Static Control?如何在静态控件中显示动画 GIF?
【发布时间】:2021-06-05 23:42:58
【问题描述】:

我正在尝试显示动画 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, 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, 124, hWnd, NULL, NULL, 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);
}

这里是负责动画 GIF 的 GDIHelper.cpp 类的函数。

#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, UINT_PTR timer_id, 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, NULL, 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, m_iCurrentFrame);
        SetTimer(hwnd, 120, ((UINT*)m_pItem[0].value)[m_iCurrentFrame] * 10, NULL);
        ++m_iCurrentFrame;
        InvalidateRect(hwnd, NULL, FALSE);
        m_bIsPlaying = TRUE;
    }
}
/** Function to draw the image in Window, must be call on WM_PAINT. **/
void GDIHelper::DrawItem(HDC hdc, int xPosition, int yPosition, int width, 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 的单帧,看起来计时器不起作用(我不确定),因为它没有动画。

【问题讨论】:

  • 为什么你不确定定时器是否工作?你试过用调试器看看是不是?
  • 在调试器下运行代码您还发现了什么? isPlayable 设置了吗? newmalloc 的快乐组合是怎么回事?为什么有一个从未使用过的strGuid?为什么超过 1 帧的图像报告为 ERROR:?为什么LoadImageFromFile 会默默地忽略错误?为什么你相信你的评论Convert the string file_name to wstring?没有转换,该语句随机丢弃其输入。
  • 我不确定问题是否出在计时器上,因为它会出现什么问题?函数GDIHelper::OnTimer() WndProc 回调上被调用,但不是在静态控制回调上,这就是我不太确定的原因。这可能是一个逻辑错误,我不知道。
  • InitializeImage 函数上的 ERROR: 实际上不是错误,我正在尝试检查是否正在调用这些函数,但我不小心输入了 ERROR: 而不是 NOTICED:没有包括在该助手上编写的所有代码。我刚刚发布了我正在使用的功能。
  • WM_TIMER 消息发布到传递给SetTimer 调用的窗口。这是代码中静态控件的父窗口。

标签: c++ winapi visual-studio-2019 gdi+


【解决方案1】:

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

应该是的;

case WM_CREATE: {
    HWND staticcontrol = CreateWindowEx(0, L"STATIC", NULL, WS_CHILD | WS_VISIBLE, 150, 200, 124, 124, hWnd, NULL, NULL, 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;
}

【讨论】:

    猜你喜欢
    • 2017-11-10
    • 1970-01-01
    • 2011-02-09
    • 1970-01-01
    • 2019-01-04
    • 1970-01-01
    • 2016-06-06
    相关资源
    最近更新 更多