65.9K
CodeProject 正在变化。 阅读更多。
Home

一个简单的图像预览类,使用 GDI+

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.50/5 (27投票s)

2004 年 3 月 6 日

CPOL

4分钟阅读

viewsIcon

143662

downloadIcon

5605

结合使用 GDI+ 和所有者绘制的静态窗口。

引言

在我正在进行的一个项目中,我需要能够在预览窗口中显示各种图像格式。我希望能够处理所有常见的图像格式,如位图、JPEG、GIF 和 PNG。经过一番搜索,没有找到完全符合我要求的,于是我决定自己动手做一个。

基础

这个任务可以分解为两个子任务。第一个任务是从某个源(如文件)加载图像,并将其解码为 Windows 可以处理的格式。然后是简单的部分,将图像渲染到显示器上。

轻松完成任务 1

从文件加载图像的常用方法是找到一个处理你感兴趣的图像格式的库(商业或开源),然后将其集成到你的程序中。我以前做过,我相信我们大多数人在某个时候都这样做过。这类任务的出现频率证明了 这篇文章 [^](Davide Pizzolato 的 **CxImage**)的受欢迎程度。

在浏览 CodeProject 并考虑再次尝试使用外部库来处理图像时,我发现了 这篇文章 [^](Christian Graus 的 **Starting with GDI+**)。在阅读了这篇文章并查阅了 GDI+ 的 MSDN 文档后,我意识到这是任务 1 的完美解决方案。(Mike Dunn 的 C++ FAQ 中的提及也起到了帮助作用)。

任务 2

这要容易得多。我发现最好的方法是从 CStatic 类派生一个类,使其成为 ownerdraw,然后将位图渲染到 static 控件中。

整合所有内容

class CImagePreviewStatic : public CStatic
{
    DECLARE_DYNAMIC(CImagePreviewStatic)
public:
                    CImagePreviewStatic();
    virtual         ~CImagePreviewStatic();

    virtual BOOL    Create();
    virtual void    DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct);

    void            SetFilename(LPCTSTR szFilename);

protected:
    WCHAR           m_wsFilename[_MAX_PATH];
    Image           *m_img;       //  GDI+ object
    Graphics        *m_graphics;  //  GDI+ object

    DECLARE_MESSAGE_MAP()
};
构造函数将 ImageGraphics 指针(这两个都是 GDI+ 对象)设置为 NULL。析构函数会删除这些指针。GDI+ 对象在 Create() 函数中创建。
BOOL CImagePreviewStatic::Create()
{
    if (GetSafeHwnd() != HWND(NULL))
    {
        m_img = new Image(m_wsFilename);
        m_graphics = new Graphics(GetSafeHwnd());
        return TRUE;
    }

    return FALSE;
}
代码非常简单。m_img 指针使用一个 Image 对象进行初始化,该对象是通过你要加载的图像的文件名创建的。m_graphics 指针使用一个 Graphics 对象进行初始化,该对象与 CStatic 的底层窗口关联。

请注意,Image 构造函数需要一个 Unicode 字符串。在 ANSI 生成中,SetFilename() 函数使用一些辅助宏将 ANSI 文件名转换为 Unicode 字符串。

void CImagePreviewStatic::SetFilename(LPCTSTR szFilename)
{
#ifndef _UNICODE
    USES_CONVERSION;
#endif

    ASSERT(szFilename);
    ASSERT(AfxIsValidString(szFilename));

    TRACE("%s\n", szFilename);

#ifndef _UNICODE
    wcscpy(m_wsFilename, A2W(szFilename));
#else
    wcscpy(m_wsFilename, szFilename);
#endif

    delete m_img;
    m_img = new Image(m_wsFilename, FALSE);
    Invalidate();
}
一旦完成字符串转换(如果需要),我们就删除现有的 Image 指针,并使用新文件名创建一个新的指针。然后我们调用 Invalidate() 窗口,让 Windows 在将来某个时候给我们发送一个绘制消息。

当 Windows 最终要求我们重绘时,ownerdraw 逻辑就会生效。

