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:
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();
}
}
virtual ~InitGDIPlus() {
if (m_bInitCtorDtor) {
Deinitialize();
}
}
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) {
if (GetLastError() == ERROR_ALREADY_EXISTS) {
CloseHandle(m_hMap);
} else {
m_bInited = true;
Gdiplus::GdiplusStartup(&m_gdiplusToken,
&m_gdiplusStartupInput, NULL);
TRACE("Inited GDIPlus\n");
}
}
}
m_initcount++;
}
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() : m_isClicked(false), m_center(50, 50)
{
InitializeIIDs(&IID_DGDIPlusControl, &IID_DGDIPlusControlEvents);
GDI_Plus_Controler.Initialize();
}
CGDIPlusControlCtrl::~CGDIPlusControlCtrl()
{
GDI_Plus_Controler.Deinitialize();
}
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.
void CGDIPlusControlCtrl::OnDraw(
CDC* pdc, const CRect& rcBounds, const CRect& rcInvalid)
{
using namespace Gdiplus;
Graphics graphics(pdc->m_hDC);
Bitmap bmp(rcBounds.Width(), rcBounds.Height());
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));
GraphicsPath gp;
gp.StartFigure();
gp.AddEllipse(2,2, rcBounds.Width() -4, rcBounds.Height() - 4);
PathGradientBrush whiteGradientHighlight(&gp);
whiteGradientHighlight.SetCenterColor(Color(255, 255, 255, 255));
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);
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);
}
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:
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.