COM組件設計與應用(十八)——屬性包

http://www.vckbase.com/code/downcode.asp?id=2785  下載源碼

一、前言   書接上回,本回着落在介紹屬性包 IPersistPropertyBag 接口的實現方法和調用方式。屬性包,是以“名稱 - 值”的方式提供組件持續性的支持,而“名稱 - 值”恰恰又適合於用文本方式來表現。下面的片段是在 HTML 中插入 Microsoft MonthView Control ActiveX 控件後的樣式:

<object classid="clsid:232E456A-87C3-11D1-8BE3-0000F8754DA1" id="MonthView1">
 <param name="_ExtentX" value="9393">
 <param name="_ExtentY" value="4974">
 <param name="_Version" value="393216">
 <param name="ForeColor" value="0">
 <param name="MaxSelCount" value="7">
 <param name="MonthColumns" value="1">
 <param name="CurrentDate" value="38632">
 <param name="MaxDate" value="2958465">
 <param name="MinDate" value="-53688">
</object>

以文本方式保存組件屬性,比較直觀、容易修改,上面 HTML 示例中的 <param name="屬性名" value="值"> 就很清晰。下面開始介紹如何在組件中實現 IPersistPropertyBag 接口。 二、組件的實現 (1)vc6.0 開發步驟 1、建立一個工作空間(WorkSpace)。 2、在這個工作空間中,建立 ATL 工程,示例程序工程爲 Simple18。 3、增加 ATL 對象類,默認全部選項。示例程序中的 ATL 對象短名稱是 Property。 4、增加一些屬性。在以前的章回中,我們只介紹了增加接口函數的方法,由於今天是首次增加接口屬性,所以稍微細緻一些。步驟是,在ClassView卡片中選擇接口(IProperty)後,執行鼠標右鍵菜單"Add Property..." 5、增加 BSTR 類型的接口屬性 str,同樣的方式,再增加一個 long 型的接口屬性 interger。在示例程序中,這兩個屬性其實只爲演示,並沒有實際的意義。 6、接口中的屬性,多數情況下會對應對象內部的一個成員變量,因此我們現在要添加成員變量。選擇對象類名,執行鼠標右鍵菜單"Add Member Variable...." 7、添加兩個成員變量,一個是 CComBSTR m_str 對應於接口屬性 str;另一個是 long m_integer 對應於接口屬性 integer。 (2)vc.net 開發步驟 1、建立一個空白解決方案。 2、在解決方案中,新增 ATL 項目。示例程序中項目名稱叫 Simple18, 注意不要選擇“屬性化編程”方式。 3、添加 ATL 類。選擇 “ATL 的簡單對象”。默認全部選項。示例程序中 ATL 類短名稱爲 Property,類名稱爲 CMyProperty。(注1) 4、 增加一些屬性。在以前的章回中,我們只介紹了增加接口函數的方法,由於今天是首次增加接口屬性,所以稍微細緻一些。步驟是,在類視圖卡片中選擇接口(IProperty)後,執行鼠標右鍵菜單"添加屬性..." 5、增加 BSTR 類型的接口屬性 str,同樣的方式,再增加一個 long 型的接口屬性 interger。在示例程序中,這兩個屬性其實只爲演示,並沒有實際的意義。 6、接口中的屬性,多數情況下會對應對象內部的一個成員變量,因此我們現在要添加成員變量。選擇對象類名,執行鼠標右鍵菜單"添加變量...." 7、添加兩個成員變量,一個是 CComBSTR m_str 對應於接口屬性 str;另一個是 long m_integer 對應於接口屬性 integer。 (3)實現代碼   至此,我們組件的框架已經完成,下面該完成函數函數的實現了:

STDMETHODIMP Cxxx::get_str(BSTR* pVal)
{
 *pVal = m_str.Copy();
 return S_OK;
}

STDMETHODIMP Cxxx::put_str(BSTR newVal)
{
 m_str = newVal;
 return S_OK;
}

STDMETHODIMP Cxxx::get_integer(LONG* pVal)
{
 *pVal = m_integer;
 return S_OK;
}

STDMETHODIMP Cxxx::put_integer(LONG newVal)
{
 m_integer = newVal;
 return S_OK;
}

沒有什麼複雜的,就是實現 str、integer 兩個屬性值的設置和讀取功能。 (4)添加 IPersistPropertyBag 接口   還記得我們在上回書中如何添加 IPersistStreamInit 的嗎?添加 IPersistPropertyBag 的方法也一樣,但這次我們換一個方式,即我們不從 IPersistPropertyBag 派生,而是從 IPersistPropertyBagImpl<> 派生。在 ATL 中,系統幫我們已經完成了很多接口的默認實現,我們只要從 IxxxImpl<> 派生,然後再添加一些必要的映射和變量,就可以了。這樣顯然要比自己去實現接口的所有函數要簡單許多了。其實,如果你明白了本回 IPersistPropertyBagImpl<> 派生的方法後,你完全可以修改前回書中的實現方法,從 IPersistStreamInit 派生改進爲從 IPersistStreamInitImpl<> 派生。

