Visual C++ 6 Unleashed

Visual C++ 6 Unleashed

By MICKEY WILLIAMS and David Bennett

Creating Scriptable Controls

A scriptable ActiveX control is a control that is constructed so that it is appropriate for use by scripting clients. Scriptable controls are designed to be safe when used in a Web browser, such as Internet Explorer, or by scripts written for the Windows Scripting Host (WSH). Scriptable controls also support mechanisms to simplify passing parameters to the control via a scripting language.

By default, ActiveX controls are not considered safe for scriptable clients. If you have an ActiveX control that could be useful by scripting clients, taking a few simple steps can expand the usefulness of your control. If you're using ATL, these changes typically take just a few minutes.

To be scriptable, an ActiveX control must meet the following requirements:

Requirements for Scriptable Controls

Microsoft has published a document that describes all aspects of component object safety with regard to scripting ActiveX controls. The document, "Designing Safe ActiveX Controls," is located in the MSDN Library included with Visual C++.

Before registering your control as safe for scripting, you must ensure that your control does not take any actions that may be unsafe when used by a scripting client. Unsafe actions include exposing information normally kept private, such as passwords and other sensitive information, executing files, and making dangerous system calls.

Another type of object safety concerns the use of parameters and properties with the control. When used with a scripting client, a safe control must ensure that the control can continue to function with any possible combination of properties defined by the user.

Later in this chapter, you'll look at an example of a control that is safe for scripting.

Persistence for ActiveX Controls

By default, ActiveX controls built with ATL support the IPersistStream interface for object persistence. Persisting data via a binary stream is an efficient way to transfer data to and from a control, and this method is preferable for most containers.

The native form for parameter information for scripting clients is a text string, however, and attempting to persist binary parameter information in a scripting language such as VBScript is painful (although not impossible). For this reason, most scriptable controls support the IPersistPropertyBag interface, which allows properties to be persisted as pairs of property names and property values. Instead of passing a binary stream to an ActiveX control, a client simply can pass pairs of parameter names and values to initialize a control.

A Scriptable ActiveX Control

This book's CD-ROM includes ScriptButton, an ATL-based ActiveX control that superclasses the standard Windows pushbutton. ScriptButton is a scriptable control; sample programs presented later in this chapter demonstrate how ScriptButton is used with Internet Explorer.

ScriptButton is similar to the standard Windows pushbutton, except that it appears to be a label until the mouse pointer is placed over the control. When the pointer is over the control, the control is redrawn with beveled edges so that it looks like a standard button.

The Basic Design of ScriptButton

As you learned in the preceding section, ScriptButton is similar to the standard Windows pushbutton control, except that it only has a 3D appearance when the mouse pointer is moved over the control. For the control to be drawn properly, the control must be able to perform the following tasks:

In addition, ScriptButton must use a set of stock properties that a user of a button control may want to adjust:

Two events are sent from the ScriptButton control to its container:

Messages Handled by ScriptButton

ScriptButton handles messages sent by the operating system, as well as one message sent by the underlying button control. The control must handle the following messages:

In addition, the control handles one notification message sent by the pushbutton control:

The BN_CLICKED message is sent after the user clicks the button with the mouse or selects the button using the keyboard. After this message is received from the underlying control, ScriptButton fires an OnClicked event to its container.

Handling Raised-Button and Flat-Button States

The WM_MOUSEMOVE and WM_MOUSELEAVE messages are used together to control the raised appearance of the control. As discussed earlier, the button initially is drawn in a flat state. After a WM_MOUSEMOVE message is received by the control, the control redraws itself in the raised state.

While the button is in the raised state, it must handle two cases:

The first case is handled easily and is discussed in the next section. If the mouse is moved away from the control rectangle, however, no event is sent to the control's window. To detect the departure of the mouse, the Win32 _TrackMouseEvent function is called when the mouse initially moves over the control. This function registers the control window as requesting additional information about mouse events. When the mouse is moved away from the control, a WM_MOUSELEAVE message is sent to the control's window, and the button is redrawn in its flat state.

Handling Button Clicks

