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 Function

  • Adding 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

  1. 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
    
  2. Implement the IProvideClassInfo interface to provide method information for the control, by adding the following code to PolyCtl.h. In the CPolyCtl 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)
    
  3. Once the polygon points are calculated, they will be stored in an array of type POINT, so add the array after the definition statement short 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

  1. 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

  1. Add the declaration of CalcPoints to the IPolyCtl public section of the CPolyCtl 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);
    
  2. 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

  1. 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

  1. 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 line TRACE( "XActiveScriptSite::GetItemInfo( %s )\n", pszNameT ); with TRACE( "XActiveScriptSite::GetItemInfo( %s )\n", pszNameT.m_psz );, and line TRACE( "Source Text: %s\n", COLE2CT( bstrSourceLineText ) ); with TRACE( "Source Text: %s\n", bstrSourceLineText );.
    For errors involving HMONITOR, open StdAfx.h in the TCProps project and replace:

    #ifndef WINVER
    #define WINVER 0x0400
    #endif
    

    with

    #ifndef WINVER
    #define WINVER 0x0500
    #define _WIN32_WINNT 0x0500
    #endif
    
  2. In Test Container, on the Edit menu, click Insert New Control.

  3. 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

  1. In Test Container, click Invoke Methods on the Control menu.

    The Invoke Method dialog box is displayed.

  2. Select the PropPut version of the Sides property from the Method Name drop-down list box.

  3. 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

  1. Update PolyCtl.cpp by adding the call to FireViewChange to the put_Sides method. When you have finished, the put_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.

Back to Step 3 | On to Step 5

See also

Tutorial
Testing Properties and Events with Test Container