ActiveX Controls: Using Fonts in an ActiveX Control
| Overview | How Do I | FAQ | | Sample
If your ActiveX control displays text, you can allow the control user to change the text appearance by changing a font property. Font properties are implemented as font objects and can be one of two types: stock or custom. Stock Font properties are preimplemented font properties that you can add using ClassWizard. Custom Font properties are not preimplemented and the control developer determines the property’s behavior and usage.
This article covers the following topics:
Using the stock Font property
Using custom font properties in your control
Using the Stock Font Property
Stock Font properties are preimplemented by the class . In addition, a standard Font property page is also available, allowing the user to change various attributes of the font object, such as its name, size, and style.
Access the font object through the , , and functions of COleControl. The control user will access the font object via the GetFont
and SetFont
functions in the same manner as any other Get/Set property. When access to the font object is required from within a control, use the InternalGetFont function.
As discussed in ActiveX Controls: Properties, adding stock properties is easy with ClassWizard’s Automation page. You choose the Font property, and ClassWizard automatically inserts the stock Font entry into the control’s dispatch map.
To add the stock Font property using ClassWizard
With your control project open, open ClassWizard by clicking ClassWizard on the View menu.
Click the Automation tab.
In the Class Name box, select your control class name.
Click Add Property.
In the External name box, click Font.
Click OK.
Click OK to confirm your choices and close ClassWizard.
ClassWizard adds the following line to the control’s dispatch map, located in the control class implementation file:
DISP_STOCKPROP_FONT()
In addition, ClassWizard adds the following line to the control .ODL file:
[id(DISPID_FONT), bindable] IFontDisp* Font;
The stock Caption property is an example of a text property that can be drawn using the stock Font property information. Adding the stock Caption property to the control uses steps similar to those used for the stock Font property.
To add the stock Caption property using ClassWizard
With your control project open, open ClassWizard by clicking ClassWizard on the View menu.
Click the Automation tab.
Click Add Property.
In the External name box, click Caption.
Click OK.
Click OK to confirm your choices and close ClassWizard.
ClassWizard adds the following line to the control’s dispatch map, located in the control class implementation file:
DISP_STOCKPROP_CAPTION()
Modifying the OnDraw Function
The default implementation of OnDraw
uses the Windows system font for all text displayed in the control. This means that you must modify the OnDraw
code by selecting the font object into the device context. To do this, call and pass the control’s device context, as shown in the following example:
void CSampleCtrl::OnDraw( CDC* pdc, const CRect& rcBounds, const CRect& rcInvalid)
{
CFont* pOldFont;
TEXTMETRIC tm;
const CString& strCaption = InternalGetText();
pOldFont = SelectStockFont( pdc );
pdc->FillRect(rcBounds, CBrush::FromHandle(
(HBRUSH )GetStockObject(WHITE_BRUSH)));
pdc->Ellipse(rcBounds);
pdc->GetTextMetrics(&tm);
pdc->SetTextAlign(TA_CENTER | TA_TOP);
pdc->ExtTextOut((rcBounds.left + rcBounds.right) / 2,
(rcBounds.top + rcBounds.bottom - tm.tmHeight) / 2,
ETO_CLIPPED, rcBounds, strCaption, strCaption.GetLength(),
NULL);
pdc->SelectObject(pOldFont);
}
Once the OnDraw
function has been modified to use the font object, any text within the control is displayed with characteristics from the control’s stock Font property.
Using Custom Font Properties in Your Control
In addition to the stock Font property, the ActiveX control can have custom Font properties. To add a custom font property you must:
Use ClassWizard to implement the custom Font property
Process standard font change notifications
Implement a new Font notification interface
Implementing a Custom Font Property
To implement a custom Font property, you use ClassWizard to add the property and then make some modifications to the code. The following sections describe how to add the custom HeadingFont
property to the Sample control.
To add a custom font property
With your control project open, open ClassWizard by clicking ClassWizard on the View menu.
Click the Automation tab.
Click Add Property.
In the External name box, type a name for the property. For this example, use HeadingFont.
In the Implementation box, click Get/Set Methods.
In the Return Type box, select LPFONTDISP for the property’s type.
Click OK.
Click OK to confirm your choices and close ClassWizard.
ClassWizard will create the code to add the HeadingFont
custom property to the CSampleCtrl
class and the SAMPLE.ODL file. Since HeadingFont
is a Get/Set property type, ClassWizard modifies the CSampleCtrl
class’s dispatch map to include a macro entry:
BEGIN_DISPATCH_MAP(CSampleCtrl, COleControl)
//{{AFX_DISPATCH_MAP(CSampleCtrl)
DISP_PROPERTY_EX(CSampleCtrl, "HeadingFont", GetHeadingFont,
SetHeadingFont, VT_DISPATCH)
//}}AFX_DISPATCH_MAP
END_DISPATCH_MAP()
The DISP_PROPERTY_EX macro associates the HeadingFont
property name with its corresponding CSampleCtrl
class Get and Set methods, GetHeadingFont
and SetHeadingFont
. The type of the property value is also specified; in this case, VT_DISPATCH.
ClassWizard also adds a declaration in the control header file (.H) for the GetHeadingFont
and SetHeadingFont
functions and adds their function templates in the control implementation file (.CPP):
LPDISPATCH CSampleCtrl::GetHeadingFont()
{
// TODO: Add your property handler here
return NULL;
}
void CSampleCtrl::SetHeadingFont(LPDISPATCH newValue)
{
// TODO: Add your property handler here
SetModifiedFlag();
}
Finally, ClassWizard modifies the control .ODL file by adding an entry for the HeadingFont
property:
[id(1)] IDispatch* HeadingFont;
Modifications to the Control Code
Now that you have added the HeadingFont
property to the control, you must make some changes to the control header and implementation files to fully support the new property.
In the control header file (.H), add the following declaration of a protected member variable:
protected:
CFontHolder m_fontHeading;
In the control implementation file (.CPP), do the following:
Initialize
m_fontHeading
in the control constructor.CSampleCtrl::CSampleCtrl( ) : m_fontHeading( &m_xFontNotification ) { // [...body of constructor...] }
Declare a static FONTDESC structure containing default attributes of the font.
static const FONTDESC _fontdescHeading = { sizeof(FONTDESC), OLESTR("MS Sans Serif"), FONTSIZE( 12 ), FW_BOLD, ANSI_CHARSET, FALSE, FALSE, FALSE };
In the control
DoPropExchange
member function, add a call to the PX_Font function. This provides initialization and persistence for your custom Font property.void CSampleCtrl::DoPropExchange(CPropExchange* pPX) { COleControl::DoPropExchange(pPX); // [...other PX_ function calls...] PX_Font(pPX, _T("HeadingFont"), m_fontHeading, &_fontdescHeading); }
Finish implementing the control
GetHeadingFont
member function.LPFONTDISP CSampleCtrl::GetHeadingFont( ) { return m_fontHeading.GetFontDispatch( ); }
Finish implementing the control
SetHeadingFont
member function.void CSampleControl::SetHeadingFont( LPFONTDISP newValue ) { m_fontHeading.InitializeFont( &_fontdescHeading, newValue); OnFontChanged(); //notify any changes SetModifiedFlag( ); }
Modify the control
OnDraw
member function to define a variable to hold the previously selected font.CFont* pOldHeadingFont;
Modify the control
OnDraw
member function to select the custom font into the device context by adding the following line wherever the font is to be used.pOldHeadingFont = SelectFontObject(pdc, m_fontHeading);
Modify the control
OnDraw
member function to select the previous font back into the device context by adding the following line after the font has been used.pdc->SelectObject(pOldHeadingFont);
After the custom Font property has been implemented, the standard Font property page should be implemented, allowing control users to change the control’s current font. To add the property page ID for the standard Font property page, insert the following line after the BEGIN_PROPPAGEIDS macro:
PROPPAGEID(CLSID_CFontPropPage)
You must also increment the count parameter of your BEGIN_PROPPAGEIDS macro by one. The following line illustrates this:
BEGIN_PROPPAGEIDS(CSampleCtrl, 2)
After these changes have been made, rebuild the entire project to incorporate the additional functionality.
Processing Font Notifications
In most cases the control needs to know when the characteristics of the font object have been modified. Each font object is capable of providing notifications when it changes by calling a member function of the IFontNotification interface, implemented by COleControl.
If the control uses the stock Font property, its notifications are handled by the OnFontChanged member function of COleControl. When you add custom font properties, you can have them use the same implementation. In the example in the previous section, this was accomplished by passing &m_xFontNotification when initializing the m_fontHeading member variable.
Implementing Multiple Font Object Interfaces
The solid lines in the figure above show that both font objects are using the same implementation of IFontNotification. This could cause problems if you wanted to distinguish which font changed.
One way to distinguish between the control’s font object notifications is to create a separate implementation of the IFontNotification interface for each font object in the control. This technique allows you to optimize your drawing code by updating only the string, or strings, that use the recently modified font. The following sections demonstrate the steps necessary to implement separate notification interfaces for a second Font property. The second font property is assumed to be the HeadingFont
property that was added in the previous section.
Implementing a New Font Notification Interface
To distinguish between the notifications of two or more fonts, a new notification interface must be implemented for each font used in the control. The following sections describe how to implement a new font notification interface by modifying the control header and implementation files.
Additions to the Header File
In the control header file (.H), add the following lines to the class declaration:
protected:
BEGIN_INTERFACE_PART(HeadingFontNotify, IPropertyNotifySink)
INIT_INTERFACE_PART(CSampleCtrl, HeadingFontNotify)
STDMETHOD(OnRequestEdit)(DISPID);
STDMETHOD(OnChanged)(DISPID);
END_INTERFACE_PART(HeadingFontNotify)
This creates an implementation of the IPropertyNotifySink interface called HeadingFontNotify
. This new interface contains a method called OnChanged
.
Additions to the Implementation File
In the code that initializes the heading font (in the control constructor), change &m_xFontNotification
to &m_xHeadingFontNotify
. Then add the following code:
STDMETHODIMP_(ULONG) CSampleCtrl::XHeadingFontNotify::AddRef( )
{
METHOD_MANAGE_STATE(CSampleCtrl, HeadingFontNotify)
return 1;
}
STDMETHODIMP_(ULONG) CSampleCtrl::XHeadingFontNotify::Release( )
{
METHOD_MANAGE_STATE(CSampleCtrl, HeadingFontNotify)
return 0;
}
STDMETHODIMP CSampleCtrl::XHeadingFontNotify::QueryInterface( REFIID iid, LPVOID FAR* ppvObj )
{
METHOD_MANAGE_STATE( CSampleCtrl, HeadingFontNotify )
if( IsEqualIID( iid, IID_IUnknown ) ||
IsEqualIID( iid, IID_IPropertyNotifySink))
{
*ppvObj= this;
AddRef( );
return NOERROR;
}
return ResultFromScode(E_NOINTERFACE);
}
STDMETHODIMP CSampleCtrl::XHeadingFontNotify::OnChanged(DISPID)
{
METHOD_MANAGE_STATE( CSampleCtrl, HeadingFontNotify )
pThis->InvalidateControl( );
return NOERROR;
}
STDMETHODIMP CSampleCtrl::XHeadingFontNotify::OnRequestEdit(DISPID)
{
return NOERROR;
}
The AddRef
and Release
methods in the IPropertyNotifySink interface keep track of the reference count for the ActiveX control object. When the control obtains access to interface pointer, the control calls AddRef
to increment the reference count. When the control is finished with the pointer, it calls Release
, in much the same way that GlobalFree might be called to free a global memory block. When the reference count for this interface goes to zero, the interface implementation can be freed. In this example, the QueryInterface
function returns a pointer to a IPropertyNotifySink interface on a particular object. This function allows an ActiveX control to query an object to determine what interfaces it supports.
After these changes have been made to your project, rebuild the project and use Test Container to test the interface.
See Also ActiveX Controls: Using Pictures in an ActiveX Control, ActiveX Controls: Using Stock Property Pages