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

在 ActiveX 控件中使用 GDI+(使用 MFC)

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.45/5 (5投票s)

2002年7月2日

5分钟阅读

viewsIcon

198596

downloadIcon

1611

一篇关于在 ActiveX 控件中使用 GDI+ 的文章。

VB Container

为什么有人想在 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 中,更改背景很容易,所以请随意尝试,看看控件如何融入新的颜色或背景图像。这是我的容器带有背景图像的屏幕截图。

VB Container with Background

这有什么明显的缺点?

GDI+ 带来的额外好处并非没有性能下降。此外,GDI+ 仅在 Windows XP 中成为 Windows 的标准部分。根据微软的说法,它不能在 Windows 95 上运行,但可以在 Windows 98+ 和 Windows NT4+ 上运行,需要额外的 DLL。对于专为 Web 设计的 ActiveX 控件来说,需要一个额外的 DLL 可能是一个真正的问题。我相信还有其他缺点,只是我没有注意到。

结论

与任何事物一样,有些情况适合这样做,有些情况则不适合。如果你的目标是创建“酷炫”的非标准 UI,这可能是一个构建可重用组件的好方法。然而,如果你主要为 Web 设计 ActiveX 控件,那么这可能不是最佳选择。
© . All rights reserved.