An ActiveX control can be 'dropped' into a form like any other control. Controls are not specific to VB, but can also be dropped into VBA forms in other Office applications. Parts of programs, like the Word Art feature packaged with Word, can be exposed as ActiveX objects and installed in any container that can hold ActiveX, including VB forms. ActiveX controls can be written either in Visual C++ or in VB. They can also be used by either language. (J++ can incorporate ActiveX, but the platform - independant versions can not, as of this writing.)
Perhaps the most exciting part of this technology from our point of view is that it gives us a framework to extend our classes into actual controls that can be used by our VB projects.
With just a small amount of study, you no doubt figured out what this code does. It reacts whenever a user clicks on a picture box, and it increments to the next qbcolor, recycling at 15. This is a pretty meaningless piece of code, yet it has some value as an example.
Let's make an ActiveX control with this same functionality.
Add a user control from the project menu. You will see the typical list of templates. Go with the generic 'user control' for now. As usual, the other options will be handy later, but often perform superflous features that are of little values when we're trying to orient ourselves.
When you look at the project window now, you will see that you now have two projects open at once. This has only been possible in the latest versions of VB, but it is necessary for ActiveX programming. What's going on here is we're actually writing two distinct programs. One of them (the exe) happens to use the other. (or will, when we add the code).
Note that the user control does have a visual interface which looks suspicously like a form, although it is not.
It would be great if the programmer could write this code in her form:
sub colorScroll1_change()
form1.backColor = colorScroll1.color
end sub
In order for this custom control to be of any use, it ought to expose
some kind of color property, so the form author can set a color based on
whatever the user selected. The purpose of this control is to shield the
programmer from worrying about qbColors. In addition to a color property,
we ought to have a few other standard properties, such as enabled, top
and left, visible, and so on.
We will also need to have some sort of event mechanism. The programmer
will be expecting a change event, so we ought to provide one.
The left hand panel contains a bunch of pre-defined properties, events,
and methods. The right hand panel consists of the set of these pre-defined
methods that will be used for your control. Note that I removed all
the properties, and all methods except 'enabled'. I wanted to see
what happens when I leave one. The property I really want (color)
does not exist on the list. Neither does the change event.
The next screen will allow me to add these custom members.
This form has a place to list any custom members. Note that the screen shot was taken AFTER I had added some members. When you click on the New button, you will see a little dialog like this:
It's pretty straightforward, as you can see. When we're done
adding members, we will go on to the next screen of the wizard...
As you can see, this screen allows us to 'map' members of the custom
control to similar members of the constituent controls. In many cases,
a member of the custom control will directly correlate with a member of
one of the controls on the custom control. In this example, all three
of my members will have an obvious mapping relationship, although you will
see that the wizard does not always get this right.
The change event of the colorScroller should be related to the change
event of the scrollbar inside the scroller. Likewise, the color of
the scroller will always have the same value as the foreColor property
of the picture box. The enabled property can be tied to the enabled
property of the userControl environment itself (the closest analogy to
a form in activeX control design). It is up to the programmer to ensure
that sensible mapping is done. We shouldn't try to map a color property
to the left property of some constituent control, for example, because
these things are not the same type of information. Generally, your
control members will be mapped to constituent members with the same or
very similar names. This is the case in our control.
When we hit next, we come to the finished screen. This has an option to print out instructions for testing the control. These instructions are useful but can be obtuse. Read them, but don't be worried if you don't understand the whole thing.
'Event Declarations:
Event Change() 'MappingInfo=scrColor,scrColor,-1,Change
Private Sub scrColor_Change()
RaiseEvent Change
picColor.BackColor = QBColor(scrColor.Value)
End Sub
'WARNING! DO NOT REMOVE OR MODIFY THE FOLLOWING COMMENTED LINES!
'MappingInfo=UserControl,UserControl,-1,Enabled
Public Property Get Enabled() As Boolean
Enabled = UserControl.Enabled
End Property
Public Property Let Enabled(ByVal New_Enabled As Boolean)
UserControl.Enabled() = New_Enabled
PropertyChanged "Enabled"
End Property
'WARNING! DO NOT REMOVE OR MODIFY THE FOLLOWING COMMENTED LINES!
'MappingInfo=picColor,picColor,-1,ForeColor
Public Property Get color() As OLE_COLOR
color = picColor.ForeColor
End Property
Public Property Let color(ByVal New_color As OLE_COLOR)
picColor.ForeColor() = New_color
PropertyChanged "color"
End Property
'Load property values from storage
Private Sub UserControl_ReadProperties(PropBag As PropertyBag)
UserControl.Enabled = PropBag.ReadProperty("Enabled",
True)
picColor.ForeColor = PropBag.ReadProperty("color",
&H80000012)
End Sub
'Write property values to storage
Private Sub UserControl_WriteProperties(PropBag As PropertyBag)
Call PropBag.WriteProperty("Enabled", UserControl.Enabled,
True)
Call PropBag.WriteProperty("color", picColor.ForeColor,
&H80000012)
End Sub
The original line is still there, in the scrColor_change event, but there's a lot of other stuff going on as well. Let's take a look at it.
When we test this code in a moment, we will see that it does not work perfectly. Look carefully at the relationship between the change event of the scrollbar and the change event of the object, and see if you can guess what the problem will be.
sub colorScroll1_change()
form1.backColor = colorScroll1.color
end sub
When we test the program, it does not work correctly!!!
When I ran it, I always got the form color changed to black, which
is qbColor zero. Interestingly, When I tried this with VB5, the results
were wrong in a different way. The color would change, but the form
color would always lag behind the scrollbar by one value.
The soloution can be found by looking closely at the scrColor_change event in the control code. There are two minor problems here.
After playing with debug.print, it became obvious that the color value was not automatically changing. Sure, we told the wizard to make picScroller.color integrated with picColor.foreColor, but it isn't happening properly. We'll need to give it a hand. Apparently, the property set of the scroller is not being called, so we'll force it by changing it as well as changing the color of the picture box. Here's my new code for scrColor_change()
RaiseEvent Change
picColor.BackColor = QBColor(scrColor.Value)
Me.color = picColor.BackColor ' I added this
This is a little better, but it causes the 'lag' behaviour that we saw in the VB5 version. When my scrollbar is on color 2, my form is on color 1. The form color is always one behind the scroll color.
The wizard put the raiseEvent call at the BEGINNING of the code. This tells the calling program to evaluate a change event before we have changed anything at this level. Clearly this is not correct. The raiseEvent should be the last thing this code does. After we move it to the bottom, we are ensured that the values have changed BEFORE the form gets a chance to deal with the colorScroller_change event.
Here's my final version of the code for scrColor_change():
Private Sub scrColor_Change()
picColor.BackColor = QBColor(scrColor.Value)
Me.color = picColor.BackColor ' I added this
RaiseEvent Change
'I moved this to the bottom
End Sub
Everything else was left alone.