MFC ActiveX Controls: Using Fonts

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 the Add Property Wizard. 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

Stock Font properties are preimplemented by the class COleControl. 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 GetFont, SetFont, and InternalGetFont 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 MFC ActiveX Controls: Properties, adding stock properties is easy with the Add Property Wizard. You choose the Font property, and the Add Property Wizard automatically inserts the stock Font entry into the control's dispatch map.

To add the stock Font property using the Add Property Wizard

  1. Load your control's project.

  2. In Class View, expand the library node of your control.

  3. Right-click the interface node for your control (the second node of the library node) to open the shortcut menu.

  4. From the shortcut menu, click Add and then click Add Property.

    This opens the Add Property Wizard.

  5. In the Property Name box, click Font.

  6. Click Finish.

The Add Property Wizard adds the following line to the control's dispatch map, located in the control class implementation file:

DISP_STOCKPROP_FONT()

In addition, the Add Property Wizard adds the following line to the control .IDL file:

[id(DISPID_FONT)] 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 the Add Property Wizard

  1. Load your control's project.

  2. In Class View, expand the library node of your control.

  3. Right-click the interface node for your control (the second node of the library node) to open the shortcut menu.

  4. From the shortcut menu, click Add and then click Add Property.

    This opens the Add Property Wizard.

  5. In the Property Name box, click Caption.

  6. Click Finish.

The Add Property Wizard 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 COleControl::SelectStockFont and pass the control's device context, as shown in the following example:

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

After 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:

Implementing a Custom Font Property

To implement a custom Font property, you use the Add Property Wizard 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 the custom Font property using the Add Property Wizard
  1. Load your control's project.

  2. In Class View, expand the library node of your control.

  3. Right-click the interface node for your control (the second node of the library node) to open the shortcut menu.

  4. From the shortcut menu, click Add and then click Add Property.

    This opens the Add Property Wizard.

  5. In the Property Name box, type a name for the property. For this example, use HeadingFont.

  6. For Implementation Type, click Get/Set Methods.

  7. In the Property Type box, select IDispatch* for the property's type.

  8. Click Finish.

The Add Property Wizard creates the code to add the HeadingFont custom property to the CSampleCtrl class and the SAMPLE.IDL file. Because HeadingFont is a Get/Set property type, the Add Property Wizard modifies the CSampleCtrl class's dispatch map to include a DISP_PROPERTY_EX_IDDISP_PROPERTY_EX macro entry:

DISP_PROPERTY_EX_ID(CMyAxFontCtrl, "HeadingFont", dispidHeadingFont,
   GetHeadingFont, SetHeadingFont, VT_DISPATCH)

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

The Add Property Wizard 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):

IDispatch* CWizardGenCtrl::GetHeadingFont(void)
{
   AFX_MANAGE_STATE(AfxGetStaticModuleState());

   // TODO: Add your dispatch handler code here

   return NULL;
}

void CWizardGenCtrl::SetHeadingFont(IDispatch* /*pVal*/)
{
   AFX_MANAGE_STATE(AfxGetStaticModuleState());

   // TODO: Add your property handler code here

   SetModifiedFlag();
}

Finally, the Add Property Wizard modifies the control .IDL 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.

    CMyAxFontCtrl::CMyAxFontCtrl()
       : m_fontHeading(&m_xFontNotification)
    {
       InitializeIIDs(&IID_DNVC_MFC_AxFont, &IID_DNVC_MFC_AxFontEvents);
    }
    
  • 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 CMyAxFontCtrl::DoPropExchange(CPropExchange* pPX)
    {
       ExchangeVersion(pPX, MAKELONG(_wVerMinor, _wVerMajor));
       COleControl::DoPropExchange(pPX);
    
       // [...other PX_ function calls...]
       PX_Font(pPX, _T("HeadingFont"), m_fontHeading, &_fontdescHeading);
    }
    
  • Finish implementing the control GetHeadingFont member function.

    IDispatch* CMyAxFontCtrl::GetHeadingFont(void)
    {
       AFX_MANAGE_STATE(AfxGetStaticModuleState());
    
       return m_fontHeading.GetFontDispatch();
    }
    
  • Finish implementing the control SetHeadingFont member function.

    void CMyAxFontCtrl::SetHeadingFont(IDispatch* pVal)
    {
       AFX_MANAGE_STATE(AfxGetStaticModuleState());
    
       m_fontHeading.InitializeFont(&_fontdescHeading, pVal);
       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(CMyAxFontCtrl, 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.
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(CMyAxFontCtrl, 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) CMyAxFontCtrl::XHeadingFontNotify::AddRef()
{
   METHOD_MANAGE_STATE(CMyAxFontCtrl, HeadingFontNotify)
      return 1;
}
STDMETHODIMP_(ULONG) CMyAxFontCtrl::XHeadingFontNotify::Release()
{
   METHOD_MANAGE_STATE(CMyAxFontCtrl, HeadingFontNotify)
      return 0;
}

STDMETHODIMP CMyAxFontCtrl::XHeadingFontNotify::QueryInterface(REFIID iid, LPVOID FAR* ppvObj)
{
   METHOD_MANAGE_STATE(CMyAxFontCtrl, HeadingFontNotify)
      if (IsEqualIID(iid, IID_IUnknown) || IsEqualIID(iid, IID_IPropertyNotifySink))
      {
         *ppvObj = this;
         AddRef();
         return NOERROR;
      }
   return ResultFromScode(E_NOINTERFACE);
}

STDMETHODIMP CMyAxFontCtrl::XHeadingFontNotify::OnChanged(DISPID)
{
   METHOD_MANAGE_STATE(CMyAxFontCtrl, HeadingFontNotify)
      pThis->InvalidateControl();
   return NOERROR;
}

STDMETHODIMP CMyAxFontCtrl::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 Testing Properties and Events with Test Container for information on how to access the test container.

See also

MFC ActiveX Controls
MFC ActiveX Controls: Using Pictures in an ActiveX Control
MFC ActiveX Controls: Using Stock Property Pages