Make your own ActiveX Control

advertisement
Make your own ActiveX Control
Make a new project, but this time choose MFC ActiveX control as the
project type, instead of MFC Application. Name the project StopLightX.
[ The screen shots below unfortunately have it named Stoplight2004.]
Accept all the defaults. The plan is to make a stoplight, complete with
blinking lights, that can be used as a control.
Examine Class View and File View to see what classes and files have been
created for you.
Note the .idl file and the classes, CStopLightXCtrl. This is where the
properties and methods of our control will be specified. The .idl file is not a
C++ file. IDL stands for Interface Definition Language. This is the
language in which interfaces are specified. An interface consists of
properties, methods, and events, and specifies how other programs can
communicate (in both directions) with the program that implements that
interface.
In Class View, right-click CStopLightXCtrl. You will see a choice Add
Event. Don’t choose it yet, just notice where that choice is. Now notice
that you also have a node labeled StopLightXLib. Expanding that node
you should see a node labeled _DStopLightX. Right-click that node and
notice that you have an option Add Property. Here’s a screen shot (with a
slightly different project name)
So now, when the time comes, you know how to add events and properties.
The Design Phase
What properties, methods, and events will we need to specify, control, and
interact with a stoplight? Properties that come to mind are the three colors
of the lights, the size of the bounding rectangle, the diameter of the lights,
possibly more numbers to specify the spacing of the lights, etc. (but for
simplicity we will use only the bounding rectangle and diameter of the
lights); then we need properties that tell us which light is on (or which lights
are on). That leads us to wonder whether we want to always have exactly
one light on (in which case it would do to have a property that tells which
light is on), or whether we want to be able to have them all off or more than
one of them on (in which case we need more properties than one). For
simplicity, let’s go for having exactly one light on at all times. Here “on”
includes the possibility of blinking. Now we need the blink time, in
milliseconds. We assume it will be the same for all lights. If it is zero that
means the light does not blink.
So we need the following properties:
LightDiameter
RectangleWidth
RectangleHeight
TopColor
MiddleColor
BottomColor
WhichLight
BlinkTime
Since we also want to demonstrate how to program using events, we want to
give the user a way to interact with the control. Let’s have the control fire
events TopLightClicked, MiddleLightClicked, and BottomLightClicked.
That completes the design. Now for the implementation.
Defining the Properties and Events
Right-click DStopLightX in Class View and choose Add Property. [You
have to expand a node first to find DStopLightX, like this:
Add the three color properties; here is an example:
You will see that you have a choice whether to implement properties using
member variables, or using Get and Set methods. I chose member
variables.
Add the other properties, using the types OLE_XSIZE_PIXELS and
OLE_YSIZE_PIXELS for RectangleWidth and RectangleHeight, and
OLE_XSIZE_PIXELS for LightDiameter, and SHORT for WhichLight and
BlinkTime. Just so we try both ways, choose Get/Set methods for these last
two properties.
You can expand the node in class view to see that your properties are there:
Now build the program. You will see that the last step in the build process is
“performing registration”. This will not work in the open labs, where you
do not have permission to modify the registry. Therefore, you will not be
able to duplicate this demo in the open labs, but it should work at home.
If you try to run your program, you’ll see that you cannot. It is not a
standalone program, it is an ActiveX control, meant to be used by another
program. We will see how to test it later on. So far, it wouldn’t do anything
anyway.
Right-click CStopLightXEvents and choose Add Event. Then add the three
events. None of these events needs a parameter so we don’t enter anything
in the parameter box. Note, this time it is CStopLight..., not DStopLight.
Programming the Control
You can see by expanding your Ctrl class in class view that member
variables and functions have been automatically created for you. All we
have to do now is initialize those variables and implement those member
functions. For the items that change the appearance of the light, we just call
Invalidate(). The work of drawing the light will be done in OnDraw. In
SetBlinkTime(), we have to kill the old timer and set a new timer. In
OnTimer, we call Invalidate(FALSE). We use FALSE since there is no
reason to erase the background—the new light has the same shape as the old,
just a different color.
Using the Timer
Where will the timer first be set? Not in the constructor, since the window
handle doesn’t exist yet, and a timer message may cause a crash, or just a
non-working timer. We could do it the first time OnDraw is called; using a
static variable to tell it this is the first time:
static int flag;
if(!flag)
{ flag = 1;
SetTimer(1,500,NULL);
}
We could avoid the static variable by setting the timer in OnCreate, after
first adding OnCreate using Class Wizard.
Where will the timer messages go? The documentation says that if the last
parameter of SetTimer is NULL, then the messages will go to the
application message queue. But this isn’t in an application! It’s in an
ActiveX control. So if we map WM_TIMER, will OnTimer get the timer
messages, or will the (presently unknown) parent get the timer messages?
Experiment shows that the ActiveX control will get them! That’s good, as
otherwise it would be difficult to create a self-contained blinking traffic light
control.
void CStoplight2004Ctrl::OnTimer(UINT nIDEvent)
{
// TODO: Add your message handler code here and/or call default
static int counter; // count the ticks mod 5
++ counter;
if(counter == 5)
counter = 0;
// light turns yellow at counter 2, red at 3, green at 0
if(counter == 0)
{ SetWhichLight(0);
}
else if(counter == 2)
{ SetWhichLight(1);
}
else if(counter == 3)
{ SetWhichLight(2);
}
Invalidate(FALSE);
COleControl::OnTimer(nIDEvent);
}
void CStopLightXCtrl::OnDraw( CDC* pDC, const CRect&
rcBounds, const CRect& rcInvalid)
{ if(m_hWnd == NULL)
return; // without this it crashes when you try to insert one in your dialog
GetClientRect(&m_BoundingBox);
static int flag;
if(!flag)
{ flag = 1;
SetTimer(1,500,NULL);
}
pDC->FillSolidRect(&m_BoundingBox,RGB(128,128,128));
CBrush Brush[3];
Brush[0].CreateSolidBrush(GetWhichLight() == 2 ?
m_TopColor: RGB(0,0,0));
Brush[1].CreateSolidBrush(GetWhichLight() == 1 ?
m_MiddleColor:RGB(0,0,0));
Brush[2].CreateSolidBrush(GetWhichLight() == 0 ?
m_BottomColor: RGB(0,0,0)); CRect r;
CBrush *oldBrush;
int i;
int x = (m_BoundingBox.Width() - m_LightDiameter)/2;
for(i=0;i<3;i++)
{ if(i==0)
oldBrush = pDC->SelectObject(&Brush[i]);
else
pDC->SelectObject(&Brush[i]);
r.SetRect(x,5*(i+1) +i*m_LightDiameter, x + m_LightDiameter,
(i+1)*(5+m_LightDiameter));
pDC->Ellipse(r);
}
pDC->SelectObject(oldBrush);
}
Here’s the code for GetBlinkTime and SetBlinkTime. Note that even though
we chose to have get and set functions, we still need to add a member
variable to make this work. But since we have to call KillTimer and
SetTimer, we needed functions here. However, it would have been easier to
use only a member variable for WhichLight. The get and set functions for
WhichLight are not shown here as all they do is change a member variable.
SHORT CStoplight2004Ctrl::GetBlinkTime(void)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
return m_BlinkTime;
}
void CStoplight2004Ctrl::SetBlinkTime(SHORT newVal)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
m_BlinkTime = newVal;
KillTimer(1);
SetTimer(1,newVal,NULL);
SetModifiedFlag();
}
Here’s the final version of the constructor, showing the initializations of all
the variables:
CStoplight2004Ctrl::CStoplight2004Ctrl()
: m_WhichLight(0)
, m_BlinkTime(0)
{
InitializeIIDs(&IID_DStoplight2004, &IID_DStoplight2004Events);
SetWhichLight(0); // Green light to start with
// SetBlinkTime(500); causes a crash as window doesn't exist yet
m_BottomColor = RGB(0,255,0);
m_TopColor = RGB(255,0,0);
m_MiddleColor = RGB(255,255,0);
m_RectangleWidth = 40;
m_RectangleHeight = 90;
m_LightDiameter = 30;
}
Testing an ActiveX control
A program that uses an ActiveX control is called a container. To debug or
test your new control, you will need a container. There are two
possibilities:
 Make a new project of your own as the container.
 Use the ActiveX Control Test Container provided by Microsoft.
