Figure 1   CComControl Property Change


template <class T, class WinBase = CWindowImpl< T > >
class ATL_NO_VTABLE CComControl : public CComControlBase, public WinBase
{
public:
    HRESULT FireOnRequestEdit(DISPID dispID)
    {
        T* pT = static_cast<T*>(this);
        return T::__ATL_PROP_NOTIFY_EVENT_CLASS::FireOnRequestEdit
            (pT->GetUnknown(), dispID);
    }
    HRESULT FireOnChanged(DISPID dispID)
    {
        T* pT = static_cast<T*>(this);
        return T::__ATL_PROP_NOTIFY_EVENT_CLASS::FireOnChanged
            (pT->GetUnknown(), dispID);
    }
 .
 .
 .
};

Figure 2   CFirePropNotifyEvent


class CFirePropNotifyEvent
{
public:
    static HRESULT FireOnRequestEdit(IUnknown* pUnk, DISPID dispID)
    {
    CComQIPtr<IConnectionPointContainer, &IID_IConnectionPointContainer> 
        pCPC(pUnk);
    if (!pCPC) return S_OK;
    CComPtr<IConnectionPoint> pCP;
    pCPC->FindConnectionPoint(IID_IPropertyNotifySink, &pCP);
    if (!pCP) return S_OK;
    CComPtr<IEnumConnections> pEnum;
    if (FAILED(pCP->EnumConnections(&pEnum))) return S_OK;
    CONNECTDATA cd;
    while (pEnum->Next(1, &cd, NULL) == S_OK) {
        if (cd.pUnk) {
            HRESULT hr = S_OK;
            CComQIPtr<IPropertyNotifySink, &IID_IPropertyNotifySink> 
                pSink(cd.pUnk);
            if (pSink) hr = pSink->OnRequestEdit(dispID);
            cd.pUnk->Release();
            if (hr == S_FALSE) return S_FALSE;
        }
    }
    return S_OK;
} 

static HRESULT FireOnChanged(IUnknown* pUnk, DISPID dispID)
{
    // Code similar to above deleted for clarity
•
•
•
    while (pEnum->Next(1, &cd, NULL) == S_OK) {
        if (cd.pUnk) {
            CComQIPtr<IPropertyNotifySink, &IID_IPropertyNotifySink> 
                pSink(cd.pUnk);
            if (pSink) pSink->OnChanged(dispID);
            cd.pUnk->Release();
        }
    }
};

Figure 3   CProxy_IBullsEyeEvents


template <class T>
class CProxy_IBullsEyeEvents :
    public IConnectionPointImpl<T, &DIID__IBullsEyeEvents, CComDynamicUnkArray>
{
    // Warning this class may be recreated by the wizard.
public:
    VOID Fire_OnRingHit(SHORT ringNumber)
    {
        T* pT = static_cast<T*>(this);

        int nConnectionIndex;
        CComVariant* pvars = new CComVariant[1];
        int nConnections = m_vec.GetSize();
        
        for (nConnectionIndex = 0;
            nConnectionIndex < nConnections; nConnectionIndex++) {
            pT->Lock();
            CComPtr<IUnknown> sp = m_vec.GetAt(nConnectionIndex);
            pT->Unlock();
            IDispatch* pDispatch = reinterpret_cast<IDispatch*>(sp.p);
            if (pDispatch != NULL) {
                pvars[0] = ringNumber;
                DISPPARAMS disp = { pvars, NULL, 1, 0 };
                pDispatch->Invoke(0x1, IID_NULL, LOCALE_USER_DEFAULT,
                                  DISPATCH_METHOD, &disp, NULL, NULL, NULL);
            }
        }
        delete[] pvars;
    }
    VOID Fire_OnScoreChanged(LONG ringValue)
    {
        // Code similar to above deleted for clarity
    } 
};

Figure 4   The ATL_DRAWINFO struct


struct ATL_DRAWINFO
 {
     UINT cbSize;            // Set to sizeof (struct ATL_DRAWINFO)
     DWORD dwDrawAspect;     // Drawing aspect - typically DVASPECT_CONTENT
     LONG lindex;            // Commonly -1, which specifies all of the data
     DVTARGETDEVICE* ptd;    // Render the object for this target device
     HDC hicTargetDev;       // Information context for the target device
     HDC hdcDraw;            // Draw on this device context
     LPCRECTL prcBounds;     // Draw within this rectangle
     LPCRECTL prcWBounds;    // Window extent and origin when rendering a 
                             // metafile
     BOOL bOptimize;         // Can control use drawing optimizations?
     BOOL bZoomed;           // Object extent differs from drawing rectangle?
     BOOL bRectInHimetric;   // Rectangle in HiMetric?
     SIZEL ZoomNum;          // Rectangle size: X zoom = ZoomNum.cx/ZoomDen.cx
     SIZEL ZoomDen;          // Extent size   : Y zoom = ZoomNum.cy/ZoomDen.cy
 };


Figure 5   Default OnDrawAdvanced Method


inline HRESULT CComControlBase::OnDrawAdvanced(ATL_DRAWINFO& di)
{
    BOOL bDeleteDC = FALSE;
    if (di.hicTargetDev == NULL) {
        di.hicTargetDev = AtlCreateTargetDC(di.hdcDraw, di.ptd);
        bDeleteDC = (di.hicTargetDev != di.hdcDraw);
    }
    RECTL rectBoundsDP = *di.prcBounds;
    BOOL bMetafile = GetDeviceCaps(di.hdcDraw, TECHNOLOGY) == DT_METAFILE;
    if (!bMetafile) {
        ::LPtoDP(di.hicTargetDev, (LPPOINT)&rectBoundsDP, 2);
        SaveDC(di.hdcDraw);
        SetMapMode(di.hdcDraw, MM_TEXT);
        SetWindowOrgEx(di.hdcDraw, 0, 0, NULL);
        SetViewportOrgEx(di.hdcDraw, 0, 0, NULL);
        di.bOptimize = TRUE; //since we save the DC we can do this
    }
    di.prcBounds = &rectBoundsDP;
    GetZoomInfo(di);

    HRESULT hRes = OnDraw(di);
    if (bDeleteDC) ::DeleteDC(di.hicTargetDev);
    if (!bMetafile) RestoreDC(di.hdcDraw, -1);
    return hRes;
}

Figure 6   BullsEye Drawing



HRESULT CBullsEye::OnDraw(ATL_DRAWINFO& di)
{
    CRect rc = *(RECT*)di.prcBounds;
    HDC hdc  = di.hdcDraw;

    // Create the background color brush only when necessary
    if (NULL == m_backBrush) {
        OLE_COLOR ocBack;
        HRESULT hr = get_BackColor (&ocBack);  // Get the background color
        ATLASSERT (SUCCEEDED (hr));

        COLORREF  crBack;                  // Translate the color to a COLORREF
        hr = ::OleTranslateColor (ocBack, NULL, &crBack);
        ATLASSERT (SUCCEEDED (hr));

        m_backBrush = ::CreateSolidBrush (crBack);  // Create the background 
                                                    // brush
        ATLASSERT (NULL != m_backBrush);
    }

    // First, fill in background color in invalid area when BackStyle is Opaque
    if (1 /* Opaque*/ == m_nBackStyle) {
        int s = ::FillRect (hdc, &rc, m_backBrush);
        ATLASSERT (0 != s);
    }

    int nPrevMapMode;
    POINT   ptWOOrig, ptVOOrig;
    SIZE szWEOrig, szVEOrig;

    BOOL bMetafile = GetDeviceCaps(di.hdcDraw, TECHNOLOGY) == DT_METAFILE;
    if (!bMetafile) {
        // OnDrawAdvanced normalized the device context
        // We are now using MM_TEXT and the coordinates are in device 
        // coordinates.

        // Establish a convenient coordinate system - how about 1000 x 1000
        nPrevMapMode = ::SetMapMode (hdc, MM_ISOTROPIC);
        ATLASSERT (0 != nPrevMapMode);

        // Map logical 0,0 to physical center of rectangle
        BOOL bSuccess = ::SetWindowOrgEx (hdc, 0, 0, &ptWOOrig);
        ATLASSERT (0 != bSuccess);
        bSuccess = ::SetViewportOrgEx (hdc, rc.left + rc.Width () / 2,
                                       rc.top  + rc.Height () / 2,
                                       &ptVOOrig);
        ATLASSERT (0 != bSuccess);

        // Map logical extent (LOGWIDTH, LOGWIDTH) to physical extent of 
        // rectangle
        bSuccess = ::SetWindowExtEx (hdc, LOGWIDTH, LOGWIDTH, &szWEOrig);
        ATLASSERT (0 != bSuccess);
        bSuccess = ::SetViewportExtEx (hdc, rc.Width (), -rc.Height (),
                                       &szVEOrig);
        ATLASSERT (0 != bSuccess);
    }
    else {
    // We will be played back in ANISOTROPIC mapping mode

    // The rectangle will be in device units
    CRect rcBoundsDP (rc) ;

    // We can't use SetViewportOrg and SetViewportExt in a metafile
    // because the container will want to place the metafile.
    // 
    // Find the center coordinate and the shorter side
    CSize size = rcBoundsDP.Size () ;
    int iShortSide = min (size.cx, size.cy) ;
    CPoint ptCenter (rcBoundsDP.left + size.cx / 2,
                     rcBoundsDP.top  + size.cy / 2) ;    

       // Compute the ratio of LOGWIDTH / shorter side
       double dRatio = (double) LOGWIDTH / (double) iShortSide ;

       // Set the logical origin of the window and swap coordinate axes
       BOOL bSuccess = SetWindowOrgEx (hdc,
                                       -int (ptCenter.x * dRatio), 
                                        int (ptCenter.y * dRatio), &ptWOOrig) ;
       ATLASSERT (0 != bSuccess);

       // Set the logical extent of the window
       // Compensate for the drawing code which draws from -LOGWIDTH to 
       // +LOGWIDTH
       bSuccess = SetWindowExtEx (hdc,  int (size.cx * dRatio),
                                  -int (size.cy * dRatio), &szWEOrig) ;
       ATLASSERT (0 != bSuccess);
    }

    // Draw the BullsEye
    DrawBullsEye (di);

    // Note on optimized drawing:
    // Even when using optimized drawing, a control cannot
    // leave a changed mapping mode, coordinate transformation value,
    // selected bitmap, clip region, or metafile in the device context.

    if (!bMetafile) {
        ::SetMapMode (hdc, nPrevMapMode);

        ::SetViewportOrgEx (hdc, ptVOOrig.x,  ptVOOrig.y,  NULL);
        ::SetViewportExtEx (hdc, szVEOrig.cx, szVEOrig.cy, NULL);
    }

    ::SetWindowOrgEx (hdc, ptWOOrig.x,  ptWOOrig.y,  NULL);
    ::SetWindowExtEx (hdc, szWEOrig.cx, szWEOrig.cy, NULL);

    return S_OK;
}

void CBullsEye::DrawBullsEye (ATL_DRAWINFO& di)
{
    HDC hdc  = di.hdcDraw;

    // Create the border pen only when necessary
    if (NULL == m_borderPen) {
        OLE_COLOR ocFore;
        HRESULT hr = get_ForeColor (&ocFore);
        ATLASSERT (SUCCEEDED (hr));

        COLORREF crFore;
        hr = ::OleTranslateColor (ocFore, NULL, &crFore);
        ATLASSERT (SUCCEEDED (hr));

        m_borderPen = ::CreatePen (PS_SOLID, 0, crFore);
        ATLASSERT (NULL != m_borderPen);
    }

    // Create the center color brush only when necessary
    if (NULL == m_centerBrush) {
        COLORREF  crCenter;
        HRESULT hr = ::OleTranslateColor (m_clrCenterColor, NULL, &crCenter);
        ATLASSERT (SUCCEEDED (hr));

        m_centerBrush = ::CreateSolidBrush (crCenter);
        ATLASSERT (NULL != m_centerBrush);
    }

    // Create the alternate color brush only when necessary
    if (NULL == m_alternateBrush) {
        COLORREF  crAlternate;
        HRESULT hr =
            ::OleTranslateColor (m_clrAlternateColor, NULL, &crAlternate);
        ATLASSERT (SUCCEEDED (hr));

        m_alternateBrush = ::CreateSolidBrush (crAlternate);
        ATLASSERT (NULL != m_alternateBrush);
    }

    // Compute the width of a ring
    short sRingCount;
    HRESULT hr = get_RingCount (&sRingCount);
    ATLASSERT (SUCCEEDED (hr));

    int ringWidth = LOGWIDTH / (sRingCount * 2 - 1);

    // Draw the border between rings using the border pen
    HPEN hOldPen = (HPEN) SelectObject (hdc, m_borderPen);

    HBRUSH hOldBrush = NULL;

    // Draw each ring from outermost to innermost
    // This isn't nearly as efficient as it could be
    for (short i = sRingCount - 1; i >= 0; i--) {

        // Compute the ring's bounding rectangle
        int ringDiameter = i * 2 * ringWidth + ringWidth;
        int ringRadius   = ringDiameter / 2;
        CRect rcRing (-ringRadius, ringRadius, ringRadius, -ringRadius);

        // Rings are numbered from 1 to N from center to outside.
        // However the loop iterates from N-1 to 0 from outside to center.
        // Therefore even numbered rings should be the center color
        // which implies odd numbered rings use the alternate color.
        HBRUSH& ringBrush = i & 1 ? m_alternateBrush : m_centerBrush;

        // Set the correct ring color
        HBRUSH hBrush = (HBRUSH) ::SelectObject (hdc, ringBrush);
        if (NULL == hOldBrush) // First time through, save the original brush
            hOldBrush = hBrush;

        BOOL bStatus =  // Draw the ring
          ::Ellipse (hdc, rcRing.left, rcRing.right, rcRing.top, 
                     rcRing.bottom);
        ATLASSERT (NULL != bStatus);
    }

    // When optimized drawing is not in effect, restore the original pen and 
    // brush
    if (!di.bOptimize) {
        ::SelectObject (hdc, hOldPen);
        ::SelectObject (hdc, hOldBrush);
    }
}
 

Figure 7   BullsEye Property Map


BEGIN_PROP_MAP(CBullsEye)
    PROP_DATA_ENTRY("_cx", m_sizeExtent.cx, VT_UI4)
    PROP_DATA_ENTRY("_cy", m_sizeExtent.cy, VT_UI4)
    PROP_ENTRY("BackStyle",      DISPID_BACKSTYLE,      CLSID_BullsEyePropPage)
    PROP_ENTRY("Beep",           DISPID_BEEP,           CLSID_BullsEyePropPage)
    PROP_ENTRY("Enabled",        DISPID_ENABLED,        CLSID_BullsEyePropPage)
    PROP_ENTRY("RingCount",      DISPID_RINGCOUNT,      CLSID_BullsEyePropPage)
    PROP_ENTRY("AlternateColor", DISPID_ALTERNATECOLOR, CLSID_StockColorPage)
    PROP_ENTRY("BackColor",      DISPID_BACKCOLOR,      CLSID_StockColorPage)
    PROP_ENTRY("CenterColor",    DISPID_CENTERCOLOR,    CLSID_StockColorPage)
    PROP_ENTRY("ForeColor",      DISPID_FORECOLOR,      CLSID_StockColorPage)
END_PROP_MAP()

Figure 8   IPersistPropertyBag_Load