Three messages are used to draw the control and report events after the user clicks the ScriptButton control. WM_LBUTTONDOWN and WM_LBUTTONUP are sent from the operating system as the user presses and releases the primary mouse button while the pointer is over the control. In response to these messages, the control is drawn in either the down state (for WM_LBUTTONDOWN) or the raised state (for WM_LBUTTONUP).

After the user clicks the ScriptButton control, the underlying button generates a BN_CLICKED notification message. This message is used to create an OnClicked event that is sent to the control's container.

Handling the Focus Rectangle

One important visual indicator supplied by user interface components is the focus rectangle. If the focus rectangle isn't drawn correctly, the user may be confused about the input state of the control.

Two messages are used to draw the focus rectangle. The WM_SETFOCUS message is sent just before the control receives input focus; it causes the familiar dotted rectangle to be drawn around the control. The WM_KILLFOCUS message is sent just before focus is lost; it causes the focus rectangle to be removed.

It's so important that the focus rectangle be drawn correctly that Windows offers an API function to handle it all for you:

DrawFocusRect(hdc, lprc);

You call DrawFocusRect with two parameters: the device context that you're drawing into, and the rectangle that describes the boundary of the focus rectangle. This function also is used to remove the focus rectangle: If you call it a second time with the same rectangle, the focus rectangle is removed.

Creating the ScrBtn Project

The ScrBtn project is used to create the ScriptButton control, which is created using the Visual C++ ATL COM AppWizard. The control begins as a DLL project, as Figure 31.6 shows.

31fig06.gif

Figure 31.6 Using the ATL COM AppWizard to create the ScrBtn project.

The ScrBtn project does not use any of the optional features offered by the ATL COM AppWizard, such as MFC or MTS support or the capability to merge proxy/stub code.

Adding the ScriptButton Control Class to the Project

The ActiveX control component in the ScrBtn project is implemented by adding a new COM class into the project with the ATL Object Wizard. Start by choosing New ATL Object from the Visual C++ Insert menu. The ATL Object Wizard appears, as shown in Figure 31.7.

31fig07.gif

Figure 31.7 The ATL Object Wizard.

The ScrBtn project will implement a full control named ScriptButton. Select Controls from the wizard's Category list box, and then select Full Control as the control type. Click Next to begin adding ScriptButton to the project.

The ATL Object Wizard Properties dialog box appears so that you can define your control's properties (see Figure 31.8). For full controls, such as ScriptButton, the dialog box offers four tabs.

31fig08.gif

Figure 31.8 The ATL Object Wizard Properties dialog box.

The Names tab is just like the Names tab used in custom COM objects in Chapter 29. You only need to fill in the Short Name property for ScriptButton—the other fields are filled in automatically and should contain the values shown in Table 31.3.

Table 31.3. Contents of the Names Tab for the ScriptButton Control

Property Name
Short Name ScriptButton
Class CScriptButton
.H File ScriptButton.h
.CPP File ScriptButton.cpp
CoClass ScriptButton
Interface IScriptButton
Type ScriptButton Class
Prog ID ScrBtn.ScriptButton

The second tab is the Attributes tab; again, it is identical to the Attributes tab discussed in Chapter 29. Table 31.4 shows the values used for ScriptButton on the Attributes tab. All these values are the default options, except that ScriptButton supports connection points. (This property appears in bold in Table 31.4, because it is not a default setting.) As discussed earlier in the chapter, connection points are used to supply events to a control's container.

Table 31.4. Contents of the Attributes Tab for the ScriptButton Control

Property Name
Threading Model Apartment
Interface Dual
Aggregation Yes
Support ISupportErrorInfo Unchecked
Support Connection Points Checked
Free Threaded Marshaler Unchecked

The third tab is the Miscellaneous tab (see Figure 31.9). It contains attributes specific to ActiveX controls.

31fig09.gif

Figure 31.9 Miscellaneous tab values for the ScriptButton control.

Table 31.5 shows the values for the ScriptButton control; nondefault values appear in bold type.

Table 31.5. Contents of the Miscellaneous Tab for the ScriptButton Control

