Click here to Skip to main content
15,881,803 members
Articles / Desktop Programming / MFC
Article

GDI+ in ActiveX Controls Using MFC

Rate me:
Please Sign up or sign in to vote.
3.45/5 (5 votes)
1 Jul 20025 min read 195.4K   1.6K   59   36
An article on using GDI+ in an ActiveX control

VB Container

Why would some one want to use GDI+ in an ActiveX control?

GDI+ offers many attractive graphics features that, though possible with GDI alone, would require a significant amount of additional work to implement them. Therefore, coupling GDI+ with ActiveX controls makes it possible to create visually appealing, portable, reusable controls in a relatively short period of time.

It seems worth mentioning that GDI+ will work in both windowed, and windowless ActiveX controls. However, in my opinion, windowless controls really show the value of GDI+ in this context. With a windowless control, you can use GDI+’s built in alpha blending and anti-aliasing to make a non-rectangular control fit seamlessly onto its container’s window.
Here is a quick list of examples:
  • Curved Controls

    Using GDI to generate a curve at low resolution results in a rather jagged object. It would be possible to make the control as a bitmap image and dither the edges of the curve to the background color. However, this only works if the color isn’t going to change, and the image isn’t going to be resized (unless you don't mind interpolation). Now, with GDI+ that same control could, through anti-aliasing and windowless activation, be used on all backgrounds, at any size, because the anti-aliasing occurs at runtime.

  • Drop Shadows/Highlights/Glows

    With a windowless control and GDI+, it is easy to add translucent effects that incorporate the container's background. For instance, blending a dark color with the color behind it gives the impression that it is a shadow being cast. Alpha blending enables this, because a windowless control is able to translucently fill over its container's background.

What is needed to do this?

As of this writing, the libraries and header files for GDI+ are available in the Microsoft Platform SDK. There are several other articles available on code project that describe how to get and install GDI+, so I will quickly enumerate the steps required to get a simple app up and running in VC++6:
  • Install the Platform SDK
  • In VC++6 go to tools->options->directories, and for both include files and library files, add the path for the Platform SDK ([install path]\include for includes, and [install path]\lib for libraries) to the top of the directory list.
  • For each project go to project->settings->link and select Input under the "category" drop down. Then type gdiplus.lib into the "Object/library modules" field.
  • Add #include "gdiplus.h" to include the headers. I usually add it to stdafx.h so I can use GDI+ anywhere in my program.
  • Call Gdiplus::GdiplusStartup before you do any GDI+ calls.
  • Call Gdiplus::GdiplusShutdown after you are done with GDI+.

How to do this?

I’m sure there are several ways to accomplish this goal. I personally used VC++6 with the MFC ActiveX ControlWizard to create a windowless ActiveX control, so that is what this article is going to deal with.

Using GDI+ in an ActiveX control is very much like using it in a standard Windows application. One very important difference that I found is in starting and stopping GDI+. In a Windows application, I typically start GDI+ when the application starts, and shut it down right before the application exits. I found that this method does not seem to work in ActiveX controls. There seem to be very particular times in which it is ok to load GDI+. In my testing, if GDI+ is initialized in the COleControl derived class's constructor, and shut down in its destructor, then everything seems to work.

The project I have submitted with this article has a class named InitGDIPlus specifically designed to deal with starting up and shutting down GDI+. It is designed to ensure that only one call to Gdiplus::GdiplusStartup is issued per process, because I have read several posts that seem to indicate that people have run into problems when Gdiplus::GdiplusStartup is called too many times. In order to properly start and stop GDI+ in an ActiveX control, I initialized GDI+ in the constructor of my COleControl derived class, and shut it down in the destructor using methods provided by the InitGDIPlus class.
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
}
One thing to note is that windowless activation is dependent on the ActiveX container, and some containers do not support it. I know it works in VB6, but as far as I can tell, the default container support offered by MFC does not support windowless activation.

Once initialized, GDI+ can be utilized just as though it were in a standard Windows application. For my test project, I added some simple GDI+ calls to my OnDraw function to show GDI+ in action. Due to the fact that this OnDraw function draws on a transparent background, this could cause problems if this control does not use windowless activation.
/////////////////////////////////////////////////////////////////////////////
// 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);
    
}
Included with my project files, I have enclosed a VB6 project that I used to test my control. My control uses a few translucent gradient fills, so it is easy to see how the windowless control blends with the container's background. In VB6 it is easy to change the background, so feel free to play around with it to see how the control incorporates the new color or background image. Here is a screen shot of my container with a background image:

VB Container with Background

What are the obvious drawbacks to this?

The added benefits of GDI+ do not come without a reduction in performance. Additionally, GDI+ only became a standard part of Windows with Windows XP. According to Microsoft, it will not run on Windows 95, but it can run on Windows 98+, and Windows NT4+ with an additional DLL. The need for an additional DLL could be a real problem for ActiveX controls designed for the web. I’m sure additional drawbacks exist, they just weren’t obvious to me.