First we will use the ActiveX Test Control Container. To do so, the easiest
way is to invoke it from the Tools menu of Visual C++.
When it comes up, you see a blank screen and some menus. This
application permits you to add ActiveX controls dynamically and invoke
their methods and change their properties. Choose Edit | Insert new control.
You should see your new control, StopLightX Control, on the list of
available controls. Choose it. One will be added to your screen. You will
see that it is an awkward size, but if you followed all the steps correctly, the
lights will be working.
Choose File | Save Session As. Save it as StopLightX. Now you can get
back to this point next time, without having to add a new control.
Click on the border of your control, so it appears selected. Then choose
Control | Invoke Methods. ( If it is grayed out, you have not selected the
control first.) This takes you to a dialog that allows you to invoke the
methods of your control, and see if they work or not.
Invoking the Control’s Methods
Recall the SetBlinkTime method:
void CStopLightXCtrl::SetBlinkTime(short nNewValue)
{
KillTimer(1);
SetTimer(1,nNewValue,NULL);
SetModifiedFlag();
}
(There is another line of automatically generated code that is not shown, but
it doesn’t do anything anyway.)
Now, to test this, choose Control | Invoke Methods and use the dialog that
comes up:
You can change the blink time from the default 500 to 200, for example, and
press Set Value and then Invoke. You will see the lights blink faster.
From here on, the development of the StopLightX control is routine.
Making Your Own Container
For serious development, you will want to make your own container
application. To do that, just create a new project using AppWizard, but at
the first screen, check the radio button Add to solution near the bottom left..
Now just create an MFC .exe project as you have done all semester. A
CFormView based project will be easy to use as a container for StopLightX
controls.
Working with Several Projects in One Workspace
When AppWizard is done, you have a workspace with two projects. The
active project is built when you choose Build. You need to keep both
projects up to date.
You can do that either manually, or using Build | Batch Build. You will see
that Batch Build is set up to allow you to develop a debug and release
version of each project, and in the case of your ActiveX project, both
Unicode and non-Unicode versions of both the debug and release versions;
so there are four versions of the ActiveX project and two versions of the
container project. For simplicity, you can uncheck all but the non-unicode
debug versions. Then you can choose Batch Build to build both projects at
once.
Warning. A common error is to have one project active, make a change in
the code for the other project, click Build, and wonder why the code change
didn’t do anything. Well, you haven’t compiled that code yet! You have
two build buttons on the toolbar. One is “build solution” and one is “build
project”. Use the “build solution” button.
Download