 HRESULT CBullsEye::IPersistPropertyBag_Load(LPPROPERTYBAG pPropBag,
     LPERRORLOG pErrorLog, ATL_PROPMAP_ENTRY* pMap)
 {
     if (NULL == pPropBag) return E_POINTER;
 
     // Load the properties described in the PROP_MAP
     // Work around ATL 3.0 bug in AtlIPersistPropertyBag_Load
     HRESULT hr = FixedAtlIPersistPropertyBag_Load(pPropBag, pErrorLog, pMap,
                                                   this, GetUnknown());
     if (SUCCEEDED(hr)) m_bRequiresSave = FALSE;
 
     if (FAILED (hr)) return hr;
 
     // Load the indexed property - RingValues
     // Get the number of rings
     short sRingCount;
     get_RingCount (&sRingCount);
 
     // For each ring, read its value
     for (short nIndex = 1; nIndex <= sRingCount; nIndex++) {
 
         // Create the base property name
         CComBSTR bstrName = OLESTR("RingValue");
 
         // Create ring number as a string
         CComVariant vRingNumber = nIndex;
         hr = vRingNumber.ChangeType (VT_BSTR);
         ATLASSERT (SUCCEEDED (hr));
 
         // Concatenate the two strings to form property name
         bstrName += vRingNumber.bstrVal;
 
         // Read ring value from the property bag
         CComVariant vValue = 0L;
         hr = pPropBag->Read(bstrName, &vValue, pErrorLog);
         ATLASSERT (SUCCEEDED (hr));
         ATLASSERT (VT_I4 == vValue.vt);
 
         if (FAILED (hr)) {
             hr = E_UNEXPECTED;
             break;
         }
 
         // Set the ring value
         put_RingValue (nIndex, vValue.lVal);
         }
 
     if (SUCCEEDED(hr)) m_bRequiresSave = FALSE;
     return hr;
 }
 

Figure 10   ICategorizeProperties.idl


