GDI+Visual Studio 6Visual C++ 7.0Visual Basic 6Windows 2000Visual C++ 6.0Windows XPMFC中级开发Visual StudioWindowsC++Visual Basic
在 ActiveX 控件中使用 GDI+(使用 MFC)






3.45/5 (5投票s)
2002年7月2日
5分钟阅读

198596

1611
一篇关于在 ActiveX 控件中使用 GDI+ 的文章。
为什么有人想在 ActiveX 控件中使用 GDI+?
GDI+ 提供了许多吸引人的图形功能,虽然使用 GDI 单独实现这些功能是可能的,但需要大量额外的工作。因此,将 GDI+ 与 ActiveX 控件结合,可以在相对较短的时间内创建视觉上吸引人、可移植、可重用的控件。值得一提的是,GDI+ 既可以在有窗口的 ActiveX 控件中,也可以在无窗口的 ActiveX 控件中工作。然而,在我看来,无窗口控件更能体现 GDI+ 在此场景下的价值。使用无窗口控件,您可以利用 GDI+ 内置的 Alpha 混合和抗锯齿功能,使非矩形控件无缝地融入其容器的窗口。
以下是一些示例:
-
圆角控件
使用 GDI 以低分辨率生成曲线会产生一个相当粗糙的对象。可以将其控件制作成位图图像,并将曲线的边缘进行抖动处理,使其与背景色融合。然而,这只在颜色不变且图像不调整大小时有效(否则会产生插值失真)。现在,有了 GDI+,同样的控件可以通过抗锯齿和无窗口激活,在任何背景、任何尺寸下使用,因为抗锯齿是在运行时发生的。
-
阴影/高光/发光效果
借助无窗口控件和 GDI+,可以轻松添加包含容器背景的半透明效果。例如,将一种深色与它后面的颜色混合,会产生阴影投射的错觉。Alpha 混合实现了这一点,因为无窗口控件能够半透明地填充其容器的背景。
需要什么才能做到这一点?
在撰写本文时,GDI+ 的库和头文件可以在 Microsoft Platform SDK 中找到。Code Project 上还有其他几篇文章介绍了如何获取和安装 GDI+,因此我将快速列出在 VC++6 中让一个简单应用程序运行起来所需的步骤。- 安装 Platform SDK
- 在 VC++6 中,转到“工具”->“选项”->“目录”,然后对于“包含文件”和“库文件”,将 Platform SDK 的路径(包含文件为 [install path]\include,库文件为 [install path]\lib)添加到目录列表的顶部。
- 对于每个项目,转到“项目”->“设置”->“链接”,然后在“类别”下拉列表中选择“输入”。然后,在“对象/库模块”字段中键入 gdiplus.lib。
- 添加
#include "gdiplus.h"
以包含头文件。我通常将其添加到 stdafx.h,这样我就可以在程序的任何地方使用 GDI+。 - 在执行任何 GDI+ 调用之前,调用
Gdiplus::GdiplusStartup
。 - 在完成 GDI+ 使用后,调用
Gdiplus::GdiplusShutdown
。
如何做到这一点?
我确信有几种方法可以实现这个目标。我个人使用 VC++6 和 MFC ActiveX 控件向导创建了一个无窗口 ActiveX 控件,因此本文将重点介绍这一点。在 ActiveX 控件中使用 GDI+ 与在标准 Windows 应用程序中使用 GDI+ 非常相似。我发现一个非常重要的区别在于 GDI+ 的启动和停止。在 Windows 应用程序中,我通常在应用程序启动时启动 GDI+,并在应用程序退出前关闭它。我发现这种方法在 ActiveX 控件中似乎不起作用。GDI+ 的加载似乎有非常特定的时间点。在我进行的测试中,如果在派生自
COleControl
的类的构造函数中初始化 GDI+,并在其析构函数中关闭它,那么一切似乎都能正常工作。本文附带的项目中有一个名为 InitGDIPlus 的类,专门用于处理 GDI+ 的启动和停止。它的设计目的是确保每个进程只调用一次
Gdiplus::GdiplusStartup
,因为我阅读了多篇帖子,其中提到人们在多次调用 Gdiplus::GdiplusStartup
时遇到过问题。为了在 ActiveX 控件中正确启动和停止 GDI+,我在派生自 COleControl
的类的构造函数中初始化 GDI+,并在析构函数中使用 InitGDIPlus
类提供的方法关闭 GDI+。class InitGDIPlus { private: HANDLE m_hMap; bool m_bInited, m_bInitCtorDtor; ULONG_PTR m_gdiplusToken; Gdiplus::GdiplusStartupInput m_gdiplusStartupInput; long m_initcount; public: // Constructor offers the ability to initialize on construction, or delay until needed. InitGDIPlus(bool bInitCtorDtor = false) : m_bInitCtorDtor(bInitCtorDtor), m_bInited(false), m_hMap(NULL), m_gdiplusToken(NULL), m_gdiplusStartupInput(NULL), m_initcount(0) { if (m_bInitCtorDtor) { Initialize(); } } // If GDI+ has not been explicitly Deinitialized, do it in the destructor virtual ~InitGDIPlus() { if (m_bInitCtorDtor) { Deinitialize(); } } // This function creates a file mapping based on the current process id. // If the mapping already exists, it knows that another instance of this class // elsewhere in the process has already taken care of starting GDI+. void Initialize() { if (!m_bInited) { char buffer[1024]; sprintf(buffer, "GDIPlusInitID=%x", GetCurrentProcessId()); m_hMap = CreateFileMapping((HANDLE) INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE | SEC_COMMIT, 0, sizeof(long), buffer); if (m_hMap != NULL) { // We might have a winner if (GetLastError() == ERROR_ALREADY_EXISTS) { CloseHandle(m_hMap); } else { // Yes, we have a winner m_bInited = true; Gdiplus::GdiplusStartup(&m_gdiplusToken, &m_gdiplusStartupInput, NULL); TRACE("Inited GDIPlus\n"); } } } m_initcount++; } // No tricks to this function. If this was the class that // originally started GDI+, and its initialization count has // reached zero, it shuts down GDI+. void Deinitialize() { m_initcount--; if (m_bInited && m_initcount == 0) { TRACE("GDIPlus shutdown\n"); Gdiplus::GdiplusShutdown(m_gdiplusToken); CloseHandle(m_hMap); m_bInited = false; } } };
///////////////////////////////////////////////////////////////////////////// // CGDIPlusControlCtrl::CGDIPlusControlCtrl - Constructor CGDIPlusControlCtrl::CGDIPlusControlCtrl() : m_isClicked(false), m_center(50, 50) { InitializeIIDs(&IID_DGDIPlusControl, &IID_DGDIPlusControlEvents); GDI_Plus_Controler.Initialize(); //GDI_Plus_Controler is a static global } ///////////////////////////////////////////////////////////////////////////// // CGDIPlusControlCtrl::~CGDIPlusControlCtrl - Destructor CGDIPlusControlCtrl::~CGDIPlusControlCtrl() { GDI_Plus_Controler.Deinitialize(); //GDI_Plus_Controler is a static global }需要注意的是,无窗口激活依赖于 ActiveX 容器,有些容器不支持它。我知道它在 VB6 中可以工作,但据我所知,MFC 提供的默认容器支持不支持无窗口激活。
一旦初始化,GDI+ 就可以像在标准 Windows 应用程序中使用一样使用。在我的测试项目中,我在
OnDraw
函数中添加了一些简单的 GDI+ 调用来展示 GDI+ 的运行效果。由于这个 OnDraw
函数是在透明背景上绘制的,如果这个控件不使用无窗口激活,可能会导致问题。///////////////////////////////////////////////////////////////////////////// // CGDIPlusControlCtrl::OnDraw - Drawing function void CGDIPlusControlCtrl::OnDraw( CDC* pdc, const CRect& rcBounds, const CRect& rcInvalid) { using namespace Gdiplus; Graphics graphics(pdc->m_hDC); // Bitmap sized to control size for double buffering Bitmap bmp(rcBounds.Width(), rcBounds.Height()); // Graphics object using the memory bitmap Graphics* pMemoryGraphics = Graphics::FromImage(&bmp); LinearGradientBrush blueGradient(Point(1,1), Point(rcBounds.Width()-2, rcBounds.Height()-2), Color(0, 0, 0, 255), Color(192, 0, 0, 255)); // Create path for the ellipse GraphicsPath gp; gp.StartFigure(); gp.AddEllipse(2,2, rcBounds.Width() -4, rcBounds.Height() - 4); // Set up a radial gradient brush PathGradientBrush whiteGradientHighlight(&gp); whiteGradientHighlight.SetCenterColor(Color(255, 255, 255, 255)); //m_center is manipulated by the mouse handlers whiteGradientHighlight.SetCenterPoint(Point(m_center.x, m_center.y)); whiteGradientHighlight.SetFocusScales(0.1f, 0.1f); Color surroundColors[] = {Color(0, 255, 255, 255)}; int surroundColorsCount = 1; whiteGradientHighlight.SetSurroundColors(surroundColors, &surroundColorsCount); // Done Setting up radial gradient brush if(m_antiAliased) { pMemoryGraphics->SetSmoothingMode(SmoothingModeAntiAlias); } pMemoryGraphics->FillPath(&blueGradient, &gp); pMemoryGraphics->FillPath(&whiteGradientHighlight, &gp); if(m_border) { Pen pen(Color(255,0,0,0), 2); pMemoryGraphics->DrawPath(&pen, &gp); } // Using rcBounds.left and rcBounds.top ensure proper // positioning with windowless activation. graphics.DrawImage(&bmp, rcBounds.left, rcBounds.top); }在我的项目文件中,我附带了一个我用来测试我的控件的 VB6 项目。我的控件使用了一些半透明渐变填充,因此很容易看到无窗口控件如何与容器的背景融合。在 VB6 中,更改背景很容易,所以请随意尝试,看看控件如何融入新的颜色或背景图像。这是我的容器带有背景图像的屏幕截图。