Property Name
Opaque Checked
Solid Background Checked
Add Control Based On Button
Invisible at Runtime Unchecked
Acts Like Button Checked
Acts Like Label Unchecked
Normalize DC Checked
Windowed Only (Disabled)
Insertable Unchecked

The final tab in the dialog box is Stock Properties. It contains a list of all stock properties that can be implemented by an ActiveX control (see Figure 31.10).

31fig10.gif

Figure 31.10 The Stock Properties tab values for the ScriptButton control.

The ScriptButton control uses these stock properties:

After you select the stock properties listed here, click OK to close the dialog box. The ATL New Object Wizard then generates the necessary code and adds it to your project.

Adding Outgoing Events

As discussed earlier, the ScriptButton control provides two events to its containers:

Open the ScrBtn.idl file and add the two lines shown in bold in Listing 31.6 to the _IScriptButtonEvents interface. Alternatively, you can add the events by right-clicking the _IScriptButtonEvents icon in ClassView and choosing Add Method from the pop-up menu.

Example 31.6. Changing the ScrBtn IDL File to Add Outgoing Events

library SCRBTNLib
{
    importlib("stdole31.tlb");
    importlib("stdole2.tlb");

    [
        uuid(416D4C0E-A2C9-49AA-887F-0E1D8BCE280F),
        helpstring("_IScriptButtonEvents Interface")
    ]
    dispinterface _IScriptButtonEvents
    {
        properties:
        methods:
     [id(1), helpstring("OnClick event")] HRESULT OnClick();

        [id(2), helpstring("OnHover event")] HRESULT OnHover();
    };

    [
        uuid(B584CB87-D818-4E6B-9DC5-F0063CF64E22),
        helpstring("ScriptButton Class")
    ]
    coclass ScriptButton
    {
        [default] interface IScriptButton;
        [default, source] dispinterface _IScriptButtonEvents;
    };
};
							

Before proceeding, compile the skeleton project. This project compiles the type library and makes it possible to add the event connection point for the control.

After the project is compiled, add the connection point to the project by right-clicking the CScriptButton icon in ClassView and choosing Implement Connection Point from the pop-up menu. The Implement Connection Point dialog box appears. Enable the check box next to _IScriptButtonEvents and click OK. The CProxy_IScriptButtonEvents class then is generated and added to the project.

Modifying the Message Map

Seven messages must be added to the CScriptButton message map. Six of the messages are generated by the operating system, and one message is generated by the superclass pushbutton control.

As discussed earlier, you can open the dialog box that adds message-handling functions by right-clicking the CScriptButton icon in ClassView and choosing Add Windows Message Handler from the pop-up menu. Add handlers for these messages:

You must manually add message-handling macros for the BN_CLICKED and WM_TRACKMOUSEMESSAGE messages to the message map. Listing 31.7 shows the finished message map; the manually added macros appear in bold. Note that all the message handlers are in the main part of the message map. Make sure your message map doesn't have any macro entries in the alternate message map.

Example 31.7. The Message Map for ScriptButton

BEGIN_MSG_MAP(CScriptButton)
    MESSAGE_HANDLER(WM_CREATE, OnCreate)
    MESSAGE_HANDLER(WM_SETFOCUS, OnSetFocus)
    MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBkgnd)
    MESSAGE_HANDLER(WM_KILLFOCUS, OnKillFocus)
    MESSAGE_HANDLER(WM_LBUTTONDOWN, OnLButtonDown)
    MESSAGE_HANDLER(WM_LBUTTONUP, OnLButtonUP)
    MESSAGE_HANDLER(WM_MOUSEMOVE, OnMouseMove)
    MESSAGE_HANDLER(WM_MOUSELEAVE, OnMouseLeave)

       COMMAND_CODE_HANDLER(BN_CLICKED, OnClicked)
    CHAIN_MSG_MAP(CComControl<CScriptButton>)
ALT_MSG_MAP(1)
    // Replace this with message map entries for superclassed Button
END_MSG_MAP()
							

Initializing the CScriptButton Object

Before adding the code needed to initialize an instance of the CScriptButton class, add an enumeration used to track button states to the ScriptButton.h header file. Insert the contents of Listing 31.8 just above the declaration of the CScriptButton class.