 [
     object, local,
     uuid(4D07FC10-F931-11CE-B001-00AA006884E5),
     helpstring("ICategorizeProperties Interface"),
     pointer_default(unique)
 ]
 interface ICategorizeProperties : IUnknown
 {
     typedef [public] int PROPCAT;
 
     const int PROPCAT_Nil        = -1;
     const int PROPCAT_Misc       = -2;
     const int PROPCAT_Font       = -3;
     const int PROPCAT_Position   = -4;
     const int PROPCAT_Appearance = -5;
     const int PROPCAT_Behavior   = -6;
     const int PROPCAT_Data       = -7;
     const int PROPCAT_List       = -8;
     const int PROPCAT_Text       = -9;
     const int PROPCAT_Scale      = -10;
     const int PROPCAT_DDE        = -11;
 
     HRESULT MapPropertyToCategory([in] DISPID dispid, 
                                   [out] PROPCAT* ppropcat);
     HRESULT GetCategoryName([in] PROPCAT propcat, [in] LCID lcid, 
                             [out] BSTR* pbstrName);
 }
 

Figure 11   ICategorizeProperties.h


 STDMETHODIMP CBullsEye::MapPropertyToCategory(/*[in]*/ DISPID dispid, /*[out]*/
 PROPCAT* ppropcat)
 {
     if (NULL == ppropcat) return E_POINTER;
 
     switch (dispid) {
     case DISPID_FORECOLOR:   case DISPID_BACKCOLOR:
     case DISPID_CENTERCOLOR: case DISPID_ALTERNATECOLOR:
     case DISPID_RINGCOUNT:   case DISPID_BACKSTYLE:
         *ppropcat = PROPCAT_Appearance;
         return S_OK;
 
     case DISPID_BEEP:        case DISPID_ENABLED:
         *ppropcat = PROPCAT_Behavior;
         return S_OK;
 
     case DISPID_RINGVALUE:
         *ppropcat = PROPCAT_Scoring;
         return S_OK;
 
     default:
         return E_FAIL;
     }
 }
 
