HOWTO Lesson 3 :
Creating .OCX's
(NOTE : This lesson
assumes that you've already read and understood Lesson 1 and 2)
Copyright© 2001-2002 by Kevin Wilson
This page outlines the steps to take to create an "OCX" file (commonly called an ActiveX Control, or COM Control), and explains the concepts behind each step.
An ActiveX control is a COM object similar to a Standard VB Class Module or ActiveX DLL with properties, methods, and events. The difference between an ActiveX control and an ActiveX DLL is that an ActiveX control has a user interface and requires you to put it on something (like a Form, MDI Form, Property Page, User Document, or User Control) so the user interface can be displayed.
A CommandButton, ListBox, PictureBox, Label, Timer, etc., are all ActiveX controls as well, only they are built into Visual Basic as native controls. ActiveX Controls are the building blocks of applications, just as native types (like String, Integer, and Long) are the building blocks of custom variable Types.
When you create an ActiveX control (OCX file), you are creating a TOOL. This tool can be a custom control to do something that no standard control can do, or it can be a control that works exactly like a standard control works, but with slightly changed functionality, properties, events, and/or methods. It can be whatever you want.
Let's say for example that you would like to create a PictureBox that loads and saves BMP, JPEG, and GIF pictures, as well as adds special effects to them when they are loaded. No standard Visual Basic control can do this custom work. You can get this functionality by putting in a HUGE amount of custom coding into your application, or by using my Advanced Bitmap Processing module (along with the JPEG module posted on this site), or something like that. But why bother if the thing you're displaying the picture on (most often a PictureBox) can do that kind of work for you? Creating a custom PictureBox that does this kind of custom functionality would take that kind of work off of your main application, and allow your code in your main application to be cleaner and simpler.
Another possible use for this tool would be to create a control that does things for you in order to take processing and code off your main application. For example, you could create an ActiveX control that contains several smaller controls like a TreeView on the left, and a ListBox on the right which resizes them dynamically when the ActiveX control is resized. It could also contain data validation for information being passed in to be displayed in the TreeView or ListBox. This takes a LARGE amount of code off of our main application so that your main application's code is cleaner and simpler.
Another advantage to using ActiveX controls over just coding the functionality into your main application is the ActiveX control runs in its own thread. If you are handling some intense processing, graphics work, database connectivity, data transferring, or sub-classing within your ActiveX Control and something goes wrong, the ActiveX will freeze instead of your entire application freezing up. The ActiveX control DOES get loaded into the same memory space as your main application though, so it is possible for something to go wrong with your ActiveX control that takes down the rest of your program, but that is VERY unlikely.
Also, because ActiveX controls run in their own threads, you can create a "multi-threaded" application by embedding functionality into an ActiveX control and having them do your application's work. For example, you could create a "Download Control" that simply goes to the FTP site you specify and downloads the file you specify to a destination file on your hard drive that you specify. All you'd have to do to download multiple files simultaneously is create a control array (or several instances of that ActiveX control) on a form, set each one to download a different file to a different destination, and cut 'em all loose! BOOM! Multi-threading, baby! Multiple downloads that are independent of each other.
But ActiveX controls are not just for use within Visual Basic, C++, Delphi, and other "COM aware" programming languages. You can also use ActiveX controls over the internet! It is possible to embed an ActiveX control right into a web page and then view it with Microsoft Internet Explorer 4.x or better.
"But how do I embed an ActiveX control into my HTML or ASP web page?"
First of all, I need to be clear that ActiveX is a Microsoft technology and Netscape does not natively support it. There are Netscape Plug-Ins that will allow you to use them within Netscape, but they obviously don't work as well as Microsoft Internet Explorer (which is designed to natively support them).
Now, let's take the "CustomButton" (CustmBtn.ocx) ActiveX control that I posted on this web site. If you wanted to embed that into a web page, the HTML code would look something like this:
<HTML> <HEAD> <TITLE>TestOCX</TITLE> <SCRIPT LANGUAGE="JavaScript"> // This function changes the caption to "Hello" and displays a MessageBox via JavaScript function ChangeCaption() { TestForm.CustomBtn.Caption = "Hello"; alert ("Click OK when ready to change the caption again"); } </SCRIPT> <SCRIPT LANGUAGE="VbScript"> ' This function changes the caption to "Whasssup!" and displays a MessageBox via VbScript Sub ChangeBackColor TestForm.CustomBtn.Caption = "Whasssup!" MsgBox "Caption was changed",vbOKOnly Or vbExclamation, " " End Sub </SCRIPT> <SCRIPT LANGUAGE="JavaScript" FOR="CustomBtn" EVENT="Click"> // This event fires when the ActiveX control is clicked and displays the About dialog TestForm.CustomBtn.About(); </SCRIPT> </HEAD> <BODY BGCOLOR="#FFFFFF" TEXT="#000000"> <!-- The ActiveX control has to be within a form in order for it to be called via code //--> <FORM ID="TestForm"> <OBJECT ID="CustomBtn" CLASSID="CLSID:BB7BA40E-784E-11D4-AF87-0008C74B19A1"> <PARAM NAME="_ExtentX" VALUE="2249"> <PARAM NAME="_ExtentY" VALUE="714"> <PARAM NAME="Appearance" VALUE="0"> <PARAM NAME="BackColor" VALUE="12632256"> <PARAM NAME="Caption" VALUE="Testing"> <PARAM NAME="Enabled" VALUE="-1"> <PARAM NAME="ForeColor" VALUE="12632319"> <PARAM NAME="Style" VALUE="0"> </OBJECT> </FORM> <SCRIPT LANGUAGE="JavaScript"> // Change the caption the first time via JavaScript ChangeCaption(); </SCRIPT> <SCRIPT LANGUAGE="VbScript"> ' Change the caption the second time via VbScript Call ChangeBackColor </SCRIPT> </BODY> </HTML> |
You'll notice a few things about this code. First is that in order to use the ActiveX control within your page, it has to be embedded as an object. Not only does it have to be embedded as an object, but it has to be embedded with an "ID" and within a form that also has an "ID". The form it's embedded within doesn't have to do anything at all, but it has to be there, and it has to have an ID. The reason for this is the embedded object can't stand alone in HTML just as a PictureBox control can't stand alone within Visual Basic without a Form, MDI Form, PropertyPage, UserControl, or UserDocument. It has to be a part of a form within VB or HTML. That is how the object is referenced in both cases... by <FORM>.<CONTROL> where in this case, the form is "TestForm" and the control is "CustomBtn". You'll notice that it is referenced in VbScript and JavaScript as such. Again, VbScript is a Microsoft technology and isn't natively supported by Netscape (though I wish it was).
The second thing you may notice is that the ActiveX control's properties are initially set using the object's "PARAM" values, and later at run-time by referencing the <FORM>.<CONTROL>.<PROPERTY>. That's easy enough, but what about Methods and Events? Well, methods are called like functions from VbScript or JavaScript similar to properties... <FORM>.<CONTROL>.<METHOD>. As far as events, those are a little bit more complex, but still fairly simple. All you do is declare a JavaScript <SCRIPT> block and tack on "FOR" pointing to the name of the object that the event is for, and "EVENT" with the name of the event that the script block will be fired for represents. In this case, the CLICK event is the one handled, and it calls the "About" method which shows the control's about message.
The third thing you may notice is that the ActiveX control is referenced not by location, but by "CLSID" (Class Identifier). The CLSID is also referred to as the "GUID" (Globally Unique Identifier) or "UUID" (Universally Unique Identifier).
"But how will I know what the CLSID of my ActiveX control is?"
You'll be able to find out by looking in your Windows Registry via a tool called "Registry Editor" (REGEDIT.EXE). Go to START > RUN and type "REGEDIT" and hit ENTER. First locate the name of the interface you wish to reference by going to the HKEY_CLASSES_ROOT section and looking for the name of the project the class module interface you wish to reference is located. This <PROJECT>.<CLASS> combination is refered to as the "ProgID" or "Program Identifier" and is used in calls to Server.CreateObject() in ASP, and CreateObject() in VB. In this case, it's Custom_Button.CustomButton. Under this registry key, you'll notice a registry key called "CLSID". The default value for this key is the "CLSID" that you use to reference your ActiveX control as I did in the above code.
If you want to take this a step further and find out the location of the actual OCX or DLL file that is referenced by the CLSID, you can do that by going to the "CLSID" section under the HKEY_CLASSES_ROOT section and finding your CLSID... in this case {BB7BA40E-784E-11D4-AF87-0008C74B19A1}. Once you find it, you'll notice there are several entries under it. One of them is "InprocServer32". The default value of this registry key will be the physical location of the file that is your ActiveX control. In this case, CustmBtn.ocx.
"But how do I get my ActiveX information into the registry so I can reference it?"
Well, when you compile an ActiveX Control, ActiveX DLL, or ActiveX EXE within Visual Basic, it automatically registers it on your machine and puts this information into the registry for you. However, if you wish to register it on someone else's computer so they can use it, you have to do one of two things:
1) Run REGSVR32.EXE and point it at your ActiveX control to register and unregister it. Do so looks like this:
C:\WINDOWS\SYSTEM\REGSVR32.EXE C:\WINDOWS\SYSTEM\CUSTMBTN.OCX
... or ...
C:\WINDOWS\SYSTEM\REGSVR32.EXE /U C:\WINDOWS\SYSTEM\CUSTMBTN.OCX
If you run REGSVR32.EXE with no parameters and without pointing it at a file, it will show you a dialog on how to use it:
2) Use the Win32 API to call the "DllRegisterServer" and "DllUnregisterServer" exported functions of the COM object. You can get code on how to do this by downloading the modREGSVR32.bas standard VB module that I wrote, or like this:
Private Declare Function CallWindowProc Lib "USER32" Alias "CallWindowProcA" (ByVal lpPrevWndFunc As Long, ByVal hWnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long Private Declare Function FreeLibrary Lib "KERNEL32" (ByVal hLibrary As Long) As Long Private Declare Function GetProcAddress Lib "KERNEL32" (ByVal hLibrary As Long, ByVal strFunctionName As String) As Long Private Declare Function LoadLibrary Lib "KERNEL32" Alias "LoadLibraryA" (ByVal strFileName As String) As Long |
An ActiveX control is just like a VB Class module or ActiveX DLL in that you have a programmatic interface that has Properties, Methods, and Events. However, an ActiveX control is different from an ActiveX DLL or VB Class modules in that it has to use a user interface called a "UserControl" and is required to be placed on a form object such as a Form, MDI Form, UserDocument, Property Page, or UserControl (or container within one of the aforementioned).
Another difference is that ActiveX controls are meant to have the ability to be destroyed and re-created, but always maintain their property settings. For example, if in design-time within Visual Basic you put an instance of the "CustomButton" on a Form, then you set the caption property to "Hello" and close the form. The form at this point has been destroyed and the CustomButton along with it. But when you re-open that form, the CustomButton is there again with the caption set to "Hello" instead of the default "CustomButton". The way this is done is through the use of a VB object called a "PropertyBag". PropertyBag objects can store property values and hold them while the ActiveX control they are assigned to is unloaded. By their nature, methods and events do not need to be saved, but you can save your ActiveX control's properties by making use of the UserControl's "ReadProperties" and "WriteProperties" events like this:
Private Sub UserControl_ReadProperties(PropBag As PropertyBag) With PropBag Set Me.Picture = .ReadProperty("Picture", Nothing) Me.Appearance = CLng(.ReadProperty("Appearance", defAppearance)) Me.BackColor = CLng(.ReadProperty("BackColor", defBackColor)) Me.Caption = CStr(.ReadProperty("Caption", defCaption)) Me.Enabled = CBool(.ReadProperty("Enabled", defEnabled)) Me.Font.Name = CStr(.ReadProperty("FontName", defFontName)) Me.Font.Size = CCur(.ReadProperty("FontSize", defFontSize)) Me.Font.Bold = CBool(.ReadProperty("FontBold", defFontBold)) Me.Font.Italic = CBool(.ReadProperty("FontItalic", defFontItalic)) Me.Font.Underline = CBool(.ReadProperty("FontUnderline", defFontUnderline)) Me.Font.Strikethrough = CBool(.ReadProperty("FontStrikeThru", defFontStrikeThru)) Me.ForeColor = CLng(.ReadProperty("ForeColor", defForeColor)) Me.Style = CLng(.ReadProperty("Style", defStyle)) End With End Sub |
Note that I convert everything here to either a string or an object while storing information to the property bag, then convert back to the original variable type when retrieving information from the property bag. This is because I've never had good luck trying to store other variable types to a PropertyBag object. The MSDN documentation also makes reference to only using String or Object values types within the PropertyBag object.
One more thing to note is when you change a property, you want to let the ActiveX control know that it's been changed. To do this, simply call the "PropertyChanged" VB function passing the name of the property that changed, like this:
Public Property Get Picture() As
Picture Set Picture = cbPicture End Property |
Now that we've discussed what an ActiveX control is, what it does, and a little about how to use it, lets actually create one! As I mentioned previously, an ActiveX control can be a control that can do whatever you program it to do, and can thereafter be placed on a Form, MDI Form, Property Page, UserControl, or UserDocument (or any container placed on any of the aforementioned). So, let's create a control that when you put it on a form, it displays the current time (according to your computer's set time). We'll call it "TimeCtrl".
First we start up Visual Basic and start a new ActiveX Control project:
Once we have started our ActiveX Control, we have to give it a name, and create an Icon for it. All programs have to have a name to make them unique, describe what they do (and draw attention or curiosity to them if it's a good name). So we select the UserControl from the "Project" window which is named "UserControl1" by default, and change it's name to "TimeControl". Lets name the project "TimeCtrl". Save the program to a place on your hard drive where you'll remember it (like "C:\Projects\TimeCtrl" or something like that).
Also, all good programs (and controls) start with a good icon. So create an Icon for your control using any icon editor. You can get a copy of Microsoft's Image Editor from www.microsoft.com or by clicking HERE. You can also go to www.Download.com and download any of the several icon editors available there. My personal favorite is Microangelo by Impact Software.
Now that you've named your program, and created an icon, you've got to create one more icon to be used within the VB IDE. Take the icon that you made and create a 16x15 pixel BITMAP and save it to your project folder. Click on your UserControl which we named "TimeControl" and go to the "ToolboxBitmap" property. Click the button in that property and browse for the 16x15 bitmap icon you just created. Once you find it and set that property to it, close the UserControl screen and you'll notice that the icon you just created is now in the Toolbox area to along with all the other controls (like CommandButton, ComboBox, ScrollBar, etc). You'll also notice that Visual Basic takes the very top left pixel [ 1,1 ] and uses that as the transparent color for the icon. This is a standard Windows behavior when working with transparent images. |
Next lets size it down to the default size of the TimeControl that we want to appear on people's forms when it's placed. The default size is 4800x3600 twips. Ever seen a CommandButton control get placed on your form THAT big by default? Since we're going to be working with measurements of text and objects, lets change the ScaleWidth of the UserControl object to "3 - Pixel". That way we can use the "ScaleWidth" and "ScaleHeight" properties to work in pixels instead of the default TWIPS (which are pixels multiplied by 15). Set the size of the control to something like 1605x405 twips (107x27 pixels).
Next, lets set a few properties for the UserControl to establish how our ActiveX control will look and feel when in use:
|
Now it's time to start adding FUNCTIONALITY to our control... to make it actually DO something. The best way to go about this is to imagine yourself as the user of this ActiveX control within your Visual Basic project. Try to imagine what would be handy, what features you'd like to see in such a control then make it happen! For my control, I'd like the ability to change the text color, change the background color, change the text font, and change the display format. I'd also like the ability to make the Time Control transparent.
First add a "Label" control to the UserControl of your ActiveX control. Name it "lblDisplay". Then add a Timer control to the UserControl and leave it's name the default "Timer1", then set the "Interval" property to 500. Set the caption of the Label control to "12:00:00 AM".
Now lets create some properties, and put some code behind them to make things work:
Option Explicit Public Enum TimeFormats tf_None = 0 tf_HM_AMPM = 1 tf_HHMM_AMPM = 2 tf_HMS_AMPM tf_HHMMSS_AMPM tf_HM tf_HHMM tf_HMS tf_HHMMSS End Enum Public Enum DateFormats df_None = 0 df_MMDDYY = 1 df_DDMMYY = 2 df_MMDDYYYY df_DDMMYYYY df_YYMMDD df_YYDDMM df_YYYYMMDD df_YYYYDDMM End Enum Private p_TimeFormat As TimeFormats Private p_DateFormat As DateFormats Private p_FontName As String Private p_FontSize As Currency Private p_FontBold As Boolean Private p_FontItalic As Boolean Private p_FontUnderline As Boolean Private p_FontStrikeThru As Boolean Private p_ForeColor As Long Private p_BackColor As Long Private p_Transparent As Boolean Private p_Enabled As Boolean Private p_Blink As Boolean Private p_Paused As Boolean 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX |
You'll notice a few things about this code. First off, notice the use of the "UserControl" when referring to the ActiveX control (which is acting as the "Form" for our project). Also, notice that I use the "PropertyChanged" call whenever a Let or Set property is called as described above. Also, notice that I've stored all the property values in variables that start with "p_". This is to make them easy to find and easy to distinguish from other variables.
This code the way it is will give us a working ActiveX control, complete with properties and a working interface. We could at this point also declare some public functions or subs which would be methods of the ActiveX control. However, the ActiveX control's properties are NOT saved and reloaded at this point if you close the form you place it on and bring it back up. This is because we haven't included the code to do so. Again, this is done using the "ReadProperties" and "WriteProperties" events of the UserControl object like this:
Private Sub
UserControl_ReadProperties(PropBag As PropertyBag) With PropBag Me.BackColor = CLng(.ReadProperty("BackColor", p_BackColor)) Me.Blink = CBool(.ReadProperty("Blink", p_Blink)) Me.DateFormat = CLng(.ReadProperty("DateFormat", p_DateFormat)) Me.Enabled = CBool(.ReadProperty("Enabled", p_Enabled)) lblDisplay.Font.Name = CStr(.ReadProperty("FontName", p_FontName)) lblDisplay.Font.Size = CCur(.ReadProperty("FontSize", p_FontSize)) lblDisplay.Font.Bold = CBool(.ReadProperty("FontBold", p_FontBold)) lblDisplay.Font.Italic = CBool(.ReadProperty("FontItalic", p_FontItalic)) lblDisplay.Font.Underline = CBool(.ReadProperty("FontUnderline", p_FontUnderline)) lblDisplay.Font.Strikethrough = CBool(.ReadProperty("FontStrikeThru", p_FontStrikeThru)) Me.ForeColor = CLng(.ReadProperty("ForeColor", p_ForeColor)) Me.Paused = CBool(.ReadProperty("Paused", p_Paused)) Me.TimeFormat = CLng(.ReadProperty("TimeFormat", p_TimeFormat)) Me.Transparent = CBool(.ReadProperty("Transparent", p_Transparent)) End With End Sub Private Sub UserControl_WriteProperties(PropBag As PropertyBag) With PropBag .WriteProperty "BackColor", CStr(p_BackColor) .WriteProperty "Blink", CStr(p_Blink) .WriteProperty "DateFormat", CStr(p_DateFormat) .WriteProperty "Enabled", CStr(p_Enabled) .WriteProperty "FontName", CStr(p_FontName) .WriteProperty "FontSize", CStr(p_FontSize) .WriteProperty "FontBold", CStr(p_FontBold) .WriteProperty "FontItalic", CStr(p_FontItalic) .WriteProperty "FontUnderline", CStr(p_FontUnderline) .WriteProperty "FontStrikeThru", CStr(p_FontStrikeThru) .WriteProperty "ForeColor", CStr(p_ForeColor) .WriteProperty "Paused", CStr(p_Paused) .WriteProperty "TimeFormat", CStr(p_TimeFormat) .WriteProperty "Transparent", CStr(p_Transparent) End With End Sub |
Notice in the above code that all values are saved (written) as STRING values, then converted back to their original variable types when they are retrieved (read). Also notice that I refer to the ActiveX control's custom properties by making use of the "Me" object. Also, the "Initialize" event of the UserControl is called BEFORE the "ReadProperties" event. This allows us to set the default values in the "Initialize" event and use those default values in the "ReadProperties" event.
Lets add a few events to our ActiveX control to allow for easy use and interaction with it on the end user side. To do this, we need to declare the events like this:
Public Event
Change(ByVal strCurrentTime As String) Public Event Click() Public Event DblClick() Public Event MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single) Public Event MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single) Public Event MouseUp(Button As Integer, Shift As Integer, X As Single, Y As Single) Public Event OLECompleteDrag(Effect As Long) Public Event OLEDragDrop(Data As Object, Effect As Long, Button As Integer, Shift As Integer, X As Single, Y As Single) Public Event OLEDragOver(Data As Object, Effect As Long, Button As Integer, Shift As Integer, X As Single, Y As Single, State As Integer) Public Event OLEGiveFeedback(Effect As Long, DefaultCursors As Boolean) Public Event OLESetData(Data As Object, DataFormat As Integer) Public Event OLEStartDrag(Data As Object, AllowedEffects As Long) |
Now that we've declared the events, we have to call (or raise) them through code. We do it for the UserControl like this:
Private Sub
UserControl_Click() RaiseEvent Click End Sub Private Sub UserControl_DblClick() RaiseEvent DblClick End Sub Private Sub UserControl_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single) RaiseEvent MouseDown(Button, Shift, X, Y) End Sub Private Sub UserControl_MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single) RaiseEvent MouseMove(Button, Shift, X, Y) End Sub Private Sub UserControl_MouseUp(Button As Integer, Shift As Integer, X As Single, Y As Single) RaiseEvent MouseUp(Button, Shift, X, Y) End Sub Private Sub UserControl_OLECompleteDrag(Effect As Long) RaiseEvent OLECompleteDrag(Effect) End Sub Private Sub UserControl_OLEDragDrop(Data As DataObject, Effect As Long, Button As Integer, Shift As Integer, X As Single, Y As Single) RaiseEvent OLEDragDrop(Data, Effect, Button, Shift, X, Y) End Sub Private Sub UserControl_OLEDragOver(Data As DataObject, Effect As Long, Button As Integer, Shift As Integer, X As Single, Y As Single, State As Integer) RaiseEvent OLEDragOver(Data, Effect, Button, Shift, X, Y, State) End Sub Private Sub UserControl_OLEGiveFeedback(Effect As Long, DefaultCursors As Boolean) RaiseEvent OLEGiveFeedback(Effect, DefaultCursors) End Sub Private Sub UserControl_OLESetData(Data As DataObject, DataFormat As Integer) RaiseEvent OLESetData(Data, DataFormat) End Sub Private Sub UserControl_OLEStartDrag(Data As DataObject, AllowedEffects As Long) RaiseEvent OLEStartDrag(Data, AllowedEffects) End Sub |
We do it for the Label control like this:
Private Sub
lblDisplay_Click() RaiseEvent Click End Sub Private Sub lblDisplay_DblClick() RaiseEvent DblClick End Sub Private Sub lblDisplay_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single) RaiseEvent MouseDown(Button, Shift, X, Y) End Sub Private Sub lblDisplay_MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single) RaiseEvent MouseMove(Button, Shift, X, Y) End Sub Private Sub lblDisplay_MouseUp(Button As Integer, Shift As Integer, X As Single, Y As Single) RaiseEvent MouseUp(Button, Shift, X, Y) End Sub Private Sub lblDisplay_OLECompleteDrag(Effect As Long) RaiseEvent OLECompleteDrag(Effect) End Sub Private Sub lblDisplay_OLEDragDrop(Data As DataObject, Effect As Long, Button As Integer, Shift As Integer, X As Single, Y As Single) RaiseEvent OLEDragDrop(Data, Effect, Button, Shift, X, Y) End Sub Private Sub lblDisplay_OLEDragOver(Data As DataObject, Effect As Long, Button As Integer, Shift As Integer, X As Single, Y As Single, State As Integer) RaiseEvent OLEDragOver(Data, Effect, Button, Shift, X, Y, State) End Sub Private Sub lblDisplay_OLEGiveFeedback(Effect As Long, DefaultCursors As Boolean) RaiseEvent OLEGiveFeedback(Effect, DefaultCursors) End Sub Private Sub lblDisplay_OLESetData(Data As DataObject, DataFormat As Integer) RaiseEvent OLESetData(Data, DataFormat) End Sub Private Sub lblDisplay_OLEStartDrag(Data As DataObject, AllowedEffects As Long) RaiseEvent OLEStartDrag(Data, AllowedEffects) End Sub |
Notice that all we're doing is forwarding the native events of the objects on the UserControl to our ActiveX control's interface.
The only event that isn't a standard one is the "Change" event. We raise that one in the "RenderDisplay" function towards the end of the function.
OKAY! At this point, we are lookin' really good, but we're not done yet. Now we need to add code and control settings that will make our ActiveX control a "professional" grade control, instead of an "I'm just learning VB" grade control.
First, lets add an "About" box similar to what most good ActiveX controls have. Add a standard Form to your project and lay it out similar to this:
For this form, all you have to do is add this simple code behind the scenes:
Private Sub
cmdOK_Click() Unload Me End Sub Private Sub Form_Load() lblVersion.Caption = "Version " & CStr(App.Major) & "." & CStr(App.Minor) & "." & CStr(App.Revision) End Sub |
Now add the following code to your ActiveX control to bring up the about box:
Public Function
ShowAbout() frmAbout.Show vbModal End Function |
Once we've
done this, lets go into the "Procedure Attributes" dialog by going to the
"Tools" menu and selecting "Procedure Attributes...". Once
there, select the "ShowAbout" method you just added to your ActiveX control and
select "AboutBox" from the "Procedure ID" drop-down under the
"Advanced" options. Doing this will put an additional property in the "Properties" window in the VB IDE when you select your ActiveX control that says "(About)" right above "(Name)". Clicking on the little box next to it with the ellipses (...) will display our About form we just created. |
While we are in the "Procedure Attributes" screen, let's also modify the other properties, methods, and events. Before we do that though, we should probably understand what each of the options here does. The following was taken from the MSDN on the Procedure Attributes dialog:
Name Description Project Help File Help Context ID OK Apply Advanced Procedure ID Note When you
set a property, method, or event as a standard type, it does not change the behavior of
the control. Caution If you
make a public property or method of a class the default member for that class, you cannot
edit the Use this Page in Property Browser Lists the Property Pages
that are in the current project so you can select one to act as a builder when the
property is Valid only for properties. Default is None. Property Category Lists available
categories to describe the selected property. You can select a standard category or type
in one of Some property browsers such as Visual Basic allow you to categorize control properties. If the control host does not support property categorization, this setting will be ignored. Valid only for properties. Default is None. Attributes
Data Binding
|
So for the following properties, methods, and events, we set the following options in the Procedure Attributes dialog:
|
At this point, we are pretty much DONE! Time to compile the OCX file and test it out!
Before we compile our ActiveX Control, we should setup it's properties. To do this, go to the "Project" menu and select "TimeCtrl Properties...". This will bring up the "Project Properties" dialog. Under the "General" tab, make sure to set the "Project Description" to something like "TimeControl - ActiveX Control", and check the "Upgrade ActiveX Controls".
The following is an important option, so I'll spend a little more time describing it. Check the "Require License Key" option if you want to make your ActiveX Control available for compiled programs (like ones you create using your ActiveX Control) but don't want anyone else to have the ability to use it to developer other applications with. Or if you want to require that developers pay for your ActiveX control before they can develop with it. This will create a ".VBL" file in the application's directory when you compile it (which is simply a ".REG" file with a different extension). If you change the extension to ".REG" and run it, it will install the license key required and allow developers on that computer to use your ActiveX control to develop other applications with. You can also programmatically insert the registry key contained within the ".VBL" file if you want to keep the registration process more secure.
Under the "Make" tab, set the version number by changing the "Major", "Minor", and "Revision" numbers. And if you want the "Revision" number to be incremented automatically every time you compile your ActiveX control, check the "Auto Increment" option. This is very useful in a professional environment where you're keeping backups of code, and need to roll-back your code to a certain version number at any given time. All version numbers are unique which makes finding the right version of the code easy. Change the "Title" to something like "TimeControl - ActiveX Control" and set the "Comments", "File Description", and "Product Name" to something similar. I usually keep all four of these the same. Put in "Company Name", "Legal Copyright", and "Legal Trademark" where applicable. I highly recommend that you check the "Remove information about unused ActiveX Controls" option. This will prevent unnecessary dependencies from showing up for your ActiveX Control.
At this point, click "OK" and select the "Make TimeCtrl.ocx" option from the "File" menu. In the "Make Project" dialog, select where you want to save the OCX file that is compiled. When you compile the OCX file, you'll notice it creates 3 files:
TimeCtrl.ocx
TimeCtrl.lib
TimeCtrl.exp
If you are developing this OCX for strict use within Visual Basic and ASP, then you can delete the "TimeCtrl.lib" and "TimeCtrl.exp" file. If you are going to be using this OCX file within C++, Delphi, or any other "COM aware" programming language, then you should probably keep the .LIB and .EXP files. The .EXP file is an EXPORT file, and the LIB file is the reference file used in C++, Dephi, etc to expose your COM object's interfaces.
Also, when you first use your OCX file within another program, you'll notice that an OCA file is automatically created in the same location as the OCX file. This is Visual Basic's version of a .LIB file. It is automatically created the first time the OCX file is referenced. You can delete this file it if shows up, because (like I said) it will be automatically recreated.
A side note... if you're in a "file deleting" kinda mood, you can also delete all .VBW files because those too are automatically recreated every time you close a Visual Basic Project (.VBP) file. DO NOT DELETE any .FRX (Form binary file), .CTX (UserControl binary file), .DOX (UserDocument binary file), or .PGX (PropertyPage binary file) file(s) that may be created. These contain the data that is on the Form, Control, UserDocument, and/or PropertyPage respectively and deleting them will screw them up.
USING YOUR OCX WITHIN OTHER PROJECTS:
Now we are ready to go ahead and use our OCX within another application.
For a web page or web application, refer to the above HTML code. For a VB application, lets go ahead and start up a standard EXE project as described in lesson 1 to test our OCX. Once we've started a new project, we need to include our ActiveX control as part of our project. To do so, select the "Project" > "Components..." menu option. This will display the Components dialog. Once in this dialog, locate our OCX by looking through the list of components for "TimeControl - ActiveX Control" or by clicking the "Browse" button and physically locating it. Once we have references our OCX, to use it we need to put it on our form. This is easy because once we reference the OCX file, the icon appears in our ToolBox and we can simply double-click on it to place it on our form. You can then resize it however you want and set whatever properties you want on it.
* NOTE - When the "Transparent" property is set to TRUE, you can't just click on the control to select it, you have to drag a selection box around it to select it. This is due to how Visual Basic handles transparent controls. This changes when you turn off transparency.
Now when you go into the code behind the form you just placed our OCX onto, you'll notice that the object "TimeControl1" (which is the default name for our OCX) is available. Selecting it gives you a listing of all the events that we defined in our ActiveX control. You can use them the same way you use the events for a CommandButton, PictureBox, or any other standard Visual Basic control.
The rest of the code is up to you! Use it however you feel is best. Like I said previously, ActiveX Controls are building blocks... tools for developers to create applications or web pages from.
ENJOY!
By the way, if you'd like a copy of the TimeControl we created during this lesson, you can download it by clicking HERE, or by going to the "Sample ActiveX Controls" section of this web page.
MAIN | DOWNLOADS | SAMPLE CODE | STEP BY STEP | DISCUSSION BOARD | LINKS | AUTHOR
E-MAIL