前面三篇文章,筆者說明了如何使用 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 中有一篇文章做了更清楚的說明。
但不像 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 的事件資訊,而且還可以與宣告於正規區塊的變數做互動 (這代表可以讀取來自事件的資料,以備後面取用)。
實際執行結果為:
Reference:
http://www.codeproject.com/KB/cs/CreateActiveXDotNet.aspx
http://msdn.microsoft.com/en-us/library/ms686567(VS.85).aspx