Example 31.8. The BtnState Enumeration

enum BtnState {  bsFlat, bsDown, bsHover } ;

Earlier in this chapter, when the stock properties were added to the ScriptButton control, the ATL Object Wizard inserted member variables into the ScriptButton.h header file. These variables, shown in Listing 31.9, are used to hold the values of the stock properties supported by the control. Add the member variables shown in bold to the class declaration; these variables are used to implement various control features that will be discussed throughout the remainder of this chapter.

Example 31.9. Additional Member Variables Used by CScriptButton

public:
    OLE_COLORm_clrBackColor;
    CComBSTR m_bstrCaption;
    OLE_COLORm_clrForeColor;
    BOOL     m_bTabStop;
    CComPtr<IFontDisp> m_pFont;      // Contains ambient font

       BtnState m_btnState;   // Tracks button state

       bool     m_fHasFocus;  // TRUE if button has focus

Add the lines shown in bold in Listing 31.10 to the CScriptButton constructor. Note that you must add a comma to the first line of the constructor initializer list.

Example 31.10. The Constructor for the CScriptButton Class

CScriptButton() :
    m_ctlButton(_T("Button"), this, 1), // add comma to this line
    m_btnState(bsFlat),

       m_fHasFocus(false),
{
    m_bWindowOnly = TRUE;
}

The default handler for OnCreate will superclass a default instance of the Windows BUTTON class. The ScriptButton control must superclass a BUTTON with pushbutton attributes, so modify the OnCreate member function, as shown in Listing 31.11, by changing the lines shown in bold.

Example 31.11. Changes to the OnCreate Member Function

LRESULT OnCreate(UINT, WPARAM, LPARAM, BOOL&)
{
    RECT rc;
    GetWindowRect(&rc);
    rc.right -= rc.left;
    rc.bottom -= rc.top;
    rc.top = rc.left = 0;
    m_ctlButton.Create(m_hWnd,

      rc,

      _T("BUTTON"),

      WS_CHILD|BS_PUSHBUTTON);
    return 0;
}
							

Retrieving Ambient Properties

When the control is initially loaded, it collects the current foreground color, background color, and font from its container. A good time to collect ambient properties is when the control and its container negotiate the client site. Add the source code in Listing 31.12 to the ScriptButton.cpp source file. This function overrides the base class implementation of SetClientSite and stores the ambient values of these three properties.

Example 31.12. A New Version of SetClientSite That Collects Ambient Properties from the Container

STDMETHODIMP CScriptButton::SetClientSite(LPOLECLIENTSITE pSite)
{
    HRESULT hr = CComControlBase::IOleObject_SetClientSite(pSite);
    if(!m_pFont && pSite)
    {
        hr = GetAmbientFontDisp(&m_pFont);
    }
    GetAmbientBackColor(m_clrBackColor);
    GetAmbientForeColor(m_clrForeColor);
    return hr;
}

Add the following member function declaration to the CScriptButton class:

STDMETHOD(SetClientSite)(LPOLECLIENTSITE pSite);

A good place for this declaration is just after all the member variables used to track properties.

Handling Focus Events for the Control

When a focus event is received by the control, the focus member variable is set with the new input focus state, and the control's window is invalidated. The control's container also is notified that the control is changing its view.

Listing 31.13 contains the OnSetFocus and OnKillFocus member functions, which handle the WM_SETFOCUS and WM_KILLFOCUS messages, respectively. Add the lines shown in bold to the member functions.

Example 31.13. The OnSetFocus and OnKillFocus Member Functions for the CScriptButton Class

LRESULT OnSetFocus(UINT uMsg, WPARAM wParam, LPARAM lParam,
     BOOL& bHandled)
{
    LRESULT lRes = CComControl<CScriptButton>::OnSetFocus(uMsg,
        wParam,
        lParam,
        bHandled);
    if (m_bInPlaceActive)
    {
        DoVerbUIActivate(&m_rcPos,  NULL);
        if(!IsChild(::GetFocus()))
  m_ctlButton.SetFocus();
    }
    m_fHasFocus = true;

       FireViewChange();
    return lRes;
}