void CImagePreviewStatic::DrawItem(LPDRAWITEMSTRUCT /*lpDrawItemStruct*/)
{
    Unit  units;
    CRect rect;

    if (m_img != NULL)
    {
        GetClientRect(&rect);

        RectF destRect(REAL(rect.left), 
                       REAL(rect.top), 
                       REAL(rect.Width()),
                       REAL(rect.Height())),
              srcRect;
        m_img->GetBounds(&srcRect, &units);
        m_graphics->DrawImage(m_img, destRect, srcRect.X, srcRect.Y, srcRect.Width, 
                              srcRect.Height, UnitPixel, NULL);
    }
}
这段代码获取底层窗口的边界矩形,并创建一个指定相同坐标的 RectF 对象。(后缀 F 表示每个元素都是 REAL 而不是 int)。然后我们获取图像本身的边界,并调用 DrawImage() 函数将图像绘制到窗口上。我使用的特定 DrawImage() 重载是将图像缩放到绘制矩形。代码非常简单!

使用代码

你可能想在 stdafx.h 文件的末尾添加这三行。
#include <gdiplus.h>
using namespace Gdiplus;
#pragma comment(lib, "gdiplus.lib")
这些行包含了 GDI+ 的头文件,并设置了 Gdiplus 命名空间。这将节省一些输入,因为你不需要在每个 GDI+ 引用前都加上 Gdiplus::。第三行将 gdiplus.lib 库的引用插入到包含它的任何对象文件中;这可以避免你必须在项目工作空间中显式添加该库。

接下来,你的应用程序必须在任何其他 GDI+ 函数之前初始化 GDI+ 库,并在退出前将其关闭。这可以通过你应用程序中的这段代码来完成。CMyApp::InitInstance() 是一个好地方。

.
.
.

// Initialize GDI+
GdiplusStartupInput gdiplusStartupInput;
GdiplusStartup(&m_gdiplusToken, &gdiplusStartupInput, NULL);

.
.
.
m_gdiplusToken 是一个 unsigned long,GDI+ 稍后在你想关闭它时使用。你的 CMyApp::ExitInstance() 将包含
GdiplusShutdown(m_gdiplusToken);
一旦你初始化了 GDI+,你就可以使用所有其他的 GDI+ 功能了。

要使用 CImagePreviewStatic,你需要将一个 static 窗口添加到你的对话框或视图中。将该窗口绑定到 CImagePreviewStatic 对象。确保 static 窗口设置了 SS_OWNERDRAW 样式。一旦将窗口绑定到 CImagePreviewStatic 对象,就调用 Create() 函数,然后设置你要预览的图像文件名。例如:

class CImageDlg : public CDialog
{
    DECLARE_DYNAMIC(CImageDlg)

    enum adviseMessages
    {
        adviseUpdatePreview,
    };
public:
                    CImageDlg();
    virtual         ~CImageDlg();

    virtual void    DoDataExchange(CDataExchange *pDX);

protected:
    CImagePreviewStatic m_preview;


    DECLARE_MESSAGE_MAP()
    virtual BOOL    OnInitDialog();

public:
};
DoDataExchange() 函数中,我们使用 DDXm_preview 成员绑定到我们对话框模板上的一个 static 控件。
void CImageDlg::DoDataExchange(CDataExchange *pDX)
{
    CDialog::DoDataExchange(pDX);
    DDX_Control(pDX, IDC_IMAGEPREVIEW, m_preview);
}
然后,在我们的 OnInitDialog() 中,我们执行:
BOOL CImageDlg::OnInitDialog()
{
    CDialog::OnInitDialog();

    m_preview.Create();
    return TRUE;
}
完成这一切后,我们的 CImagePreviewStatic 控件就初始化好了,可以显示图像了。我们所要做的就是调用 SetFilename() 函数,Windows 中的 ownerdraw 机制就会处理所有剩余的事情。

演示程序是演示该控件所必需的最小实现。它有一个硬编码的图像名称,并且期望在程序的当前目录中找到该图像。

历史

2004 年 3 月 6 日 - 初始版本。

© . All rights reserved.