MFC ActiveX 控件:使用字体

如果 ActiveX 控件显示文本,可以允许控件用户通过更改字体属性来更改文本外观。 字体属性是作为字体对象实现的,可以是两种类型之一:常用或自定义。 常用字体属性是预先实现的字体属性,你可以使用添加属性向导来添加。 自定义字体属性不是预先实现的,由控件开发人员决定该属性的行为和用法。

本文涵盖以下主题:

使用常用字体属性

常用字体属性是由 COleControl 类预先实现的。 此外,标准字体属性页也可用,用户可以更改字体对象的各种属性,例如它的名称、大小和样式。

通过 COleControlGetFontSetFontInternalGetFont 函数访问字体对象。 控件用户将以与任何其他 Get/Set 属性相同的方式通过 GetFontSetFont 函数访问字体对象。 当需要从控件中访问字体对象时,请使用 InternalGetFont 函数。

MFC ActiveX 控件:属性中所述,使用添加属性向导可以轻松地添加常用属性。 选择字体属性后,添加属性向导会自动将常用字体条目插入到控件的调度映射中。

使用添加属性向导添加常用字体属性

  1. 加载控件的项目。

  2. 在“类视图”中,展开控件的库节点。

  3. 右键单击控件的接口节点(库节点的第二个节点)以打开快捷菜单。

  4. 从快捷菜单中,单击“添加”,然后单击“添加属性”。

    这会打开添加属性向导。

  5. 在“属性名称”框中,单击“字体”

  6. 单击“完成” 。

添加属性向导会将以下行添加到控件的调度映射(位于控件类实现文件中):

DISP_STOCKPROP_FONT()

此外,添加属性向导还会将以下行添加到控件 .IDL 文件中:

[id(DISPID_FONT)] IFontDisp* Font;

常用标题属性是一个可以使用常用字体属性信息绘制的文本属性的示例。 将常用标题属性添加到控件所用的步骤与添加常用字体属性的步骤类似。

若要使用添加属性向导添加常用的 Caption 属性

  1. 加载控件的项目。

  2. 在“类视图”中,展开控件的库节点。

  3. 右键单击控件的接口节点(库节点的第二个节点)以打开快捷菜单。

  4. 从快捷菜单中,单击“添加”,然后单击“添加属性”。

    这会打开添加属性向导。

  5. 在“属性名称”框中,单击“Caption”。

  6. 单击“完成” 。

添加属性向导会将以下行添加到控件的调度映射(位于控件类实现文件中):

DISP_STOCKPROP_CAPTION()

修改 OnDraw 函数

OnDraw 的默认实现对控件中显示的所有文本使用 Windows 系统字体。 这意味着必须通过将字体对象选入设备上下文来修改 OnDraw 代码。 为此,请调用 COleControl::SelectStockFont 并传递控件的设备上下文,如以下示例所示:

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

修改 OnDraw 函数以使用字体对象后,控件中的任何文本均以控件的常用字体属性中的特征显示。

在控件中使用自定义字体属性

除了常用字体属性之外,ActiveX 控件还可以具有自定义字体属性。 若要添加自定义字体属性,必须:

实现自定义字体属性

若要实现自定义字体属性,请使用添加属性向导来添加属性,然后对代码进行一些修改。 下面几个部分介绍了如何将自定义 HeadingFont 属性添加到示例控件。

使用添加属性向导添加自定义字体属性
  1. 加载控件的项目。

  2. 在“类视图”中,展开控件的库节点。

  3. 右键单击控件的接口节点(库节点的第二个节点)以打开快捷菜单。

  4. 从快捷菜单中,单击“添加”,然后单击“添加属性”。

    这会打开添加属性向导。

  5. 在“属性名称”框中,键入属性的名称。 对于本示例,使用“HeadingFont”

  6. 对于“实现类型”,请单击“Get/Set 方法”

  7. 在“属性类型”框中,选择 IDispatch* 作为属性类型

  8. 单击“完成” 。

添加属性向导将创建代码,以将 HeadingFont 自定义属性添加到 CSampleCtrl 类和 SAMPLE.IDL 文件。 由于 HeadingFont 是 Get/Set 属性类型,因此添加属性向导会修改 CSampleCtrl 类的调度映射,包含一个 DISP_PROPERTY_EX_IDDISP_PROPERTY_EX 宏条目:

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

DISP_PROPERTY_EX 宏将 HeadingFont 属性名称与其相应的 CSampleCtrl 类 Get 和 Set 方法、GetHeadingFont 以及 SetHeadingFont 相关联。 此外,还指定了属性值的类型;在本例中为 VT_FONT。

添加属性向导还在控件头文件 (.H) 中添加了 GetHeadingFontSetHeadingFont 函数的声明,并在控件实现文件 (.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();
}

最后,添加属性向导通过添加 HeadingFont 属性的条目来修改控件 .IDL 文件:

[id(1)] IDispatch* HeadingFont;

对控件代码的修改

HeadingFont 属性添加到控件后,必须对控件头和实现文件进行一些更改,才能完全支持新属性。

在控件头文件 (.H) 中,添加受保护成员变量的以下声明:

protected:
   CFontHolder m_fontHeading;

