Step 1 of 2) and Click The Next Button
Step 1 of 2) and Click The Next Button
This article is for those enthusiastic VC++ developers who want to build their own ActiveX controls but
don’t know where to start with. This article will take you to build your first ActiveX control. This article will
show you the step by step method to build a control which draws different waveforms (Sin/Cos).I assume
that you are familiar with VC++ and know some basics of ActiveX control.
o Appearance
o BackColor
o ForeColor
o BorderStyle
o Font
o Caption
o Enable
o Text
o hWnd
From these, we will work with the Appearance, BackColor, ForeColor and BorderStyle properties.
Finally, ClassWizard will also add the code to invalidate the control whenever the value of
the BackColor property changes, thereby forcing the OnDraw function to redraw the
control. The only thing you have to do is to use the color contained in the property to
paint the background color of control.
7. At this point, add the following two (2) lines of code to the end of the CPlotCtrl::OnDraw
member function
8. CBrush bkBrush(TranslateColor(GetBackColor()));
9. pdc->FillRect(rcBounds,&bkBrush);
10. Now, let's add a Color property page to the program. This page will allow users of the
control to change the BackColor our newly added property property at design-time. To
do this, simply open the PlotCtrl.cpp file and locate the // Property pages comment.
BEGIN_PROPPAGEIDS(CPlotCtrl, 1)
PROPPAGEID(CPlotPropPage::guid)
END_PROPPAGEIDS(CPlotCtrl)
The first line tells the compiler how many pages exist. Notice that it's set to 1. Change
this value to 2 as we're going to add a new page.
Now insert the following line just before the END_PROPPAGEIDS line (The
CLSID_CColorPropPage is defined automatically since this is a property page CLSID for a
stock property).
PROPPAGEID(CLSID_CColorPropPage)
Once you've finished, your new property page id map should look like the following:
BEGIN_PROPPAGEIDS(CPlotCtrl, 2)
PROPPAGEID(CPlotPropPage::guid)
PROPPAGEID(CLSID_CColorPropPage)
END_PROPPAGEIDS(CPlotCtrl)
Once you make the above changes,the stock property page is automatically linked to the
BackColor property.
11. Now that you've seen how to add the BackColor stock property to an ActiveX control,
follow these same steps in order to add the Appearance, ForeColor and BorderStyle
properties. Note that you do not need to add a property page for the other
properties.
12. After adding these stock properties, build your control and test it using ActiveX Test
Container (which is usually found under the Tools menu. As you can see in the figure
below, the ClassWizard has added the appropriate controls for changing the stock
properties.
3. Adding Custom Properties
Custom properties are properties you devise yourself for your control. For the plot
control I have added only four custom properties "Grid On/Off" and "X-Log". The grid
properties will control the visibility of the control grid. The "x-log" property will be used
to plot the horizontal axis in the logarithmic scale. Let's start with the grid properties.
void CPlotCtrl::OnShowGridChanged()
{
InvalidateControl();
SetModifiedFlag();
}
27. Since ShowGrid is a custom property we have to do little more work to have it included
on the property page. To do this, open the Resource View tab and open the Property
page dialog (IDD_PROPPAGE_PLOT).
28. Using the dialog editor add a check box with the ID IDC_CHECK1 and text value of
"Show Grid".
29. Run the ClassWizard, select the Member Variables tab
30. Add a member variable for the control id IDC_CHECK1 called m_bShowGrid of type
BOOL. Make sure that you set the Optional property name to ShowGrid. When you're
finished, the dialog should look like the figure below.
31. Now, add the custom property "X-Log" where the following values are shown in the
following figure.
32. Add the following line of code to the CPlotCtrl::DoPropExchange member function:
33. PX_Bool(pPX,_T("ShowGrid"),m_xLog,FALSE);
34. Just as you did with the ShowGrid property, add a checkbox to the control's property
page for the X-Log property and then add a member variable for it as well.
35. Insert the following code (marked in bold) in the CPlotCtrl::OnXLogChanged member
function
41. To implement these properties, add the following members to the control:
42. private:
43. CRect wndRect,m_DrawRect;
44. CDC* m_pDC;
45.
46. //Function to Intialize DC and mapping mode
47. void PrepareForPlotting(CRect rect);
48. void DrawGrid();
49. Modify the CPlotCtrl::OnDraw member function as follows. (Note: you will need to
include the math.h file due to the use of the sin function)
81. Add the following code for the DrawGrid function that you declared.
82.
83. void CPlotCtrl::DrawGrid()
84. {
85. CPen Pen (PS_SOLID|
PS_INSIDEFRAME,1,TranslateColor(GetForeColor()));
86. CPen* oldPen = m_pDC->SelectObject (&Pen);
87.
88. switch(m_xLog)
89. {
90. case FALSE:
91. int i;
92.
93. for (i = m_DrawRect.left;
94. i <= m_DrawRect.right ;
95. i = i+(( m_DrawRect.right - m_DrawRect.left )/10))
96. {
97. m_pDC->MoveTo (i, m_DrawRect.top );
98. m_pDC->LineTo (i, m_DrawRect.bottom );
99. }
100.
101. for (i = m_DrawRect.top;
102. i <= m_DrawRect.bottom;
103. i = i+ (( m_DrawRect.bottom - m_DrawRect.top )/8))
104. {
105. m_pDC->MoveTo (m_DrawRect.left,i );
106. m_pDC->LineTo (m_DrawRect.right,i);
107. }
108. break;
109.
110. case TRUE:
111. int x,X;
112.
113. for(int j=1;j<= 10;j++)
114. {
115. x= (int)(log10(j)*285.7143);
116. m_pDC->MoveTo (x,m_DrawRect.top);
117. m_pDC->LineTo (x,m_DrawRect.bottom );
118. }
119.
120. X= x;
121.
122. m_pDC->SelectObject(&Pen);
123. m_pDC->MoveTo (x,m_DrawRect.top);
124. m_pDC->LineTo (x,m_DrawRect.bottom );
125. m_pDC->SelectObject (&Pen);
126. m_pDC->TextOut (x,m_DrawRect.bottom-5,"10.0K");
127.
128. for( j=1;j<= 10;j++)
129. {
130. x= X+(int)(log10(j)*285.7143);
131. m_pDC->MoveTo (x,m_DrawRect.top);
132. m_pDC->LineTo (x,m_DrawRect.bottom );
133. }
134.
135. X= x;
136.
137. m_pDC->SelectObject(&Pen);
138. m_pDC->MoveTo (x,m_DrawRect.top);
139. m_pDC->LineTo (x,m_DrawRect.bottom );
140. m_pDC->SelectObject (&Pen);
141. m_pDC->TextOut (x,m_DrawRect.bottom-5,"100.0K");
142.
143. for( j=1;j<= 10;j++)
144. {
145. x= X+(int)(log10(j)*285.7143);
146. m_pDC->MoveTo (x,m_DrawRect.top);
147. m_pDC->LineTo (x,m_DrawRect.bottom );
148. }
149.
150. X= x;
151.
152. m_pDC->SelectObject(&Pen);
153. m_pDC->MoveTo (x,m_DrawRect.top);
154. m_pDC->LineTo (x,m_DrawRect.bottom );
155. m_pDC->SelectObject (&Pen);
156. m_pDC->TextOut (x,m_DrawRect.bottom-5,"1.0M");
157.
158. for( j=1;j<= 10;j++)
159. {
160. x= X+(int)(log10(j)*285.7143);
161. m_pDC->MoveTo (x,m_DrawRect.top);
162. m_pDC->LineTo (x,m_DrawRect.bottom );
163. }
164.
165. X= x;
166.
167. m_pDC->SelectObject(&Pen);
168. m_pDC->MoveTo (x,m_DrawRect.top);
169. m_pDC->LineTo (x,m_DrawRect.bottom );
170. m_pDC->SelectObject (&Pen);
171. m_pDC->TextOut (x,m_DrawRect.bottom-5,"10.0M");
172.
173. for( j=1;j<= 10;j++)
174. {
175. x= X+(int)(log10(j)*285.7143);
176. m_pDC->MoveTo (x,m_DrawRect.top);
177. m_pDC->LineTo (x,m_DrawRect.bottom );
178. }
179.
180. X= x;
181.
182. m_pDC->SelectObject(&Pen);
183. m_pDC->MoveTo (x,m_DrawRect.top);
184. m_pDC->LineTo (x,m_DrawRect.bottom );
185. m_pDC->SelectObject (&Pen);
186. m_pDC->TextOut (x,m_DrawRect.bottom-5,"10.0M");
187.
188. for( j=1;j<= 10;j++)
189. {
190. x= X+(int)(log10(j)*285.7143);
191. m_pDC->MoveTo (x,m_DrawRect.top);
192. m_pDC->LineTo (x,m_DrawRect.bottom );
193. }
194.
195. X= x;
196.
197. m_pDC->SelectObject(&Pen);
198. m_pDC->MoveTo (x,m_DrawRect.top);
199. m_pDC->LineTo (x,m_DrawRect.bottom );
200. m_pDC->SelectObject (&Pen);
201. m_pDC->TextOut (x,m_DrawRect.bottom-5,"10.0M");
202.
203. for( j=1;j<= 10;j++)
204. {
205. x= X+(int)(log10(j)*285.7143);
206. m_pDC->MoveTo (x,m_DrawRect.top);
207. m_pDC->LineTo (x,m_DrawRect.bottom );
208. }
209.
210. break;
211. }
212.}
213.Add the following code for the PrepareForPlotting function that you declared.
At this point, you have completed your control and should be able to build and test it using the ActiveX
Test Container The following figure shows an example of the control being tested.
ACTIVEX……….
Good Morning and welcome to the first instalment in this ActiveX control tutorial.
I'm your amazingly geeky host Karl Moore — and it's my job to ensure your ride on
the Visual Basic train to ActiveX control land is an exciting one. Well, maybe just a
slight-amusing one. Hmm, perhaps just a ride.
But don't let my pair of glass-bottle-bottom spectacles fool you - this isn't just a
journey for mega-geeks.
Whatever Visual Basic programming experience you may have, learning about the
wonderful world of ActiveX could improve your career, your bank balance and your
love life*.
* (Love life claims based solely on life-long research by author Karl Moore and his numerous worldwide cyber-
girlfriends)
So without further ado, let's tootle off into the magical realms of ActiveX...
Bonjour and welcome to the second part in this jabber-wockingly cool ActiveX
tutorial.
As ever, I'm your surprisingly sophisticated host Karl Moore — and this week we'll be
covering:
If you missed part one, it's probably a good idea to check it out before reading on -
click here!
I can see you're getting all giddy now, so let's don our anoraks and thick glasses as
we hop on the pink boat destined for the fluffy world of ActiveX controls...
Guten Morgan and welcome to the third part of this surprisingly super ActiveX control
tutorial.
If you've missed the previous two instalments, be sure to check out part one here
and part two here.
As ever, I'm your host Karl Moore and this week we'll continue to chip away at the
Super Cool Text Box project we started a couple of weeks ago.
We'll be:
So mount the stallion they call VeeBee — it's time to ride off into the glorious, 24-bit
pixelised Microsoft sunset...
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:
As ever, I'm your wizzy host Karl Moore and this week, we'll be:
So grab that copy of Visual Basic and let's fly off into the programmatic world of
ActiveX controls...
Ciao and welcome to the fifth and final instalment of our ActiveX control tutorial.
As ever, I'm your host Karl Moore, and if you've missed any of the previous slots,
check them out here:
So grab your poisoned-nib arrow and SAS survival handbook - and let's finish the
adventure of a lifetime. Alternatively, sit back and enjoy the tutorial.
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?
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'.
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.
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?
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.
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)
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:
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.
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:
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:
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:
PropertyChanged "ConvertCase"
...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:
<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>
Ahem.
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.
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:
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:
Top Tip: Our project will only have one property page, General. But you could have
many more as the above screenshot demonstrates!
• Click Next
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.
Open up the property page our Wizard created. It should look something like this:
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:
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:
SelectedControls(0).PropertyName
For example:
SelectedControls(0).PasswordChar
... 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.
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.
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
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).
With cboConvertCase
.AddItem ("Anything")
.AddItem ("Upper Case")
.AddItem ("Lower Case")
End With
End Sub
Try testing your project now. When you open the property page, are you able to
select an item from the list?
• In the Click event behind each Combo Box, add the code:
Changed=True
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.
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.
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.
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!
Well done!
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.
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!