[.NET] 使用 .NET Framework 開發 ActiveX Control (4) - 開放事件給 JavaScript

前面三篇文章,筆者說明了如何使用 C# 並配合 .NET Framework 來開發 ActiveX Control,相信只要有動手做的讀者現在應該都很快樂的在使用它吧,不過最近有一個新的需求出來:如何由 ActiveX Control 開放事件,並且由 JavaScript 依事件作反應。

前面三篇文章,筆者說明了如何使用 C# 並配合 .NET Framework 來開發 ActiveX Control,相信只要有動手做的讀者現在應該都很快樂的在使用它吧,不過最近有一個新的需求出來:如何由 ActiveX Control 開放事件,並且由 JavaScript 依事件作反應。

在說明如何做之前,筆者還是要簡單說明一下它的原理,COM 的事件 (event) 基本上都是由函式來組成的,和 .NET 不同的是,COM 的事件是使用 Connection Point (連接點) 來處理,概念和 .NET 的事件與委派機制很類似,然而 COM 的連接點是由一個介面 IConnectionPoint 來控制,不像 .NET 只需要做 delegate 與 event 宣告就能使用,在 COM Server (我們寫的 ActiveX 控制項) 內,必須要使用程式碼登錄 IConnectionPoint,COM 核心函式庫即使用此介面登錄的行為來呼叫 COM 用戶端的事件處理常式,以達到事件的目的,這個機制稱為 event sink。在 Code Project 中有一篇文章做了更清楚的說明。

ms686567.1cd44fec-5d2c-4427-846b-ccab7ec0b08a(en-us,VS.85).png

 

但不像 COM Programmer 這麼辛苦,.NET Programmer 只要透過 COM Interoperability 機制,就可以輕鬆的使用現有的 event/delegate 模型來享有開放事件給 COM 用戶端 (含 scripting) 的好處,不過要做的事情會多一點點。我們仍以前面第二篇文章中的程式碼為基礎,來說明如何開放 COM 事件給 COM 用戶端。

 

首先,我們要為事件新增一個介面,這個介面會宣告要開放給 COM 用戶端的事件清單,每個事件都要宣告一個 DispIdAttribute 特徵項資料,以識別不同的事件,但這裡要注意的是,Guid 不可以和控制項相同,否則會無法產生事件,同時也要宣告這個介面的 COM 類型為 IDispatch 介面,以供 scripting 用戶端取用。

[Guid("36425958-6530-4587-8CF0-AFDA8200F69F")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
[ComVisible(true)]
public interface IMyDateControlEvent
{
    [DispId(0)]
    void Event1(string EventArg);
    [DispId(1)]
    void Event2(int EventArg);
    [DispId(2)]
    void Event3(int EventArg1, string EventArg2);
}

 

接下來,我們回到 MyDateControl 的主體,修改 MyDateControl 的 COM 介面類型為雙介面 (AutoDual),讓 Scripting 可存取到 COM 事件,並註冊剛剛宣告的 IMyDateControlEvent 作為要開放的 COM 事件 (備註:如果不需要 JavaScript 回傳資料的話,用 AutoDispatch 也可以)。然後依照我們平常在用的 event/delegate 機制來產生事件以及委派,同時在函式中使用 (備註:事件的委派參數必須要和 COM 事件介面宣告的相同)。

[Guid("C90E96C1-8534-4243-9530-960D9AF982CB")]
[ComVisible(true)]

[ClassInterface(ClassInterfaceType.AutoDual), ComSourceInterfaces(typeof(IMyDateControlEvent))]
public class MyDateControl : IObjectSafety
{
    public delegate void MyDateEventHandler1(string data);
    public delegate void MyDateEventHandler2(int data);
    public delegate void MyDateEventHandler3(int data1, string data2);

    public event MyDateEventHandler1 Event1;
    public event MyDateEventHandler2 Event2;
    public event MyDateEventHandler3 Event3;

    public DateTime Today { get { return DateTime.Today; } }
    public string GetTodayDateString()
    {
        if (Event1 != null)
            Event1("event data");
        if (Event2 != null)
            Event2(12345);
        if (Event3 != null)
            Event3(23456, "event data 2");

        return DateTime.Today.ToString("yyyy/MM/dd HH:mm:ss");
    }

    #region IObjectSafety 成員

    public void GetInterfaceSafetyOptions(int riid, out int pdwSupportedOptions, out int pdwEnabledOptions)
    {
        pdwSupportedOptions = IObjectSafetyEnums.INTERFACESAFE_FOR_UNTRUSTED_CALLER | IObjectSafetyEnums.INTERFACESAFE_FOR_UNTRUSTED_DATA;
        pdwEnabledOptions = IObjectSafetyEnums.INTERFACESAFE_FOR_UNTRUSTED_CALLER | IObjectSafetyEnums.INTERFACESAFE_FOR_UNTRUSTED_DATA;
    }

    public void SetInterfaceSafetyOptions(int riid, int pdwSupportedOptions, int pdwEnabledOptions)
    {
    }

    #endregion
}

 

修改好以後,即可編譯產生新的 ActiveX Control,然後回到用戶端的部份,基於 ActiveX Control 的特性,我們無法直接用 object.event = new function() {…} 的作法來存取 ActiveX Control 的事件,而在網路上有些會教這樣的方法:

function MyControl::MyEvent()
{
    …
}

這個方法在 IE8 會失效,所以大概只有這個方法可用 (紅色粗體字部份):

<script language="javascript" type="text/javascript">

    var v;

    function displayDateFromProperty() {
        alert(document.getElementById("MyDateControl").Today);
    }
    function displayDate() {

        alert(document.getElementById("MyDateControl").GetTodayDateString());
        alert(v);
    }

</script>
<script type="text/javascript" for="MyDateControl" event="Event1(data)">
        alert("Event1 fired, data: " + data);
</script>
<script type="text/javascript" for="MyDateControl" event="Event2(data)">
        alert("Event2 fired, data: " + data);
</script>
<script type="text/javascript" for="MyDateControl" event="Event3(data1, data2)">
        v = "Event3 fired, data1: " + data1 + ", data2: " + data2;
</script>

透過獨立宣告的 JavaScript Event Handler,JavaScript 即可收到來自 ActiveX Control 的事件資訊,而且還可以與宣告於正規區塊的變數做互動 (這代表可以讀取來自事件的資料,以備後面取用)。

實際執行結果為:

image

 

Reference:

http://www.codeproject.com/KB/cs/CreateActiveXDotNet.aspx

http://msdn.microsoft.com/en-us/library/ms686567(VS.85).aspx