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
}
};
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);
}