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 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.
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.
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.
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
};
};
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.
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.
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.
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.
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.
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
};
};
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
};
};
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.
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
};
};
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.
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.
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>
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.
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.
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.)
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.
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>
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/.
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.