 STDMETHODIMP CBullsEye::GetCategoryName(/*[in]*/ PROPCAT propcat,
                                         /*[in]*/ LCID lcid,
                                         /*[out]*/ BSTR* pbstrName)
 {
     if(PROPCAT_Scoring == propcat) {
         *pbstrName = ::SysAllocString(L"Scoring"); 
         return S_OK;
     }
     return E_FAIL;
 }
 

Figure 12   GetDisplayString


STDMETHODIMP CBullsEye::GetDisplayString(DISPID dispid,BSTR *pBstr)
{
    ATLTRACE2(atlTraceControls,2,_T("CBullsEye::GetDisplayString\n"));
    switch (dispid) {
    case DISPID_BEEP:
        if (VARIANT_TRUE == m_beep) *pBstr = SysAllocString (OLESTR("Yes"));
        else                        *pBstr = SysAllocString (OLESTR("No"));

        return *pBstr ? S_OK : E_OUTOFMEMORY;

    case DISPID_BACKSTYLE:
        if (1 == m_nBackStyle) *pBstr = SysAllocString (OLESTR("Opaque"));
        else                   *pBstr = SysAllocString (OLESTR("Transparent"));

        return *pBstr ? S_OK : E_OUTOFMEMORY;

    case DISPID_ALTERNATECOLOR: // Make Visual Basic apply default formatting
    case DISPID_BACKCOLOR:      //   for these color properties
    case DISPID_CENTERCOLOR: // Otherwise it displays color values in decimal
    case DISPID_FORECOLOR:   // and doesn't draw the color sample correctly
        return S_FALSE;  // This is an undocumented return value that works...
    }

    return IPerPropertyBrowsingImpl<CBullsEye>::GetDisplayString(dispid, 
                                                                 pBstr);
}
 

Figure 13   GetPredefinedStrings and GetPredefinedValue


/************************/
/* GetPredefinedStrings */
/************************/

#define DIM(a) (sizeof(a)/sizeof(a[0]))

static const LPCOLESTR    rszBeepStrings [] = { OLESTR("Yes, make noise"),
                                                OLESTR("No, be mute") };
static const DWORD        rdwBeepCookies [] = { 0, 1 };
static const VARIANT_BOOL rvbBeepValues  [] = { VARIANT_TRUE, VARIANT_FALSE };

static const UINT cBeepStrings = DIM(rszBeepStrings);
static const UINT cBeepCookies = DIM(rdwBeepCookies);
static const UINT cBeepValues  = DIM(rvbBeepValues);

static const LPCOLESTR    rszBackStyleStrings [] = { OLESTR("Opaque"),
                                                     OLESTR("Transparent") };
static const DWORD        rdwBackStyleCookies [] = { 0, 1 };
static const long         rvbBackStyleValues  [] = { 1, 0 };

static const UINT cBackStyleStrings = DIM(rszBackStyleStrings);
static const UINT cBackStyleCookies = DIM(rdwBackStyleCookies);
static const UINT cBackStyleValues  = DIM(rvbBackStyleValues);

STDMETHODIMP CBullsEye::GetPredefinedStrings(/*[in]*/ DISPID dispid,
                                             /*[out]*/ CALPOLESTR *pcaStringsOut,
                                             /*[out]*/ CADWORD *pcaCookiesOut)
{
    ATLTRACE2(atlTraceControls,2,_T("CBullsEye::GetPredefinedStrings\n"));
    if (NULL == pcaStringsOut || NULL == pcaCookiesOut) return E_POINTER;

    ATLASSERT (cBeepStrings == cBeepCookies);
    ATLASSERT (cBeepStrings == cBeepValues);

    ATLASSERT (cBackStyleStrings == cBackStyleCookies);
    ATLASSERT (cBackStyleStrings == cBackStyleValues);

    pcaStringsOut->cElems = 0;
    pcaStringsOut->pElems = NULL;
    pcaCookiesOut->cElems = 0;
    pcaCookiesOut->pElems = NULL;

    HRESULT hr = S_OK;
    switch (dispid) {
    case DISPID_BEEP:
        hr = SetStrings (cBeepValues, rszBeepStrings, pcaStringsOut);
        if (FAILED (hr)) return hr;
        return SetCookies (cBeepValues, rdwBeepCookies, pcaCookiesOut);

    case DISPID_BACKSTYLE:
        hr = SetStrings (cBackStyleValues, rszBackStyleStrings, pcaStringsOut);
        if (FAILED (hr)) return hr;
        return SetCookies (cBackStyleValues, rdwBackStyleCookies, pcaCookiesOut);
    }
    return IPerPropertyBrowsingImpl<CBullsEye>::GetPredefinedStrings(dispid, 
        pcaStringsOut, pcaCookiesOut);
}

/**********************/
/* GetPredefinedValue */
/**********************/

STDMETHODIMP CBullsEye::GetPredefinedValue(DISPID dispid, DWORD dwCookie,
                                           VARIANT* pVarOut)
{
    if (NULL == pVarOut) return E_POINTER;

    ULONG i;
    switch (dispid) {
    case DISPID_BEEP:
        // Walk through cookie array looking for matching value
        for (i = 0; i < cBeepCookies; i++) {
            if (rdwBeepCookies[i] == dwCookie) {
                pVarOut->vt = VT_BOOL;
                pVarOut->boolVal = rvbBeepValues [i];
                return S_OK;
            }
        }
        return E_INVALIDARG;

    case DISPID_BACKSTYLE:
        // Walk through cookie array looking for matching value
        for (i = 0; i < cBackStyleCookies; i++) {
            if (rdwBackStyleCookies[i] == dwCookie) {
                pVarOut->vt = VT_I4;
                pVarOut->lVal = rvbBackStyleValues [i];
                return S_OK;
            }
        }
        return E_INVALIDARG;
    }

    return
        IPerPropertyBrowsingImpl<CBullsEye>::GetPredefinedValue(dispid,
                                                                dwCookie, 
                                                                pVarOut);
}