ActiveX Controls: Using Fonts in an ActiveX Control

OverviewHow Do IFAQSample

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

  1. With your control project open, open ClassWizard by clicking ClassWizard on the View menu.

  2. Click the Automation tab.

  3. In the Class Name box, select your control class name.

  4. Click Add Property.

  5. In the External name box, click Font.

  6. Click OK.

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

  1. With your control project open, open ClassWizard by clicking ClassWizard on the View menu.

  2. Click the Automation tab.

  3. Click Add Property.

  4. In the External name box, click Caption.

  5. Click OK.

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

  1. With your control project open, open ClassWizard by clicking ClassWizard on the View menu.

  2. Click the Automation tab.

  3. Click Add Property.

  4. In the External name box, type a name for the property. For this example, use HeadingFont.

  5. In the Implementation box, click Get/Set Methods.

  6. In the Return Type box, select LPFONTDISP for the property’s type.

  7. Click OK.

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