ActiveX Control Tutorial - Part 4
Hola and welcome to the fourth instalment of the only ActiveX control tutorial that's groovier than Austin Powers and perhaps even hotter than Felicity Shagwell's pants*.
* 'Course, that all depends on whether you adopt the American or saucier English definition. I opt for the latter. Any complaints?
If you've missed any of the previous slots, be sure to check them out before continuing:
- Part One — Introduction to ActiveX controls
- Part Two — Properties, Enumeration, Resizing
- Part Three — Events, Mapping, ActiveX Wizard
As ever, I'm your wizzy host Karl Moore and this week, we'll be:
- Discussing what that wizard did last week
- Figuring out what property bags are all about
- Figuring out what old bags are all about
- Adding our very own custom property pages (cool stuff!)
So grab that copy of Visual Basic and let's fly off into the programmatic world of ActiveX controls...
Thankfully, by the end of this tutorial you will have finished the Super Cool Text Box control we've been working on. And that's good, 'cause I'm getting bored now.
In fact, I haven't experienced this much boredom since the time London Ritz Hotel made a slight booking error and placed the entire Wool Appreciation Society in the same meeting hall as the International Morris Dancer's convention.
<Ed: Really? How did you find out about that, Karl?>
<Karl: I'm Morris Dancer number #2538>
So far in our lil' project, we've added a few funky properties, checked up on enumeration and resizing, figured out raising events and even used a wizard to help save time with any run-of-the-mill coding.
But last week, I left you on a cliffhanger. Oh yes, don't deny it.
You see, after the wizard finished running, it left a wad of 'read and write properties' code all over the place, remember? The question was, what does it all do?
Well, sit back my little cherubs, as I explain all.
<Babble mode: ON>
Let's say you go ahead and add our spicy control to a nice test project. You then alter the 'AcceptType' property to 'Letters', save everything and marinade overnight in a healthy dollop of hard disk juice.
The next morning, you return and open the project. But <shock, horror> the 'AcceptType' property is now displaying the default 'All'.
So what happened? Who stole the property?
Well, let me give you a clue: it wasn't the Butler. Oh no. The truth is we simply haven't told Visual Basic that it should save the property.
"Why doesn't VB save it automatically instead of forcing programmers to do all the hard work?" - Good question.
Let's think about it hmm, I guess some properties don't really need saving. For example, the SelText property of a Text Box, which returns any text currently selected, doesn't need saving but it is still a property.
Yet other properties such as 'AcceptType' clearly do need saving and Visual Basic does provides a neat method for doing this. OK, so how do you actually tell VB to save your property?
<Babble mode: STILL ON>
When an instance of your control is saved, the WriteProperties event of the UserControl (that's your control 'workspace') fires. In addition, a 'PropertyBag' is passed as an argument to this event.
Private Sub UserControl_WriteProperties(PropBag As PropertyBag)
A property bag is just a virtual bag that stores, err, properties.
<Ed: No? Gee, nothing gets past you, Karl!>
In effect, it's a holding place for values. And you can read and write to this group of values by using two of the property bag methods.
So let's pretend a developer is using our Super Cool Text Box control and changes the Text property in design mode. The geezer then hits the Save button.
Quicker than you can say antidisestablishmentarianism, Visual Basic (*) yells to your control, "Hey, I'm trying to save here" and fires its WriteProperties event passing a property bag to it.
Private Sub UserControl_WriteProperties(PropBag As PropertyBag)
You can imagine this property bag as a shopping trolley without the wonky wheels your task is to fill it up with the information you need to save. You do the "filling up" like this:
Call PropBag.WriteProperty("Text", _txtTextBox.Text, "")
Here you're running the WriteProperty method of the passed property bag. You're telling it to save this information under the heading of "Text", with a value of txtTextBox.Text. The final double-quotation marks indicate a default value of, well, nothing actually.
So you write values to the property bag and hence, save your control properties, such as Text - using this template:
Call PropBag.WriteProperty(PropertyName, _PropValue, Default)
Let's take a peek at another code example:
Call PropBag.WriteProperty("ConvertCase", _m_ConvertCase, m_def_ConvertCase)
Here, our code is saying "Save this item under a heading of ConvertCase, with a value of m_ConvertCase and a default value of m_def_ConvertCase".
So in brief, the WriteProperties method fires when a control is being saved. You're passed an 'empty shopping trolley' and asked to put all the stuff you need to save into it. Typically this trolley is stored along with the thing that uses it, such as in a Form FRM file.
Take a quick look at your Super Cool Text Box project. Notice how the Wizard automatically added all this code for you? You'll get a chance at writing your own WriteProperty code later.
Anyway, that's how you tell Visual Basic to save the values of your properties, such as AcceptType.
But what about when the user opens his or her project? How do we get the property values we've just put into the property bag back out?
Top Tip: You may wonder why, in addition to passing a 'PropValue' value, we also send the WriteProperties method a 'Default' value. Erm, why bother passing across two values? Won't it simply be the 'PropValue' that gets saved not the default? That one puzzled me for a while. But it appears the WriteProperty method compares both the property value and the default and only saves if they're different. After all, it would waste disk space to save the redundant default value of a control such as the Text Box BackColour being white, when it's white by default. But by supplying this default, it allows the property bag to compare and decide which bits it should save. Clever stuff, eh? You'll understand this concept more as we delve further into default values over the next section.
* Asteriskical Top Tip (hah, and they sed I couldn't spel!): Actually, when you finally distribute your control it's not only Visual Basic developers who'll be able to utilise it. When you create an ActiveX component of any type, it's automatically compatible with all ActiveX/COM-aware programming languages, including Visual C++
Next up on my nerdy curriculum, we have the ReadProperties event. Excited yet? Not exactly an inspiring name, I grant you — but please bear with it.
Ironically, despite doing exactly the opposite of the WriteProperties method we just uncovered, ReadProperties is surprisingly similar in usage.
Hmm, I could cunningly ask you to reread the previous section backwards or something, but I don't think the Ed would like that. In fact, he'd have me off the site before you can say antidis...
<Fading 'Arghhh!' sound as author Karl Moore is thrown out the nearest window by an evil, hooded figure bearing a suspicious resemblance to the Ed. Can't be certain it was him though. Recognised nothing major, just silly little similarities. Like the thick cigar sticking out of the balaclava. Oh, and the one-legged limp. And the 'I am the Editor' tee shirt he wore. Strange coincidence, that. Ho-humm. Minutes later, a slightly-bruised Karlos sneaks back indoors to continue the tutorial>
Now, let's pretend one groovy son-of-a-developer used your Super Cool Text Box control yesterday to start developing his Parcel Tracking program. He's now returned to continue work and opens the Visual Basic project.
What happens? As the form bearing your creation opens, Visual Basic says "Hey diddly dandy, I'm loading you up here" and fires the control's ReadProperties event:
Private Sub UserControl_ReadProperties(PropBag _As PropertyBag)
Here, you're passed a property bag that potentially contains a handful of property values, such as those previously saved. It's at this stage you can read those properties. Let's peek at an example:
m_AcceptType = PropBag.ReadProperty("AcceptType", _m_def_AcceptType)
Here, we're passing ReadProperty the name of the item to find within the bag ('AcceptType') and a default value of m_def_AcceptType.
If, after a quick rummage around, the ReadProperty method manages to find a value for our item, it's passed back otherwise our default value is returned.
Either way our m_AcceptType variable, which ultimately handles the AcceptType property is set appropriately.
Let's look at another supercool example:
txtTextBox.Text = PropBag.ReadProperty("Text", "")
Here, we're directly setting the Text property of our Text Box equal to the "Text" value in the property bag. If no value exists in the bag, ReadProperty passes back our default of "" an empty string.
So you read values from the property bag in other words, retrieve your control properties - using this template:
Destination = PropBag.ReadProperty(PropertyName, Default)
In brief, the ReadProperties event fires when your control is being loaded. It passes you a possible bagful of properties and allows you to pick out the items you want and set variables or object properties accordingly.
Do you understand how both the reading and writing of properties fit together? Take a closer look at your project. Do you understand what the wizard has done?
Why don't you practice these techniques by adding a 'play around' property, such as Text2? You can access it via a property Let and Get routine, then use the WriteProperty and ReadProperty skills you've just learned to 'permanently' store the information alongside your control.
Top Tip: Here's a great way to chat up geekesses. Instead of saying you used the 'ReadProperty' and 'WriteProperty' methods to save stuff, simply mention how you 'persisted' the values. Trust me, you'll sound much more of an anorak and the grrls just love it!
But hold on one lil' minute, missy we've still not figured out the meaning of that mysterious 'PropertyChanged' statement our wizard threw inside every Let procedure - remember? Grab that cigar Sam; it's time to investigate...
Behind virtually all of our Property Let routines, you will notice the wizard added a strange 'PropertyChanged' statement, as so:
Public Property Let ConvertCase(ByVal _New_ConvertCase As CaseType) m_ConvertCase = New_ConvertCase PropertyChanged "ConvertCase"End Property
Why? Well, this is very simple. And I'm not talking about 'E=mc2' simple here. I'm taking more along the lines of '1=1' simple.
All PropertyChanged does is inform the thing using your control that a particular property has changed. In other words:
...would send a message back to Visual Basic saying, "Hey, somebody has altered ConvertCase so make sure the Properties window is up-to-date. Oh, and don't forget that 'cause a change has been made, you'll probably want to save the project some time soon!"
Once again, the Wizard has automatically added all this for you. If you need to do it for yourself however, use this format:
Public Property Let PropName(ByVal _vNewValue As Variant) ' Usual code for processing property goes here PropertyChanged PropNameEnd Property
Hey, wait a minute. I've done nothing but babble on about theory so far. It's time to get physical!
<Karl starts grooving away to a song of the same name, unaware of onlookers. Ten minutes later, after one final dazzling disco floor split, he composes himself then returns to the computer>
Have you used the ADO control before? If you right click on it, then select Properties you'll see a small form that enables you to select various options. It's really just an advanced version of the Properties window.
Click here for larger image
Lot's of professional components have these 'property pages'. They allow you, as the developer to have much more control over how you can display and allow users to select properties.
That means you can use Tabs, Combo Boxes, TreeViews, Command Buttons - basically anything you can add to a regular form - to brighten up the selection of options.
For the rest of today, I'm going to show you how to create your own property page. My example won't be anything spectacular, but hopefully it will give you a few ideas as to how you can create your own.
So wave goodbye to alphabetically ordered Property window, and say hello to the all-singing, all-dancing property page:
- Open our Super Cool Text Box control project
- Click Project, Add Property Page
You should be presented with a screen allowing you to add a raw property page or use the Property Page Wizard. For now, let's be lazy and go with the Wizard we'll do a little manual work later.
- Select the 'VB Property Page Wizard' and click Open
- Click Next at the introduction
Your screen should now look like this:
Each item in this list will represent one 'tab' when you view the property page of your control. For instance, this control has five different property pages each represented by a tab:
- Uncheck the StandardColor and StandardFont boxes
- Click 'Add' and type: General
- Click OK
Top Tip: Our project will only have one property page, General. But you could have many more as the above screenshot demonstrates!
Note that Visual Basic has already selected a list of 'Available Properties', though sadly not 'AcceptType' or 'ConvertCase'. Don't worry, we'll deal with this later.
- Add all available properties to the 'General' list
- Click Next
- Select No for the Summary Report and click Finish
- After the Wizard has finished, click OK
Open up the property page our Wizard created. It should look something like this:
Click here for larger image
Huh, not very exciting. In fact, you're looking at the only computer screenshot so plain it ships with its own aviation certificate.
Let's take a peek at some of the code behind this rather dull property page.
Underneath all the Change events of the Text Boxes and the Click events of the Check Boxes, you should find this code:
Changed = True
This tells Visual Basic that at least one property on the page has changed. Of course, it doesn't signify that the change should be saved - you mustn't forget that all property pages have an OK, Cancel and Apply button. It simply informs VB that a property has been altered a little like the PropertyChanged method of your control.
Now, just as a regular Form has its own events, so does the Property Page. When someone hits the OK or Apply buttons, the ApplyChanges event runs then it's up to you to save the properties. This is a little like the WriteProperties event of your control.
Likewise, when someone first opens your Property Page, the SelectionChanged event fires. This can be compared to the ReadProperties event of your control it allows you to 'set' the various whatnots on your property page.
So let's take a peek at some of the code the wizard threw behind those two events:
Private Sub PropertyPage_ApplyChanges() SelectedControls(0).Text = txtText.Text SelectedControls(0).PasswordChar = txtPasswordChar.Text ' ... etc ... ' This is like WriteProperties - ' It passes the changes on your ' property page back to the control ' SelectedControls(0)End SubPrivate Sub PropertyPage_SelectionChanged() txtText.Text = SelectedControls(0).Text txtPasswordChar.Text = SelectedControls(0).PasswordChar ' ... etc ... ' This is like ReadProperties - ' It reads the properties of your control ' via SelectedControls(0) and displays ' the info via your property page text boxes, ' check boxes, combos, etc.End Sub
Now, you've probably noticed 'SelectedControls' appearing once or thrice throughout the code. This is a method of your property page - basically a direct 'link' to the running instance of your control.
So if you wanted to check out a property of your control, say the PasswordChar value, you can access it via the SelectedControls(0) item. You can read any of our control properties using this format:
... passes back the value of the controls PasswordChar property. The SelectedConrols(0) bit here is just a gateway to your control and its properties.
And if you can understand that, the code should look simple.
Excellent! Let's summarise:
In the ApplyChanges event, you're simply setting properties of the user's control dependant on the various Text Boxes, Check Boxes, etc. present on your property page. You're applying changes made in the property page direct to the control.
In the SelectionChanged event, which remember is similar to our control's ReadProperties event, you merely set the values of your Text Boxes, Check Boxes, etc. dependant on the properties of the user's control.
Put simply, the SelectedControls(0) statement just gives you direct access to the user's instance of your control and all it's properties.
Try testing your control! Add it to a demo project, right click and select Properties. Try changing a few of the options, then click Apply or OK.
Notice how the Properties window updates itself?
Now I don't know about you, but I ain't awfully satisfied with my property page. It looks about as stunning as a sumo wrestler's bottom.
So let's try to spruce it up a little by adding support for our custom AcceptType and ConvertCase properties:
- Add two Labels and two Combo Boxes to your Property Page
Click here for larger image
- Name the Combo Boxes 'cboAcceptType' and 'cboConvertCase' respectively
- Change both their Style properties to '2 - Dropdown List'
Now, when the property page opens, we want those two Combo Boxes to hold our enumeration options such as 'All' or 'Letters'. Unfortunately there isn't an easy way to automatically do this, so we'll add them manually under the property page's Initialize event (similar to Form_Load).
- Add the following code to your property page:
Private Sub PropertyPage_Initialize() With cboAcceptType .AddItem ("Anything") .AddItem ("Just Numbers") .AddItem ("Just Letters") End With With cboConvertCase .AddItem ("Anything") .AddItem ("Upper Case") .AddItem ("Lower Case") End WithEnd Sub
Try testing your project now. When you open the property page, are you able to select an item from the list?
Good let's continue:
- In the Click event behind each Combo Box, add the code:
Remember, this tells Visual Basic that at least one property on the page has been changed.
Now let's add a little code to the bit which reads properties and sets the value of your Text Boxes, etc.
- Add the following code to the SelectionChanged event:
Select Case SelectedControls(0).AcceptType' Note that I'm using the enumeration name prefix' instead of the ambiguous "All"Case CharacterType.AllcboAcceptType.Text = "Anything"Case CharacterType.LetterscboAcceptType.Text = "Just Letters"Case CharacterType.NumberscboAcceptType.Text = "Just Numbers"End SelectSelect Case SelectedControls(0).ConvertCaseCase CaseType.AllcboConvertCase.Text = "Anything"Case CaseType.LowerCasecboConvertCase.Text = "Lower Case"Case CaseType.UpperCasecboConvertCase.Text = "Upper Case"End Select
Here, we're just examining the current AcceptType and ConvertCase properties, then changing the Combos as appropriate.
So that's how we get the initial properties into the property page. Now we need to deal with how to get them out, after the user clicks OK or Apply.
- Add the following code to the ApplyChanges event:
Select Case cboAcceptType.TextCase "Anything"SelectedControls(0).AcceptType = _ CharacterType.AllCase "Just Letters"SelectedControls(0).AcceptType = _ CharacterType.LettersCase "Just Numbers"SelectedControls(0).AcceptType = _ CharacterType.NumbersEnd SelectSelect Case cboConvertCase.TextCase "Anything"SelectedControls(0).ConvertCase = _ CaseType.AllCase "Lower Case"SelectedControls(0).ConvertCase = _ CaseType.LowerCaseCase "Upper Case"SelectedControls(0).ConvertCase = _ CaseType.UpperCaseEnd Select
Here, we're just doing the reverse of the SelectionChanged event. We're analysing the content of the two Combo Box controls, then changing the user's control properties to reflect them.
Top Tip: There are numerous ways in which this code can be improved, but has been left as shown for simplicity. Can you think of a different method to set our properties other than via text comparison in a Select Case statement? Can you see where you could use the With keyword?
Go ahead; test your property page in the usual manner. Does it work? Notice how changes made in the Properties window are instantly reflected in your property page, and vice versa. That's all down to the mysterious 'PropertyChanged' command, keeping everyone up-to-date as to the latest happenings.
Phew! What a difficult section!
But it's time to celebrate congratulations on getting this far! You've successfully completed all the main components of the Super Cool Text Box control!
<Karl gives you a big kiss>
Note: A full download of the Super Cool Text Box control is available here.
Today, we've explained the mysterious property keywords that cropped up in last week's tutorial.
The three major methods we discovered were WriteProperty, ReadProperty and PropertyChanged. Each assist in keeping your control both stable and user-friendly.
In the last half of this tutorial, we looked at the advantages of Property Pages, their similarity with the UserControl concepts discussed in the first section, plus learned how you can implement them to enhance your control's usability.
We discovered that if you're after something more than just an alphabetical list, it's time to turn to the property page. And I'm not talking about the back cover of your local rag.
For a little homework, why not try to improve some of the code here? Add your own properties, along with the appropriate read and write code. You could even inject a little razzmatazz into the Property Page we finished building just a few minutes ago.
And guess what prizes we're offering for the best design? Well, somebody claimed the three-year old Mars bar last week, so we're down to... oh. Nothing, actually.
Next week, we'll conclude the tutorial with instructions on how you can package your control for the end user. I'll also be providing a host of free, commercial-quality examples (with full source code!) plus a handful of top tips to ensure your control stands out above the crowd.
But until next time, this is your host Karl Moore waving you all a goodnight for tonight. Goodnight!