GDI+Visual C++ 7.1Visual C++ 7.0Visual Studio .NET 2003Windows 2003Windows 2000Visual C++ 6.0Windows XPMFCIntermediateDevVisual StudioWindowsC++
一个简单的图像预览类,使用 GDI+






4.50/5 (27投票s)
结合使用 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()
};
构造函数将 Image
和 Graphics
指针(这两个都是 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()
函数中,我们使用 DDX
将 m_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 日 - 初始版本。