在控件实现文件 (.CPP) 中,执行以下操作:

  • 初始化控件构造函数中的 m_fontHeading

    CMyAxFontCtrl::CMyAxFontCtrl()
       : m_fontHeading(&m_xFontNotification)
    {
       InitializeIIDs(&IID_DNVC_MFC_AxFont, &IID_DNVC_MFC_AxFontEvents);
    }
    
  • 声明一个包含字体的默认属性的静态 FONTDESC 结构。

    static const FONTDESC _fontdescHeading =
    { sizeof(FONTDESC), OLESTR("MS Sans Serif"), FONTSIZE(12), FW_BOLD,
      ANSI_CHARSET, FALSE, FALSE, FALSE };
    
  • 在控件 DoPropExchange 成员函数中,添加一个对 PX_Font 函数的调用。 这将为自定义字体属性提供初始化和持久性。

    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);
    }
    
  • 完成控件 GetHeadingFont 成员函数的实现。

    IDispatch* CMyAxFontCtrl::GetHeadingFont(void)
    {
       AFX_MANAGE_STATE(AfxGetStaticModuleState());
    
       return m_fontHeading.GetFontDispatch();
    }
    
  • 完成控件 SetHeadingFont 成员函数的实现。

    void CMyAxFontCtrl::SetHeadingFont(IDispatch* pVal)
    {
       AFX_MANAGE_STATE(AfxGetStaticModuleState());
    
       m_fontHeading.InitializeFont(&_fontdescHeading, pVal);
       OnFontChanged();    //notify any changes
       SetModifiedFlag();
    }
    
  • 修改控件 OnDraw 成员函数以定义用于保留之前选定的字体的变量。

    CFont* pOldHeadingFont;
    
  • 修改控件 OnDraw 成员函数,在要使用字体的任何位置添加以下行,将自定义字体选入设备上下文。

    pOldHeadingFont = SelectFontObject(pdc, m_fontHeading);
    
  • 修改控件 OnDraw 成员函数,通过在已使用字体后添加以下行,将之前的字体选择回设备上下文中。

    pdc->SelectObject(pOldHeadingFont);
    

实现自定义字体属性后,应实现标准字体属性页,使控件用户能够更改控件的当前字体。 若要为标准字体属性页添加属性页 ID,请在 BEGIN_PROPPAGEIDS 宏后面插入下面一行:

PROPPAGEID(CLSID_CFontPropPage)

还必须以 1 为增量逐渐增加 BEGIN_PROPPAGEIDS 宏的计数参数。 下面一行阐释了这一点:

BEGIN_PROPPAGEIDS(CMyAxFontCtrl, 2)

完成这些更改后,重新生成整个项目以合并其他功能。

处理字体通知

在大多数情况下,控件需要知道何时修改了字体对象的特征。 每个字体对象都可以通过调用 IFontNotification 接口的成员函数(由 COleControl 实现)来提供更改通知。

如果控件使用常用字体属性,则其通知由 COleControlOnFontChanged 成员函数处理。 当你添加自定义字体属性时,可以让它们使用相同的实现。 在上一部分的示例中,这是通过在初始化 m_fontHeading 成员变量时传递 &m_xFontNotification 来实现的。

Implementing multiple font object interfaces.
实现多个字体对象接口

上图中的实线显示,两个字体对象都使用 IFontNotification 的相同实现。 如果你要区分哪个字体已更改,这可能会导致问题。

区分控件的字体对象通知的一种方法是为控件中的每个字体对象创建一个单独的 IFontNotification 接口实现。 借助此方法,只更新使用最近修改的字体的一个或多个字符串,就能优化绘制代码。 下面几个部分演示了为第二个字体属性实现单独的通知接口所需的步骤。 假定第二个字体属性是上一部分中添加的 HeadingFont 属性。

实现新的字体通知接口

为了区分两种或更多字体的通知,必须为控件中使用的每个字体实现一个新的通知接口。 下面几个部分介绍了如何通过修改控件头和实现文件来实现新的字体通知接口。

头文件的新增内容

在控件头文件 (.H) 中,将以下行添加到类声明:

protected:
   BEGIN_INTERFACE_PART(HeadingFontNotify, IPropertyNotifySink)
      INIT_INTERFACE_PART(CMyAxFontCtrl, HeadingFontNotify)
      STDMETHOD(OnRequestEdit)(DISPID);
   STDMETHOD(OnChanged)(DISPID);
   END_INTERFACE_PART(HeadingFontNotify)

这将创建名为 HeadingFontNotifyIPropertyNotifySink 接口的实现。 这个新接口包含一个名为 OnChanged 的方法。

实现文件的新增内容

在初始化标题字体(在控件构造函数中)的代码中,将 &m_xFontNotification 更改为 &m_xHeadingFontNotify。 然后,添加以下代码:

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

IPropertyNotifySink 接口中的 AddRefRelease 方法跟踪 ActiveX 控件对象的引用计数。 当控件获取对接口指针的访问权限时,控件调用 AddRef 来增加引用计数值。 当控件完成对指针的操作后,它会调用 Release(其方式与调用 GlobalFree 相同)来释放全局内存块。 当此接口的引用计数值变为零时,可以释放接口实现。 在本示例中,QueryInterface 函数返回一个指向特定对象上的 IPropertyNotifySink 接口的指针。 此函数允许 ActiveX 控件查询对象,确定它支持哪些接口。

对项目进行这些更改后,重新生成项目并使用测试容器来测试接口。 请参阅 使用测试容器测试属性和事件 了解有关如何访问测试容器的信息。

另请参阅

MFC ActiveX 控件
MFC ActiveX 控件:在 ActiveX 控件中使用图片
MFC ActiveX 控件:使用常用属性页