LRESULT OnKillFocus(UINT uMsg, WPARAM wParam, LPARAM lParam,
      BOOL& bHandled)
{
    m_fHasFocus = false;

       FireViewChange();
    return 0;
}
							

Handling Mouse Events for the Control

The following four message handlers control mouse and click events for the CScriptButton class:

  • OnLButtonDown updates the button state variable and invalidates the control's rectangle so that it will be redrawn.
  • OnLButtonUp updates the button state variable and invalidates the control's rectangle so that it can be redrawn. If the mouse pointer is currently over the control, the new button state is bsHover; if the mouse pointer is not over the control, the new button state is bsFlat.
  • OnMouseMove updates the button state if the current button state is bsFlat. If the button state is moving from bsFlat to bsHover, the control's rectangle is invalidated so that it can be redrawn. In addition, the OnHover event is fired to the control's container.
  • OnClick is sent to the CScriptButton class when the underlying button control generates a click event. In response, the OnClick member function fires an OnClick event to the container.

To implement these member functions, add the lines shown in bold in Listing 31.14 to the member functions in the CScriptButton class.

Example 31.14. Event-Handling Member Functions in the CScriptButtonClass

LRESULT OnLButtonDown(UINT uMsg, WPARAM wParam, LPARAM lParam,
        BOOL& bHandled)
{
   // Change the state to bsDown, and redraw the control

      m_btnState = bsDown;

      FireViewChange();

      m_ctlButton.DefWindowProc(uMsg, wParam, lParam);
   return 0;
}

LRESULT OnLButtonUP(UINT uMsg, WPARAM wParam, LPARAM lParam,
      BOOL& bHandled)
{
   if(MouseOverCtl())

          m_btnState = bsHover;

      else

          m_btnState = bsFlat;

      FireViewChange();

      m_ctlButton.DefWindowProc(uMsg, wParam, lParam);
   return 0;
}

LRESULT OnMouseMove(UINT uMsg, WPARAM wParam, LPARAM lParam,
      BOOL& bHandled)
{
   if(m_btnState == bsFlat)

      {

          // Moving from flat to hover...

          Fire_OnHover();

          m_btnState = bsHover;

          FireViewChange();


          TRACKMOUSEEVENT tme;

          ZeroMemory(&tme,  sizeof(tme));

          tme.cbSize = sizeof(tme);

          tme.dwFlags = TME_LEAVE;

          tme.hwndTrack = m_hWnd;

          TrackMouseEvent(&tme);

      }

      m_ctlButton.DefWindowProc(uMsg, wParam, lParam);
   return 0;
}

// BN_CLICKED

   LRESULT OnClicked(WORD wNotifyCode, WORD wID, HWND hWndCtl,

        BOOL& bHandled)

   {

      Fire_OnClick();

      m_btnState = bsFlat;

      FireViewChange();

      bHandled = TRUE;

      return 0;

   }

Note that when the mouse pointer is initially moved over the control, the TrackMouseEvent function is called to request a notification when the pointer leaves the control's window. When the pointer leaves the control window, the operating system sends the control a WM_MOUSELEAVE message. Add the message-handling function for WM_MOUSELEAVE provided in Listing 31.15 to the ScriptButton.h header file, inside the CScriptButton class declaration.

Example 31.15. Handling the WM_MOUSELEAVE Message

LRESULT OnMouseLeave(UINT uMsg, WPARAM wParam, LPARAM lParam,
       BOOL& bHandled)
{
    m_btnState = bsFlat;
    FireViewChange();
    bHandled = TRUE;
    return 0;
}

Several of the functions in CScriptButton need to determine whether the mouse pointer is over the control. The MouseOverCtl function provided in Listing 31.16 returns TRUE if the pointer is over the control; otherwise, it returns FALSE. Add this function to the ScriptButton.h header file, inside the CScriptButton class declaration.

Example 31.16. Handling the WM_MOUSELEAVE Message

BOOL MouseOverCtl()
{
    RECT  rc;
    POINT pt;

    GetWindowRect(&rc);
    GetCursorPos(&pt);

    return PtInRect(&rc, pt);
}
							

Drawing the Control