class ATL_NO_VTABLE Cxxx : 
 public CComObjectRootEx<...>,
 public CComCoClass<...>,
 public IDispatchImpl<...>,
 public IPersistPropertyBagImpl<Cxxx> // 手工添加派生類
{
... ... ...

BEGIN_COM_MAP(Cxxx)
 ... ... ...
 COM_INTERFACE_ENTRY(IPersistPropertyBag) // 手工添加接口表
END_COM_MAP()
... ... ...
 // 手工添加屬性映射表,這是 IPersistXXXImpl 所必須的。
 // 將來你在寫 ActiveX 的時候,ATL 嚮導會幫我們添加屬性映射表
BEGIN_PROP_MAP(Cxxx)
 // 參數:"屬性名稱", 接口屬性序號(見IDL文件), 屬性頁對話窗
 PROP_ENTRY("str", 1, CLSID_NULL)
 PROP_ENTRY("integer", 2, CLSID_NULL)
END_PROP_MAP()
... ... ...
public:
 ... ... ...
 // 這個成員變量,是 IPersistXXXImpl 所必須的
 bool m_bRequiresSave; // 表示屬性數據是否已經改變而需要保存
};

  我們只要手工添加以上內容,而不用自己寫任何 IPersistPropertyBag 接口的函數,多簡單呀!天空出彩霞呀,地上開紅花呀......會唱這隻歌的同學請舉手,每個人獎勵 vckbase 的專家分 500 ! 三、調用者的實現   我們在閱讀 MSDN 關於 IPersistPropertyBag 接口函數的時候,你會發現還需要一個接口 IPropertyBag 與之配合才能實現屬性包功能。而 IPropertyBag 則需要我們在調用者(容器)中來實現該接口。它們之間的關係如下:   前面幾回書中,我們已經學會了從 IUnknown 派生類,也學會了從 IDispatch 派生類,也學會了從 ICallBack 派生類......同樣,這回我們要從 IPropertyBag 派生了。在示例程序中,我們添加了一個類 CPropertyBag::public IPropertyBag,同時重載了所有的虛函數。

 

STDMETHODIMP CPropertyBag::QueryInterface(const struct _GUID &iid,void ** ppv)
{
 *ppv = this;
 return S_OK;
}

ULONG __stdcall CPropertyBag::AddRef(void)
{ return 1;  } // 做個假的就可以,因爲反正這個對象在程序結束前是不會退出的

ULONG __stdcall CPropertyBag::Release(void)
{ return 0;  } // 做個假的就可以,因爲反正這個對象在程序結束前是不會退出的

STDMETHODIMP CPropertyBag::Read(LPCOLESTR pszPropName,VARIANT *pVar,IErrorLog *pErrorLog)
{
 // 根據 pszPropName 指定的屬性名稱,你要提供該屬性的值。
 // 而值的數據類型已經在 pVal->vt 中指定了。
 if( 如果能提供指定的數據 ) return S_OK;
 else return E_FAIL;
}

STDMETHODIMP CPropertyBag::Write(LPCOLESTR pszPropName,VARIANT *pVar)
{
 // 根據 psaPropName 指定的屬性名稱和 pVar 提供的值
 // 你保存到文本中去吧。
 return S_OK;
}

  以上是調用者(容器)程序的關鍵部分,其它的管理和協調部分,讀者去閱讀示例程序代碼。編譯註冊組件,並運行調用者示例程序,顯示如下:   在編輯窗口中你可以隨便指定 str 和 interger 的值,然後“啓動組件”,那麼你設定的屬性值就會在啓動組件的同時,通過 IPersistPropertyBag 接口設置到組件中(還原了持續性的環境)。而後,你就可以在下面的 Property 分組操作中,“設置/讀取”組件的屬性了。當“關閉組件”的時候,程序通過調用 IPersistPropertyBag 接口函數,又重新取得組件的屬性名稱和值保存到編輯窗的文本中了。 四、小結   理解了本回屬性包接口的功能,你就能體會出 IE 是如何裝載 ActiveX (注2)控件並設置控件的狀態了。


注1:在 vc.net 中,由於系統已經有 CProperty 類,所以這裏我們改換名稱爲 CMyProperty。 注2:通過十八回的學習,我們已經瞭解組件的一些常用接口,爲我們學習 ActiveX 的組件編程打下了基礎。下回書,我們就開始學習 ActiveX。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章