Developing the Control

[Previous] [Next]

Implementing an ActiveX control involves a lot of boilerplate code. Look at a basic control's hierarchy and you'll see how much code is necessary for only a bare-bones control. Of course, ATL provides most of that boilerplate code. The basic infrastructure code for most controls is going to be the same from one control to the next, so it makes sense to move most of the standard code to the library.

Once the control is generated, your job is to add the code that makes the control useful. Among other tasks, you'll need to add drawing code, supply properties (and make these properties persist), and develop a set of meaningful events. We'll look at each issue separately, starting with rendering the control.

Rendering the Control

ATL's rendering mechanism is very straightforward. CComControlBase:: OnPaint sets up an ATL_DRAWINFO structure, including a painting device context. Then ATL calls your control's OnDrawAdvanced function. OnDrawAdvanced sets up the metafile and then calls your control's OnDraw method, which uses the information in the ATL_DRAWINFO structure to know how to draw on the screen. The following code shows the ATL_DRAWINFO structure:

 struct ATL_DRAWINFO {     UINT cbSize;     DWORD dwDrawAspect;     LONG lindex;     DVTARGETDEVICE* ptd;     HDC hicTargetDev;     HDC hdcDraw;     LPCRECTL prcBounds;  // Rectangle in which to draw     LPCRECTL prcWBounds; // WindowOrg and Ext if metafile     BOOL bOptimize;     BOOL bZoomed;     BOOL bRectInHimetric;     SIZEL ZoomNum;       // ZoomX =     SIZEL ZoomDen; }; 

ATL fills this structure for you. When you're drawing on the screen, the most important fields are the hdcDraw field and the prcBounds field. The other fields are important if you're drawing on a metafile or you need to pay attention to zoom factors and such. The following code shows how the ATL-based message traffic control handles drawing. Notice that when using ATL, you're back to dealing with a raw device context and Graphics Device Interface (GDI) handles.

 void CATLMsgTrafficCtl::ShowGraph(HDC hDC,      RECT& rectBound, long nMessages) {     HPEN hgraphPen;     COLORREF rgbGraphLineColor;     OleTranslateColor(m_graphLineColor, NULL,          &rgbGraphLineColor);     hgraphPen = CreatePen(PS_SOLID,          2, rgbGraphLineColor);     HPEN hOldPen;     hOldPen = (HPEN)SelectObject(hDC, hgraphPen);     m_anMessages[m_nCurrentSlot] = nMessages;     if(m_nCurrentSlot == m_nElements - 1)     {         memcpy(m_anMessages,          m_anMessages + 1,          (m_nElements - 1)*sizeof(long));     }     else     {         m_nCurrentSlot++;     }     int cx = (rectBound.right - rectBound.left) / m_nElements;     int cy = (rectBound.bottom -;     int x = 0;     int y = 0;     int i = 0;     // m_anMessages represents an array showing the number of      //  messages that came through at any given interval.     while (i < m_nElements - 1) {         y = (cy - ((cy * m_anMessages[i]) / m_threshold)) - 2;         ::MoveToEx(hDC, x, y, FALSE);         i++;         x += cx;         y = (cy - ((cy * m_anMessages[i]) / m_threshold)) - 2;         LineTo(hDC, x, y);     }     SelectObject(hDC, hOldPen);     DeleteObject(hgraphPen); } HRESULT CATLMsgTrafficCtl::OnDraw(ATL_DRAWINFO& di) {     RECT& rc = *(RECT*)di.prcBounds;     HBRUSH hBrush = CreateSolidBrush(RGB(255, 255, 255));     FillRect(di.hdcDraw, &rc, hBrush);     DeleteObject(hBrush);     Rectangle(di.hdcDraw, rc.left,, rc.right, rc.bottom);     ShowGraph(di.hdcDraw, rc, nMessagesToShow);     return S_OK; } 

In ATL, you call your control's FireViewChange function to force a redraw of the control. For example, the message traffic control updates itself at regular intervals by responding to the WM_TIMER message, as shown here:

 LRESULT CATLMsgTrafficCtl::OnTimer(UINT msg, WPARAM wParam,      LPARAM lParam, BOOL& bHandled) {     nMessagesToShow = nMessagesThisInterval;     nMessagesThisInterval = 0;     FireViewChange();     bHandled = TRUE;     return 0; } 

In addition to drawing on a live drawing context, your control will sometimes draw on a metafile owned by the client. For example, when your control is being used in design mode in Visual Studio, the programmer will want to see a rendering of it. When the client calls IViewObject[Ex]::Draw, the client is asking the control to draw on a metafile that will be stored. The metafile is a sort of snapshot of the control's current state.

ATL already provides this redraw functionality. When your control needs to redraw for some reason (either to render on the metafile or to draw on the live screen), the code always ends up in the control's OnDraw code. This means that the metafile is the same rendering as the one that appears on the live screen. If you want to make your snapshot look different, you can examine the device capabilities of the device context to find out if the device context is a metafile (call the GetDeviceCaps API to find this out) and draw accordingly.

Next let's look at what's involved in adding methods to your control.

Adding Methods

A control is simply another COM class, and as such it has incoming interfaces. Developing an incoming interface for your control is just like developing an incoming interface for any other class. First decide how you'd like your control's clients to interact programmatically, and then add functions to let the client drive your control.

For example, the ATL-based message traffic monitor should obviously have methods for starting and stopping the control. You can add these functions easily using ClassView. When you add the functions using ClassView, Visual Studio updates your IDL file, your control's header file, and your control's source code (.cpp) file. In this example, calling StartGraph causes the control to install the message hook and start the timer. StopGraph kills the timer and removes the hook procedure. The following code shows the control's IDL and the StartGraph and StopGraph methods:

 [     object,     uuid(C30CCA3D-A482-11D2-8038-6425D1000000),     dual,     helpstring("IATLMsgTrafficCtl Interface"),     pointer_default(unique) ] interface IATLMsgTrafficCtl : IDispatch {     [id(1), helpstring("method StartGraph")] HRESULT StartGraph();     [id(2), helpstring("method StopGraph")] HRESULT StopGraph(); }; STDMETHODIMP CATLMsgTrafficCtl::StartGraph() {     // TODO: Add your implementation code here     if(!g_hhook)     {         g_hhook = ::SetWindowsHookEx(WH_DEBUG,              HookWndProc,              _Module.m_hInst,              0);      }     if(::IsWindow(m_hWnd))     {         SetTimer(1, m_interval);     }     return S_OK; } STDMETHODIMP CATLMsgTrafficCtl::StopGraph() {     if(::IsWindow(m_hWnd))     {         KillTimer(1);     }     if(g_hhook)     {         UnhookWindowsHookEx(g_hhook);          g_hhook = NULL;     }     return S_OK; } 

Adding Properties

In addition to having methods, a control's interface often includes functions for accessing and mutating properties. For example, the message traffic control's properties might include the graph line color and the interval over which messages are sampled. Adding properties to the control is a matter of defining them in the control's source code and then providing accessor and mutator functions through the ClassWizard. The following code shows the property accessor and mutator functions listed in the control's IDL file:

     [         object,         uuid(C30CCA3D-A482-11D2-8038-6425D1000000),         dual,         helpstring("IATLMsgTrafficCtl Interface"),         pointer_default(unique)     ]     interface IATLMsgTrafficCtl : IDispatch     {         // Other methods and properties here                  [propget, id(3), helpstring("property Interval")]          HRESULT Interval([out, retval] long *pVal);         [propput, id(3), helpstring("property Interval")]          HRESULT Interval([in] long newVal);         [propget, id(4), helpstring("property GraphLineColor")]          HRESULT GraphLineColor([out, retval] OLE_COLOR *pVal);         [propput, id(4), helpstring("property GraphLineColor")]          HRESULT GraphLineColor([in] OLE_COLOR newVal);         // Other methods and properties here              }; 

Stock Properties

The properties we just mentioned are custom properties; that is, they're specific to the message traffic control. In addition to the control's custom properties, you can define some stock properties for the control. Stock properties are properties with well-known names that any control might have. Examples of stock properties include the control's caption and its background color. Table 10-1 shows all the stock properties the Object Wizard generates, their DISPIDs, their types, and a description of each one.

Table 10-1. Object Wizard-Supported Stock Properties.

Stock Property DISPID Type Description
Auto Size DISPID_AUTOSIZE VARIANT_BOOL Indicates whether the control can be sized; when set to TRUE,IOleObjectImpl::SetExtent returns E_FAIL (meaning that the control can't be sized)
Background Color DISPID_BACKCOLOR OLE_COLOR Represents the control's background color
Background Style DISPID_BACKSTYLE Long Represents the control's background style (either transparent or opaque)
Border Color DISPID_BORDERCOLOR OLE_COLOR Represents the control's border color
Border Width DISPID_BORDERWIDTH Long Represents the control's border width
Draw Mode DISPID_DRAWMODE Long Represent the control's drawing mode; that is, manages how the control manages GDI output, such as XOR Pen or Invert Colors
Draw Style DISPID_DRAWSTYLE Long Represents the control's drawing style, that is, how the control draws lines—dashed, dotted, solid, and so forth
Draw Width DISPID_DRAWWIDTH Long Represents the width of the pen the control uses to draw lines
Fill Color DISPID_FILLCOLOR OLE_COLOR Represents the color used to fill the control
Fill Style DISPID_FILLSTYLE Long Represents the style used to fill the control— solid, transparent, hatched, and so forth
Font DISPID_FONT IFontDisp* Represents the font the control uses when outputting text
Foreground Color DISPID_FORECOLOR OLE_COLOR Represents the control's foreground color
Enabled DISPID_ENABLED VARIANT_BOOL Indicates whether the control is enabled
HWnd DISPID_HWND Long Represents the control's window handle
Tab Stop DISPID_TABSTOP VARIANT_BOOL Represents whether the control is a tab stop
Text DISPID_TEXT BSTR Represents the text the control is to display
Caption DISPID_CAPTION BSTR Represents the control's caption
Border Visible DISPID_BORDERVISIBLE VARIANT_BOOL Represents whether the control's border should be visible
Appearance DISPID_APPEARANCE Short Represents the general appearance of the control, that is, whether the control is three-dimensional or flat
Mouse Pointer DISPID_MOUSEPOINTER Long Represents the mouse pointer to be shown as the mouse hovers over the control (that is, an arrow, crosshairs, or an I bar)
Mouse Icon DISPID_MOUSEICON IPictureDisp* Represents a graphic image the control is to display when the mouse hovers over the control (that is, a bitmap, an icon, or a metafile)
Picture DISPID_PICTURE IPictureDisp* Represents a graphic image the control is to display (that is, an icon, a bitmap, or a metafile)
Valid DISPID_VALID VARIANT_BOOL Represents whether the control is valid

Property Persistence

Once your control has properties, you might decide to have some of these properties persist. For example, let's say you want to put the message traffic control inside an MFC dialog box. First you pull the component into your project by using Component Gallery, and then you paste an instance of the control into your dialog box. As you develop your dialog box, you decide that the graph line should be a certain color. Of course, the graph line color is one of the control's properties, available through the control's main incoming interface. One option might be to initialize the message traffic control's graph line color in the dialog box's initialization code every time the control starts up. That seems like overkill, though, doesn't it? A better way would be to let the developer configure the control's graph line color and then have the dialog box save the control configuration as part of the dialog resource. This is exactly how ATL's persistence works.

This chapter covers what you need to have to make your properties persist; the next chapter covers how property persistence works.

Making your control's properties persist in ATL involves two steps. First, throw in the ATL implementations of the persistence interfaces you want the client to be able to use. ATL includes the classes IPersistStorageImpl, IPersistStreamInitImpl, and IPersistPropertyBagImpl, which implement the three main COM persistence mechanisms. With these implementations as part of your class, your control's clients can ask the control to persist itself.

The second step to property persistence is to insert the properties into the control's property map. Whenever a client asks to save or load the ATL-based control using one of ATL's persistence mechanisms, ATL looks to the control's property map to transfer the control's properties to and from the storage medium. The property map is a table of property names and DISPIDs and, sometimes, a property page GUID. ATL goes through this table to find which properties to persist and then persists them to the appropriate medium. The following code shows the ATLMsgTraffic control inheriting all three persistence interface implementations and a property map:

 class ATL_NO_VTABLE CATLMsgTrafficCtl :  // More interfaces      public IPersistStreamInitImpl<CATLMsgTrafficCtl>,     public IPersistPropertyBagImpl<CATLMsgTrafficCtl>,     public IPersistStorageImpl<CATLMsgTrafficCtl>, // More interfaces  { }; BEGIN_PROP_MAP(CATLMsgTrafficCtl)     PROP_ENTRY("BackColor", DISPID_BACKCOLOR, CLSID_StockColorPage)     PROP_ENTRY("GraphLineColor", 4, CLSID_StockColorPage)     PROP_DATA_ENTRY("_cx",, VT_UI4)     PROP_DATA_ENTRY("_cy",, VT_UI4)     PROP_ENTRY("Interval", 3, CLSID_MainPropPage)     PROP_PAGE(CLSID_StockColorPage) END_PROP_MAP() 

Property Pages

As you're learning, ActiveX controls are reusable software gadgets (mostly user interface components) built using COM. Even though there's some confusion about what really constitutes an ActiveX control, most folks generally agree that ActiveX controls are COM objects that live within DLLs and implement some sort of visual user interface.

In general, most ActiveX controls have both a presentation state and an internal state. The presentation state of the control is reflected when the control draws itself. The control's internal state is a set of variables exposed to the outside world via one or more interfaces—the properties.

For example, think about the message traffic control we've been building in this chapter. The control has an external presentation state and a set of internal variables for describing the state of the control. The properties of the message traffic control include the graph line color and the sampling interval, among others.

Because ActiveX controls are usually user interface gadgets meant to be mixed into much larger applications, they often find their homes in places like Visual Basic forms and MFC form views and dialog boxes. When a control is instantiated, the client code can usually reach into the control and manipulate its properties by exercising interface functions. When an ActiveX control is in design mode, however, it's usually not practical to access the properties through the interfaces. It would be unkind to force tool developers to go through the interface functions just to tweak some properties in the control. Why should the tool vendor have to provide a user interface for managing properties? That's what property pages are for. Property pages are sets of dialog boxes, some of which belong to Visual C++, that are used for manipulating properties. That way, the tool vendors don't have to keep re-creating dialog boxes for tweaking the properties of an ActiveX control.

How Property Pages Are Used

Client code usually accesses property pages in one of two ways. The first is for the client to call IOleObject's DoVerb, passing in the property verb identifier (named OLEIVERB_PROPERTIES and defined as the number -7). The control then shows a dialog frame with all the control's property pages.

Property pages are a testament to the power of COM. Each single property page is a separate COM object (represented by GUIDs, of course). When a client asks a control to show its property pages via the properties verb, the ActiveX control passes its own list of property page GUIDs into a function named OleCreatePropertyFrame. This function enumerates the property page GUIDs, calling CoCreateInstance for each property page. The property frame gets a copy of an interface for talking to the control (so the frame can change the properties within the control). OleCreatePropertyFrame calls back to the control when the user presses the OK or Apply button.

The second way for clients to use property pages is for the client to ask the control for a list of property page GUIDs. Then the client calls CoCreateInstance on each property page and installs each in its own frame. Figure 10-3 shows an example of how Visual C++ uses the message traffic control's property sheets in its own property dialog frame.

click to view at full size.

Figure 10-3. Visual C++ using the message traffic control's property page.

This is by far the most common way for clients to use a control's property pages. Notice that the property sheet in Figure 10-3 contains a General tab in addition to the control's property pages. The General property page belongs to Visual C++. The remaining property pages belong to the control (even though they're shown within the context of Visual C++).

For property pages to work correctly, the COM objects implementing property pages need to implement ISpecifyPropertyPages and each property page object needs to implement an interface named IPropertyPage. With this in mind, let's take a look at exactly how ATL implements property sheets.

Inside Atl
Inside ATL (Programming Languages/C)
ISBN: 1572318589
EAN: 2147483647
Year: 1998
Pages: 127 © 2008-2017.
If you may any questions please contact us: