Developer.com Click here to support our advertisers
Click here to support our advertisers
SOFTWARE
FOR SALE
BOOKS
FOR SALE
SEARCH CENTRAL
* JOB BANK
* CLASSIFIED ADS
* DIRECTORIES
* REFERENCE
Online Library Reports
* TRAINING CENTER
* JOURNAL
* NEWS CENTRAL
* DOWNLOADS
* DISCUSSIONS
* CALENDAR
* ABOUT US
----- Journal:

Get the weekly email highlights from the most popular online Journal for developers!
Current issue -----
developer.com
developerdirect.com
htmlgoodies.com
javagoodies.com
jars.com
intranetjournal.com
javascripts.com

REFERENCE

All Categories : ActiveX

Chapter 5

ActiveX Controls


CONTENTS

The ActiveX controls (OCXs) are among the defining technologies in the Microsoft ActiveX media campaign. These controls enable you to deliver complete applications to an end user via the World Wide Web in a seamless fashion.

Microsoft is not the first to deliver this sort of technology. It has been seen in the form of Java applets and, to an extent, plug-ins. OCXs, however, can take advantage of MFC. They can also include and build on other OCXs. Plug-ins suffer from the disadvantage that they are scattered throughout the Internet, and Netscape cannot always predict the effects of interaction between its browser and a particular vendor's plug-in, or even what happens between two external plug-ins. On the other hand, Microsoft's ActiveX controls, coupled with the company's code signing initiative (see Chapter 8on security), support much better control over the behavior and quality of code in the Explorer context.

In terms of the functionality an OCX can deliver, just about anything is possible. You can conceivably deliver just about any sort of application to an end user via an OCX, although file size of course will be an issue. From simple animated graphics, to OCXs that interact with a database residing on a Web server, to interactive client applications that let you communicate over the Internet with other users in real time-all of this is possible.

Given the nearly infinite range of possibilities, we decided to focus on a class of relatively simple yet interesting applications. We will look at controls that interact with the Internet Explorer client window, which is a concept apart from stand-alone applications delivered over the Internet. We discovered that, "Yes, Virginia, there is a Santa Claus," and you can get a handle to that window. After all, even if an application is not complicated under the hood, it appears that way to the end user observing the client window's nifty visual effects. The "How did they do that?" effect is a strategic advantage for individuals and firms who wish to capture their share of an ever more competitive marketplace.

With this focus, we'll take a look at the fundamentals of creating ActiveX controls, using the framework provided by the Visual C++ OLE Control Wizard and MFC. We will also look at the fundamentals of communication between a control and the world outside of it, via properties, methods, and events. For this study we'll use Visual C++ and the popular scripting language, Visual Basic Script.

OCXs Old and New

OCXs have been around for a while. With the development of ActiveX, a few specific changes and enhancements were made to the OCX technology, making it much more feasible to deliver an OCX via the Web. Previously, an OCX had to support a number of interfaces. The result was that even the simplest OCXs were bulkier than what was unacceptable for delivering an application via the Web.

With the development of ActiveX, these requirements were relaxed; now the only interface required is IUnknown. OCX file sizes can now be much smaller, making OCXs viable for delivery via a potentially slow Internet/HTTP connection. The stripped-down OCX specifications have been termed "light" OLE controls; a "heavy" control with bulky requirements would be completely unsuitable for distributed programming.

Creating and Testing Controls

Visual C++ includes a Wizard for creating an OCX framework, and in VC++ 4.2 this Wizard has been improved. It now takes advantage of specific enhancements for creating controls intended for use in a Web page. Let's take a look at how to use Control Wizard to help create OCXs.

Using the OCX Control Wizard

The OCX Control Wizard creates a skeleton, allowing you to specify various options at the time you create the OCX. After you select Create New Project and the OCX Wizard, the first option screen lets you select the number of controls in the project. You also choose whether to generate a run-time license, source file comments, and help files. For each of our sample projects, we only created a single control.

The next options screen is shown in Figure 5.1. You should make the object insertable (check the option called "Available in Insert Object Dialog"). Also, if your control is only going to do background work, make it "Invisible at runtime." This is for controls that the end user doesn't need to see, in which case the control need not draw or paint itself in the user's screen. (Microsoft's Timer control is an example of this.) You will be able to see the control while working on it in the Integrated Development Environment (IDE), however.

Figure 5.1 : Step 2 in the OLE Control Wizard is this screen of options.

You can include other controls with the subclass option. This allows a control to inherit the built-in capabilities of the control you are subclassing. See the Xyz sample application later in this chapter for an example of subclassing a control.

With Visual C++ 4.2, another screen of options was added to the Control Wizard, shown in Figure 5.2. The "Windowless activation" feature means that your control will not have to create the usual window that most Windows applications require. Dispensing with this reduces the size of the control and accelerates it somewhat. If you select this option, the next two are irrelevant. "Unclipped device context" and "Flicker-free activation" both affect how the control is drawn and its performance.

Figure 5.2 : The Advanced ActiveX Features screen in the OLE Control Wizard.

The next feature, "Mouse pointer notifications when inactive," makes your control track the mouse while it's positioned outside of your control, or while the focus is set to another part of the screen. For most our controls, we selected the next option, "Optimized drawing code." This speeds up the control's painting of itself, depending on the capabilities of the container. The final option, "Loads properties asynchronously," lets your control load some of its data in the background.

By the way, each of these ActiveX features can be changed after the control is created. You can add or remove the flags assigned to the constant

BASED_CODE _dw[appname]OleMisc variable

where [appname] is the application name you assigned to the project when running Control Wizard.

Debugging Your Controls

The first place to test out your control will be the Test Container in the Developer Studio (located in the Tools menu). This container application includes menu items that let you insert multiple controls and test out their properties, methods, and events.

Once the control works as desired in the Test Container, the next stop is to test it in Internet Explorer itself. Create a sample HTML page, using Control Pad, to hold the control. You can then either run Explorer separately or specify the path to Explorer under Build | Settings Debug. Testing the control in the IDE through Explorer gives you the usual range of debugging tools, including breakpoints, viewing Trace statements and watch variables, and so forth.

If you will be writing VBScripts with your control and you have Visual Basic, you can use VB to test out the script and the control. You need to be mindful of the limitations in VBScript, however, if you are developing in VB. Nevertheless, with the absence of useful debugging capabilities in VBScript, the Visual Basic environment is handy.

Framework of a Control

Before we get down to our study of how a control communicates outside itself, let's look at a "stand-alone" OCX. This will give you an idea of the general framework of a control generated by the OLE Control Wizard.

This OLE Notes control is a simple yet effective application that we created to satisfy one of our pesky managers, who always wants the latest whiz-bang toy on his desktop. In this case, he wanted a Web page that would help him make and save a series of notes, much like those little yellow things that you stick everywhere.

We could have developed this using ISAPI or a CGI script, but we didn't want to clutter our server with the electronic notes' data. The OCX we created not only solved our problem but can be made available to any user who cares to load a Web page containing the control. Figure 5.3 presents a sample Web page with the OLE Notes control embedded in it.

Figure 5.3 : Our handy OLE Notes control.

This control allows the user to enter text into a series of individual note pages, moving between the notes with the buttons provided at the bottom. The notes file can be saved to the user's local PC. Whenever the Web page is reloaded, the file is restored. Options are also provided to clear or delete individual notes. (Fortunately for us, the manager hasn't yet considered the concept of a subject line, or indexing, because we have other things we'd like to tackle.)

The OCX Control Wizard will create a number of files that are included in the OCX project. Typically you will need to modify only two of those files. Listing 5.1 and 5.2 are the source and header files modified for this OCX.


Listing 5.1 ONotesCtl.cpp
// ONotesCtl.cpp : Implementation of the CONotesCtrl OLE control class.

#include "stdafx.h"
#include "ONotes.h"
#include "ONotesCtl.h"
#include "ONotesPpg.h"


#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif


IMPLEMENT_DYNCREATE(CONotesCtrl, COleControl)


/////////////////////////////////////////////////////////////////////////////
// Message map

BEGIN_MESSAGE_MAP(CONotesCtrl, COleControl)
     //{{AFX_MSG_MAP(CONotesCtrl)
     ON_WM_SIZE()
     ON_WM_MOUSEMOVE()
     ON_WM_CREATE()
     ON_BN_CLICKED(1,clickFirst)
     ON_BN_CLICKED(2,clickPrev)
     ON_BN_CLICKED(3,clickNext)
     ON_BN_CLICKED(4,clickLast)
     ON_BN_CLICKED(5,clickNew)
     ON_BN_CLICKED(6,clickOpen)
     ON_BN_CLICKED(7,clickSave)
     ON_BN_CLICKED(8,clickDel)
     ON_WM_DESTROY()
     //}}AFX_MSG_MAP
     ON_OLEVERB(AFX_IDS_VERB_PROPERTIES, OnProperties)
END_MESSAGE_MAP()


/////////////////////////////////////////////////////////////////////////////
// Dispatch map

BEGIN_DISPATCH_MAP(CONotesCtrl, COleControl)
     //{{AFX_DISPATCH_MAP(CONotesCtrl)
     DISP_PROPERTY_NOTIFY(CONotesCtrl, "FileName", m_fileName, OnFileNameChanged, VT_BSTR)
     //}}AFX_DISPATCH_MAP
     DISP_FUNCTION_ID(CONotesCtrl, "AboutBox", DISPID_ABOUTBOX, AboutBox, VT_EMPTY, VTS_NONE)
END_DISPATCH_MAP()


/////////////////////////////////////////////////////////////////////////////
// Event map

BEGIN_EVENT_MAP(CONotesCtrl, COleControl)
     //{{AFX_EVENT_MAP(CONotesCtrl)
     // NOTE - ClassWizard will add and remove event map entries
     //    DO NOT EDIT what you see in these blocks of generated code !
     //}}AFX_EVENT_MAP
END_EVENT_MAP()


/////////////////////////////////////////////////////////////////////////////
// Property pages

// TODO: Add more property pages as needed. Remember to increase the count!
BEGIN_PROPPAGEIDS(CONotesCtrl, 1)
     PROPPAGEID(CONotesPropPage::guid)
END_PROPPAGEIDS(CONotesCtrl)


/////////////////////////////////////////////////////////////////////////////
// Initialize class factory and guid

IMPLEMENT_OLECREATE_EX(CONotesCtrl, "ONOTES.ONotesCtrl.1",
     0xae2e4103, 0xdbee, 0x11cf, 0x87, 0x12, 0x44, 0x45, 0x53, 0x54, 0, 0)


/////////////////////////////////////////////////////////////////////////////
// Type library ID and version

IMPLEMENT_OLETYPELIB(CONotesCtrl, _tlid, _wVerMajor, _wVerMinor)


/////////////////////////////////////////////////////////////////////////////
// Interface IDs

const IID BASED_CODE IID_DONotes =
          { 0xae2e4101, 0xdbee, 0x11cf, { 0x87, 0x12, 0x44, 0x45, 0x53, 0x54, 0, 0 } };
const IID BASED_CODE IID_DONotesEvents =
          { 0xae2e4102, 0xdbee, 0x11cf, { 0x87, 0x12, 0x44, 0x45, 0x53, 0x54, 0, 0 } };


/////////////////////////////////////////////////////////////////////////////
// Control type information

static const DWORD BASED_CODE _dwONotesOleMisc =
     OLEMISC_ACTIVATEWHENVISIBLE |
     OLEMISC_SETCLIENTSITEFIRST |
     OLEMISC_INSIDEOUT |
     OLEMISC_CANTLINKINSIDE |
     OLEMISC_RECOMPOSEONRESIZE;

IMPLEMENT_OLECTLTYPE(CONotesCtrl, IDS_ONOTES, _dwONotesOleMisc)


/////////////////////////////////////////////////////////////////////////////
// CONotesCtrl::CONotesCtrlFactory::UpdateRegistry -
// Adds or removes system registry entries for CONotesCtrl

BOOL CONotesCtrl::CONotesCtrlFactory::UpdateRegistry(BOOL bRegister)
{
     // TODO: Verify that your control follows apartment-model threading rules.
     // Refer to MFC TechNote 64 for more information.
     // If your control does not conform to the apartment-model rules, then
     // you must modify the code below, changing the 6th parameter from
     // afxRegApartmentThreading to 0.

     if (bRegister)
          return AfxOleRegisterControlClass(
               AfxGetInstanceHandle(),
               m_clsid,
               m_lpszProgID,
               IDS_ONOTES,
               IDB_ONOTES,
               afxRegApartmentThreading,
               _dwONotesOleMisc,
               _tlid,
               _wVerMajor,
               _wVerMinor);
     else
          return AfxOleUnregisterClass(m_clsid, m_lpszProgID);
}


/////////////////////////////////////////////////////////////////////////////
// CONotesCtrl::CONotesCtrl - Constructor

CONotesCtrl::CONotesCtrl()
{
     InitializeIIDs(&IID_DONotes, &IID_DONotesEvents);
     size.left=size.top=size.right=size.bottom=0;
     count=0;
     pos=NULL;
     note="";
     // TODO: Initialize your control's instance data here.
}


/////////////////////////////////////////////////////////////////////////////
// CONotesCtrl::~CONotesCtrl - Destructor

CONotesCtrl::~CONotesCtrl()
{
     // TODO: Cleanup your control's instance data here.
}


/////////////////////////////////////////////////////////////////////////////
// CONotesCtrl::OnDraw - Drawing function

void CONotesCtrl::OnDraw(
               CDC* pdc, const CRect& rcBounds, const CRect& rcInvalid)
{
     // TODO: Replace the following code with your own drawing code.
//     pdc->FillRect(rcBounds, CBrush::FromHandle((HBRUSH)GetStockObject(WHITE_BRUSH)));
//     pdc->Ellipse(rcBounds);
     pdc->MoveTo(0,0);
     pdc->LineTo(size.right,0);
     pdc->LineTo(size.right,size.bottom);
     pdc->LineTo(size.left,size.bottom);
     pdc->LineTo(0,0);
     pdc->Rectangle(160,0,size.right,20);
}


/////////////////////////////////////////////////////////////////////////////
// CONotesCtrl::DoPropExchange - Persistence support

void CONotesCtrl::DoPropExchange(CPropExchange* pPX)
{
     ExchangeVersion(pPX, MAKELONG(_wVerMinor, _wVerMajor));
     COleControl::DoPropExchange(pPX);

     // TODO: Call PX_ functions for each persistent custom property.

}


/////////////////////////////////////////////////////////////////////////////
// CONotesCtrl::OnResetState - Reset control to default state

void CONotesCtrl::OnResetState()
{
     COleControl::OnResetState();  // Resets defaults found in DoPropExchange

     // TODO: Reset any other control state here.
}


/////////////////////////////////////////////////////////////////////////////
// CONotesCtrl::AboutBox - Display an "About" box to the user

void CONotesCtrl::AboutBox()
{
     CDialog dlgAbout(IDD_ABOUTBOX_ONOTES);
     dlgAbout.DoModal();
}


/////////////////////////////////////////////////////////////////////////////
// CONotesCtrl message handlers

void CONotesCtrl::OnSize(UINT nType, int cx, int cy) 
{
     COleControl::OnSize(nType, cx, cy);
     size.left=0;
     size.top=20;
     size.right=cx;
     size.bottom=cy-20;
     if(edit!=NULL)
          edit.SetWindowPos(&wndTop,0,20, cx, cy-20,SWP_SHOWWINDOW );

     
     // TODO: Add your message handler code here
     
}

void CONotesCtrl::OnMouseMove(UINT nFlags, CPoint point) 
{
     // TODO: Add your message handler code here and/or call default
     
     COleControl::OnMouseMove(nFlags, point);
}

int CONotesCtrl::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
     if (COleControl::OnCreate(lpCreateStruct) == -1)
          return -1;
     RECT bsize;
     bsize.left=0;
     bsize.top=0;
     bsize.right=20;
     bsize.bottom=20;
     button[0].Create("",BS_PUSHBUTTON|WS_CHILD|WS_VISIBLE|BS_OWNERDRAW ,bsize,this,1);
     button[0].LoadBitmaps(IDB_BITMAPFIRSTUP,IDB_BITMAPFIRSTDOWN);
     bsize.left=20;
     bsize.right=40;
     button[1].Create("",BS_PUSHBUTTON|WS_CHILD|WS_VISIBLE|BS_OWNERDRAW ,bsize,this,2);
     button[1].LoadBitmaps(IDB_BITMAPPREVUP,IDB_BITMAPPREVDOWN);
     bsize.left=40;
     bsize.right=60;
     button[2].Create("",BS_PUSHBUTTON|WS_CHILD|WS_VISIBLE|BS_OWNERDRAW ,bsize,this,3);
     button[2].LoadBitmaps(IDB_BITMAPNEXTUP,IDB_BITMAPNEXTDOWN);
     bsize.left=60;
     bsize.right=80;
     button[3].Create("",BS_PUSHBUTTON|WS_CHILD|WS_VISIBLE|BS_OWNERDRAW ,bsize,this,4);
     button[3].LoadBitmaps(IDB_BITMAPLASTUP,IDB_BITMAPLASTDOWN);
     bsize.left=80;
     bsize.right=100;
     button[4].Create("",BS_PUSHBUTTON|WS_CHILD|WS_VISIBLE|BS_OWNERDRAW ,bsize,this,5);
     button[4].LoadBitmaps(IDB_BITMAPNEWUP,IDB_BITMAPNEWDOWN);
     bsize.left=100;
     bsize.right=120;
     button[5].Create("",BS_PUSHBUTTON|WS_CHILD|WS_VISIBLE|BS_OWNERDRAW ,bsize,this,6);
     button[5].LoadBitmaps(IDB_BITMAPOPENUP,IDB_BITMAPOPENDOWN);
     bsize.left=120;
     bsize.right=140;
     button[6].Create("",BS_PUSHBUTTON|WS_CHILD|WS_VISIBLE|BS_OWNERDRAW ,bsize,this,7);
     button[6].LoadBitmaps(IDB_BITMAPSAVEUP,IDB_BITMAPSAVEDOWN);
     bsize.left=140;
     bsize.right=160;
     button[7].Create("",BS_PUSHBUTTON|WS_CHILD|WS_VISIBLE|BS_OWNERDRAW ,bsize,this,8);
     button[7].LoadBitmaps(IDB_BITMAPDELUP,IDB_BITMAPDELDOWN);
     edit.Create(ES_WANTRETURN|ES_MULTILINE|ES_AUTOHSCROLL|ES_AUTOVSCROLL|WS_CHILD|WS
	 _VISIBLE|WS_BORDER|WS_HSCROLL|WS_VSCROLL,size,this,9);
     edit.SetBackgroundColor(0,RGB(255,255,0));
     if(RegOpenKeyEx(HKEY_CURRENT_USER,"Software\\ONotes\\File",0,KEY_ALL_ACCESS,&hkFile)!
	 =ERROR_SUCCESS){
          RegCreateKey(HKEY_CURRENT_USER,"Software\\ONotes\\File",&hkFile);
          file="c:\\onotes.bin";
          RegSetValue(hkFile,NULL,REG_SZ,(LPCTSTR)file,file.GetLength());
          clickNew();
     }else{
          char fileStr[MAX_PATH];
          long size;
          RegQueryValue(hkFile,NULL,fileStr,&size);
          file=fileStr;
          Open();
     }
     return 0;
}

void CONotesCtrl::clickFirst( ){
     POSITION p=pos;
     SaveNote();
     p=notes.GetHeadPosition( );
     if(p==NULL){
          SetNote();
          return;
     }
     pos=p;
     note=notes.GetAt(pos);
     SetNote();
}
void CONotesCtrl::clickPrev( ){
     POSITION p=pos;
     SaveNote();
     notes.GetPrev(p);
     if(p==NULL){
          SetNote();
          return;
     }
     pos=p;
     note=notes.GetAt(pos);
     SetNote();
}

void CONotesCtrl::clickNext( ){
     POSITION p=pos;
     SaveNote();
     notes.GetNext(p);
     if(p==NULL){
          SetNote();
          return;
     }
     pos=p;
     note=notes.GetAt(pos);
     SetNote();
}
void CONotesCtrl::clickLast( ){
     POSITION p=pos;
     SaveNote();
     p=notes.GetTailPosition( );
     if(p==NULL){
          SetNote();
          return;
     }
     pos=p;
     note=notes.GetAt(pos);
     SetNote();
}
void CONotesCtrl::clickNew( ){
     SaveNote();
     edit.HideSelection( 1, 1 );
     edit.SetSel(0,edit.GetTextLength( ));
     edit.Clear();
     edit.HideSelection( 0, 1 );
     note="";
     pos= notes.AddTail(note);
}
void CONotesCtrl::clickOpen( ){
     CFileDialog open(1,"*.bin",NULL,OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT,NULL,this);
     if(open.DoModal()==IDOK){
          file=open.GetPathName( );
          RegSetValue(hkFile,NULL,REG_SZ,(LPCTSTR)file,file.GetLength());
          Open();
     }
}

void CONotesCtrl::clickSave( ){
     CFileDialog save(0,"*.bin",(LPCTSTR)file,OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT,NULL,this);
     if(save.DoModal()==IDOK){
          file=save.GetPathName( );
          RegSetValue(hkFile,NULL,REG_SZ,(LPCTSTR)file,file.GetLength());
          Close();
     }
}

void CONotesCtrl::clickDel( ){
     POSITION p=pos,p1=pos;
     notes.GetNext(p1);
     if(p1!=NULL)
          pos=p1;
     else{
          p1=pos;
          notes.GetPrev(p1);
          if(p1==NULL)
               clickNew();
          else
               pos=p1;
     }
     notes.RemoveAt(p);
     note=notes.GetAt(pos);
     SetNote();
}


void CONotesCtrl::OnFileNameChanged(){
     // TODO: Add notification handler code
     SetModifiedFlag();
     file=m_fileName;
}

void CONotesCtrl::SetNote(){
     edit.HideSelection( 1, 1 );
     edit.SetSel(0,edit.GetTextLength( ));
     edit.ReplaceSel((LPCTSTR)note);
     edit.HideSelection( 0, 1 );
}

void CONotesCtrl::GetNote(){
     edit.HideSelection( 1, 1 );
     edit.SetSel(0,edit.GetTextLength( ));
     note=edit.GetSelText( );
     edit.HideSelection( 0, 1 );
}

void CONotesCtrl::SaveNote(){
     if(pos!=NULL){
          GetNote();
          notes.SetAt(pos,note);
     }
}

void CONotesCtrl::OnDestroy() 
{
     Close( );
     RegCloseKey(hkFile);
     COleControl::OnDestroy();
     // TODO: Add your message handler code here
}

void CONotesCtrl::Open(){
     CString nt;
     CFile f((LPCTSTR)file,CFile::modeRead);
     int count,length;
     char *n;
     notes.RemoveAll( );
     f.Read((void*)&count,sizeof(int));
     while(count>0){
          f.Read((void*)&length,sizeof(int));
          n=new char[length+1];
          f.Read((void*)(LPCTSTR)n,length);
          n[length]='\0';
          nt=n;
          pos=notes.AddTail(nt);
          delete n;
          count--;
     }
     note=nt;
     SetNote();
}
void CONotesCtrl::Close(){
     POSITION p;
     CString n;
     int length;
     CFile f((LPCTSTR)file,CFile::modeCreate|CFile::modeWrite);
     length=notes.GetCount();
     f.Write((const void*)&length,sizeof(int));
     SaveNote();
     p=notes.GetHeadPosition( );
     while(p!=NULL){
          n=notes.GetAt(p);
          length=n.GetLength();
          f.Write((const void*)&length,sizeof(int));
          f.Write((const void*)(LPCTSTR)n,n.GetLength());
          notes.GetNext(p);
     }
}


Listing 5.2 ONotesCtl.h
// ONotesCtl.h : Declaration of the CONotesCtrl OLE control class.

/////////////////////////////////////////////////////////////////////////////
// CONotesCtrl : See ONotesCtl.cpp for implementation.

class CONotesCtrl : public COleControl
{
     DECLARE_DYNCREATE(CONotesCtrl)

// Constructor
public:
     CONotesCtrl();

// Overrides

     // Drawing function
     virtual void OnDraw(
                    CDC* pdc, const CRect& rcBounds, const CRect& rcInvalid);

     // Persistence
     virtual void DoPropExchange(CPropExchange* pPX);

     // Reset control state
     virtual void OnResetState();

// Implementation
protected:
     CRichEditCtrl edit;
     ~CONotesCtrl();
     CBitmapButton button[8];
     RECT size;
     DWORD count;
     POSITION pos;
     CList <CString,CString&> notes;
     CString note,file;
     void SetNote();
     void GetNote();
     void SaveNote();
     void Open();
     void Close();
     HKEY hkFile;

     DECLARE_OLECREATE_EX(CONotesCtrl)    // Class factory and guid
     DECLARE_OLETYPELIB(CONotesCtrl)      // GetTypeInfo
     DECLARE_PROPPAGEIDS(CONotesCtrl)     // Property page IDs
     DECLARE_OLECTLTYPE(CONotesCtrl)          // Type name and misc status

// Message maps
     //{{AFX_MSG(CONotesCtrl)
     afx_msg void OnSize(UINT nType, int cx, int cy);
     afx_msg void OnMouseMove(UINT nFlags, CPoint point);
     afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
     afx_msg void clickFirst( );
     afx_msg void clickPrev( );
     afx_msg void clickNext( );
     afx_msg void clickLast( );
     afx_msg void clickNew( );
     afx_msg void clickOpen( );
     afx_msg void clickSave( );
     afx_msg void clickDel( );
     afx_msg void OnDestroy();
     //}}AFX_MSG
     DECLARE_MESSAGE_MAP()

// Dispatch maps
     //{{AFX_DISPATCH(CONotesCtrl)
     CString m_fileName;
     afx_msg void OnFileNameChanged();
     //}}AFX_DISPATCH
     DECLARE_DISPATCH_MAP()

     afx_msg void AboutBox();

// Event maps
     //{{AFX_EVENT(CONotesCtrl)
     //}}AFX_EVENT
     DECLARE_EVENT_MAP()

// Dispatch and event IDs
public:
     enum {
     //{{AFX_DISP_ID(CONotesCtrl)
     dispidFileName = 1L,
     //}}AFX_DISP_ID
     };
};

Component Categories

The skeleton application handed to you by the Control Wizard includes everything you need to build an OCX. As you can see in Listing 5.1, GUIDs are included to uniquely identify the control. They are followed a few lines later by the UpdateRegistry function, which causes the control to add or update its information in the System Registry when the OCX is loaded. Self-registration is one of the new requirements for ActiveX controls, as outlined in the document "OLE Control and Control Container Guidelines" located in the \Specs directory of the ActiveX SDK.

Another item that we want to discuss here is the registration of Component Categories, also outlined in the Guidelines document. A Component Category is the identification of an "area of functionality" that is either (1) implemented by the component and used by the container application, or (2) required by the component and implemented by the container application. An "area of functionality" is more than just the interfaces that a component requires or supports; a Component Category is meant to give an indication of the component's purpose or function.

The identification of categories is made through one of the unique GUIDs established by Microsoft for this purpose. The Guidelines document lists a few of the categories and their currently defined GUIDs. There does not appear to be a definitive list of all the GUIDs assigned at this time. In our sample application, "Sizer," we register two categories for the control, SafeForScripting and SafeForInitializing. Those GUIDs are in the sample applications in the \BaseCtl directory of the ActiveX SDK.

Adding these extra keys to the System Registry is easy enough, using the helper functions that Microsoft supplies. They're in the \BaseCtl directory, in the files CATHELP.CPP and CATHELP.H. For our Sizer application, we simply borrowed the functions from CATHELP.CPP to register the control for the two SafeFor… categories.

Properties, Events, and Methods

Delivering OCXs via the Web is nice, but wouldn't mean much if the control could not communicate with its environment-including the program hosting the control, and the user of the control. An ActiveX control has three means for communicating with the world outside: properties, events, and methods. Experienced programmers will immediately recognize these terms as classic object-oriented notions. Indeed, both the Visual Basic and Visual C++ programming languages, with their object-oriented flavor, provide a solid platform to develop applications that communicate with OCXs.

Properties

A property is a means for you to define an attribute of your control. Attributes are factors that can be changed either when the control is loaded or while the control is running. They can be any number of things, such as the control's background color or font style; they're usually elements that you want the user to be able to change. The property is a hook, or exposed portion of a control that can be changed by an event (such as a mouse click) or the passage of time. It can also be an attribute that the user can't directly change but that the control itself will change in response to an event or action. One way or another, a property represents something that can change during the lifetime of the control.

Adding a Property to a Control

You add a property using the OLE Automation tab in the ClassWizard. There are two means of adding a property: either as a member variable or with Get/Set methods. Adding a property as a member variable gives you "direct" access to the property; the Get/Set method gives you "controlled" access.

When you add a property, you give it an external name. This is the name the user will use to set or read the value of the property. Figure 5.4 shows the Add Property screen in the ClassWizard.

Figure 5.4 : Adding a property in ClassWizard.

If you wish to allow the user to set properties on the control by supplying parameters in the <object> tag when the control is loaded via an HTML page, then you add the property to be set as a member variable. The external name you supply is the one you use in the <object> tag; the internal name is the one you use in your program.

The function that takes the value of this property from the <object> tag and passes it to your program is the DoPropExchange function, called when the control is loaded. For example:

void CEvntCtrl::DoPropExchange(CPropExchange* pPX)
{
     ExchangeVersion(pPX, MAKELONG(_wVerMinor, _wVerMajor));
     COleControl::DoPropExchange(pPX);
     PX_Short(pPX, _T("total"), m_total, 0);
}

In this case we have a parameter named "total" that the user can put in the <object> tag, and whatever value the user specifies in that tag will be placed into our m_total variable. If the user supplies an incorrect value (such as a text string in this case) or no data, then m_value will default to zero.

Another way to set up methods is with Get and Set functions, which allow programmatic access to your control's properties. This means a VBScript, for instance, could query or change the value of the property as the script is running. Let's say we set up Get and Set functions for a property called State. The ClassWizard will add two skeleton handler functions for us.

In the following bit of code, these two functions appear with an additional line we added to do something when the functions are called:

short CEvntCtrl::GetState() 
{
     return m_value;
}
void CEvntCtrl::SetState(short nNewValue) 
{
     m_value = nNewValue;
     SetModifiedFlag();
}

The SetState function will be invoked when the host program assigns something to our external name State, and the GetState function will be invoked when the host program attempts to read State.

Consider these lines of VBScript:

X = 5
Evnt1.State = X
MsgBox Evnt1.State

Here the statement Evnt1.State assigns the value of X to our external property name of State, invoking the SetState function. The next VBScript statement invokes the GetState function, which in our example simply returns the current value of a global variable.

Events

An event, in classic programming parlance, is a change in the state of the world. In the context of OCXs, an event is a notification sent from the control to the control container. What triggers the event-that is, the change in state-is left up to the control developer. You can trigger an event on a user action such as a mouse click, for example. Or your control might have an internal timer or counter that fires an event when a certain count is reached.

Let's take the simplest example, the Click event, which occurs when the user clicks the mouse on an OCX control in a Web page. First we add a stock event handler in the ClassWizard, as shown in Figure 5.5. Now our control will respond when the mouse is clicked on the control. The response is to tell the container program to "FireClick" (or to fire whatever function the container program has, if any, for the Click event). In our VBScript, this is simply a subroutine like this one:

Figure 5.5 : Adding a handler for the Click event.

Sub Evnt1_Click()
...[some program statements]
End Sub

You'll need to add event handlers in your control for the events about which you want your container program notified. This is different from simply responding in your control to something that takes place in the control or container window, in which case your control is responding to messages.

Methods

A method that is coded into your control can be invoked by the container program. You accomplish this by adding a method handler as shown in Figure 5.6.

Figure 5.6 : Adding a method to a control.

In this case, we have already added a method called Test() and are now adding another method, ReTest(). Along with the method handler, the ClassWizard generates a skeleton function, to which you add your implementation of the method. Our Test method does the following:

short CEvntCtrl::ReTest() 
{
     count++;
     return count;
}

Now, to call this method in a VBScript, we do the following:

X = Evnt1.ReTest

to invoke the ReTest function. This returns the new value and places it in the X variable.

All the aspects of using methods, properties, and events form a complex topic, beyond the scope of this chapter. In particular, the differences between these three elements can be a bit confusing, especially when you throw in the messages that the control can respond to. Our purpose here has been to define these three characteristics of controls in the simplest possible terms, so that you can create a test control and begin interacting with a VBScript that contains that control. Let's turn now to a few sample controls, followed by a section on using VBScript.

Interacting with the Explorer Client Window

Our three sample controls all demonstrate how to change graphical images in the Explorer window. They also show how to exchange parameter data supplied by the user in the <object> tag, and how to respond to Windows messages.

This section examines the VC++ source code. We'll follow it with a study of scripting controls, along with a couple of sample scripts for the controls shown here.

The Xyz Control

Our first experiment with the Internet Explorer client window is a relatively simple control that draws itself in the window using a user-specified color that changes in reaction to mouse movement. The starting color for the control is determined by the user specifying three parameters to the object tag for the red, green, and blue (RGB) values of the color. Three additional parameters allow the user to set a factor for how much the RGB values will change with each mouse movement. A sample of the <object> tag is in Listing 5.3.


Listing 5.3 Sample object tag for the Xyz control
<OBJECT ID="Xyz1" WIDTH=100 HEIGHT=100
     CLASSID="CLSID:59E80A13-01CB-11D0-AD51-000000000000">
        <PARAM NAME="_Version" VALUE="65536">
        <PARAM NAME="_ExtentX" VALUE="2646">
        <PARAM NAME="_ExtentY" VALUE="1323">
        <PARAM NAME="_StockProps" VALUE="0">
        <PARAM NAME="red" VALUE="0100">
        <PARAM NAME="green" VALUE="0200">
        <PARAM NAME="blue" VALUE="050">
        <PARAM NAME="rfactor" VALUE="002">
        <PARAM NAME="gfactor" VALUE="001">
        <PARAM NAME="bfactor" VALUE="003">
</OBJECT>

Figure 5.7 contains a sample Web page using the Xyz control. Here we simply dumped several copies of the control, with varying start-up parameters. Along with reacting to mouse movement, the control will also respond to a mouse click on the control itself, changing its state to turn on/off the color-changing effect.

Figure 5.7 : Sample Web page using the Xyz control.

Listings 5.4 and 5.5 are the main source and header files for this control.


Listing 5.4 XyzCtl.cpp
// XyzCtl.cpp : Implementation of the CXyzCtrl OLE control class.

#include "stdafx.h"
#include "xyz.h"
#include "XyzCtl.h"
#include "XyzPpg.h"


#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif


IMPLEMENT_DYNCREATE(CXyzCtrl, COleControl)


/////////////////////////////////////////////////////////////////////////////
// Message map

BEGIN_MESSAGE_MAP(CXyzCtrl, COleControl)
     //{{AFX_MSG_MAP(CXyzCtrl)
     ON_WM_CREATE()
     ON_WM_MOUSEMOVE()
     //}}AFX_MSG_MAP
     ON_MESSAGE(OCM_COMMAND, OnOcmCommand)
     ON_OLEVERB(AFX_IDS_VERB_EDIT, OnEdit)
     ON_OLEVERB(AFX_IDS_VERB_PROPERTIES, OnProperties)
END_MESSAGE_MAP()


/////////////////////////////////////////////////////////////////////////////
// Dispatch map

BEGIN_DISPATCH_MAP(CXyzCtrl, COleControl)
     //{{AFX_DISPATCH_MAP(CXyzCtrl)
     DISP_PROPERTY(CXyzCtrl, "red", m_red, VT_I2)
     DISP_PROPERTY(CXyzCtrl, "green", m_green, VT_I2)
     DISP_PROPERTY(CXyzCtrl, "blue", m_blue, VT_I2)
     DISP_PROPERTY(CXyzCtrl, "rfactor", m_rfactor, VT_I2)
     DISP_PROPERTY(CXyzCtrl, "bfactor", m_bfactor, VT_I2)
     DISP_PROPERTY(CXyzCtrl, "gfactor", m_gfactor, VT_I2)
     DISP_PROPERTY_EX(CXyzCtrl, "State", GetState, SetState, VT_BOOL)
     DISP_FUNCTION_ID(CXyzCtrl, "DoClick", DISPID_DOCLICK, DoClick, VT_EMPTY, VTS_NONE)
     //}}AFX_DISPATCH_MAP
     DISP_FUNCTION_ID(CXyzCtrl, "AboutBox", DISPID_ABOUTBOX, AboutBox, VT_EMPTY, VTS_NONE)
END_DISPATCH_MAP()


/////////////////////////////////////////////////////////////////////////////
// Event map

BEGIN_EVENT_MAP(CXyzCtrl, COleControl)
     //{{AFX_EVENT_MAP(CXyzCtrl)
     EVENT_CUSTOM_ID("Click", DISPID_CLICK, FireClick, VTS_NONE)
     //}}AFX_EVENT_MAP
END_EVENT_MAP()


/////////////////////////////////////////////////////////////////////////////
// Property pages

// TODO: Add more property pages as needed. Remember to increase the count!
BEGIN_PROPPAGEIDS(CXyzCtrl, 1)
     PROPPAGEID(CXyzPropPage::guid)
END_PROPPAGEIDS(CXyzCtrl)


/////////////////////////////////////////////////////////////////////////////
// Initialize class factory and guid

IMPLEMENT_OLECREATE_EX(CXyzCtrl, "XYZ.XyzCtrl.1",
     0x59e80a13, 0x1cb, 0x11d0, 0xad, 0x51, 0, 0, 0, 0, 0, 0)


/////////////////////////////////////////////////////////////////////////////
// Type library ID and version

IMPLEMENT_OLETYPELIB(CXyzCtrl, _tlid, _wVerMajor, _wVerMinor)


/////////////////////////////////////////////////////////////////////////////
// Interface IDs

const IID BASED_CODE IID_DXyz =
          { 0xf01ddb51, 0x1d5, 0x11d0, { 0xad, 0x51, 0, 0, 0, 0, 0, 0 } };
const IID BASED_CODE IID_DXyzEvents =
          { 0xf01ddb52, 0x1d5, 0x11d0, { 0xad, 0x51, 0, 0, 0, 0, 0, 0 } };


/////////////////////////////////////////////////////////////////////////////
// Control type information

static const DWORD BASED_CODE _dwXyzOleMisc =
     OLEMISC_ACTIVATEWHENVISIBLE |
     OLEMISC_IGNOREACTIVATEWHENVISIBLE |
     OLEMISC_SETCLIENTSITEFIRST |
     OLEMISC_INSIDEOUT |
     OLEMISC_CANTLINKINSIDE |
     OLEMISC_RECOMPOSEONRESIZE;

IMPLEMENT_OLECTLTYPE(CXyzCtrl, IDS_XYZ, _dwXyzOleMisc)


/////////////////////////////////////////////////////////////////////////////
// CXyzCtrl::CXyzCtrlFactory::UpdateRegistry -
// Adds or removes system registry entries for CXyzCtrl

BOOL CXyzCtrl::CXyzCtrlFactory::UpdateRegistry(BOOL bRegister)
{
     // TODO: Verify that your control follows apartment-model threading rules.
     // Refer to MFC TechNote 64 for more information.
     // If your control does not conform to the apartment-model rules, then
     // you must modify the code below, changing the 6th parameter from
     // afxRegInsertable | afxRegApartmentThreading to afxRegInsertable.

     if (bRegister)
          return AfxOleRegisterControlClass(
               AfxGetInstanceHandle(),
               m_clsid,
               m_lpszProgID,
               IDS_XYZ,
               IDB_XYZ,
               afxRegInsertable | afxRegApartmentThreading,
               _dwXyzOleMisc,
               _tlid,
               _wVerMajor,
               _wVerMinor);
     else
          return AfxOleUnregisterClass(m_clsid, m_lpszProgID);
}


/////////////////////////////////////////////////////////////////////////////
// CXyzCtrl::CXyzCtrl - Constructor

CXyzCtrl::CXyzCtrl()
{
     InitializeIIDs(&IID_DXyz, &IID_DXyzEvents);

     // TODO: Initialize your control's instance data here.
}


/////////////////////////////////////////////////////////////////////////////
// CXyzCtrl::~CXyzCtrl - Destructor

CXyzCtrl::~CXyzCtrl()
{
     // TODO: Cleanup your control's instance data here.
}


/////////////////////////////////////////////////////////////////////////////
// CXyzCtrl::OnDraw - Drawing function

void CXyzCtrl::OnDraw(
               CDC* pdc, const CRect& rcBounds, const CRect& rcInvalid)
{

//     SetControlSize(m_width,m_height);
//     CRect crect(0,0,m_width,m_height);
     CRect crect(&rcBounds);
     DWORD dwColor = RGB(m_red,m_green,m_blue);
     CBrush pBrush(dwColor);
      CBrush* pOldBrush = pdc->SelectObject(&pBrush);
//     DoSuperclassPaint(pdc, rcBounds);
     pdc->FillRect(crect,&pBrush);
     pdc->SelectObject(&pOldBrush);

     
     if (!IsOptimizedDraw())
     {
          // The container does not support optimized drawing.

          // TODO: if you selected any GDI objects into the device context *pdc,
          //          restore the previously-selected objects here.
          //          For more information, please see MFC technical note #nnn,
          //          "Optimizing an ActiveX Control".
     }
}


/////////////////////////////////////////////////////////////////////////////
// CXyzCtrl::DoPropExchange - Persistence support

void CXyzCtrl::DoPropExchange(CPropExchange* pPX)
{
     ExchangeVersion(pPX, MAKELONG(_wVerMinor, _wVerMajor));
     COleControl::DoPropExchange(pPX);
     
     // TODO: Call PX_ functions for each persistent custom property.

     PX_Short(pPX, _T("red"), m_red, 0);
     PX_Short(pPX, _T("green"), m_green, 0);
     PX_Short(pPX, _T("blue"), m_blue, 0);
     PX_Short(pPX, _T("rfactor"), m_rfactor, 0);
     PX_Short(pPX, _T("bfactor"), m_bfactor, 0);
     PX_Short(pPX, _T("gfactor"), m_gfactor, 0);
     
}


/////////////////////////////////////////////////////////////////////////////
// CXyzCtrl::GetControlFlags -
// Flags to customize MFC's implementation of ActiveX controls.
//
// For information on using these flags, please see MFC technical note
// #nnn, "Optimizing an ActiveX Control".
DWORD CXyzCtrl::GetControlFlags()
{
     DWORD dwFlags = COleControl::GetControlFlags();


     // The control can activate without creating a window.
     // TODO: when writing the control's message handlers, avoid using
     //          the m_hWnd member variable without first checking that its
     //          value is non-NULL.
     dwFlags |= windowlessActivate;

     // The control can receive mouse notifications when inactive.
     // TODO: if you write handlers for WM_SETCURSOR and WM_MOUSEMOVE,
     //          avoid using the m_hWnd member variable without first
     //          checking that its value is non-NULL.
     dwFlags |= pointerInactive;

     // The control can optimize its OnDraw method, by not restoring
     // the original GDI objects in the device context.
     dwFlags |= canOptimizeDraw;
     return dwFlags;
}


/////////////////////////////////////////////////////////////////////////////
// CXyzCtrl::OnResetState - Reset control to default state

void CXyzCtrl::OnResetState()
{
     COleControl::OnResetState();  // Resets defaults found in DoPropExchange

     // TODO: Reset any other control state here.
}


/////////////////////////////////////////////////////////////////////////////
// CXyzCtrl::AboutBox - Display an "About" box to the user

void CXyzCtrl::AboutBox()
{
     CDialog dlgAbout(IDD_ABOUTBOX_XYZ);
     dlgAbout.DoModal();
}


/////////////////////////////////////////////////////////////////////////////
// CXyzCtrl::PreCreateWindow - Modify parameters for CreateWindowEx

BOOL CXyzCtrl::PreCreateWindow(CREATESTRUCT& cs)
{
     cs.lpszClass = _T("BUTTON");
     return COleControl::PreCreateWindow(cs);
}


/////////////////////////////////////////////////////////////////////////////
// CXyzCtrl::IsSubclassedControl - This is a subclassed control

BOOL CXyzCtrl::IsSubclassedControl()
{
     return TRUE;
}


/////////////////////////////////////////////////////////////////////////////
// CXyzCtrl::OnOcmCommand - Handle command messages

LRESULT CXyzCtrl::OnOcmCommand(WPARAM wParam, LPARAM lParam)
{
#ifdef _WIN32
     WORD wNotifyCode = HIWORD(wParam);
#else
     WORD wNotifyCode = HIWORD(lParam);
#endif

     // TODO: Switch on wNotifyCode here.
     switch (wNotifyCode) {
     case 0:
          DoClick();
          break;
     default:
     InvalidateControl();
     }

     return 0;
}


/////////////////////////////////////////////////////////////////////////////
// CXyzCtrl message handlers

void CXyzCtrl::DoClick() 
{
TRACE("DoClick\n");
     if(m_state) // equals TRUE
     { m_state=FALSE; }
     else
     { m_state=TRUE; }
     InvalidateControl();
}


int CXyzCtrl::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
     
     
     if (COleControl::OnCreate(lpCreateStruct) == -1)
          return -1;

     SetControlSize(lpCreateStruct->cx,lpCreateStruct->cy);
     bHitUpper=FALSE; bHitLower=FALSE;
     return 0;
}

void CXyzCtrl::OnMouseMove(UINT nFlags, CPoint point) 
{
     if(GetState())
     {
     int nUpBound = 250;
     if(m_red>nUpBound||m_red<1)
          {m_rfactor=m_rfactor*-1;}

     if(m_green>nUpBound||m_green<1)
          {m_gfactor=m_gfactor*-1;}

     if(m_blue>nUpBound||m_blue<1)
          {m_bfactor=m_bfactor*-1;}

     m_red=m_red+m_rfactor;
     m_green=m_green+m_gfactor;
     m_blue=m_blue+m_bfactor;
//     TRACE("%d %d %d\n", m_red, m_blue, m_green);

     InvalidateControl();
     }

//     COleControl::OnMouseMove(nFlags, point);
}



BOOL CXyzCtrl::GetState() 
{
     // TODO: Add your property handler here
     return m_state;
     //return TRUE;
}

void CXyzCtrl::SetState(BOOL bNewValue) 
{
     // TODO: Add your property handler here
     TRACE("SetState\n");
     SetModifiedFlag();
}


void CXyzCtrl::OnClick(USHORT iButton) 
{
     TRACE("onclick\n");
     
     COleControl::OnClick(iButton);
}


Listing 5.5 XyzCtl.h
// XyzCtl.h : Declaration of the CXyzCtrl OLE control class.

/////////////////////////////////////////////////////////////////////////////
// CXyzCtrl : See XyzCtl.cpp for implementation.

class CXyzCtrl : public COleControl
{
     DECLARE_DYNCREATE(CXyzCtrl)

// Constructor
public:
     BOOL m_state;
     CXyzCtrl();
     SHORT red;
     SHORT green;
     SHORT blue;
     BOOL bHitUpper;
     BOOL bHitLower;


// Overrides
     // ClassWizard generated virtual function overrides
     //{{AFX_VIRTUAL(CXyzCtrl)
     public:
     virtual void OnDraw(CDC* pdc, const CRect& rcBounds, const CRect& rcInvalid);
     virtual void DoPropExchange(CPropExchange* pPX);
     virtual void OnResetState();
     virtual DWORD GetControlFlags();
     virtual void OnClick(USHORT iButton);
     //}}AFX_VIRTUAL

// Implementation
protected:
     ~CXyzCtrl();

     DECLARE_OLECREATE_EX(CXyzCtrl)    // Class factory and guid
     DECLARE_OLETYPELIB(CXyzCtrl)      // GetTypeInfo
     DECLARE_PROPPAGEIDS(CXyzCtrl)     // Property page IDs
     DECLARE_OLECTLTYPE(CXyzCtrl)          // Type name and misc status

     // Subclassed control support
     BOOL PreCreateWindow(CREATESTRUCT& cs);
     BOOL IsSubclassedControl();
     LRESULT OnOcmCommand(WPARAM wParam, LPARAM lParam);

// Message maps
     //{{AFX_MSG(CXyzCtrl)
     afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
     afx_msg void OnMouseMove(UINT nFlags, CPoint point);
     //}}AFX_MSG
     DECLARE_MESSAGE_MAP()

// Dispatch maps
     //{{AFX_DISPATCH(CXyzCtrl)
     short m_red;
     short m_green;
     short m_blue;
     short m_rfactor;
     short m_bfactor;
     short m_gfactor;
     afx_msg BOOL GetState();
     afx_msg void SetState(BOOL bNewValue);
     afx_msg void DoClick();
     //}}AFX_DISPATCH
     DECLARE_DISPATCH_MAP()

     afx_msg void AboutBox();

// Event maps
     //{{AFX_EVENT(CXyzCtrl)
     void FireClick()
          {FireEvent(DISPID_CLICK,EVENT_PARAM(VTS_NONE));}
     //}}AFX_EVENT
     DECLARE_EVENT_MAP()

// Dispatch and event IDs
public:
     enum {
     //{{AFX_DISP_ID(CXyzCtrl)
     dispidRed = 1L,
     dispidGreen = 2L,
     dispidBlue = 3L,
     dispidState = 7L,
     dispidRfactor = 4L,
     dispidBfactor = 5L,
     dispidGfactor = 6L,
     //}}AFX_DISP_ID
     };
};

A Hyperlinking Button

Our next example continues the theme of changing graphics while actually doing something useful in the process. This time the control draws a button on the screen that will change its image when the mouse is over it, and also when the left mouse button is down. (We couldn't resist borrowing this idea from a Java applet we saw somewhere.) The images we used for the button are rather ordinary, but you get the idea: Your pages can be much more interesting if you use something other than the standard HTML Submit button. Although you can use an image with the standard button, you can only supply one image for it.

This Button control also demonstrates the ActiveX hyperlinking functions. By supplying a value for the URL parameter, we arrange for navigation to the target page when the user clicks on the button. Using the hyperlinking functions in the Internet Explorer frame turns out to be a relatively simple task compared to using those functions in an application built from scratch-something we tried mightily to include in this book, but couldn't quite complete in time. In Figure 5.8 you see a sample HTML screen with our Button control, leading the user to the inevitable. Listings 5.6 and 5.7 are the source and header files for this implementation.

Figure 5.8 : The Button control.



Listing 5.6 ButtonCtl.cpp
// ButtonCtl.cpp : Implementation of the CButtonCtrl OLE control class.

#include "stdafx.h"
#include "Button.h"
#include "ButtonCtl.h"
#include "ButtonPpg.h"

#include "afxpriv.h"
#include "urlhlink.h"


#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

USES_CONVERSION;


CBrush black(RGB(0,0,0)),white(RGB(255,255,255)),lightgrey(RGB(223,223,223)),
grey(RGB(195,195,195)),darkgrey(RGB(165,165,165));

IMPLEMENT_DYNCREATE(CButtonCtrl, COleControl)


/////////////////////////////////////////////////////////////////////////////
// Message map

BEGIN_MESSAGE_MAP(CButtonCtrl, COleControl)
     //{{AFX_MSG_MAP(CButtonCtrl)
     ON_WM_MOUSEMOVE()
     ON_WM_LBUTTONUP()
     ON_WM_LBUTTONDOWN()
     ON_WM_CREATE()
     //}}AFX_MSG_MAP
     ON_OLEVERB(AFX_IDS_VERB_PROPERTIES, OnProperties)
END_MESSAGE_MAP()


/////////////////////////////////////////////////////////////////////////////
// Dispatch map

BEGIN_DISPATCH_MAP(CButtonCtrl, COleControl)
     //{{AFX_DISPATCH_MAP(CButtonCtrl)
     DISP_PROPERTY_NOTIFY(CButtonCtrl, "ButtonText",
m_buttonText, OnButtonTextChanged, VT_BSTR)
     DISP_PROPERTY_NOTIFY(CButtonCtrl, "Url", m_url, OnUrlChanged, VT_BSTR)
     //}}AFX_DISPATCH_MAP
     DISP_FUNCTION_ID(CButtonCtrl, "AboutBox", DISPID_ABOUTBOX, AboutBox, VT_EMPTY, VTS_NONE)
END_DISPATCH_MAP()


/////////////////////////////////////////////////////////////////////////////
// Event map

BEGIN_EVENT_MAP(CButtonCtrl, COleControl)
     //{{AFX_EVENT_MAP(CButtonCtrl)
     // NOTE - ClassWizard will add and remove event map entries
     //    DO NOT EDIT what you see in these blocks of generated code !
     //}}AFX_EVENT_MAP
END_EVENT_MAP()


/////////////////////////////////////////////////////////////////////////////
// Property pages

// TODO: Add more property pages as needed. Remember to increase the count!
BEGIN_PROPPAGEIDS(CButtonCtrl, 1)
     PROPPAGEID(CButtonPropPage::guid)
END_PROPPAGEIDS(CButtonCtrl)


/////////////////////////////////////////////////////////////////////////////
// Initialize class factory and guid

IMPLEMENT_OLECREATE_EX(CButtonCtrl, "BUTTON.ButtonCtrl.1",
     0x81640a23, 0x1e5, 0x11d0, 0x87, 0x12, 0x44, 0x45, 0x53, 0x54, 0, 0)


/////////////////////////////////////////////////////////////////////////////
// Type library ID and version

IMPLEMENT_OLETYPELIB(CButtonCtrl, _tlid, _wVerMajor, _wVerMinor)


/////////////////////////////////////////////////////////////////////////////
// Interface IDs

const IID BASED_CODE IID_DButton =
          { 0x81640a21, 0x1e5, 0x11d0, { 0x87, 0x12, 0x44, 0x45, 0x53, 0x54, 0, 0 } };
const IID BASED_CODE IID_DButtonEvents =
          { 0x81640a22, 0x1e5, 0x11d0, { 0x87, 0x12, 0x44, 0x45, 0x53, 0x54, 0, 0 } };


/////////////////////////////////////////////////////////////////////////////
// Control type information

static const DWORD BASED_CODE _dwButtonOleMisc =
     OLEMISC_ACTIVATEWHENVISIBLE |
     OLEMISC_SETCLIENTSITEFIRST |
     OLEMISC_INSIDEOUT |
     OLEMISC_CANTLINKINSIDE |
     OLEMISC_RECOMPOSEONRESIZE;

IMPLEMENT_OLECTLTYPE(CButtonCtrl, IDS_BUTTON, _dwButtonOleMisc)


/////////////////////////////////////////////////////////////////////////////
// CButtonCtrl::CButtonCtrlFactory::UpdateRegistry -
// Adds or removes system registry entries for CButtonCtrl

BOOL CButtonCtrl::CButtonCtrlFactory::UpdateRegistry(BOOL bRegister)
{
     // TODO: Verify that your control follows apartment-model threading rules.
     // Refer to MFC TechNote 64 for more information.
     // If your control does not conform to the apartment-model rules, then
     // you must modify the code below, changing the 6th parameter from
     // afxRegApartmentThreading to 0.

     if (bRegister)
          return AfxOleRegisterControlClass(
               AfxGetInstanceHandle(),
               m_clsid,
               m_lpszProgID,
               IDS_BUTTON,
               IDB_BUTTON,
               afxRegApartmentThreading,
               _dwButtonOleMisc,
               _tlid,
               _wVerMajor,
               _wVerMinor);
     else
          return AfxOleUnregisterClass(m_clsid, m_lpszProgID);
}


/////////////////////////////////////////////////////////////////////////////
// CButtonCtrl::CButtonCtrl - Constructor

CButtonCtrl::CButtonCtrl()
{
     InitializeIIDs(&IID_DButton, &IID_DButtonEvents);
     m_mouseon=0;
     m_mousepress=0;
     m_buttonText="";
     // TODO: Initialize your control's instance data here.
}


/////////////////////////////////////////////////////////////////////////////
// CButtonCtrl::~CButtonCtrl - Destructor

CButtonCtrl::~CButtonCtrl()
{
     // TODO: Cleanup your control's instance data here.
}


/////////////////////////////////////////////////////////////////////////////
// CButtonCtrl::OnDraw - Drawing function

void CButtonCtrl::OnDraw(
               CDC* pdc, const CRect& rcBounds, const CRect& rcInvalid)
{
     pdc->SelectObject(&white);
     pdc->Rectangle(&m_size);
     pdc->SelectObject(&black);
     if(m_mousepress==1){
          pdc->Rectangle(m_size.left,m_size.top,m_size.right,m_size.top+3);
          pdc->Rectangle(m_size.left,m_size.top,m_size.left+3,m_size.bottom);
     }else{
          pdc->Rectangle(m_size.right-3,m_size.top,m_size.right,m_size.bottom);
          pdc->Rectangle(m_size.left,m_size.bottom-3,m_size.right,m_size.bottom);
     }
     if(m_mouseon==1){
          if(m_mousepress==1){
               pdc->SelectObject(&darkgrey);
               pdc->SetBkColor(RGB(165,165,165));
          }     else{
               pdc->SelectObject(&lightgrey);
               pdc->SetBkColor(RGB(223,223,223));
          }
     }else{
          pdc->SelectObject(&grey);
          pdc->SetBkColor(RGB(195,195,195));
     }
     pdc->Rectangle(m_size.left+3,m_size.top+3,m_size.right-3,m_size.bottom-3);
     pdc->DrawText(m_buttonText,&m_size, DT_CENTER|DT_VCENTER|DT_SINGLELINE);

}


/////////////////////////////////////////////////////////////////////////////
// CButtonCtrl::DoPropExchange - Persistence support

void CButtonCtrl::DoPropExchange(CPropExchange* pPX)
{
     ExchangeVersion(pPX, MAKELONG(_wVerMinor, _wVerMajor));
     COleControl::DoPropExchange(pPX);
     PX_String(pPX, _T("ButtonText"), m_buttonText);
     PX_String(pPX, _T("Url"), m_url);

     // TODO: Call PX_ functions for each persistent custom property.

}


/////////////////////////////////////////////////////////////////////////////
// CButtonCtrl::OnResetState - Reset control to default state

void CButtonCtrl::OnResetState()
{
     COleControl::OnResetState();  // Resets defaults found in DoPropExchange

     // TODO: Reset any other control state here.
}


/////////////////////////////////////////////////////////////////////////////
// CButtonCtrl::AboutBox - Display an "About" box to the user

void CButtonCtrl::AboutBox()
{
     CDialog dlgAbout(IDD_ABOUTBOX_BUTTON);
     dlgAbout.DoModal();
}


/////////////////////////////////////////////////////////////////////////////
// CButtonCtrl message handlers

void CButtonCtrl::OnMouseMove(UINT nFlags, CPoint point) 
{
     COleControl::OnMouseMove(nFlags, point);
     if(m_mouseon==0){
          m_mouseon=1;
          SetCapture();
          RedrawWindow();
     }else{
          if((point.x>m_size.left)&&(point.x<m_size.right)&&
               (point.y>m_size.top)&&(point.y<m_size.bottom)){
          }else{
               ReleaseCapture();
               m_mouseon=0;
               RedrawWindow();
          }
     }
}

void CButtonCtrl::OnLButtonUp(UINT nFlags, CPoint point) 
{
     m_mouseon=0;
     m_mousepress=0;
     RedrawWindow();
     IUnknown *pUnknown;
     HRESULT hr=ExternalQueryInterface(&IID_IUnknown,(void**)&pUnknown);
     ExternalRelease();
     HlinkNavigateString(pUnknown,A2W((LPCSTR)m_url));
}

void CButtonCtrl::OnLButtonDown(UINT nFlags, CPoint point) 
{
     m_mousepress=1;
     RedrawWindow();
}

int CButtonCtrl::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
     if (COleControl::OnCreate(lpCreateStruct) == -1)
          return -1;
     GetClientRect(&m_size);
     // TODO: Add your specialized creation code here
     
     return 0;
}

void CButtonCtrl::OnButtonTextChanged() 
{
     // TODO: Add notification handler code

     SetModifiedFlag();
     RedrawWindow();
}

void CButtonCtrl::OnUrlChanged() 
{
     // TODO: Add notification handler code

     SetModifiedFlag();
}


Listing 5.7 ButtonCtl.h
// ButtonCtl.h : Declaration of the CButtonCtrl OLE control class.

/////////////////////////////////////////////////////////////////////////////
// CButtonCtrl : See ButtonCtl.cpp for implementation.

class CButtonCtrl : public COleControl
{
     DECLARE_DYNCREATE(CButtonCtrl)

     int m_mouseon,m_mousepress;
     RECT m_size;

// Constructor
public:
     CButtonCtrl();

// Overrides
     // ClassWizard generated virtual function overrides
     //{{AFX_VIRTUAL(CButtonCtrl)
     public:
     virtual void OnDraw(CDC* pdc, const CRect& rcBounds, const CRect& rcInvalid);
     virtual void DoPropExchange(CPropExchange* pPX);
     virtual void OnResetState();
     //}}AFX_VIRTUAL

// Implementation
protected:
     ~CButtonCtrl();

     DECLARE_OLECREATE_EX(CButtonCtrl)    // Class factory and guid
     DECLARE_OLETYPELIB(CButtonCtrl)      // GetTypeInfo
     DECLARE_PROPPAGEIDS(CButtonCtrl)     // Property page IDs
     DECLARE_OLECTLTYPE(CButtonCtrl)          // Type name and misc status

// Message maps
     //{{AFX_MSG(CButtonCtrl)
     afx_msg void OnMouseMove(UINT nFlags, CPoint point);
     afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
     afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
     afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
     //}}AFX_MSG
     DECLARE_MESSAGE_MAP()

// Dispatch maps
     //{{AFX_DISPATCH(CButtonCtrl)
     CString m_buttonText;
     afx_msg void OnButtonTextChanged();
     CString m_url;
     afx_msg void OnUrlChanged();
     //}}AFX_DISPATCH
     DECLARE_DISPATCH_MAP()

     afx_msg void AboutBox();

// Event maps
     //{{AFX_EVENT(CButtonCtrl)
     //}}AFX_EVENT
     DECLARE_EVENT_MAP()

// Dispatch and event IDs
public:
     enum {
     //{{AFX_DISP_ID(CButtonCtrl)
     dispidButtonText = 1L,
     dispidUrl = 2L,
     //}}AFX_DISP_ID
     };
};

Moving Graphics

What can you do once you have a handle to the Internet Explorer's client window? In this next simple example, we take a handle to that window and place a small graphic over a portion of the Web browser's client area. When placing this graphic, we first make a copy of the portion of the screen that we are about to cover. At a later time we move that graphic, restoring the original area covered by the graphic.

Figure 5.9 shows a sample of the Explorer screen with this bitmap of a frog drawn in the client window. Listings 5.8 and 5.9 are the source code for our Frog control. In the VBScripting section later in this chapter, we will show a script to make the frog hop randomly around the Explorer window.

Figure 5.9 : The hopping Explorer frog (actually a horny toad).


Listing 5.8 FrogCtl.cpp
// FrogCtl.cpp : Implementation of the CFrogCtrl OLE control class.

#include "stdafx.h"
#include "Frog.h"
#include "FrogCtl.h"
#include "FrogPpg.h"


#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif


IMPLEMENT_DYNCREATE(CFrogCtrl, COleControl)


/////////////////////////////////////////////////////////////////////////////
// Message map

BEGIN_MESSAGE_MAP(CFrogCtrl, COleControl)
     //{{AFX_MSG_MAP(CFrogCtrl)
     ON_WM_CREATE()
     ON_WM_SIZE()
     ON_WM_TIMER()
     //}}AFX_MSG_MAP
     ON_OLEVERB(AFX_IDS_VERB_PROPERTIES, OnProperties)
END_MESSAGE_MAP()


/////////////////////////////////////////////////////////////////////////////
// Dispatch map

BEGIN_DISPATCH_MAP(CFrogCtrl, COleControl)
     //{{AFX_DISPATCH_MAP(CFrogCtrl)
     DISP_PROPERTY_NOTIFY(CFrogCtrl, "XPosition", m_xPosition, OnXPositionChanged, VT_I4)
     DISP_PROPERTY_NOTIFY(CFrogCtrl, "YPosition", m_yPosition, OnYPositionChanged, VT_I4)
     DISP_PROPERTY_NOTIFY(CFrogCtrl, "time", m_time, OnTimeChanged, VT_I4)
     DISP_FUNCTION(CFrogCtrl, "SetXY", SetXY, VT_EMPTY, VTS_I4 VTS_I4)
     //}}AFX_DISPATCH_MAP
     DISP_FUNCTION_ID(CFrogCtrl, "AboutBox", DISPID_ABOUTBOX, AboutBox, VT_EMPTY, VTS_NONE)
END_DISPATCH_MAP()


/////////////////////////////////////////////////////////////////////////////
// Event map

BEGIN_EVENT_MAP(CFrogCtrl, COleControl)
     //{{AFX_EVENT_MAP(CFrogCtrl)
     // NOTE - ClassWizard will add and remove event map entries
     //    DO NOT EDIT what you see in these blocks of generated code !
     //}}AFX_EVENT_MAP
END_EVENT_MAP()


/////////////////////////////////////////////////////////////////////////////
// Property pages

// TODO: Add more property pages as needed. Remember to increase the count!
BEGIN_PROPPAGEIDS(CFrogCtrl, 1)
     PROPPAGEID(CFrogPropPage::guid)
END_PROPPAGEIDS(CFrogCtrl)


/////////////////////////////////////////////////////////////////////////////
// Initialize class factory and guid

IMPLEMENT_OLECREATE_EX(CFrogCtrl, "FROG.FrogCtrl.1",
     0x11199423, 0x1dc, 0x11d0, 0xb9, 0xac, 0, 0x55, 0, 0x45, 0xcc, 0xe8)


/////////////////////////////////////////////////////////////////////////////
// Type library ID and version

IMPLEMENT_OLETYPELIB(CFrogCtrl, _tlid, _wVerMajor, _wVerMinor)


/////////////////////////////////////////////////////////////////////////////
// Interface IDs

const IID BASED_CODE IID_DFrog =
          { 0x11199421, 0x1dc, 0x11d0, { 0xb9, 0xac, 0, 0x55, 0, 0x45, 0xcc, 0xe8 } };
const IID BASED_CODE IID_DFrogEvents =
          { 0x11199422, 0x1dc, 0x11d0, { 0xb9, 0xac, 0, 0x55, 0, 0x45, 0xcc, 0xe8 } };


/////////////////////////////////////////////////////////////////////////////
// Control type information

static const DWORD BASED_CODE _dwFrogOleMisc =
     OLEMISC_ACTIVATEWHENVISIBLE |
     OLEMISC_SETCLIENTSITEFIRST |
     OLEMISC_INSIDEOUT |
     OLEMISC_CANTLINKINSIDE |
     OLEMISC_RECOMPOSEONRESIZE;

IMPLEMENT_OLECTLTYPE(CFrogCtrl, IDS_FROG, _dwFrogOleMisc)


/////////////////////////////////////////////////////////////////////////////
// CFrogCtrl::CFrogCtrlFactory::UpdateRegistry -
// Adds or removes system registry entries for CFrogCtrl

BOOL CFrogCtrl::CFrogCtrlFactory::UpdateRegistry(BOOL bRegister)
{
     // TODO: Verify that your control follows apartment-model threading rules.
     // Refer to MFC TechNote 64 for more information.
     // If your control does not conform to the apartment-model rules, then
     // you must modify the code below, changing the 6th parameter from
     // afxRegApartmentThreading to 0.

     if (bRegister)
          return AfxOleRegisterControlClass(
               AfxGetInstanceHandle(),
               m_clsid,
               m_lpszProgID,
               IDS_FROG,
               IDB_FROG,
               afxRegApartmentThreading,
               _dwFrogOleMisc,
               _tlid,
               _wVerMajor,
               _wVerMinor);
     else
          return AfxOleUnregisterClass(m_clsid, m_lpszProgID);
}


/////////////////////////////////////////////////////////////////////////////
// CFrogCtrl::CFrogCtrl - Constructor

CFrogCtrl::CFrogCtrl()
{
     InitializeIIDs(&IID_DFrog, &IID_DFrogEvents);
     m_xPosition=0;
     m_yPosition=0;
     m_Bitmap.LoadBitmap(IDB_BITMAP);
     m_bsize.cx=60;
     m_bsize.cy=60;

     m_DC.CreateCompatibleDC(NULL);
     m_DC.SelectObject(&m_Bitmap);
     oldx=oldy=0;
     m_time=0;
     draw = 1;
     // TODO: Initialize your control's instance data here.
}


/////////////////////////////////////////////////////////////////////////////
// CFrogCtrl::~CFrogCtrl - Destructor

CFrogCtrl::~CFrogCtrl()
{
     // TODO: Cleanup your control's instance data here.
}


/////////////////////////////////////////////////////////////////////////////
// CFrogCtrl::OnDraw - Drawing function

void CFrogCtrl::OnDraw(
               CDC* pdc, const CRect& rcBounds, const CRect& rcInvalid){
     if(draw == 1){
          m_parent = GetParent();
          m_parent = m_parent->GetParent();
          CDC *parentDC=m_parent->GetDC();
          parentDC->BitBlt(m_xPosition,m_yPosition,m_bsize.cx,m_bsize.cy,&m_DC,0,0,SRCCOPY);
          m_parent->ReleaseDC(parentDC);
     }
}


/////////////////////////////////////////////////////////////////////////////
// CFrogCtrl::DoPropExchange - Persistence support

void CFrogCtrl::DoPropExchange(CPropExchange* pPX)
{
     ExchangeVersion(pPX, MAKELONG(_wVerMinor, _wVerMajor));
     COleControl::DoPropExchange(pPX);
     PX_Long(pPX, _T("XPosition"), m_xPosition, 0);
     PX_Long(pPX, _T("YPosition"), m_yPosition, 0);
     PX_Long(pPX, _T("time"), m_time, 0);
     OnTimeChanged();

     // TODO: Call PX_ functions for each persistent custom property.

}


/////////////////////////////////////////////////////////////////////////////
// CFrogCtrl::OnResetState - Reset control to default state

void CFrogCtrl::OnResetState()
{
     COleControl::OnResetState();  // Resets defaults found in DoPropExchange

     // TODO: Reset any other control state here.
}


/////////////////////////////////////////////////////////////////////////////
// CFrogCtrl::AboutBox - Display an "About" box to the user

void CFrogCtrl::AboutBox()
{
     CDialog dlgAbout(IDD_ABOUTBOX_FROG);
     dlgAbout.DoModal();
}


/////////////////////////////////////////////////////////////////////////////
// CFrogCtrl message handlers

void CFrogCtrl::OnXPositionChanged(){
     PutCopy();
     oldx=m_xPosition;
     SetModifiedFlag();
     GetCopy();
     draw=1;
     RedrawWindow();

}

void CFrogCtrl::OnYPositionChanged(){
     PutCopy();
     SetModifiedFlag();
     oldy=m_yPosition;
     GetCopy();
     draw=1;
     RedrawWindow();
}


void CFrogCtrl::SetXY(long x, long y) {
     PutCopy();
     oldx=m_xPosition=x;
     oldy=m_yPosition=y;
     GetCopy();
     draw=1;
     RedrawWindow();
}

int CFrogCtrl::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
     if (COleControl::OnCreate(lpCreateStruct) == -1)
          return -1;
     m_parent = GetParent();
     m_parent = m_parent->GetParent();
     CDC *parentDC=m_parent->GetDC();
     m_ParentDCCopy.CreateCompatibleDC(parentDC);
     m_ParentBMCopy.CreateCompatibleBitmap(parentDC,m_bsize.cx,m_bsize.cy);
     m_ParentDCCopy.SelectObject(&m_ParentBMCopy);
     m_parent->ReleaseDC(parentDC);
     GetCopy();
     return 0;
}

void CFrogCtrl::GetCopy(){
     m_parent = GetParent();
     m_parent = m_parent->GetParent();
     CDC *parentDC=m_parent->GetDC();
     m_ParentDCCopy.BitBlt(0,0,m_bsize.cx,m_bsize.cy,parentDC,m_xPosition,m_yPosition,SRCCOPY);
     m_parent->ReleaseDC(parentDC);
}

void CFrogCtrl::PutCopy(){
     m_parent = GetParent();
     m_parent = m_parent->GetParent();
     CDC *parentDC=m_parent->GetDC();
     parentDC->BitBlt(oldx,oldy,m_bsize.cx,m_bsize.cy,&m_ParentDCCopy,0,0,SRCCOPY);
     m_parent->ReleaseDC(parentDC);
}



void CFrogCtrl::OnSize(UINT nType, int cx, int cy) 
{
     COleControl::OnSize(nType, cx, cy);
     MoveWindow(0,0,1,1,1);
     m_parent = GetParent();
     m_parent->MoveWindow(0,0,1,1,1);
     m_parent = m_parent->GetParent();
}

void CFrogCtrl::OnTimeChanged() 
{
     // TODO: Add notification handler code

     SetModifiedFlag();
     if(m_time!=0){
          KillTimer(1);
          SetTimer(1,m_time*1000,NULL);
     }
}

void CFrogCtrl::OnTimer(UINT nIDEvent) 
{
     draw=0;
     KillTimer(1);
     PutCopy();
}


Listing 5.9 FrogCtl.h
// FrogCtl.h : Declaration of the CFrogCtrl OLE control class.

/////////////////////////////////////////////////////////////////////////////
// CFrogCtrl : See FrogCtl.cpp for implementation.

class CFrogCtrl : public COleControl
{
     DECLARE_DYNCREATE(CFrogCtrl)
     CBitmap m_Bitmap,m_ParentBMCopy;
     CDC          m_DC,m_ParentDCCopy;
     CWnd     *m_parent;
     RECT     m_parentSize;
     CSize     m_bsize;
     BOOL     draw;
     int oldx,oldy;

     void GetCopy();
     void PutCopy();
// Constructor
public:
     CFrogCtrl();

// Overrides
     // ClassWizard generated virtual function overrides
     //{{AFX_VIRTUAL(CFrogCtrl)
     public:
     virtual void OnDraw(CDC* pdc, const CRect& rcBounds, const CRect& rcInvalid);
     virtual void DoPropExchange(CPropExchange* pPX);
     virtual void OnResetState();
     //}}AFX_VIRTUAL

// Implementation
protected:
     ~CFrogCtrl();

     DECLARE_OLECREATE_EX(CFrogCtrl)    // Class factory and guid
     DECLARE_OLETYPELIB(CFrogCtrl)      // GetTypeInfo
     DECLARE_PROPPAGEIDS(CFrogCtrl)     // Property page IDs
     DECLARE_OLECTLTYPE(CFrogCtrl)          // Type name and misc status

// Message maps
     //{{AFX_MSG(CFrogCtrl)
     afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
     afx_msg void OnSize(UINT nType, int cx, int cy);
     afx_msg void OnTimer(UINT nIDEvent);
     //}}AFX_MSG
     DECLARE_MESSAGE_MAP()

// Dispatch maps
     //{{AFX_DISPATCH(CFrogCtrl)
     long m_xPosition;
     afx_msg void OnXPositionChanged();
     long m_yPosition;
     afx_msg void OnYPositionChanged();
     long m_time;
     afx_msg void OnTimeChanged();
     afx_msg void SetXY(long x, long y);
     //}}AFX_DISPATCH
     DECLARE_DISPATCH_MAP()

     afx_msg void AboutBox();

// Event maps
     //{{AFX_EVENT(CFrogCtrl)
     //}}AFX_EVENT
     DECLARE_EVENT_MAP()

// Dispatch and event IDs
public:
     enum {
     //{{AFX_DISP_ID(CFrogCtrl)
     dispidXPosition = 1L,
     dispidYPosition = 2L,
     dispidTime = 3L,
     dispidSetXY = 4L,
     //}}AFX_DISP_ID
     };
};

Our frog example shows that there is often much more to creating controls than just dropping them into a Web page. In order to actually make our frog hop around, we had to create a VBScript to change the parameters to the control, so the control could reposition the graphic. The good news is that an object with intuitive methods and properties can easily be transformed by a quick VBScript into an arresting, interactive masterpiece.

Adventures in Framing

Just to take our experiments one step further, we looked into creating an application that allows the control's user to manipulate the Internet Explorer frame. You can minimize or maximize the frame; change the positioning of the frame on the user's desktop; and change the size of the frame. Once we got this control working, we had no choice but to write a simple VBScript that would animate the Internet Explorer application itself, which we'll present later on in the upcoming section, "Examples of the ActiveX Controls with Visual Basic Script."

This example also shows the use of the helper functions to register Component Categories, discussed earlier in this chapter. These helper functions were borrowed from the IELnk example located in the \Samples\BaseCtl directory of the ActiveX SDK.

Listings 5.10 and 5.11 are the relevant source and header file for the Sizer control.



Listing 5.10 Sizer.cpp
// SizerCtl.cpp : Implementation of the CSizerCtrl OLE control class.

#include "stdafx.h"

#include <comcat.h>

#include "Sizer.h"
#include "SizerCtl.h"
#include "SizerPpg.h"


#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif


IMPLEMENT_DYNCREATE(CSizerCtrl, COleControl)


const CATID CATID_SafeForScripting          = {0x7dd95801,0x9882,0x11cf,{0x9f,0xa9,0x00,0xaa,0x00,0x6c,0x42,0xc4}};
const CATID CATID_SafeForInitializing     = {0x7dd95802,0x9882,0x11cf,{0x9f,0xa9,0x00,0xaa,0x00,0x6c,0x42,0xc4}};

/////////////////////////////////////////////////////////////////////////////
// Message map

BEGIN_MESSAGE_MAP(CSizerCtrl, COleControl)
     //{{AFX_MSG_MAP(CSizerCtrl)
     ON_WM_SIZE()
     //}}AFX_MSG_MAP
     ON_OLEVERB(AFX_IDS_VERB_PROPERTIES, OnProperties)
END_MESSAGE_MAP()


/////////////////////////////////////////////////////////////////////////////
// Dispatch map

BEGIN_DISPATCH_MAP(CSizerCtrl, COleControl)
     //{{AFX_DISPATCH_MAP(CSizerCtrl)
     DISP_PROPERTY_NOTIFY(CSizerCtrl, "parentX", m_parentX, OnParentXChanged, VT_I4)
     DISP_PROPERTY_NOTIFY(CSizerCtrl, "parentY", m_parentY, OnParentYChanged, VT_I4)
     DISP_PROPERTY_NOTIFY(CSizerCtrl, "parentLeft", m_parentLeft, OnParentLeftChanged, VT_I4)
     DISP_PROPERTY_NOTIFY(CSizerCtrl, "parentTop", m_parentTop, OnParentTopChanged, VT_I4)
     DISP_PROPERTY_NOTIFY(CSizerCtrl, "Hide", m_hide, OnHideChanged, VT_BOOL)
     DISP_PROPERTY_NOTIFY(CSizerCtrl, "Title", m_title, OnTitleChanged, VT_BSTR)
     DISP_PROPERTY_NOTIFY(CSizerCtrl, "Minimize", m_minimize, OnMinimizeChanged, VT_BOOL)
     DISP_PROPERTY_NOTIFY(CSizerCtrl, "Maximize", m_maximize, OnMaximizeChanged, VT_BOOL)
     DISP_FUNCTION(CSizerCtrl, "SetParentFrameSize", SetParentFrameSize, VT_EMPTY, VTS_I4 VTS_I4 VTS_I4 VTS_I4)
     DISP_FUNCTION(CSizerCtrl, "HideWindow", HideWindow, VT_EMPTY, VTS_BOOL)
     //}}AFX_DISPATCH_MAP
     DISP_FUNCTION_ID(CSizerCtrl, "AboutBox", DISPID_ABOUTBOX, AboutBox, VT_EMPTY, VTS_NONE)
END_DISPATCH_MAP()


/////////////////////////////////////////////////////////////////////////////
// Event map

BEGIN_EVENT_MAP(CSizerCtrl, COleControl)
     //{{AFX_EVENT_MAP(CSizerCtrl)
     // NOTE - ClassWizard will add and remove event map entries
     //    DO NOT EDIT what you see in these blocks of generated code !
     //}}AFX_EVENT_MAP
END_EVENT_MAP()


/////////////////////////////////////////////////////////////////////////////
// Property pages

// TODO: Add more property pages as needed.  Remember to increase the count!
BEGIN_PROPPAGEIDS(CSizerCtrl, 1)
     PROPPAGEID(CSizerPropPage::guid)
END_PROPPAGEIDS(CSizerCtrl)


/////////////////////////////////////////////////////////////////////////////
// Initialize class factory and guid

IMPLEMENT_OLECREATE_EX(CSizerCtrl, "SIZER.SizerCtrl.1",
     0xed343be3, 0x59e, 0x11d0, 0xb9, 0xad, 0, 0x55, 0, 0x45, 0xcc, 0xe8)


/////////////////////////////////////////////////////////////////////////////
// Type library ID and version

IMPLEMENT_OLETYPELIB(CSizerCtrl, _tlid, _wVerMajor, _wVerMinor)


/////////////////////////////////////////////////////////////////////////////
// Interface IDs

const IID BASED_CODE IID_DSizer =
          { 0xed343be1, 0x59e, 0x11d0, { 0xb9, 0xad, 0, 0x55, 0, 0x45, 0xcc, 0xe8 } };
const IID BASED_CODE IID_DSizerEvents =
          { 0xed343be2, 0x59e, 0x11d0, { 0xb9, 0xad, 0, 0x55, 0, 0x45, 0xcc, 0xe8 } };


/////////////////////////////////////////////////////////////////////////////
// Control type information

static const DWORD BASED_CODE _dwSizerOleMisc =
     OLEMISC_ACTIVATEWHENVISIBLE |
     OLEMISC_SETCLIENTSITEFIRST |
     OLEMISC_INSIDEOUT |
     OLEMISC_CANTLINKINSIDE |
     OLEMISC_RECOMPOSEONRESIZE;

IMPLEMENT_OLECTLTYPE(CSizerCtrl, IDS_SIZER, _dwSizerOleMisc)



//////////////////////////////////////////////////////////////////////
// Helper function to create a component category and associated description
HRESULT CreateComponentCategory(CATID catid, WCHAR* catDescription)
     {

    ICatRegister* pcr = NULL ;
    HRESULT hr = S_OK ;

    hr = CoCreateInstance(CLSID_StdComponentCategoriesMgr, 
               NULL, CLSCTX_INPROC_SERVER, IID_ICatRegister, (void**)&pcr);
     if (FAILED(hr))
          return hr;

    // Make sure the HKCR\Component Categories\{..catid...}
    // key is registered
    CATEGORYINFO catinfo;
    catinfo.catid = catid;
    catinfo.lcid = 0x0409 ; // english

     // Make sure the provided description is not too long.
     // Only copy the first 127 characters if it is
     int len = wcslen(catDescription);
     if (len>127)
          len = 127;
    wcsncpy(catinfo.szDescription, catDescription, len);
     // Make sure the description is null terminated
     catinfo.szDescription[len] = '\0';

    hr = pcr->RegisterCategories(1, &catinfo);
     pcr->Release();

     return hr;
     }

// Helper function to register a CLSID as belonging to a component category
HRESULT RegisterCLSIDInCategory(REFCLSID clsid, CATID catid)
     {
// Register your component categories information.
    ICatRegister* pcr = NULL ;
    HRESULT hr = S_OK ;
    hr = CoCreateInstance(CLSID_StdComponentCategoriesMgr, 
               NULL, CLSCTX_INPROC_SERVER, IID_ICatRegister, (void**)&pcr);
    if (SUCCEEDED(hr))
    {
       // Register this category as being "implemented" by
       // the class.
       CATID rgcatid[1] ;
       rgcatid[0] = catid;
       hr = pcr->RegisterClassImplCategories(clsid, 1, rgcatid);
    }

    if (pcr != NULL)
        pcr->Release();
  
     return hr;
     }

// Helper function to unregister a CLSID as belonging to a component category
HRESULT UnRegisterCLSIDInCategory(REFCLSID clsid, CATID catid)
     {
    ICatRegister* pcr = NULL ;
    HRESULT hr = S_OK ;
    hr = CoCreateInstance(CLSID_StdComponentCategoriesMgr, 
               NULL, CLSCTX_INPROC_SERVER, IID_ICatRegister, (void**)&pcr);
    if (SUCCEEDED(hr))
    {
       // Unregister this category as being "implemented" by
       // the class.
       CATID rgcatid[1] ;
       rgcatid[0] = catid;
       hr = pcr->UnRegisterClassImplCategories(clsid, 1, rgcatid);
    }

    if (pcr != NULL)
        pcr->Release();
  
     return hr;
     }
////////// End MS code
//////////////////////////////////////////////////////////////////////

/////////////////////////////////////////////////////////////////////////////
// CSizerCtrl::CSizerCtrlFactory::UpdateRegistry -
// Adds or removes system registry entries for CSizerCtrl

BOOL CSizerCtrl::CSizerCtrlFactory::UpdateRegistry(BOOL bRegister)
{
     HRESULT hr;

     if (bRegister) 
     {
          if(!AfxOleRegisterControlClass(
               AfxGetInstanceHandle(),
               m_clsid,
               m_lpszProgID,
               IDS_SIZER,
               IDB_SIZER,
               afxRegApartmentThreading,
               _dwSizerOleMisc,
               _tlid,
               _wVerMajor,
               _wVerMinor)) return FALSE;

     hr = CreateComponentCategory(CATID_SafeForScripting,L"Controls that are safely scriptable") ;
     hr = CreateComponentCategory (CATID_SafeForInitializing,L"Controls safely initializable from persistent data");
    
     hr = RegisterCLSIDInCategory (m_clsid, CATID_SafeForScripting) ;
     hr = RegisterCLSIDInCategory (m_clsid,CATID_SafeForInitializing);

     return TRUE;
     }
          
     else

     {
          if(!AfxOleUnregisterClass(m_clsid, m_lpszProgID))
               return FALSE;
     hr = UnRegisterCLSIDInCategory (m_clsid,CATID_SafeForScripting);
    hr = UnRegisterCLSIDInCategory (m_clsid,CATID_SafeForInitializing) ;

     return TRUE;
     }

}


/////////////////////////////////////////////////////////////////////////////
// CSizerCtrl::CSizerCtrl - Constructor

CSizerCtrl::CSizerCtrl()
{
     InitializeIIDs(&IID_DSizer, &IID_DSizerEvents);
     m_parentLeft=m_parentTop=m_parentX=m_parentY=50;
     m_hide=0;
     m_minimize=0;
     m_maximize=0;
}


/////////////////////////////////////////////////////////////////////////////
// CSizerCtrl::~CSizerCtrl - Destructor

CSizerCtrl::~CSizerCtrl()
{
     // TODO: Cleanup your control's instance data here.
}


/////////////////////////////////////////////////////////////////////////////
// CSizerCtrl::OnDraw - Drawing function

void CSizerCtrl::OnDraw(
               CDC* pdc, const CRect& rcBounds, const CRect& rcInvalid)
{
     // TODO: Replace the following code with your own drawing code.
     pdc->FillRect(rcBounds, CBrush::FromHandle((HBRUSH)GetStockObject(WHITE_BRUSH)));
     pdc->Ellipse(rcBounds);
}


/////////////////////////////////////////////////////////////////////////////
// CSizerCtrl::DoPropExchange - Persistence support

void CSizerCtrl::DoPropExchange(CPropExchange* pPX)
{
     ExchangeVersion(pPX, MAKELONG(_wVerMinor, _wVerMajor));
     COleControl::DoPropExchange(pPX);
     PX_Long(pPX, _T("parentLeft"), m_parentLeft, 50);
     PX_Long(pPX, _T("parentTop"), m_parentTop, 50);
     PX_Long(pPX, _T("parentX"), m_parentX, 50);
     PX_Long(pPX, _T("parentY"), m_parentY, 50);
     PX_Bool(pPX, _T("Hide"), m_hide, 0);
     PX_Bool(pPX, _T("Maximize"), m_maximize, 0);
     PX_Bool(pPX, _T("Minimize"), m_minimize, 0);
     PX_String(pPX, _T("Title"), m_title, "");
}


/////////////////////////////////////////////////////////////////////////////
// CSizerCtrl::OnResetState - Reset control to default state

void CSizerCtrl::OnResetState()
{
     COleControl::OnResetState();  // Resets defaults found in DoPropExchange

     // TODO: Reset any other control state here.
}


/////////////////////////////////////////////////////////////////////////////
// CSizerCtrl::AboutBox - Display an "About" box to the user

void CSizerCtrl::AboutBox()
{
     CDialog dlgAbout(IDD_ABOUTBOX_SIZER);
     dlgAbout.DoModal();
}


/////////////////////////////////////////////////////////////////////////////
// CSizerCtrl message handlers

void CSizerCtrl::OnParentXChanged(){
     SetParentFrameSize(     m_parentLeft,m_parentTop,m_parentX,m_parentY);
     SetModifiedFlag();
}

void CSizerCtrl::OnParentYChanged() {
     SetParentFrameSize(     m_parentLeft,m_parentTop,m_parentX,m_parentY);
     SetModifiedFlag();
}

void CSizerCtrl::OnParentLeftChanged() {
     SetParentFrameSize(     m_parentLeft,m_parentTop,m_parentX,m_parentY);
     SetModifiedFlag();
}

void CSizerCtrl::OnParentTopChanged() {
     SetParentFrameSize(     m_parentLeft,m_parentTop,m_parentX,m_parentY);
     SetModifiedFlag();
}

void CSizerCtrl::SetParentFrameSize(long left, long top, long width, long height) {
     GetMyParent();
     parentWnd->SetWindowPos(&wndTop,left,top,width,height,SWP_SHOWWINDOW);
     m_parentLeft=left;
     m_parentTop=top;
     m_parentX=width;
     m_parentY=height;
}

void CSizerCtrl::OnHideChanged() {
     HideWindow(m_hide);
     SetModifiedFlag();
}

void CSizerCtrl::HideWindow(BOOL hide) 
{
     m_hide=hide;
     GetMyParent();
     if(hide==1)
          parentWnd->ShowWindow(SW_HIDE);
     else
          parentWnd->ShowWindow(SW_SHOWNORMAL);
}

void CSizerCtrl::OnTitleChanged() 
{
     GetMyParent();
     parentWnd->SetWindowText((LPCSTR)m_title);
     SetModifiedFlag();
}

void CSizerCtrl::OnMinimizeChanged() 
{
     GetMyParent();
     if(m_minimize==1)
          parentWnd->ShowWindow(SW_SHOWMINIMIZED);
     else
          parentWnd->ShowWindow(SW_SHOWNORMAL);
     SetModifiedFlag();
}

void CSizerCtrl::OnMaximizeChanged() 
{
     GetMyParent();
     if(m_maximize==1)
          parentWnd->ShowWindow(SW_SHOWMAXIMIZED);
     else
          parentWnd->ShowWindow(SW_SHOWNORMAL);
     SetModifiedFlag();
     SetModifiedFlag();
}

void CSizerCtrl::OnSize(UINT nType, int cx, int cy) 
{
     COleControl::OnSize(nType, cx, cy);
     MoveWindow(0,0,0,0,1);
     parentWnd=GetParent();
     parentWnd->MoveWindow(0,0,0,0,1);
}



void CSizerCtrl::GetMyParent(){
     parentWnd=GetParent();
     parentWnd=parentWnd->GetParent();
     parentWnd=parentWnd->GetParent();
     parentWnd=parentWnd->GetParent();
}


Listing 5.11 Sizer.h
// SizerCtl.h : Declaration of the CSizerCtrl OLE control class.

/////////////////////////////////////////////////////////////////////////////
// CSizerCtrl : See SizerCtl.cpp for implementation.

class CSizerCtrl : public COleControl
{
     DECLARE_DYNCREATE(CSizerCtrl)

     CWnd *parentWnd;
     void GetMyParent();

// Constructor
public:
     CSizerCtrl();

// Overrides
     // ClassWizard generated virtual function overrides
     //{{AFX_VIRTUAL(CSizerCtrl)
     public:
     virtual void OnDraw(CDC* pdc, const CRect& rcBounds, const CRect& rcInvalid);
     virtual void DoPropExchange(CPropExchange* pPX);
     virtual void OnResetState();
     //}}AFX_VIRTUAL

// Implementation
protected:
     ~CSizerCtrl();

     DECLARE_OLECREATE_EX(CSizerCtrl)    // Class factory and guid
     DECLARE_OLETYPELIB(CSizerCtrl)      // GetTypeInfo
     DECLARE_PROPPAGEIDS(CSizerCtrl)     // Property page IDs
     DECLARE_OLECTLTYPE(CSizerCtrl)          // Type name and misc status

// Message maps
     //{{AFX_MSG(CSizerCtrl)
     afx_msg void OnSize(UINT nType, int cx, int cy);
     //}}AFX_MSG
     DECLARE_MESSAGE_MAP()

// Dispatch maps
     //{{AFX_DISPATCH(CSizerCtrl)
     long m_parentX;
     afx_msg void OnParentXChanged();
     long m_parentY;
     afx_msg void OnParentYChanged();
     long m_parentLeft;
     afx_msg void OnParentLeftChanged();
     long m_parentTop;
     afx_msg void OnParentTopChanged();
     BOOL m_hide;
     afx_msg void OnHideChanged();
     CString m_title;
     afx_msg void OnTitleChanged();
     BOOL m_minimize;
     afx_msg void OnMinimizeChanged();
     BOOL m_maximize;
     afx_msg void OnMaximizeChanged();
     afx_msg void SetParentFrameSize(long left, long top, long width, long height);
     afx_msg void HideWindow(BOOL hide);
     //}}AFX_DISPATCH
     DECLARE_DISPATCH_MAP()

     afx_msg void AboutBox();

// Event maps
     //{{AFX_EVENT(CSizerCtrl)
     //}}AFX_EVENT
     DECLARE_EVENT_MAP()

// Dispatch and event IDs
public:
     enum {
     //{{AFX_DISP_ID(CSizerCtrl)
     dispidParentX = 1L,
     dispidParentY = 2L,
     dispidParentLeft = 3L,
     dispidParentTop = 4L,
     dispidHide = 5L,
     dispidTitle = 6L,
     dispidMinimize = 7L,
     dispidMaximize = 8L,
     dispidSetParentFrameSize = 9L,
     dispidHideWindow = 10L,
     //}}AFX_DISP_ID
     };
};

Examples of the ActiveX Controls with Visual Basic Script

Visual Basic Script provides a convenient medium to manipulate the ActiveX controls. Many of Microsoft's ActiveX controls are documented at Microsoft's Site Builder Workshop Web site at

http://www.microsoft.com/activex/gallery

There you'll find the Chart, Gradient, Pop-up Menu, and Label controls, and many more. The controls range from the utilitarian Chart control (which is a useful component to display graphical data) to the cosmetic enhancements of a color gradient furnished by the Gradient control.

With the release of Internet Explorer 3.0, Microsoft improved its documentation on the ActiveX controls and consolidated the control descriptions at the URL

http://www.microsoft.com/intdev/controls/ctrlref-f.htm.

In this section we demonstrate how to manipulate many of these controls with VBScript. These examples will give you a good feel for ways in which a script can take advantage of the published properties, methods, and events associated with each control.

Once you've gained facility with the VBScript language and some experience with the ActiveX controls, you'll be in a very good position to create attractive and versatile Web pages.

PopUp Menu

The first control we'll tackle is the PopUp Menu control. A quick check of the Microsoft control reference page at

http://www.microsoft.com/intdev/controls/ctrlref-f.htm

reveals that the PopUp Menu control has the property ItemCount (the number of menu items in the current menu); the parameter Tag MenuItem[], which is an item requiring an array offset (the menu item to be displayed); and several possible methods as follows:

  • AboutBox, which displays the About dialog box.
  • PopUp(x,y), which pops up the menu at the parameter position defined by the x,y coordinates. These parameters are optional; if they are not supplied, the current mouse position is used.
  • Clear, which clears the menu items.
  • RemoveItem(index), which removes the item corresponding to the index offset.
  • Additem(string,index), which adds a menu item with value equal to the string parameter at the offset specified by index. If index is not provided, the menu item is added to the end of the list.

An event that can be utilized with the PopUp control is the Click event. The Click event passes the menu item that was clicked on.

To sum up, then, we've listed the PopUp control's properties, parameter tags, methods, and one event. Armed with these specifications, we can now embed VB Script inside an HTML page to use the PopUp control. You'll remember from Chapter 2that coding Class IDs by hand is tedious, so here we'll use the Control Pad to insert the controls into the page, and then code the VB Script around it.

In Figure 5.10, the PopUp control is providing a list of fonts to pick from. The user is confronted with two blank boxes but can click on the down-arrow button at the right of either box to display a list. There is a font display area at the bottom of the page, which is a default result the first time into the form. When we examine the script, you'll see how to do this.

Figure 5.10: The user sees two blank pull-down boxes and a default font display.

When the user clicks on the Font Name box and chooses Times New Roman, Figure 5.11 is the result. Notice how this operation temporarily obscures the Font Size pull-down box. (It reappears when a font name is selected.) After items from both pull-downs are selected, the script will display results on the screen. See Figure 5.12.

Figure 5.11: The user picks Times New Roman from the list.

Figure 5.12: The script displays a font test of Times New Roman, 72-point.

Let's move now to the VBScript code that produces the results shown in Figure 5.12. Remember, the script has to be able to access the menu items selected by the user in both pull-downs, use the values of the items selected, and then show the appropriate font family and font size in the display area.

Listing 5.12 shows the HTML and the embedded VBScript code. The general structure is HTML tags; then embedded objects (via Control Pad) representing the pull-down boxes; then the Label control; and then the VBScript code. Pay particular attention to the VBScript; we'll discuss it immediately after the listing.


Listing 5.12 Fonts.htm
<BASE HREF="file:///C|/ActiveX Samples/Fonts.htm">

<HTML>
<HEAD>
<TITLE>New Page</TITLE>
</HEAD>
Font Name<BR>
<!-- ComboBox object  -->
<OBJECT ID="cboFontName" WIDTH=187 HEIGHT=30
 CLASSID="CLSID:8BD21D30-EC42-11CE-9E0D-00AA006002F3">
    <PARAM NAME="VariousPropertyBits" VALUE="746604571">
    <PARAM NAME="DisplayStyle" VALUE="3">
    <PARAM NAME="Size" VALUE="3964;635">
    <PARAM NAME="MatchEntry" VALUE="1">
    <PARAM NAME="ShowDropButtonWhen" VALUE="2">
    <PARAM NAME="FontCharSet" VALUE="0">
    <PARAM NAME="FontPitchAndFamily" VALUE="2">
    <PARAM NAME="FontWeight" VALUE="0">
</OBJECT>
<BR>

<BR>
Font Size<BR>
<!-- ComboBox object  -->
<OBJECT ID="cboFontSIze" WIDTH=187 HEIGHT=30
 CLASSID="CLSID:8BD21D30-EC42-11CE-9E0D-00AA006002F3">
    <PARAM NAME="VariousPropertyBits" VALUE="746604571">
    <PARAM NAME="DisplayStyle" VALUE="3">
    <PARAM NAME="Size" VALUE="3964;635">
    <PARAM NAME="MatchEntry" VALUE="1">
    <PARAM NAME="ShowDropButtonWhen" VALUE="2">
    <PARAM NAME="FontCharSet" VALUE="0">
    <PARAM NAME="FontPitchAndFamily" VALUE="2">
    <PARAM NAME="FontWeight" VALUE="0">
</OBJECT>
<BR>
<BR>
<BR>

<!-- Checkbox Object -->
<OBJECT ID="chkBold" WIDTH=144 HEIGHT=24
 CLASSID="CLSID:8BD21D40-EC42-11CE-9E0D-00AA006002F3">
    <PARAM NAME="BackColor" VALUE="2147483663">
    <PARAM NAME="ForeColor" VALUE="2147483666">
    <PARAM NAME="DisplayStyle" VALUE="4">
    <PARAM NAME="Size" VALUE="3810;635">
    <PARAM NAME="Value" VALUE="False">
    <PARAM NAME="Caption" VALUE="Bold">
    <PARAM NAME="FontCharSet" VALUE="0">
    <PARAM NAME="FontPitchAndFamily" VALUE="2">
    <PARAM NAME="FontWeight" VALUE="0">
</OBJECT>
<BR>

<!-- Checkbox Object -->
<OBJECT ID="chkItalic" WIDTH=144 HEIGHT=24
 CLASSID="CLSID:8BD21D40-EC42-11CE-9E0D-00AA006002F3">
    <PARAM NAME="BackColor" VALUE="2147483663">
    <PARAM NAME="ForeColor" VALUE="2147483666">
    <PARAM NAME="DisplayStyle" VALUE="4">
    <PARAM NAME="Size" VALUE="3810;635">
   <PARAM NAME="Value" VALUE="False">
    <PARAM NAME="Caption" VALUE="Italic">
    <PARAM NAME="FontCharSet" VALUE="0">
    <PARAM NAME="FontPitchAndFamily" VALUE="2">
    <PARAM NAME="FontWeight" VALUE="0">
</OBJECT>


<BR><BR><BR><BR><BR>
<CENTER>
<!-- Label object -->
<OBJECT ID="lblFonts" WIDTH=577 HEIGHT=247
 CLASSID="CLSID:99B42120-6EC7-11CF-A6C7-00AA00A47DD2">
    <PARAM NAME="_ExtentX" VALUE="15266">
    <PARAM NAME="_ExtentY" VALUE="6535">
    <PARAM NAME="Caption" VALUE="Font Test">
    <PARAM NAME="Angle" VALUE="0">
    <PARAM NAME="Alignment" VALUE="4">
    <PARAM NAME="Mode" VALUE="1">
    <PARAM NAME="FillStyle" VALUE="0">
    <PARAM NAME="FillStyle" VALUE="0">
    <PARAM NAME="ForeColor" VALUE="#000000">
    <PARAM NAME="BackColor" VALUE="#B4C3DC">
    <PARAM NAME="FontName" VALUE="Arial">
    <PARAM NAME="FontSize" VALUE="12">
    <PARAM NAME="FontItalic" VALUE="0">
    <PARAM NAME="FontBold" VALUE="0">
    <PARAM NAME="FontUnderline" VALUE="0">
    <PARAM NAME="FontStrikeout" VALUE="0">
    <PARAM NAME="TopPoints" VALUE="0">
    <PARAM NAME="BotPoints" VALUE="0">
</OBJECT>


<SCRIPT LANGUAGE="VBS">
 
' This subprocedure initializes the two Comboboxes
Sub FIllList()

 cboFontName.AddItem  "Wingdings"
 cboFontName.AddItem  "ARIAL"
 cboFontName.AddItem  "TIMES NEW ROMAN"
 cboFontSIze.AddItem "12"
 cboFontSIze.AddItem "24"
 cboFontSIze.AddItem "36"
 cboFontSIze.AddItem "48"
 cboFontSIze.AddItem "72"

End Sub

' Trap the click event of the FontName Combobox and set the label fontname accordingly
Sub cboFontName_Click()
 lblFonts.FontName = cboFontName.Text
End Sub


' Trap the click event of the FontSize Combobox and set the label font size accordingly
Sub cboFontSize_Click()
 lblFonts.FontSize = cboFontSize.Text
End Sub

' Trap the Checkbox click and set the label text to bold or not
Sub chkBold_Click()
 If chkBold.Value = True then
   lblFonts.FontBold = True
Else
  lblFonts.FontBold = False
End If
End Sub

' Trap the Checkbox click and set the label text to italic or not
Sub chkItalic_Click()
 If chkItalic.Value = True then
   lblFonts.FontItalic = True
Else
  lblFonts.FontItalic = False
End If
End Sub

</SCRIPT>

<BODY onLoad="FillList">

</BODY>
</HTML>

Discussion of FONTAPP

Experienced VB programmers will have no difficulty reading the FONT-APP code and finding it sufficiently explained by the comments we have inserted there. For the benefit of beginners, however, it is a worthwhile exercise to delve a little deeper into the code and get a feel for manipulating ActiveX controls.

In Listing 5.12, the first Visual Basic procedure that we encounter is FillList, which takes no arguments. This procedure illustrates the technique for populating a combination box ("combo box") with items. The general syntax is

form-element-name.AddItem "<some-value>"

In this example, we populate the cboFontName combo box with three items, and the cboFontSize combo box with five items. Note, though, that this population is not automatic. It is necessary to tell the browser to perform this subroutine when the page is loaded (with the onLoad method, discussed shortly).

Next, we need to handle the user's click on the FontName combo box. We make use of the Click method for this, by suffixing the method to the form element as shown in the subroutine cboFontName_Click. This procedure passes to the Label control the combo-box item selected by the user. In exactly the same manner, we need to trap a click on the other combo box, cboFontSize, and we do that in the subroutine cboFontSize_Click.

The next order of business is the user's click on either the Bold or Italic check box. This is handled with the routines chkBold_Click and chkItalic_Click, respectively. Notice how the form element chkBold can take either a True or False value. Hence, the check box is a Boolean form element; when it is checked, the value is True; otherwise it is false. We perform a simple If test to pass the bold or italic conditions on to the Label control. You can see in the object listing that the Label control is instantiated with FontItalic and FontBold properties set to 0 (False), the FontName set to Arial, and the FontSize set to 12. These are the defaults the user sees before interacting with the combo boxes and check boxes, as shown earlier in Figure 5.10.

After the script is completed with the </SCRIPT> tag, our work continues. It is necessary in the <BODY> tag to tell the browser to fill (initialize) the combo boxes when the page is loaded. We do this with the onLoad method (onload="FillList"), which runs the subroutine FillList to prepare the form.

The last thing to note in this application is that user-supplied parameters to the Label control (font name, font size, bold and/or italic) are instantly reflected in the visual appearance of the page. There is no need to reload the page to have the changes take effect. The browser interprets the embedded VBScript code, and the label object immediately takes on the new properties communicated via the combo boxes and check boxes.

The FONTMENU Application

To get a better feeling for the interaction of Visual Basic Script with ActiveX controls, we now demonstrate a very similar application that tests fonts. This time, though, let's use the Menu control instead of the combo box.

Figure 5.13 shows the user's entry point to the new application. The user sees a default Font message, "FONT TEST," and two buttons at the top of the screen. When the user clicks on either of these Menu controls, a list of the choices appears.

Figure 5.13: Using Menu controls for Font Name and Font Size.

Functionally, Menu controls are similar to the combo box method; the interface is slightly different, as are the control's attributes. Figure 5.14 shows the results of the user clicking on the Font Name button. Notice the slight peculiarity of the list appearing below and to the right of the Menu control, temporarily obscuring the Font Size item. Similarly, if the user clicks on the Font Size menu, a list of values appears below and to the right (Figure 5.15).

Figure 5.14: The user clicks on the Font Name menu and a list of choices appears.

Figure 5.15: The Font Size menu presents a list of size values.

In Listing 5.13 you can study the HTML tags, the embedded objects that were inserted via Control Pad, and the VBScript code.


Listing 5.13 Menu.htm
<BASE HREF="file:///C|/ActiveX Samples/Menu.htm">

<HTML>
<HEAD>

