用于 MFC 的 SG_PNG 自定义控件





5.00/5 (9投票s)
SG_PNG 是一个用于显示带透明度 PNG 资源的自定义 MFC 控件
Github 仓库: https://github.com/securedglobe/SG_PNG
引言
在为 索尼 进行一个持续项目时,我们 需要开发一个现代化且外观精美的应用程序。在这种情况下,通常会设计和集成一些图形。我主要使用 PNG 图像,因为它们支持透明背景。请参阅我在 Stackoverflow 上的回答。
PNG 图像允许元素与背景无缝融合,从而为用户界面带来更精致、更专业的观感。
然而,在 MFC 中显示这些带有透明度的 PNG 图像可能很具挑战性。在本篇文章中介绍了一种带有 PNG 图像的 MFC 按钮,但我找不到任何能够显示 PNG 并保持其透明度的图片或静态控件。
因此,我开发了 **SG_PNGView**,这是一个自定义控件,通过使开发人员能够在 MFC 对话框上轻松显示带透明度的 PNG 图像来解决此问题。
让我们从一些基本概念开始...
GDI+
GDI+(图形设备接口增强版)是 Microsoft 提供的一个强大的图形库,用于渲染 2D 图形。它提供了一套全面的函数和类,用于在 Windows 应用程序中创建、操作和显示图形图像和文本。GDI+ 在底层图形硬件和操作系统之上提供了一个高级抽象,使开发人员能够轻松创建视觉上吸引人且交互式强的用户界面。
GDI+ 非常适合处理图形对象,例如通过相应的类 画笔、画刷、字体、图像 和 路径,允许开发人员执行各种操作,例如绘制线条、形状和文本、用颜色或渐变填充区域、转换图形对象以及处理不同格式的图像。
**SG_PNGView** 使用 GDI+ 在控件内加载和显示 PNG 图像。GDI+ 中的 Bitmap 类用于表示 PNG 图像,并使用其方法执行诸如从资源加载图像、在绘制时将图像绘制到设备上下文以及处理图像加载过程中的错误等操作。GDI+ 也被用于高效地操作和渲染图像的功能,确保 **SG_PNGView** 控件具有流畅且高质量的图形输出。在开发过程中,我将静态控件的 Sizing type(尺寸类型)设置为 Vertical(垂直),因此可以看到图像如何实时拉伸。
总而言之,GDI+ 是一个通用且必不可少的工具,开发人员可以使用它在 Windows 应用程序中创建丰富且视觉吸引人的图形用户界面,提供广泛的功能,可以轻松高效地处理 2D 图形。
神奇之处
SetPNGImage()
首先,调用 `SetPNGImage()`,它将 PNG 资源与静态控件关联起来。
调用该函数后,它会从指定的资源 ID 加载 PNG 图像,并使用加载的图像初始化控件。
// Loads a PNG image from the specified resource ID
Status SG_PNGView::SetPNGImage(UINT nIDResource)
{
// Get the instance handle of the application
HINSTANCE hInstance = AfxGetInstanceHandle();
// Find the specified resource in the application's executable file
HRSRC hResource = ::FindResource(hInstance, MAKEINTRESOURCE(nIDResource), _T("PNG"));
if (!hResource)
{
return GenericError; // Resource not found
}
// Get the size of the resource
DWORD imageSize = ::SizeofResource(hInstance, hResource);
// Get a pointer to the resource data
const void* pResourceData = ::LockResource(::LoadResource(hInstance, hResource));
if (!pResourceData)
{
return OutOfMemory; // Failed to lock resource
}
// Allocate global memory to hold the resource data
HGLOBAL hBuffer = ::GlobalAlloc(GMEM_MOVEABLE, imageSize);
if (!hBuffer)
{
return OutOfMemory; // Memory allocation failed
}
// Lock the allocated memory and copy the resource data into it
void* pBuffer = ::GlobalLock(hBuffer);
if (!pBuffer)
{
::GlobalFree(hBuffer); // Failed to lock memory, free the buffer
return OutOfMemory;
}
CopyMemory(pBuffer, pResourceData, imageSize);
// Create an IStream object from the allocated memory
IStream* pStream = NULL;
if (::CreateStreamOnHGlobal(hBuffer, FALSE, &pStream) != S_OK)
{
::GlobalUnlock(hBuffer);
::GlobalFree(hBuffer);
return GenericError; // Failed to create stream
}
// Delete the previous bitmap if it exists
delete m_pBitmap;
// Create a GDI+ Bitmap object from the stream
m_pBitmap = Bitmap::FromStream(pStream);
// Release the IStream object
pStream->Release();
// Unlock and free the allocated memory
::GlobalUnlock(hBuffer);
::GlobalFree(hBuffer);
// Check if the bitmap was created successfully
if (m_pBitmap == NULL)
{
return OutOfMemory; // Failed to create bitmap
}
Status status = m_pBitmap->GetLastStatus();
if (status != Ok)
{
delete m_pBitmap;
m_pBitmap = NULL;
}
return status;
}
函数首先获取应用程序的实例句柄,并在应用程序的可执行文件中查找指定的 PNG 资源。
如果未找到资源,函数将返回 GenericError
状态。
接下来,函数检索资源的大小,并分配全局内存来存储资源数据。如果内存分配失败,函数将返回 OutOfMemory
状态。
随后,函数从分配的内存创建一个 IStream
对象,并使用它创建一个代表 PNG 图像的 GDI+ Bitmap 对象。
如果位图创建失败,函数将返回 OutOfMemory
状态。
最后,函数检查位图创建过程的状态,如果发生任何错误,它会清理资源并返回相应的状态。
否则,它返回指示操作成功的状态。总的来说,SetPNGImage
函数提供了一个全面的机制,用于将 PNG 图像加载到 SG_PNGView 控件中,确保了健壮的错误处理和可靠的初始化。
处理 WM_PAINT 事件
在使用 **SG_PNGView** 控件时,使用 SetPNGImage()
加载的任何图像都会在其内部的 OnPaint()
事件处理程序中绘制。
每当控件需要在屏幕上重绘时,都会调用此函数。
void SG_PNGView::OnPaint()
{
CPaintDC dc(this); // device context for painting
CRect rect;
GetClientRect(&rect);
if (m_pBitmap != nullptr)
{
Graphics graphics(dc.GetSafeHdc());
// Get the dimensions of the image
int imageWidth = m_pBitmap->GetWidth();
int imageHeight = m_pBitmap->GetHeight();
// Calculate the scaling factors to fit the image inside the control
float scaleX = static_cast<float>(rect.Width()) / imageWidth;
float scaleY = static_cast<float>(rect.Height()) / imageHeight;
float scale = min(scaleX, scaleY); // Use the minimum scaling factor to preserve aspect ratio
// Calculate the dimensions of the scaled image
int scaledWidth = static_cast<int>(imageWidth * scale);
int scaledHeight = static_cast<int>(imageHeight * scale);
// Calculate the position to center the scaled image within the control
int xPos = (rect.Width() - scaledWidth) / 2;
int yPos = (rect.Height() - scaledHeight) / 2;
// Draw the scaled image
graphics.DrawImage(m_pBitmap, xPos, yPos, scaledWidth, scaledHeight);
}
}
在此函数中,控件获取用于绘制的设备上下文 (CPaintDC),并使用 GetClientRect()
获取客户区尺寸。
如果控件中已加载有效的 PNG 图像,它将使用 GDI+ 库将图像绘制到设备上下文中。绘制过程包括计算适当的缩放因子,以确保图像比例适当地适应控件的边界,同时保持其纵横比。
然后,使用 Graphics::DrawImage()
方法将缩放后的图像绘制到设备上下文中,确保图像在控件中居中显示。此函数对于确保 PNG 图像在 SG_PNGView 控件中正确显示至关重要,能够动态适应控件大小的变化,并为用户提供视觉上吸引人的图像呈现。
在您自己的程序中使用代码
我的类的想法是让用户使用起来无烦恼。这是通过要求用户执行最少的步骤来实现的
- 将 SG_PNGView.cpp 和 SG_PNGView.h 文件添加到您的项目中。
- 在您想使用该控件的源文件中包含 SG_PNGView.h 头文件。
#include "SG_PNGView.h"
- 在您的对话框资源中添加一个静态控件,并将其类设置为
SG_PNGView
。SG_PNGView m_pngView; m_pngView.Create(NULL, _T("SG_PNGView Control"), WS_CHILD | WS_VISIBLE, CRect(10, 10, 200, 200), this);
- 使用 **SG_PNGView** 控件的
SetPNGImage
方法显示 PNG 图像。图像将拉伸以适应静态控件的边界,因此如果您调整控件的大小(即使在运行时),图像也会相应地拉伸。m_pngView.SetPNGImage(IDB_PNG_IMAGE);