The most complex part of the ScriptButton control involves drawing the control. Before getting into the actual drawing code, there are two functions that make it easy to determine which state the button is in. Listing 31.17 contains the IsSelected and IsRaised member functions, which are used by the CScriptButton class when drawing the control. These functions can be found in the ScriptButton.h file.

Example 31.17. The IsSelected and IsRaised Member Functions

BOOL IsSelected()
{
    return m_btnState == bsDown;
}

BOOL IsRaised()
{
    return m_btnState == bsHover;
}

The source code provided in Listing 31.18 contains OnDraw, the function primarily responsible for drawing the control. The complete drawing code for the control is not shown here, but you can find the full source code on the CD-ROM that accompanies this book.

Example 31.18. The CScriptButton::OnDraw Function

HRESULT OnDraw(ATL_DRAWINFO& di)
{
    RECT& rc  = *(RECT*)di.prcBounds;
    HDC   hdc = di.hdcDraw;

    COLORREF clrFore, clrBack;
    OleTranslateColor(m_clrForeColor, NULL, &clrFore);
    OleTranslateColor(m_clrBackColor, NULL, &clrBack);
    SetTextColor(hdc, m_clrForeColor);
    HBRUSH hbrBtn = CreateSolidBrush(m_clrBackColor);

    FillRect(hdc, &rc, hbrBtn);
    DrawEdges(hdc, &rc);
    DrawButtonText(hdc, &rc, m_bstrCaption);

    if(m_fHasFocus)
    {
        InflateRect(&rc, -2, -2);
        DrawFocusRect(hdc, &rc);
    }

    DeleteObject(hbrBtn);
    return 0;
}
							

Implementing IPersistPropertyBag

To simplify the use of ScriptButton in a scripting client, such as Internet Explorer, the control must support the IPersistPropertyBag interface. The ATL class library includes a class, IPersistPropertyBagImpl, that provides a default implementation of IPersistPropertyBag that is sufficient in most cases. IPersistPropertyBagImpl is a parameterized class that takes one argument—the name of the class to be persisted:

IPersistPropertyBagImpl<CLatte>

To add support for IPersistPropertyBagImpl to CScriptButton, make the changes shown in Listing 31.19 to the declaration of the CScriptButton class. These changes, shown in bold, add IPersistPropertyBagImpl to the classes that CScriptButton is derived from.

Example 31.19. Deriving CScriptButton From IPersistPropertyBagImpl

class ATL_NO_VTABLE CScriptButton :
    public CComObjectRootEx<CComSingleThreadModel>,
    public CStockPropImpl<CScriptButton,IScriptButton,
      &IID_IScriptButton, &LIBID_SCRBTNLib>,
    public CComControl<CScriptButton>,
    public IPersistStreamInitImpl<CScriptButton>,
    public IPersistPropertyBagImpl<CScriptButton>,
    public IOleControlImpl<CScriptButton>,
    public IOleObjectImpl<CScriptButton>,
    public IOleInPlaceActiveObjectImpl<CScriptButton>,
    public IViewObjectExImpl<CScriptButton>,
    public IOleInPlaceObjectWindowlessImpl<CScriptButton>,
    public IConnectionPointContainerImpl<CScriptButton>,
    public IPersistStorageImpl<CScriptButton>,
    public ISpecifyPropertyPagesImpl<CScriptButton>,
    public IQuickActivateImpl<CScriptButton>,
    public IDataObjectImpl<CScriptButton>,
    public IProvideClassInfo2Impl<&CLSID_ScriptButton,
         &DIID__IScriptButtonEvents, &LIBID_SCRBTNLib>,
    public IPropertyNotifySinkCP<CScriptButton>,
    public CComCoClass<CScriptButton, &CLSID_ScriptButton>,
    public CProxy_IScriptButtonEvents< CScriptButton >

You also must add IPersistPropertyBagImpl to the COM_MAP macro by making the changes shown in bold in Listing 31.20.

Example 31.20. Adding IPersistPropertyBagImpl to the COM_MAP