<!-- Menu Object containing Font Names, you can also add more names at
     runtime with the syntax as follows
          MenuFontName.AddItem ( varname, index# )
          eg. MenuFontName.AddItem("Courier New", 5 )
-->

<OBJECT   
     ID="MenuFontName"
         TYPE="application/x-oleobject"
         CLASSID="clsid:7823A620-9DD9-11CF-A662-00AA00C066D2"
         WIDTH=1
         HEIGHT=1
>
     <PARAM NAME="Menuitem[0]" value="Arial">
     <PARAM NAME="Menuitem[1]" value="Courier">
     <PARAM NAME="Menuitem[2]" value="Times New Roman">
     <PARAM NAME="Menuitem[3]" value="Wingdings">
</OBJECT>



<!-- Menu object containing Font Sizes, dynamic addition applies here as well. -->
<OBJECT
        ID="MenuFontSize"
         TYPE="application/x-oleobject"
         CLASSID="clsid:7823A620-9DD9-11CF-A662-00AA00C066D2"
         WIDTH=1
         HEIGHT=1
>
     <PARAM NAME="Menuitem[0]" value="24">
     <PARAM NAME="Menuitem[1]" value="36">
     <PARAM NAME="Menuitem[2]" value="48">
     <PARAM NAME="Menuitem[3]" value="72">
</OBJECT>


<INPUT TYPE="button" NAME="FontName" VALUE="Font Name" ALIGN=RIGHT>
<INPUT TYPE="button" NAME="FontSize" VALUE="Font Size" ALIGN=RIGHT>

<BR><BR><BR><BR><BR>
<CENTER>

<!-- Label object for Text -->
<OBJECT ID="lblFonts" WIDTH=863 HEIGHT=401
 CLASSID="CLSID:978C9E23-D4B0-11CE-BF2D-00AA003F40D0">
    <PARAM NAME="Caption" VALUE="Font Test">
    <PARAM NAME="Size" VALUE="22828;10605">
    <PARAM NAME="FontCharSet" VALUE="0">
    <PARAM NAME="FontPitchAndFamily" VALUE="2">
    <PARAM NAME="FontWeight" VALUE="0">
</OBJECT>

<SCRIPT Language="VBS">

' This sets the font name based on the index set, strange but the Base statement
' seems to have no effect, upon definition of the menu, the index starts at 0 ,
' however, upon accessing the menu, the index starts at 1. Case statment used here.
' x is the index passed in.
Sub MenuFontName_Click(ByVal x)

Select Case x
Case 1: lblFonts.FontName = "Arial"
Case 2: lblFonts.FontName = "Courier"
Case 3: lblFonts.FontName = "Times New Roman"
Case 4: lblFonts.FontName = "Wingdings"
End Select

End Sub

' Sets the font sizes, same anomaly with the index as previous.
Sub MenuFontSize_Click(ByVal x)

Select Case x
Case 1: lblFonts.FontSize = "24"
Case 2: lblFonts.FontSize = "36"
Case 3: lblFonts.FontSize = "48"
Case 4: lblFonts.FontSize = "72"
End Select

End Sub

' Pops up the menu
Sub FontName_onClick
     MenuFontName.PopUp
End Sub

' Pops up the menu
Sub FontSize_onClick
     MenuFontSize.PopUp
End Sub

</SCRIPT> 
<BODY >
</BODY>
</HTML>

Discussion of FONTMENU

Examine the MenuFontName object definition near the top of the FONTMENU program (Listing 5.13). In our previous example, FONTAPP (Listing 5.12), we used the onLoad event to initialize the combo boxes when the browser loads the page. Here in FONTMENU, we are explicitly initializing the MenuFontName with a string array.

The array starts at offset 0; hence, Arial is filling the first menu slot, which is referenced as Menuitem[0]. There is no intrinsic advantage in initializing the structure with this technique instead of onLoad. The argument can be made, however, that providing the initial values in the object structure makes the application more readable than deferring the assignments until the VBScript code section.

Similarly, we initialize the MenuFontSize object with its four values: font sizes of 24, 36, 48, and 72 points.

Two HTML button elements are then defined, with the names Font Name and Font Size. For clarity, the actual values of the buttons, FontName and FontSize, correspond to the Menu objects MenuFontName and MenuFontSize.

The final object defined is the Label control. The default values are set up just as they were in the preceding combo-box application.

The Visual Basic Script presented some interesting challenges, typical of the fact that VBScript is a subset of VB. Many of the VB functions and statements fall short to various degrees (they fail to run at all or do not produce expected results). For example, in this code, we noticed that accessing the menu structure (via the Click event, in the subroutine MenuFontName_Click) started the index at 1. This is inconsistent with the Menu structure that we initialized. (Recall that the first MenuItem started at offset 0.)

We tried to change the offset of the access to 0 for consistency with the Base statement, but this hasn't yet been implemented in VBScript as of this writing. So we coded a workaround in this subroutine with the Case statement. We passed the routine the offset (which inconsistently starts at 1) and set the Label control FontName property to the hardcoded Menu Item. Awkward, to be sure-but sometimes workarounds are unavoidable. As VBScript matures and its feature set expands, it's likely that fewer of these kludges will be necessary.

The next subroutine, MenuFontSize_Click, is completely analogous to MenuFontName_Click and sets the label's FontSize property.

Finally, it is necessary to define the action that occurs when the user clicks on either the Font Name button or the Font Size button; this is accomplished with the two routines FontName_onClick and FontSize_onClick. In each case, we want the menu options to pop up for the user, and we invoke the PopUp method with this line:

<menuformelement>.PopUp

The <menuformelement> value is either MenuFontName or MenuFontSize, the two menu objects we inserted at the top of the HTML form.

Again, notice how changes to the Label control are communicated via the Menu form elements, and that the changes are reflected immediately on the screen. There is no CGI-style, round-trip communication to a Web server. Here both the VBScript code and the object definitions are embedded in the form, making for faster response time.

The Gradient Control

Another interesting control is the Gradient control, which, as the name implies, produces a nice effect of a start color fading gradually to an end color. The direction of gradient is also a property that can be manipulated. Figure 5.16 shows the start screen of a simple application to change a Gradient control.

Figure 5.16: The user starts the Gradient control and sees blue in the middle of the object fading to green on the outside.

Let's take a look at the HTML code in Listing 5.14, including the embedded Gradient control, and the associated VBScript necessary to manipulate the start and end colors of the gradient.


Listing 5.14 Grad.htm
<html>
<HEAD>
<TITLE>Gradient Example</TITLE>
</HEAD>

Gradient Start<BR>
<!-- ComboBox object  -->
<OBJECT ID="cboGradStart" WIDTH=187 HEIGHT=30
 CLASSID="CLSID:8BD21D30-EC42-11CE-9E0D-00AA006002F3">
    <PARAM NAME="VariousPropertyBits" VALUE="746604571">
    <PARAM NAME="DisplayStyle" VALUE="3">
    <PARAM NAME="Size" VALUE="3964;635">
    <PARAM NAME="MatchEntry" VALUE="1">
    <PARAM NAME="ShowDropButtonWhen" VALUE="2">
    <PARAM NAME="FontCharSet" VALUE="0">
    <PARAM NAME="FontPitchAndFamily" VALUE="2">
    <PARAM NAME="FontWeight" VALUE="0">
</OBJECT>
<BR>
Gradient End <br>
<OBJECT ID="cboGradEnd" WIDTH=187 HEIGHT=30
 CLASSID="CLSID:8BD21D30-EC42-11CE-9E0D-00AA006002F3">
    <PARAM NAME="VariousPropertyBits" VALUE="746604571">
    <PARAM NAME="DisplayStyle" VALUE="3">
    <PARAM NAME="Size" VALUE="3964;635">
    <PARAM NAME="MatchEntry" VALUE="1">
    <PARAM NAME="ShowDropButtonWhen" VALUE="2">
    <PARAM NAME="FontCharSet" VALUE="0">
    <PARAM NAME="FontPitchAndFamily" VALUE="2">
    <PARAM NAME="FontWeight" VALUE="0">
</OBJECT>
<BR>

  <OBJECT
          id=iegrad1
          type="application/x-oleobject"
          classid="clsid:017C99A0-8637-11CF-A3A9-00A0C9034920"
          width=50
          height=50
   >
   <param name="StartColor" value="#0000ff">
   <param name="EndColor" value="#00ff00">
   <param name="Direction" value="4">
   </OBJECT>

<SCRIPT LANGUAGE="VBS">
 

' This subprocedure initializes the two Comboboxes
Sub FillList()

 cboGradStart.AddItem  "White"

 cboGradEnd.AddItem  "Black"


End Sub

' Trap the click event of the GradStart Combobox and set the Gradient Start Color
Sub cboGradStart_Click()

   iegrad1.StartColor = int(16^6 - 1)  ' the integer repr. of #ffffff (white)
   iegrad1.Repaint    ' must do a repaint and the start color becomes white

End Sub

' Trap the click event of the GradStart Combobox and set the Gradient End Color
Sub cboGradEnd_Click()
 iegrad1.EndColor = int(16^0 - 1)  ' of course this is 0 (#000000) which is black
 iegrad1.Repaint  ' must do this or else change does not take effect
End Sub

</SCRIPT>

<BODY onLoad="FillList">

</BODY>

</html>

The code to manipulate the control was a bit tricky. We needed to refer to an on-line example at

http://microsoft.saltmine.com/activexisv/msctls/ie/gradient.htm

There we learned that the Gradient StartColor and EndColor properties, although they appear to be text, actually can be set only as a decimal representation of the hexadecimal color codes. For example, the white color that is hex FFFFFF must be converted to the decimal representation of 16 to the 6th power minus 1, before the property can be set.

We also learned that the change in property does not instantly affect the Gradient control, as it did with the Label control in the two examples above. For the gradient, you have to invoke the Repaint method to refresh the gradient's appearance.

In the code, notice how the exposed properties of the gradient, StartColor, EndColor, and Direction, suggest themselves as targets for manipulation. In this simple example, the Gradient Start and Gradient End menu boxes are only filled with White and Black, respectively. In Figure 5.17 you see the result of selecting both White and Black: a monochromatic image with a white diagonal ribbon in the middle, fading to black on all exterior borders. It is also possible to yield a halfway result. That is, if only White is selected, the start color in the middle is white, and it fades to the default outside color of green.

Figure 5.17: In the Gradient control, the user changes the Gradient Start color to White and the Gradient End color to Black.

A Frames Example: The VBScript World Clock

To demonstrate how VBScript handles frames, we built an application that shows the time around the world in various time zones (see Figure 5.18). In addition to its handling of frames, this example is also instructive for its use of the Timer and Clock controls.

Figure 5.18: Times around the world: the VBScript World Clock.

The single "main" HTML page actually is making use of five smaller frame documents, each one corresponding to a time zone. Listing 5.15 is the source code of the main page, TESTF.htm.


Listing 5.15 TESTF.htm
<HTML >              
<HEAD>
<TITLE>VBSCRIPT FRAME FLASHER</TITLE>
</HEAD>

<BODY BGCOLOR=Gray>
<FRAMESET ROWS="15%,85%">
   <FRAMESET COLS="20%,20%,20%,20%,20%,">
      <FRAME NAME="Z1" SRC="CLOCK-ET.HTM" scrolling=no Frameborder=0>
      <FRAME NAME="Z2" SRC="CLOCK-PT.HTM" scrolling=no Frameborder=0>
      <FRAME NAME="Z3" SRC="CLOCK-GW.HTM" scrolling=no Frameborder=0>
      <FRAME NAME="Z4" SRC="CLOCK-MW.HTM" scrolling=no Frameborder=0>
      <FRAME NAME="Z5" SRC="CLOCK-TK.HTM" scrolling=no Frameborder=0>
   </FRAMESET>
<FRAME NAME="Main" SRC="MAIN.HTM" SCROLLING=NO>
</FRAMESET>
</BODY>


</HTML>

Note the arrangement of the frames. They are named Z1 through Z5, and their source code is referenced by the SRC= tag. They do not scroll (scrolling=no) and they have no border (Frameborder=0).

Let's now look at the source for one of the small frames, CLOCK-ET.HTM (Eastern Time), as shown in Listing 5.16.


Listing 5.16 Clock-et.htm
<HTML>
<BODY LANGUAGE="VBS" onLoad="SetUpDisplay"  >

<CENTER>
Eastern Time
<OBJECT ID="Clock" WIDTH=181 HEIGHT=27
 CLASSID="CLSID:978C9E23-D4B0-11CE-BF2D-00AA003F40D0">
    <PARAM NAME="Size" VALUE="4784;706">
    <PARAM NAME="FontName" VALUE="Times New Roman">
    <PARAM NAME="FontEffects" VALUE="1073741825">
    <PARAM NAME="FontHeight" VALUE="400">
    <PARAM NAME="FontCharSet" VALUE="0">
    <PARAM NAME="FontPitchAndFamily" VALUE="2">
    <PARAM NAME="FontWeight" VALUE="700">
</OBJECT>


<OBJECT ID="Timer1" WIDTH=38 HEIGHT=38
 CLASSID="CLSID:59CCB4A0-727D-11CF-AC36-00AA00A47DD2">
    <PARAM NAME="_ExtentX" VALUE="804">
    <PARAM NAME="_ExtentY" VALUE="804">
    <PARAM NAME="Interval" VALUE="1000">
</OBJECT>


<SCRIPT LANGUAGE="VBSCRIPT">
Dim Zone

Sub SetUpDisplay

   Clock.Caption=(Time)

End Sub

Sub timer1_timer

     Clock.Caption = Time

End sub

</SCRIPT>

</BODY>
</HTML>

This page, like all the other time-zone frame pages, calls a set-up routine, SetUpDisplay, when it is loaded. SetUpDisplay sets the Caption property of the Clock Object (a digital clock, as you see in Figure 5.18) to the current system time. The script also makes use of a Timer control, called Timer1, which updates every 1,000 milliseconds (1 second). Then all that is necessary is to reference the Timer method of the Timer1 object, which is done in the subroutine timer1_timer. Here, we see that the Clock object's time is updated every second.

The other time-zone frames work in exactly the same way, except that they require some additional algebraic manipulation to display the correct time. Visual Basic's strong suit is not regular expression manipulation (Perl has a much better string toolkit), but it does get the job done, however ugly it might look. Listing 5.17 is the code for the GMT time zone with the extra formatting code.


Listing 5.17 Clock-gw.htm
<HTML>
<BODY>
<CENTER>
GMT Time
<OBJECT ID="Clock" WIDTH=225 HEIGHT=27
 CLASSID="CLSID:978C9E23-D4B0-11CE-BF2D-00AA003F40D0">
    <PARAM NAME="ForeColor" VALUE="16711680">
    <PARAM NAME="Size" VALUE="5962;706">
    <PARAM NAME="FontName" VALUE="Times New Roman">
    <PARAM NAME="FontEffects" VALUE="1073741825">
    <PARAM NAME="FontHeight" VALUE="400">
    <PARAM NAME="FontCharSet" VALUE="0">
    <PARAM NAME="FontPitchAndFamily" VALUE="2">
    <PARAM NAME="FontWeight" VALUE="700">
</OBJECT>


<OBJECT ID="Timer1" WIDTH=39 HEIGHT=39
 CLASSID="CLSID:59CCB4A0-727D-11CF-AC36-00AA00A47DD2">
    <PARAM NAME="_ExtentX" VALUE="1032">
    <PARAM NAME="_ExtentY" VALUE="1032">
    <PARAM NAME="Interval" VALUE="1000">
</OBJECT>



<SCRIPT LANGUAGE="VBSCRIPT">


Sub timer1_timer

Dim TempTime
Dim Hours
Dim AMPM
Dim TTIME
Dim FTime
TempTime = Time
If IsNumeric(Left(Time,2)) Then
 Hours = Left(Time,2)
Else
 Hours = Left(Time,1)
End If

TempTime = CInt(Hours) + 5

     If TempTime > 12 then
           TempTime = TempTime - 12
           AMPM = "AM"
     Else
          AMPM = Right(Time,2)
     End If
If TempTime < 0 Then
 TempTime = TempTime * -1
End If
Hours = Right(CStr(Time),9)
PSTTime = TempTime & Hours
 Clock.Caption = Left(PSTTime,8) & " " & AMPM

End sub

</SCRIPT>

</BODY>
</HTML>

In the case of Greenwich Mean Time (GMT), we are assuming that the user's computer is actually set to Eastern Standard Time (EST). We need to advance 5 hours to get to GMT, and we do so in the routine timer1_timer. We add 5 hours to the time, and then worry whether to display AM or PM on the GMT clock.

The other time-zone frames work like the GMT case. For each, we simply manipulate the EST time to suit ourselves. (Note: The application grows considerably in complexity if we cannot assume the user is starting from EST.)

The Sizer Control Script

We created the Sizer control to have real value, mainly to force the client browser into an appropriate size when the user loads an HTML page. Usefulness notwithstanding, we felt compelled to try out some Stupid Browser Tricks with it, and Listing 5.18 is a short VBScript to do just that. Figure 5.19 shows a few views of the browser in motion.

Figure 5.19: The Sizer control putting Explorer through the paces.


Listing 5.18 Sizer.htm
<HTML><HEAD><TITLE>Sizer Test</TITLE>
</HEAD>
<BODY>
Sizer Test 
<HR>
<FORM>
<INPUT TYPE=BUTTON NAME=Start VALUE="Start">
</FORM>

<OBJECT ID="Sizer1" WIDTH=100 HEIGHT=51
 CODEBASE="http://166.84.253.193/sizer.ocx" 
 CLASSID="CLSID:ED343BE3-059E-11D0-B9AD-00550045CCE8">
    <PARAM NAME="_Version" VALUE="65536">
    <PARAM NAME="_ExtentX" VALUE="2646">
    <PARAM NAME="_ExtentY" VALUE="1341">
    <PARAM NAME="_StockProps" VALUE="0">
</OBJECT>
<HR>
<SCRIPT LANGUAGE="VBSCRIPT">

SUB Start_OnClick

x = 320
y = 240
width = 0
height = 0
call Sizer1.SetParentFrameSize(x,y,width,height)

     FOR h = 1 to 40
     width = width+20     
     call Sizer1.SetParentFrameSize(x,y,width,height)
     x = x - 10
     call Sizer1.SetParentFrameSize(x,y,width,height)
     NEXT

     FOR h = 40 to 1 step -1
     width = width-20     
     call Sizer1.SetParentFrameSize(x,y,width,height)
     x = x +10
     call Sizer1.SetParentFrameSize(x,y,width,height)
     NEXT

     width = 1
     FOR h = 1 to 60
     height = height+10     
     call Sizer1.SetParentFrameSize(x,y,width,height)
     y = y - 5
     call Sizer1.SetParentFrameSize(x,y,width,height)
     NEXT

     FOR h = 60 to 1 step -1
     height = height-10     
     call Sizer1.SetParentFrameSize(x,y,width,height)
     y = y +  5
     call Sizer1.SetParentFrameSize(x,y,width,height)
     NEXT
     
     call Sizer1.SetParentFrameSize(40,40,400,300)
End Sub

</SCRIPT>
</BODY>
</HTML>
  

This is one of the benign scripts we wrote, as the browser stops moving within a short time. In the "malicious" version, the browser never stops moving, and you either have to try to hit the close button with the mouse, or else use the Task Manager to kill Explorer.

The Frog Control Script

To wrap up this chapter, here is the promised script (Listing 5.19) to make our frog bitmap move around the Internet Explorer window. This VBScript includes the Timer.ocx and generates some random numbers for positioning the image. The one thing we want to do here is check for the edges of the windows, so that our bitmap does not get completely lost in cyberspace.


Listing 5.19 Frogvbs.htm
<HTML>
<HEAD>
<TITLE>New Page</TITLE>
</HEAD>
<!-- When the page loads, make the frog slide across -->
<BODY onLoad="timer1_timer">
<HR>
Round and Round It Goes
<HR>
Where It Stops Nobody Knows
<HR>

<!-- The Frog Object -->

<OBJECT ID="Frog1" WIDTH=100 HEIGHT=51
 CLASSID="CLSID:11199423-01DC-11D0-B9AC-00550045CCE8">
    <PARAM NAME="_Version" VALUE="65536">
    <PARAM NAME="_ExtentX" VALUE="2646">
    <PARAM NAME="_ExtentY" VALUE="1323">
    <PARAM NAME="_StockProps" VALUE="0">
  <PARAM NAME="XPosition" VALUE="20">
    <PARAM NAME="YPosition" VALUE="20">
  <PARAM NAME="time" VALUE="6">
</OBJECT>

<!-- The Timer Object -->

<OBJECT ID="Timer1" WIDTH=38 HEIGHT=38
 CLASSID="CLSID:59CCB4A0-727D-11CF-AC36-00AA00A47DD2">
    <PARAM NAME="_ExtentX" VALUE="804">
    <PARAM NAME="_ExtentY" VALUE="804">
    <PARAM NAME="Interval" VALUE="002">
</OBJECT>

<HR>
Line 3
<HR>
Line 4
<HR>
Line 5

<SCRIPT LANGUAGE="VBSCRIPT">

Sub timer1_timer

Dim XPos, YPos, xold, yold

XPos = Frog1.XPosition
YPos = Frog1.YPosition

xold = XPos 
yold = YPos 

XPos = XPos + 1
YPos = YPos +  1

If (XPos > 400) Then
    XPos = 400 * rnd()  ' Randomize frog x position if frog too far to right    
    Timer1.Interval = 10 * rnd() + 1 ' reset timer interval
End If  

If (YPos > 400) Then
    YPos = 400 * rnd()  ' randomize the y position if frog too low
    Timer1.Interval = 10 * rnd() + 1  ' reset timer interval
End If 

if (XPos = xold) and (YPos = yold) Then
    MsgBox "Stop Smoking, your cigarette is not moving anymore"
End If

Frog1.XPosition = XPos
Frog1.YPosition = YPos
End sub

</SCRIPT>
</BODY>
</HTML>

Future Controls

We've only scratched the surface in this chapter of what can be accomplished with ActiveX controls. At this writing, very few examples of controls that were specifically built for use in Web pages are out there on the Web, except for what Microsoft and a few third-party vendors have made available. We expect to see quite a few new toys in the coming months. The component gallery at http://www.microsoft.com/activex/controls/ is one place to start looking. Two other sites of interest are http://www.active-x.com/ and http://www.activex.com/.

By the Way...

A couple of issues were bypassed in this chapter-such as how OCXs are downloaded in a secure fashion. We will take up that subject in Chapter 8

Another issue is our use of MFC to build controls. Throughout this book we use MFC, mainly because it lends itself well to illustrating some point or another. The one minor drawback is that end users of such an OCX will need to have the appropriate MFC and C run-time DLLs installed on their machines before the OCXs will work. This is a one-time download of about 1MB of code that will subsequently work with all of the OCXs. The only other hitch is that the DLLs are periodically updated.

The final item we want to mention before ending this chapter is the BaseCtl skeleton code in the ActiveX SDK. This is true, bare-bones Microsoft-style code for developing controls. If you are well experienced with OLE development, the BaseCtl framework will probably suit your needs better than using the MFC framework, which adds overhead to an OCX.



HomeAbout UsSearchSubscribeAdvertising InfoContact UsFAQs
Use of this site is subject to certain Terms & Conditions.
Copyright (c) 1996-1998 EarthWeb, Inc.. All rights reserved. Reproduction in whole or in part in any form or medium without express written permission of EarthWeb is prohibited.
Please read the Acceptable Usage Statement.
Contact reference@developer.com with questions or comments.
Copyright 1998 Macmillan Computer Publishing. All rights reserved.

Click here for more info

Click here for more info