ArcEngine 眾多元件 (如 MapControl、Workspace) 透過 COM 事件 (Connection Point) 通知外部變更。本文示範如何在 VC++ (ATL/MFC) 中實作 IWorkspaceEditEvents 接口,並透過 AtlAdvise / AtlUnadvise 建立/移除事件連接,達到監聽編輯狀態 (開始/停止/復原/重做) 的目的。
struct __declspec(uuid("0B437962-89F9-11D4-8B5F-000000000000"))
IWorkspaceEditEvents : IUnknown {
virtual HRESULT __stdcall OnStartEditing(VARIANT_BOOL withUndoRedo) = 0;
virtual HRESULT __stdcall OnStopEditing(VARIANT_BOOL saveEdits) = 0;
virtual HRESULT __stdcall OnStartEditOperation() = 0;
virtual HRESULT __stdcall OnAbortEditOperation() = 0;
virtual HRESULT __stdcall OnStopEditOperation() = 0;
virtual HRESULT __stdcall OnUndoEditOperation() = 0;
virtual HRESULT __stdcall OnRedoEditOperation() = 0;
};
#include <atlbase.h>
#include <atlcom.h>
class CAEEditEvents :
public CComObjectRootEx<CComSingleThreadModel>,
public IWorkspaceEditEvents
{
public:
CAEEditEvents() {}
~CAEEditEvents() {}
BEGIN_COM_MAP(CAEEditEvents)
COM_INTERFACE_ENTRY(IWorkspaceEditEvents)
END_COM_MAP()
// STDMETHOD 宏展開為 virtual HRESULT __stdcall
STDMETHOD(OnStartEditing)(VARIANT_BOOL withUndoRedo) {
::MessageBox(NULL, _T("開始編輯了呢?"), _T("注意!!!"), MB_OK);
return S_OK; // 建議回傳 S_OK 而非 E_NOTIMPL
}
STDMETHOD(OnStopEditing)(VARIANT_BOOL saveEdits) { return S_OK; }
STDMETHOD(OnStartEditOperation)() { return S_OK; }
STDMETHOD(OnAbortEditOperation)() { return S_OK; }
STDMETHOD(OnStopEditOperation)() { return S_OK; }
STDMETHOD(OnUndoEditOperation)() { return S_OK; }
STDMETHOD(OnRedoEditOperation)() { return S_OK; }
};
關鍵點:繼承CComObjectRootEx提供參考計數與QueryInterface實作,BEGIN_COM_MAP註冊介面項目,使AtlAdvise能正確查詢IWorkspaceEditEvents。
CComObject<CAEEditEvents>* g_pEvents = nullptr;
DWORD m_dwCookie = 0; // 連線餅乾,用於解除建議
// ipWorkspace 為 IWorkspaceEdit 介面指標 (支援 IConnectionPointContainer)
HRESULT hr = S_OK;
g_pEvents = new CComObject<CAEEditEvents>();
CComPtr<IUnknown> pUnk = g_pEvents; // 取得 IUnknown
// AtlAdvise 內部會 QI IConnectionPointContainer -> FindConnectionPoint -> Advise
hr = AtlAdvise(ipWorkspace, pUnk, __uuidof(IWorkspaceEditEvents), &m_dwCookie);
if (FAILED(hr)) {
// 處理錯誤:介面不支援事件、Cookie 衝突等
delete g_pEvents; g_pEvents = nullptr;
}
if (g_pEvents && m_dwCookie) {
hr = AtlUnadvise(ipWorkspace, __uuidof(IWorkspaceEditEvents), m_dwCookie);
if (SUCCEEDED(hr)) {
m_dwCookie = 0;
g_pEvents->Release(); // CComObject 參考計數歸零自動刪除
g_pEvents = nullptr;
}
}
OnCreate、InitInstance) 時呼叫 Advise。Unadvise,避免事件觸發時存取已釋放物件。Advise 並保存各自 dwCookie。| 現象 | 可能原因 | 解決 |
|---|---|---|
| AtlAdvise 回傳 E_NOINTERFACE | ipWorkspace 不支援 IConnectionPointContainer | 確認物件實際型別、查詢支援介面 |
| 事件不觸發 | Cookie 遺失、Sink 物件已釋放、執行緒模型不符 | 檢查 g_pEvents 生命週期、使用 CComMultiThreadModel |
| 程式在 Unadvise 時崩潰 | 重複 Unadvise、ipWorkspace 已無效 | 加入旗標防重複、在有效範圍內呼叫 |