BEGIN_COM_MAP(CScriptButton)
    COM_INTERFACE_ENTRY(IScriptButton)
    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_ENTRY(IPersistPropertyBag)
    COM_INTERFACE_ENTRY2(IPersist, IPersistStreamInit)
    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)
    COM_INTERFACE_ENTRY_IMPL(IConnectionPointContainer)
END_COM_MAP()

With these changes, a container that supports scripting can obtain a pointer to the control's IPersistPropertyBag interface, and will use that interface for persisting data instead of IPersistStreamInit. Containers that prefer to persist data with binary streams still will query for IPersistStream or IPersistStreamInit.

Marking the Control as Safe for Scriptable Clients

You can use two methods to mark a control as safe for scripting clients:

  • Make appropriate entries directly in the System Registry.
  • Implement the IObjectSafety interface.

Microsoft recommends that you use new controls to implement the IObjectSafety interface instead of making Registry entries. IObjectSafety enables the control to apply much finer-grained safety policies than possible using the Registry.

ATL provides a default implementation of IObjectSafety that you easily can take advantage of by deriving your class from the IObjectSafetyImpl class. To add this class to CScriptButton, add the code shown in bold in Listing 31.21 to the declaration of CScriptButton.

Example 31.21. Deriving CScriptButton from IObjectSafetyImpl

class ATL_NO_VTABLE CScriptButton :
.
.
.
    public IPersistPropertyBagImpl<CScriptButton>,//
    public IObjectSafetyImpl<CScriptButton,

           INTERFACESAFE_FOR_UNTRUSTED_CALLER|

           INTERFACESAFE_FOR_UNTRUSTED_DATA>,
    public IOleControlImpl<CScriptButton>,
.
.
.

Two template arguments are passed to IObjectSafetyImpl:

  • The name of the class deriving from IObjectSafetyImpl
  • The type of safety to be applied to the control

Two values may be passed for the safety options:

  • INTERFACESAFE_FOR_UNTRUSTED_CALLER specifies that your control can be used safely by a scripting client and does not violate any of Microsoft's security or safety guidelines.
  • INTERFACESAFE_FOR_UNTRUSTED_DATA specifies that your control will work (or at least degrade gracefully) in the presence of any possible set of parameters passed by a client, including invalid or conflicting parameters.

You can combine either or both of these values as appropriate for your control.

You also must add IObjectSafetyImpl to the COM_MAP macro by making the changes shown in bold in Listing 31.22.

Example 31.22. Adding IObjectSafetyImpl to the COM_MAP

BEGIN_COM_MAP(CScriptButton)
.
.
.
    COM_INTERFACE_ENTRY(IPersistPropertyBag)
    COM_INTERFACE_ENTRY(IObjectSafety)
    COM_INTERFACE_ENTRY2(IPersist, IPersistStreamInit)
.
.
.
END_COM_MAP()
							

Testing ScriptButton with Internet Explorer

Compile the CScriptButton project. You can test the control in a variety of control containers, including Visual Basic and TstCon31.exe—the test container included with Visual C++. Because the ScriptButton control is scriptable, you also can use the control on an HTML page with Internet Explorer.

The ScriptButton control can be inserted on an HTML page using the OBJECT tag, as Listing 31.23 shows. The complete source for an HTML file used to test ScriptButton is located in the ScriptButton project directory as IETest.htm.

Example 31.23. Inserting ScriptButton in an HTML File

<OBJECT classid=clsid:B584CB87-D818-4E6B-9DC5-F0063CF64E22
        id=ScriptButton1
        style="HEIGHT: 39px; WIDTH: 192px"VIEWASTEXT>
        <PARAM NAME="_cx" VALUE="5080">
        <PARAM NAME="_cy" VALUE="1032">
        <PARAM NAME="BackColor" VALUE="8438015">
        <PARAM NAME="Caption" VALUE="Click Me!">
        <PARAM NAME="TabStop" VALUE="-1">
</OBJECT>

When the IETest.htm file runs in Internet Explorer, the ScriptButton control is displayed, as shown in Figure 31.11. When the mouse pointer moves over the control, the button raises up and changes its caption. After the button is clicked, the caption also changes.

31fig11.gif

Figure 31.11 The ScriptButton control running in Internet Explorer.

Share ThisShare This

Informit Network