610 likes | 625 Views
ActiveX Control Recipe. Jim Fawcett CSE791 – Distributed Objects Spring 2002. References. ATL Internals, Rector and Sells, Addison Wesley, 1999. ActiveX Control Attributes. Inproc COM component, using Apartment Model
E N D
ActiveX Control Recipe Jim Fawcett CSE791 – Distributed Objects Spring 2002
References • ATL Internals, Rector and Sells, Addison Wesley, 1999
ActiveX Control Attributes • Inproc COM component, using Apartment Model • Must be apartment model since it will be used to present part of the user interface (UI). • Supports methods through a dispatch interface so it’s accessible to Visual Basic (pre .Net) and scripts. • Supports events using Connectable Objects interfaces so VB and scripts can register for its events. • Built using an outgoing dispatch interface. • Supports a user interface by overriding an OnDraw method
Building an ActiveX Control using ATL • Use ATL COM AppWizard to build server. • Use Class Wizard to: • Insert control object from Class View. • Add methods called by control host. • Add properties initialized by control and possibly modified by host. • Add persistence for property values. • Add events to be fired by control, e.g., control calls host on outgoing dispatch interface. • Implement categories, e.g., SafeForScripting and SafeForInitializing to avoid getting “ActiveX object on this page may be unsafe …” MessageBox. • Modify UI by adding code to OnDraw, provided as part of a Full Control or Lite Control object, inserted as the first Class Wizard step.
Building Ticker Control • Provide visual display of system time, updated each second. • Fire tick event. • Fire mouse button event. • Set stock background and text color properties. • Set custom DrawBorder property • Build dialog host. • Build web page host.
Build Control Server #2 You need to make some manual changes to code to get proxy/stub code merged with control dll
Add Full Control Object #1 Right Click on TickerControl Classes in Class View to get pop-up wizard menu
Add Full Control #4 Allows us to throw and handle COM exceptions Must be Apartment if control has UI Need Connection Points to support Events
Add Full Control #5 Control is Opaque, e.g., non of container shows through. These checks set flags in the DECLARE_VIEW_STATUS macro Normalize DC causes your control to Override OnDraw method for rendering. Otherwise control overrides OnDrawAdvanced Allows your control to be inserted into containers like Word. Causes control to support IPersistStorage and IDataObject interfaces
Add Full Control #6 Creates accessor methods for stock properties.
ATL Classes Supporting Control // CTicker class ATL_NO_VTABLE CTicker : public CComObjectRootEx<CComSingleThreadModel>, public CStockPropImpl<CTicker, ITicker, &IID_ITicker, &LIBID_TICKERCONTROLLib>, public CComControl<CTicker>, public IPersistStreamInitImpl<CTicker>, public IOleControlImpl<CTicker>, public IOleObjectImpl<CTicker>, public IOleInPlaceActiveObjectImpl<CTicker>, public IViewObjectExImpl<CTicker>, public IOleInPlaceObjectWindowlessImpl<CTicker>, public ISupportErrorInfo, public IConnectionPointContainerImpl<CTicker>, public IPersistStorageImpl<CTicker>, public ISpecifyPropertyPagesImpl<CTicker>, public IQuickActivateImpl<CTicker>, public IDataObjectImpl<CTicker>, public IProvideClassInfo2Impl<&CLSID_Ticker, &DIID__ITickerEvents, &LIBID_TICKERCONTROLLib>, public IPropertyNotifySinkCP<CTicker>, public CComCoClass<CTicker, &CLSID_Ticker>
COM Interface Map BEGIN_COM_MAP(CTicker) COM_INTERFACE_ENTRY(ITicker) COM_INTERFACE_ENTRY(IDispatch) COM_INTERFACE_ENTRY(IViewObjectEx) COM_INTERFACE_ENTRY(IViewObject2) COM_INTERFACE_ENTRY(IViewObject) COM_INTERFACE_ENTRY(IOleInPlaceObjectWindowless) COM_INTERFACE_ENTRY(IOleInPlaceObject) COM_INTERFACE_ENTRY2(IOleWindow, IOleInPlaceObjectWindowless) COM_INTERFACE_ENTRY(IOleInPlaceActiveObject) COM_INTERFACE_ENTRY(IOleControl) COM_INTERFACE_ENTRY(IOleObject) COM_INTERFACE_ENTRY(IPersistStreamInit) COM_INTERFACE_ENTRY2(IPersist, IPersistStreamInit) COM_INTERFACE_ENTRY(ISupportErrorInfo) COM_INTERFACE_ENTRY(IConnectionPointContainer) COM_INTERFACE_ENTRY(ISpecifyPropertyPages) COM_INTERFACE_ENTRY(IQuickActivate) COM_INTERFACE_ENTRY(IPersistStorage) COM_INTERFACE_ENTRY(IDataObject) COM_INTERFACE_ENTRY(IProvideClassInfo) COM_INTERFACE_ENTRY(IProvideClassInfo2) END_COM_MAP()
Property, Connection Point, MSG Maps BEGIN_PROP_MAP(CTicker) PROP_DATA_ENTRY("_cx", m_sizeExtent.cx, VT_UI4) PROP_DATA_ENTRY("_cy", m_sizeExtent.cy, VT_UI4) PROP_ENTRY("BackColor", DISPID_BACKCOLOR, CLSID_StockColorPage) PROP_ENTRY("Font", DISPID_FONT, CLSID_StockFontPage) PROP_ENTRY("ForeColor", DISPID_FORECOLOR, CLSID_StockColorPage) // Example entries // PROP_ENTRY("Property Description", dispid, clsid) // PROP_PAGE(CLSID_StockColorPage) END_PROP_MAP() BEGIN_CONNECTION_POINT_MAP(CTicker) CONNECTION_POINT_ENTRY(IID_IPropertyNotifySink) END_CONNECTION_POINT_MAP() BEGIN_MSG_MAP(CTicker) CHAIN_MSG_MAP(CComControl<CTicker>) DEFAULT_REFLECTION_HANDLER() END_MSG_MAP()
UI Code Goes Here HRESULT OnDraw(ATL_DRAWINFO& di) { RECT& rc = *(RECT*)di.prcBounds; Rectangle(di.hdcDraw, rc.left, rc.top, rc.right, rc.bottom); SetTextAlign(di.hdcDraw, TA_CENTER|TA_BASELINE); LPCTSTR pszText = _T("ATL 3.0 : Ticker"); TextOut(di.hdcDraw, (rc.left + rc.right) / 2, (rc.top + rc.bottom) / 2, pszText, lstrlen(pszText)); return S_OK; }
Web Page Contents <HTML> <HEAD> <TITLE>ATL 3.0 test page for object Ticker</TITLE> </HEAD> <BODY> <OBJECT ID="Ticker" CLASSID="CLSID:4DE54986-77C3-4A96-9CB1-EB6378B942D6"></OBJECT> </BODY> </HTML>
Next Modifications • Add timer to generate ticks – one per second. • Use timer callback function to call CTicker::FireViewChange(), inherited from CComControlBase. • This causes container to call control’s OnDraw(…) function. • Modify OnDraw(…) to collect system time and date and display in its window. • Uses ::GetLocalTime(LPSYSTEMTIME lpSystemTime); • Uses std::istringstream to format the time and data strings.
Create Timer to Generate Ticks static CTicker *pTick; // private global pointer CTicker::CTicker() { ::SetTimer(NULL,0,1000,TimerProc); pTick = this; } // global callback function VOID CALLBACK TimerProc(HWND hwnd, UINT message, UINT iTimerID, DWORD dwTime) { pTick->FireViewChange(); }
OnDraw HRESULT CTicker::OnDraw(ATL_DRAWINFO& di) { RECT& rc = *(RECT*)di.prcBounds; SetTextAlign(di.hdcDraw, TA_CENTER|TA_BASELINE); std::string date = GetDate(); LPCTSTR pszText = _T(date.c_str()); TextOut(di.hdcDraw, (rc.left + rc.right) / 2, (rc.top + rc.bottom) / 2, pszText, lstrlen(pszText) ); std::string time = GetTime(); pszText = _T(time.c_str()); TextOut( di.hdcDraw, (rc.left + rc.right) / 2, (rc.top + rc.bottom + 60) / 2, pszText, lstrlen(pszText) ); return S_OK; }
Starting to Look like a Control Very simple MFC dialog client Same web page we looked at earlier
Getting Reference to Control After selecting all defaults, you get left in this resource editor. Select Custom Control from control palette.
Adding Ticker Control to MFC Dialog Now, insert Ticker Control in dialog.
We’ve got it, and its running! In order to compile and run, you have to remove this site placeholder. Don’t ask me why.
Here’s the MFC Client Running instance of control embedded in client MFC dialog.
Handling Events • We used the FireViewchange event to update our window with new times, once per second. That event was inherited from the CComControlBase class. • Next we add our own event. We will do two things: • Trap left mouse button down event in the control – just like any windows event. • Fire this event back to our container, so it knows about the event and can react to it. • The standard way to do this uses Connection Points.
Adding Windows Message Handler Right Click on Class Name Select Add Windows Message Handler
Adding Windows Message HandlerOnLButtonDown(…) Select Message WM_LBUTTONDOWN Click Add and Edit to create OnLButtonDown(…)
Right Now it Just Beeps – We need to pass this event to Container LRESULT CTicker::OnLButtonDown(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { Beep(200,200); return 0; }
Events Supported by Connection Points • When we first created the control, we checked Support Connection Points. • That gave us an ITicker events interface, not yet populated with any methods. • To see that look at the Library section of TickerControl.idl • Note that there are two default interfaces: • ITicker, an incoming interface used by container to send us requests. • _ITickerEvents, an outgoing interface used by control to send notifications to container. This is implemented by container, not control. • Scripts can use only the single default interfaces, each way.
TickerControl.idl – Library Section library TICKERCONTROLLib { importlib("stdole32.tlb"); importlib("stdole2.tlb"); [ uuid(9ECB71F2-9EB5-4AA8-B2B8-321F81D87E87), helpstring("_ITickerEvents Interface") ] dispinterface _ITickerEvents { properties: methods: }; [ uuid(4DE54986-77C3-4A96-9CB1-EB6378B942D6), helpstring("Ticker Class") ] coclass Ticker { [default] interface ITicker; [default, source] dispinterface _ITickerEvents; }; }; Events Interface with no methods yet. Defines the default interfaces, both incoming and outgoing used for scripting
Adding Event Method Right Click on ITickerEvents in Class View, select Add Method
Implement Connection Points • IDL Event Interface now has the method declaration: dispinterface _ITickerEvents { properties: methods: [id(1), helpstring("method OnMouseLBDown")] HRESULT OnMouseLBDown(); }; • Now, compile the IDL file by right clicking on it in File View, and select Compile IDL. • Finally, Right Click in Class View on CTicker class and Choose Implement Connection Point.
Implementing Connection Point Right Click on CTicker Class in Class View, select Implement Connection Point
Which Adds a Proxy Class with Fire Method Fire_OnMouseLBDown Method called by your control to notify container of this event
Code Fix-Up Required • Wizard generated code has an error in Connection Point Map that you have to fix: • Change IID__ITickerEvents to DIID__ITickerEvents. BEGIN_CONNECTION_POINT_MAP(CTicker) CONNECTION_POINT_ENTRY(IID_IPropertyNotifySink) CONNECTION_POINT_ENTRY(DIID__ITickerEvents) END_CONNECTION_POINT_MAP() • Now everything should compile and you have an outgoing interface that MFC clients and web pages can use.
Client Event Handler • MFCClient handler for TickerControl event simply displays a message box: void CMFCClientDlg::OnOnMouseLBDownTicker2() { ::MessageBox( NULL,"Left Mouse Button Click in Ticker Control","Demonstration",MB_OK ); } • We could have chosen a better title – you can fix that by making a few manual changes to your client code.