属性表和属性页

对象的属性通过 COM 接口或对象的 IDispatch 实现向客户端公开,从而允许通过调用这些方法的程序更改属性。 属性页的 OLE 技术提供根据 Windows 用户界面标准为对象的属性生成用户界面的方法。 因此,这些属性向最终用户公开。 对象的属性表是选项卡式对话框,其中每个选项卡对应特定的属性页。 用于处理属性页的 OLE 模型包含以下功能:

  • 每个属性页由实现 IPropertyPageIPropertyPage2 的进程内对象管理。 每个页面都使用自己的唯一 CLSID 进行标识。
  • 对象通过实现 ISpecifyPropertyPages 来指定对属性页的支持。 通过此接口,调用方可以获取标识对象支持的特定属性页的 CLSID 列表。 如果对象指定属性页 CLSID,该对象必须能够从属性页接收属性更改。
  • 要显示对象的属性表的任何代码段(客户端或对象)都会将对象的 IUnknown 指针(如果要影响多个对象,则是数组)以及属性页 CLSID 的数组传递到 OleCreatePropertyFrameOleCreatePropertyFrameIndirect,以创建选项卡式对话框。
  • 属性帧对话框在每个 CLSID 上使用 CoCreateInstance 实例实例化每个属性页的单个实例。 属性帧每个页面至少获取一个 IPropertyPage 指针。 此外,该帧还会为每个页面创建一个自身的属性页网站对象。 每个网站实现 IPropertyPageSite,此指针将传递给每个页面。 然后,该页面通过此接口指针与网站通信。
  • 每个页面还都知道已为其调用的一个或多个对象;也就是说,属性帧将对象的 IUnknown指针传递给每个页面。 当指示对对象应用更改时,每个页面都会查询相应的接口指针,并根据需要以任何方式将新的属性值传递给对象。 未规定如何进行这类通信。
  • 对象还可以支持按属性浏览 IPerPropertyBrowsing 接口,从而允许对象指定在显示属性页时应接收初始焦点的属性,以及指定客户端可以在其自己的用户界面中显示的字符串和值。

下图说明了这些功能:

Diagram that shows the property sheets and property pages features.

这些接口定义如下:

interface ISpecifyPropertyPages : IUnknown 
  { 
    HRESULT GetPages([out] CAUUID *pPages); 
  }; 
 
 
interface IPropertyPage : IUnknown 
  { 
    HRESULT SetPageSite([in] IPropertyPageSite *pPageSite); 
    HRESULT Activate([in] HWND hWndParent, [in] LPCRECT prc 
        , [in] BOOL bModal); 
    HRESULT Deactivate(void); 
    HRESULT GetPageInfo([out] PROPPAGEINFO *pPageInfo); 
    HRESULT SetObjects([in] ULONG cObjects 
        , [in, max_is(cObjects)] IUnknown **ppunk); 
    HRESULT Show([in] UINT nCmdShow); 
    HRESULT Move([in] LPCRECT prc); 
    HRESULT IsPageDirty(void); 
    HRESULT Apply(void); 
    HRESULT Help([in] LPCOLESTR pszHelpDir); 
    HRESULT TranslateAccelerator([in] LPMSG pMsg); 
  } 
 
interface IPropertyPageSite : IUnknown 
  { 
    HRESULT OnStatusChange([in] DWORD dwFlags); 
    HRESULT GetLocaleID([out] LCID *pLocaleID); 
    HRESULT GetPageContainer([out] IUnknown **ppUnk); 
    HRESULT TranslateAccelerator([in] LPMSG pMsg); 
  } 
 

ISpecifyPropertyPages::GetPages 方法返回 UUID (GUID) 值的计数数组,其中每个值描述对象要显示的属性页的 CLSID。 使用 OleCreatePropertyFrameOleCreatePropertyFrameIndirect 调用属性表的人员将此数组传递给相应的函数。 请注意,如果调用方希望显示多个对象的属性页,则只能将所有对象的 CLSID 列表的交集传递给这些函数。 换句话说,调用方只能调用所有对象通用的属性页。

此外,调用方还会将指向受影响的对象的 IUnknown 指针传递给 API 函数。 这两个 API 函数都会创建属性帧对话框,并使用 IPropertyPageSite 为将加载的每个页面实例化一个页面网站。 通过此接口,属性页可以:

  • 通过 GetLocaleID 检索属性表中使用的当前语言。
  • 通过 TranslateAccelerator 要求帧处理击键。
  • 通过 OnStatusChange 通知帧页面上的更改。
  • 通过 GetPageContainer 获取帧本身的接口指针,尽管目前没有为该帧定义让此函数始终返回 E_NOTIMPL 的接口。

属性帧实例化每个属性页对象,并获取每个页面的 IPropertyPage 接口。 通过此接口,该帧通知页面其页面网站 (SetPageSite)、检索页面维度和字符串 (GetPageInfo)、将接口指针传递给受影响的对象 (SetObjects)、告知页面何时创建和销毁其控件(ActivateDeactivate)、指示页面显示或重新定位自身(ShowMove)、指示页面对受影响对象应用当前值 (Apply)、检查页面上的脏状态 (IsPageDirty)、调用帮助 (Help),并将击键传递给页面 (TranslateAccelerator)。

对象还可以支持按属性浏览,从而提供:

  1. 一种指定在首次显示属性表时应向哪个属性页上的哪个属性提供初始焦点的方法(通过 IPerPropertyBrowsingIPropertyPage2
  2. 一种帮助对象指定可以在客户端自己的属性用户界面中显示的预定义值和相应的描述性字符串的方法(通过 IPerPropertyBrowsing)。

对象可以选择支持 (2),而不支持 (1),例如当对象没有属性表时。

IPropertyPage2IPerPropertyBrowsing 接口定义如下:

interface IPerPropertyBrowsing : IUnknown 
  { 
    HRESULT GetDisplayString([in] DISPID dispID, [out] BSTR *pbstr); 
    HRESULT MapPropertyToPage([in] DISPID dispID, [out] CLSID *pclsid); 
    HRESULT GetPredefinedStrings([in] DISPID dispID, [out] CALPOLESTR *pcaStringsOut, [out] CADWORD *pcaCookiesOut); 
    HRESULT GetPredefinedValue([in] DISPID dispID, [in] DWORD dwCookie, [out] VARIANT *pvarOut); 
  } 
 
interface IPropertyPage2 : IPropertyPage 
  { 
    HRESULT EditProperty([in] DISPID dispID); 
  } 
 

若要指定对此类功能的支持,该对象将实现 IPerPropertyBrowsing。 通过此接口,调用方可以请求实现浏览所需的信息,例如预定义字符串 (GetPredefinedStrings) 和值 (GetPredefinedValue),以及给定属性 (GetDisplayString) 的显示字符串。

此外,客户端还可以获取属性页的 CLSID,以允许用户编辑使用 DISPID (MapPropertyToPage) 标识的给定属性。 然后,客户端通过将 CLSID 和 DISPID 传递给 OleCreatePropertyFrameIndirect,指示最初激活该页的属性帧。 该帧首先激活该页面,并通过 IPropertyPage2::EditProperty 将 DISPID 传递给页面。 然后,页面将焦点设置为该属性的编辑字段。 这样,客户端就可以从其自己的用户界面中的属性名称跳转到可以操作该属性的属性页。

属性页和属性表