Conclusion

As with anything, there are situations where this is useful, and situations where it is not. If your goal is to create a “cool” non-standard UI, this could be a good way to build reusable components. If however, you are designing ActiveX controls exclusively for the web, then this may not be the way to go.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
Questiongood job Pin
zirandeai12-Aug-12 15:18
zirandeai12-Aug-12 15:18 
GeneralProblem with using different GDI+ ActiveX controls together Pin
Gilicek27-Apr-11 1:59
Gilicek27-Apr-11 1:59 
GeneralMemory leak! Pin
Shenbrun3-Sep-07 8:02
Shenbrun3-Sep-07 8:02 
GeneralGDI+Window wont Shut Down Pin
websiteking30-Jul-06 16:36
websiteking30-Jul-06 16:36 
QuestionQuestion? Pin
User 5924117-Jun-05 16:18
User 5924117-Jun-05 16:18 
AnswerRe: Question? Pin
Philippe Lhoste25-Jul-06 1:57
Philippe Lhoste25-Jul-06 1:57 
GeneralRe: Question? Pin
User 5924125-Jul-06 14:07
User 5924125-Jul-06 14:07 
GeneralTranslucency and other ActiveX controls Pin
smjones18-Apr-05 6:07
smjones18-Apr-05 6:07 
Nice work. I am using the radial gradient. But I have an activex control and translucency question that you may have run across and solved. I have been creating some transparent Mfc ActiveX controls myself by and have gotten them to work with seeing the desktop but not other ActiveX controls underneath. I'm sure this has to do with the device context that we are passed but I'm sure how to solve getting the image or device context for an ActiveX control that is sitting underneath our transparent or translucent control.

Have you run across this?

BTW, you can tell the parent to refresh itself by the following code. For windowless controls the container may need to be told to refresh the drawing area.

// In our windowless control, in some update function
IOleInPlaceSiteWindowless * pInPlaceSiteWindowless = NULL;
m_pClientSite->QueryInterface(IID_IOleInPlaceSiteWindowless,(void**)&pInPlaceSiteWindowless);

if (pInPlaceSiteWindowless)
{
pInPlaceSiteWindowless->InvalidateRgn(NULL,true);
}
else
{
InvalidateControl(); // must use this call to invalidate ourself rather than
// Invalidate() because we are windowless
}



Manufacturing Software Developer
Hewlett-Packard Company
GeneralRe: Translucency and other ActiveX controls Pin
Ryan Johnston18-Apr-05 18:56
Ryan Johnston18-Apr-05 18:56 
Generalweb based ActiveX Control using MFC in VS.net Pin
kamal_nathP30-Mar-05 22:35
susskamal_nathP30-Mar-05 22:35 
QuestionHi,,, Can i ask something ? Pin
Koreanboy20-Sep-04 8:29
sussKoreanboy20-Sep-04 8:29 
AnswerRe: Hi,,, Can i ask something ? Pin
Ryan Johnston20-Sep-04 8:44
Ryan Johnston20-Sep-04 8:44 
GeneralProblems with Font in an ActiveX control Pin
MTurner14-Apr-04 3:17
MTurner14-Apr-04 3:17 
GeneralRe: Problems with Font in an ActiveX control Pin
shashkunina1-Dec-04 23:52
shashkunina1-Dec-04 23:52 
GeneralCompilation error with vc6 Pin
neoviper23-Feb-04 21:13
neoviper23-Feb-04 21:13 
GeneralRe: Compilation error with vc6 Pin
Ryan Johnston23-Feb-04 22:32
Ryan Johnston23-Feb-04 22:32 
GeneralPrint Preview does not work in Access Pin
biondello3-Jan-04 21:29
biondello3-Jan-04 21:29 
GeneralDoes not work in Excel Pin
biondello3-Jan-04 21:26
biondello3-Jan-04 21:26 
GeneralMemory leak Pin
Hidde Wallaart21-Oct-03 23:20
Hidde Wallaart21-Oct-03 23:20 
GeneralRe: Memory leak Pin
Ryan Johnston22-Oct-03 6:28
Ryan Johnston22-Oct-03 6:28 
GeneralRe: Memory leak Pin
Bill Arnette26-Nov-03 5:52
Bill Arnette26-Nov-03 5:52 
QuestionHow do I clear what the control draws? Pin
Bernie Mills8-Jun-03 7:20
Bernie Mills8-Jun-03 7:20 
AnswerRe: How do I clear what the control draws? Pin
Hidde Wallaart22-Oct-03 0:23
Hidde Wallaart22-Oct-03 0:23 
GeneralRe: How do I clear what the control draws? - Windows XP theming Pin
Hidde Wallaart22-Oct-03 22:37
Hidde Wallaart22-Oct-03 22:37 
Generalcompilation error with vc6 Pin
cweng200317-Apr-03 13:20
cweng200317-Apr-03 13:20 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.