This chapter covers a mixed bag of ActiveX control features. Many of these features are cosmetic in nature, but even the seemingly superficial features can be crucial if you want to give your control the feature set and interface that users expect.
This chapter is chock-full of demonstrations that show how each feature works; many of the demonstrations include information on non-obvious techniques you can use to achieve a number of interesting effects in your ActiveX control project.
Enabling your control to show the background of its container is a feature that's particularly useful when your control is based on several constituent controls.
You can make the background of your UserControl property transparent by setting its BackStyle property to 0 - Transparent. Bear in mind that making the background of your control transparent creates a significant performance hit on your control because it must do much more calculation in order to redraw itself.
Although some properties are design-time-only or runtime-only, most properties can be set at any time. The nature of each property will help you determine whether your control should expose properties that can't be set at runtime or design time. You should obviously avoid restricting user access to properties if you can help it.
You can make a property design-time-only or runtime-only by inspecting the UserMode property of the Ambient object in the Property Let procedure. If the UserMode property is True, then it is runtime. If the property is False, the control is in design mode.
Here's a demonstration of a property that can't be set at runtime.
' Declarations Private mlngArkeyMalarkey As Long Public Property Get ArkeyMalarkey() As Long ArkeyMalarkey = mlngArkeyMalarkey End Property Public Property Let ArkeyMalarkey(ByVal lngNewValue As Long) If Ambient.UserMode = True Then ' It's runtime. MsgBox "You ninny! You can't set this at runtime!" Else mlngArkeyMalarkey = lngNewValue End If End Property
ArkeyMalarkey1.ArkeyMalarkey = 14
Figure 10.1 : What part of runtime dont you understand?.
When the user attempts to set a design-time-only property at runtime, your control should raise an error, rather than display a message box, as this simplified demonstration does. For more information on raising errors, see Chapter 15, "Debugging and Error Trapping."
Just as a Visual Basic form can contain one or more ActiveX controls, a control can also contain one or more ActiveX controls. Not all controls can act as containers for other controls; examples of controls that can contain other controls include the PictureBox control and the Frame control.
Controls are contained in other controls for a variety of reasons. For example, the Frame control gives end users a choice of options, usually presented in the form of multiple OptionButton controls.
Sometimes controls are contained in other controls for cosmetic purposes. A PictureBox grouping of a collection of controls enables you to place a different color or bitmap behind the control group. Additionally, when you move the PictureBox control, all the controls contained in it move along with it, which can be a real help when you're coding complicated Resize events.
To let your UserControl contain other controls, you set its ControlContainer property to True. Once you do that, users can deposit a hypothetically unlimited number of controls in it. The only problem with this, as the following demonstration shows, is writing code to handle them.
To see how to program a control that has the ability to host other controls, you'll create a control called ViewPort. This control is designed to conserve screen space by housing a large number of controls in a very small space on the screen. It can do this because not all of the controls are visible at once; the user must scroll left or right to locate the correct control within the ViewPort control. So, this control trades off ease of use for the end user with conservation of screen space.
The ViewPort control consists of nothing more than some code and a constituent horizontal scroll bar control. The scroll bar enables the user to scroll left and right to view all the controls.
To create this control:
Private Sub UserControl_Resize() HScroll1.Move 0, Height - HScroll1.Height, Width End Sub
' Declaration Private mlngVirtualWidth As Long Private Sub UserControl_WriteProperties(PropBag As PropertyBag) PropBag.WriteProperty "VirtualWidth", mlngVirtualWidth, Width End Sub Private Sub UserControl_ReadProperties(PropBag As PropertyBag) mlngVirtualWidth = PropBag.ReadProperty("VirtualWidth", Width) End Sub Public Property Get VirtualWidth() As Long VirtualWidth = mlngVirtualWidth End Property Public Property Let VirtualWidth(ByVal lngNewValue As Long) mlngVirtualWidth = lngNewValue HScroll1.Max = lngNewValue End Property
Private mlngPreviousScroll As Long
Private Sub HScroll1_Change() Dim c As Control For Each c In UserControl.ContainedControls c.Left = c.Left + (mlngPreviousScroll - HScroll1.Value) Next mlngPreviousScroll = HScroll1.Value End Sub
Private Sub Form_Load() ' Set virtual width of view port to ' the right side of the rightmost command button ViewPort1.VirtualWidth = Command3.Left + Command3.Width End Sub
The ViewPort control should work for an unlimited number of contained
controls, as long as the code you entered in the Load event of
Form1 that sets the control's VirtualWidth property takes all
the controls stored in the control into account; the property
must always be large enough to store the rightmost control contained
If you feel like getting a little more show-offy, you might consider adding a vertical scroll bar and some more code to this control to make it scroll in two dimensions, both horizontally and vertically. Such a control would be a very handy way to view large graphics in a small area on screen.
An access key is a key that the user presses in combination with the Alt key. Controls can be programmed to respond to access keys. A typical use for this feature is for a control to take the focus in response to an access key, but you could have a control respond by executing a method.
Controls that can respond to access keys have a Caption property that responds to the ampersand (&) character in a special way. When the user assigns a caption that contains an ampersand, it means that the next character in the caption string is to be treated as that control's access key. A control with a caption of ABC&DEFG would then have an access key of d (and the caption would display as ABCDEFG). So, to produce the result specified in this caption, the user would press ALT+D.
For example, let's say you have a form with a frame and two option buttons. The first option button has the caption &Jes, and the second button has the caption &Ne. (I'm talkin' Esperanto here; try to keep up with me.) Pressing Alt+J on your keyboard is the equivalent of clicking the Jes button, while pressing Alt+N is the equivalent of clicking Ne, as illustrated in Figure 10.4.
Figure 10.4 : Access keys allow an alternative to mouse clicks.
On the other hand, if you create a CommandButton control and give it the Caption property of J&erky, the control's access key becomes Alt+E. If you run the program and press Alt+J, the CommandButton's Click event is triggered.
If you rely on the behavior of constituent controls to provide access key behavior, you don't have to write any code at all. To see how this works, do the following:
Property Get Caption() As String Caption = Label1.Caption End Property Property Let Caption(NewCaption As String) Label1.Caption = NewCaption End Property Private Sub UserControl_ReadProperties(PropBag As PropertyBag) Caption = PropBag.ReadProperty("Caption") End Sub Private Sub UserControl_WriteProperties(PropBag As PropertyBag) PropBag.WriteProperty "Caption", Label1.Caption End Sub
Relying on a Label to serve as an access key for another control is a neat (albeit not-entirely-obvious) trick. It works because the Label can't take the focus itself, so instead, the next control in the tab order (in this case, the constituent text box) is given the focus instead.
Hijacking the Label control's access key works well when you have a Label in your control. But what happens when you want to (for example) build your own avant-garde command button out of a constituent PictureBox control? A PictureBox control doesn't respond to an access key, so you have to build that functionality into your control manually.
You can build manual access key functionality into your control by using the AccessKeys property and AccessKeyPress event of the UserControl. The AccessKeys property denotes the key or keys that will cause the AccessKeyPress event to be raised. The AccessKeyPress event enables you to write code that will take an action based on the end user pressing the control's access key. Whether the code in the AccessKeyPress event simply moves the focus onto your control or initiates an action (such as another event) is up to you. To see how this works:
' Declarations Public Event Click() Private Sub Picture1_Click() RaiseEvent Click End Sub
Private Sub UserControl_Paint() Picture1.Font.Bold = True Picture1.Font.Size = 24 Picture1.Font.Underline = True Picture1.Print "C"; Picture1.Font.Underline = False Picture1.Print "lick me!" Picture1.CurrentX = 0 Picture1.CurrentY = 0 End Sub
Private Sub UserControl_AccessKeyPress(KeyAscii As Integer) SetFocus Debug.Print "You sunk my battleship." End Sub
Figure 10.7 : AccessKeys property.
This will cause the control's AccessKeyPress event to be triggered when the end-user presses the keystroke combination Alt+C.
To test the control, place an instance of it on an EXE project form, along with a normal control such as a text box. The EXE project form should look like Figure 10.8. Then run the EXE project by clicking in the normal text box, then pressing Alt+C. You should be able to see the focus move off of the text box. The Immediate window will also indicate that the control's AccessKeyPress event was run.
Figure 10.8 : ClickyPicture control.
So you can see in this example how to provide an access key through the AccessKeyPress event. Providing such shortcuts can make your control easier to use, providing users with the kind of interface they expect from controls.
Not too many controls are invisible at runtime. This is because the vast majority of controls provide user-interface elements, and as every user-interface designer knows, an invisible user interface is an unhappy user interface.
Controls that are invisible at runtime usually provide some sort of calculation, or they're used as wrappers to Windows API calls. The Timer control is the most common example of this. Because it only serves to tick off time, the Timer doesn't need to have a visual interface; instead, it serves only to generate events after a certain period of time has passed.
Creating a control that is invisible at runtime is simplicity
itself-simply set the control's InvisibleAtRuntime
property to True.
Controls that are invisible at runtime do not generate Paint events. This seems like a no-brainer (because nothing that's invisible ever needs to be painted) but it's worth mentioning. In Chapter 11 you'll see an example of a control that is desperately crying out for a Paint event, but simply can't have one because it's invisible at runtime.
To set up a control that is invisible at runtime:
Private Sub Timer1_Timer() If Ambient.UserMode = True Then Debug.Print "This has been an annoying ping." Beep End If End Sub
(You're beginning to see why we call this control AnnoyingPing. You're also beginning to see why I live in a two-bedroom apartment all by myself.)
One interesting thing about the AnnoyingPing control is the fact that it only beeps when the program it's in is running (that is, Ambient.UserMode) is True. This conditional test is necessary because the Timer control will happily continue to tick off Timer events no matter whatÐwhether it's runtime or design-time. If only my dad's old Ford Pinto were that reliable.
Anyway, to test this control, close the form designer, create
an instance of the control on an EXE project form, then run the
EXE project. The AnnoyingPing control will start pinging wildly.
You may wish to consider dropping this program off on the doorstep
of any cranky old men (or ladies) you have living in your neighborhood.
Try as they might, they won't be able to figure out what's causing
the AnnoyingPing control to annoy them, because it has no visual
interface. Science marches on.
It's a good idea to consider placing the functionality of an invisible control in some other Visual Basic component, such as a class or an ActiveX DLL (formerly referred to as an OLE DLL). This is because an ActiveX control can have greater overhead (in terms of distributables and potentially also in terms of run-time resources) than a code module or a DLL. You can't create DLLs with the Control Creation Edition, but you can create them with the Professional or Enterprise edition of Visual Basic.
Some controls, such as the PictureBox control, have the ability to automatically align themselves to the edge of their containers. Such controls can be set to align themselves to either the top, left, right, or bottom of the form on which they reside. Such controls also automatically resize themselves when their parent form is resized.
Controls that are alignable expose an Align property. The Align property can be set to None (meaning the control's alignment feature is disabled), Top, Bottom, Left, or Right.
Alignability is used most often to implement a status bar that sits at the bottom of the form. You can make a control align to the edge of its container by setting the Alignable property of the UserControl to True.
Since status bars usually have textual and graphical components to inform the user of what's going on in the application, it would make sense to create a status bar control out of a PictureBox control. But because a PictureBox control doesn't have a Caption property, you'll have to create one yourself.
To demonstrate how to implement the UserControl's Alignable property, you'll create a status bar control that can align itself to the edge of the form it resides on and can resize automatically to match the form. To do this:
Private mstrCaption As String Private Sub UserControl_InitProperties() mstrCaption = Extender.Name End Sub Private Sub UserControl_Paint() Picture1.Cls Picture1.Font.Name = "MS Sans Serif" Picture1.Font.Size = 10 Picture1.CurrentX = 0 Picture1.CurrentY = 0 Picture1.Print mstrCaption End Sub Private Sub UserControl_Resize() Picture1.Move 0, 0, ScaleWidth, ScaleHeight UserControl_Paint End Sub Private Sub UserControl_ReadProperties(PropBag As PropertyBag) mstrCaption = PropBag.ReadProperty("Caption", Extender.Name) End Sub Public Property Get Caption() As String Caption = mstrCaption End Property Public Property Let Caption(ByVal strNewValue As String) mstrCaption = strNewValue UserControl_Paint End Property Private Sub UserControl_WriteProperties(PropBag As PropertyBag) PropBag.WriteProperty "Caption", mstrCaption, Extender.Name End Sub
Figure 10.9 : Form with StatusBar control.
An About box is a way to stamp your control with your name or your company's name, as well as other information about your control. Including this information has become more important now that ActiveX controls are downloadable over the Internet. It's possible that users will download your control and scarcely pay attention to where it came from. Which means that when they want to pay you big bucks for the licensed version of your control (or harass your tech support people because the control doesn't work right) they have no idea whom to call. Providing an About box resolves that dilemma by including a sort of electronic business card with every control you create.
In addition to information about your control and your company, you might also consider adding information on your online presence in an About box, including your company's Web page, if you have one. You can add anything to the About box you can add to a normal Visual Basic form.
The standard Visual Basic way to provide an About box in a control is to attach the About box to the control's About property. This "property" can't be set, so it really acts like a method, but it's customarily implemented as a property so that users of your control can access it easily through the About property in the Properties window. The About property in the Properties window is illustrated in Figure 10.10.
Figure 10.10 : AboutBox property in the Properties window.
It's common for licensed, commercial controls to display their About boxes every time the control is run on a system that does not have a license to run the control. This enables unlicensed users to play with the control and see how it works, even though they haven't paid for it yet. But it discourages them from redistributing the control in the applications they build, because, you know, who wants to see your goofy About box every time the control runs? For more information on licensing your control, see Chapter 12, "Distributing Your Control."
To create your own About box, do the following:
|BorderStyle||3 - Fixed Dialog|
|StartUpPosition||2 - CenterScreen|
If you don't care to create your own About box for this demonstration, you can instead add the About box About.frm (from the CD-ROM that accompanies this book) to your template project. To add this About box to your project:
Be sure you've added the About box to Project1, your control project, not Project2, your test EXE project.
Now that your project contains an About box, you need to enable it. To do this:
Public Sub AboutBox() frmAbout.Show vbModal Unload frmAbout Set frmAbout = Nothing End Sub
The code Set frmAbout = Nothing is used to remove the form from memory.
Figure 10.12 : About box.
If you find yourself creating the same type of project again and again, you should consider setting up a project template. Project templates are a new feature of Visual Basic that let you create a prefabricated set of visual design and code elements that are included in every project you create.
It's worth noting that the CtlGroup project you've been basing
most of your Visual Basic control projects on is itself a template.
You can modify that template as well. For example, if your control
projects always contain a certain set of API declarations, ActiveX
controls (built by you or somebody else), a custom class library
you've devised, or a third-party add-in, you can alter the standard
project templates to load these automatically every time you create
a new project.
Project templates take the place of the autoload.vpj file in Visual Basic 4.0 that loaded a programmer-defined set of custom controls each time you launched VB.
Let's say you work for a software company that makes ActiveX controls that enable users to create custom charts and other graphics. Your company's standards dictate that every control have a similar About box with your company's logo. Additionally, since all your company's controls have to do with charting, you want all of your new controls to contain a PictureBox control with the appropriate resize code already included.
Start by creating the visual design of the project that will become your template. To do this:
Private Sub UserControl_Resize() ' The Move method is the fastest and most ' sanitary way to resize a control Picture1.Move 0, 0, ScaleWidth, ScaleHeight End Sub
At this point, you could have added more code to expose the constituent Picture property of Picture1 and take care of PropertyBag operations (for example), but we've proven our point for the time being.
Now it's time to take the skeletal project group you've created and move it into the Visual Basic templates directory so you can use it as a template. The trick here is that you're going to save the control group (that is, the .VBG file) into the templates folder, but you're going to save the individual elements of the project into a separate folder alongside the templates folder. That way, only the project group becomes a template. To do this:
To see that your project has been made into a template, start a new project by choosing the menu command File, New Project. The New Project dialog box appears. You should be able to see your BasicPicture template in the dialog box, as illustrated in Figure 10.15.
Figure 10.15 : New Project dialog box .
If for some reason you don't care to store your templates under the VB folder, you can change it to whichever directory you like. For example, if you are part of a team of developers, you might want to store a collection of templates on a file server accessible to everyone in your work group. In this case, you'd want all the developers in your work group to use the same templates folder.
To do this, choose the Tools, Options menu command, then select the Environment tab. The dialog box looks like Figure 10.16.
Figure 10.16 : The Environment tab under Tools, Options.
Enter your preferred directory in the Templates Directory area, then click OK. The directory you chose will be the directory Visual Basic will look in to find templates. If you're working in a group, each member of the group must set this option in their copy of Visual Basic.
You can add a bitmap to your UserControl. This bitmap represents your control in the Visual Basic toolbox.
Adding a bitmap that will represent your control in the Visual Basic toolbox is similar to setting the Picture property of a PictureBox control.
You add a toolbox bitmap by assigning a bitmap to the ToolboxBitmap property of your UserControl. The bitmap you assign can be of any size. However, you should tailor your toolbox bitmaps to the standard size of 16 pixels wide by 15 pixels high. If you make your bitmaps any bigger than that, Visual Basic will automatically scale them down and they'll look heinous.
To add a bitmap to a control project:
Figure 10.17 : Icon in the toolbox.
Paint Shop Pro is a righteous tool for creating goofy little bitmaps like this. It's orders of magnitude better than Windows Paintbrush. Better yet, it's shareware, so you can torture it before you purchase it. You can get a copy from the good folks as JASC, Inc. by visiting their web site at http://www.jasc.com.
In this chapter, you learned probably more than any human being should be permitted to learn about a smattering of varied yet vital features of a well-crafted ActiveX control. In the next chapter, you'll learn how to take advantage of the Windows API to enable your control to perform tasks not available in Visual Basic.