Changing the Drawing Code (ATL Tutorial, Part 4)
By default, the control's drawing code displays a square and the text PolyCtl. In this step, you will change the code to display something more interesting. The following tasks are involved:
Modifying the Header File
Modifying the
OnDraw
FunctionAdding a Method to Calculate the Polygon Points
Initializing the Fill Color
Modifying the Header File
Start by adding support for the math functions sin
and cos
, which will be used calculate the polygon points, and by creating an array to store positions.
To modify the header file
Add the line
#include <math.h>
to the top of PolyCtl.h. The top of the file should look like this:#include <math.h> #include "resource.h" // main symbols
Implement the
IProvideClassInfo
interface to provide method information for the control, by adding the following code to PolyCtl.h. In theCPolyCtl
class, replace line:public CComControl<CPolyCtl>
with
public CComControl<CPolyCtl>, public IProvideClassInfo2Impl<&CLSID_PolyCtl, &DIID__IPolyCtlEvents, &LIBID_PolygonLib>
and in
BEGIN_COM_MAP(CPolyCtl)
, add the lines:COM_INTERFACE_ENTRY(IProvideClassInfo) COM_INTERFACE_ENTRY(IProvideClassInfo2)
Once the polygon points are calculated, they will be stored in an array of type
POINT
, so add the array after the definition statementshort m_nSides;
in PolyCtl.h:POINT m_arrPoint[100];
Modifying the OnDraw Method
Now you should modify the OnDraw
method in PolyCtl.h. The code you will add creates a new pen and brush with which to draw your polygon, and then calls the Ellipse
and Polygon
Win32 API functions to perform the actual drawing.
To modify the OnDraw function
Replace the existing
OnDraw
method in PolyCtl.h with the following code:HRESULT CPolyCtl::OnDraw(ATL_DRAWINFO& di) { RECT& rc = *(RECT*)di.prcBounds; HDC hdc = di.hdcDraw; COLORREF colFore; HBRUSH hOldBrush, hBrush; HPEN hOldPen, hPen; // Translate m_colFore into a COLORREF type OleTranslateColor(m_clrFillColor, NULL, &colFore); // Create and select the colors to draw the circle hPen = (HPEN)GetStockObject(BLACK_PEN); hOldPen = (HPEN)SelectObject(hdc, hPen); hBrush = (HBRUSH)GetStockObject(WHITE_BRUSH); hOldBrush = (HBRUSH)SelectObject(hdc, hBrush); Ellipse(hdc, rc.left, rc.top, rc.right, rc.bottom); // Create and select the brush that will be used to fill the polygon hBrush = CreateSolidBrush(colFore); SelectObject(hdc, hBrush); CalcPoints(rc); Polygon(hdc, &m_arrPoint[0], m_nSides); // Select back the old pen and brush and delete the brush we created SelectObject(hdc, hOldPen); SelectObject(hdc, hOldBrush); DeleteObject(hBrush); return S_OK; }
Adding a Method to Calculate the Polygon Points
Add a method, called CalcPoints
, that will calculate the coordinates of the points that make up the perimeter of the polygon. These calculations will be based on the RECT variable that is passed into the function.
To add the CalcPoints method
Add the declaration of
CalcPoints
to theIPolyCtl
public section of theCPolyCtl
class in PolyCtl.h:void CalcPoints(const RECT& rc);
The last part of the public section of the
CPolyCtl
class will look like this:void FinalRelease() { } public: void CalcPoints(const RECT& rc);
Add this implementation of the
CalcPoints
function to the end of PolyCtl.cpp:void CPolyCtl::CalcPoints(const RECT& rc) { const double pi = 3.14159265358979; POINT ptCenter; double dblRadiusx = (rc.right - rc.left) / 2; double dblRadiusy = (rc.bottom - rc.top) / 2; double dblAngle = 3 * pi / 2; // Start at the top double dblDiff = 2 * pi / m_nSides; // Angle each side will make ptCenter.x = (rc.left + rc.right) / 2; ptCenter.y = (rc.top + rc.bottom) / 2; // Calculate the points for each side for (int i = 0; i < m_nSides; i++) { m_arrPoint[i].x = (long)(dblRadiusx * cos(dblAngle) + ptCenter.x + 0.5); m_arrPoint[i].y = (long)(dblRadiusy * sin(dblAngle) + ptCenter.y + 0.5); dblAngle += dblDiff; } }
Initializing the Fill Color
Initialize m_clrFillColor
with a default color.
To initialize the fill color
Use green as the default color by adding this line to the
CPolyCtl
constructor in PolyCtl.h:m_clrFillColor = RGB(0, 0xFF, 0);
The constructor now looks like this:
CPolyCtl()
{
m_nSides = 3;
m_clrFillColor = RGB(0, 0xFF, 0);
}
Building and Testing the Control
Rebuild the control. Make sure the PolyCtl.htm file is closed if it is still open, and then click Build Polygon on the Build menu. You could view the control once again from the PolyCtl.htm page, but this time use the ActiveX Control Test Container.
To use the ActiveX Control Test Container
Build and start the ActiveX Control Test Container. The TSTCON Sample: ActiveX Control Test Container can be found on GitHub.
Note
For errors involving
ATL::CW2AEX
, in Script.Cpp, replace lineTRACE( "XActiveScriptSite::GetItemInfo( %s )\n", pszNameT );
withTRACE( "XActiveScriptSite::GetItemInfo( %s )\n", pszNameT.m_psz );
, and lineTRACE( "Source Text: %s\n", COLE2CT( bstrSourceLineText ) );
withTRACE( "Source Text: %s\n", bstrSourceLineText );
.
For errors involvingHMONITOR
, open StdAfx.h in theTCProps
project and replace:#ifndef WINVER #define WINVER 0x0400 #endif
with
#ifndef WINVER #define WINVER 0x0500 #define _WIN32_WINNT 0x0500 #endif
In Test Container, on the Edit menu, click Insert New Control.
Locate your control, which will be called
PolyCtl class
, and click OK. You will see a green triangle within a circle.
Try changing the number of sides by following the next procedure. To modify properties on a dual interface from within Test Container, use Invoke Methods.
To modify a control's property from within the Test Container
In Test Container, click Invoke Methods on the Control menu.
The Invoke Method dialog box is displayed.
Select the PropPut version of the Sides property from the Method Name drop-down list box.
Type
5
in the Parameter Value box, click Set Value, and click Invoke.
Note that the control does not change. Although you changed the number of sides internally by setting the m_nSides
variable, this did not cause the control to repaint. If you switch to another application and then switch back to Test Container, you will find that the control has repainted and has the correct number of sides.
To correct this problem, add a call to the FireViewChange
function, defined in IViewObjectExImpl
, after you set the number of sides. If the control is running in its own window, FireViewChange
will call the InvalidateRect
method directly. If the control is running windowless, the InvalidateRect
method will be called on the container's site interface. This forces the control to repaint itself.
To add a call to FireViewChange
Update PolyCtl.cpp by adding the call to
FireViewChange
to theput_Sides
method. When you have finished, theput_Sides
method should look like this:STDMETHODIMP CPolyCtl::put_Sides(short newVal) { if (2 < newVal && newVal < 101) { m_nSides = newVal; FireViewChange(); return S_OK; } else { return Error(_T("Shape must have between 3 and 100 sides")); } }
After adding FireViewChange
, rebuild and try the control again in the ActiveX Control Test Container. This time when you change the number of sides and click Invoke
, you should see the control change immediately.
In the next step, you will add an event.