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



PURPOSE:

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.


WHAT IS AN ACTIVEX CONTROL?:

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.


WHY USE AN ACTIVEX CONTROL?:

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.

Screen Shot - Regedit

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.

Screen Shot - Regedit

"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:

Screen Shot - RegSvr32.exe

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
   

  
Public Function
RegisterCom(ByVal strFileName As String) As Boolean
  
  Dim
hLibrary As Long
  Dim
hFunction As Long
  
 
' Validate parameters
 
strFileName = Trim(strFileName)
  If
Dir(strFileName, vbArchive Or vbHidden Or vbNormal Or vbReadOnly Or vbSystem) = "" Then
    Exit Function
  End If
  If
Right(strFileName, 1) <> Chr(0) Then strFileName = strFileName & Chr(0)
  
 
' Load the COM object using the LoadLibrary function
 
hLibrary = LoadLibrary(strFileName)
  If
hLibrary = 0 Then Exit Function
  
 
' Get the handle to the function to call
 
hFunction = GetProcAddress(hLibrary, "DllRegisterServer" & Chr(0))
  If
hFunction = 0 Then GoTo CleanUp
  
 
' Call the function
  If
CallWindowProc(hFunction, 0, 0, 0, 0) = 0 Then RegisterCom= True
  
CleanUp:
  
  If
hLibrary <> 0 Then FreeLibrary hLibrary
  
End Function
  


  
Public Function UnregisterCom(ByVal strFileName As String) As Boolean
  
  Dim
hLibrary As Long
  Dim
hFunction As Long
  
 
' Validate parameters
 
strFileName = Trim(strFileName)
  If
Dir(strFileName, vbArchive Or vbHidden Or vbNormal Or vbReadOnly Or vbSystem) = "" Then
    Exit Function
  End If
  If
Right(strFileName, 1) <> Chr(0) Then strFileName = strFileName & Chr(0)
  
 
' Load the COM object using the LoadLibrary function
 
hLibrary = LoadLibrary(strFileName)
  If
hLibrary = 0 Then Exit Function
  
 
' Get the handle to the function to call
 
hFunction = GetProcAddress(hLibrary, "DllUnregisterServer" & Chr(0))
  If
hFunction = 0 Then GoTo CleanUp
  
 
' Call the function
  If
CallWindowProc(hFunction, 0, 0, 0, 0) = 0 Then UnregisterCom = True
  
CleanUp:
  
  If
hLibrary <> 0 Then FreeLibrary hLibrary
  
End Function
  

    


HOW IT WORKS:

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
  

  
Private Sub
UserControl_WriteProperties(PropBag As PropertyBag)
On Error Resume Next
  
  With
PropBag
    .
WriteProperty "Picture",        cbPicture, Nothing
    .
WriteProperty "Appearance",     CStr(cbAppearance), CStr(defAppearance)
    .
WriteProperty "BackColor",      CStr(cbBackColor), CStr(defBackColor)
    .
WriteProperty "Caption",        CStr(cbCaption), CStr(defCaption)
    .
WriteProperty "Enabled",        CStr(UserControl.Enabled), CStr(defEnabled)
    .
WriteProperty "FontName",       CStr(UserControl.Font.Name), CStr(defFontName)
    .
WriteProperty "FontSize",       CStr(UserControl.Font.Size), CStr(defFontSize)
    .
WriteProperty "FontBold",       CStr(UserControl.Font.Bold), CStr(defFontBold)
    .
WriteProperty "FontItalic",     CStr(UserControl.Font.Italic), CStr(defFontItalic)
    .
WriteProperty "FontUnderline",  CStr(UserControl.Font.Underline), CStr(defFontUnderline)
    .
WriteProperty "FontStrikeThru", CStr(UserControl.Font.Strikethrough), CStr(defFontStrikeThru)
    .
WriteProperty "ForeColor",      CStr(cbForeColor), CStr(defForeColor)
    .
WriteProperty "Style",          CStr(cbStyle), CStr(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
  

  
Public Property Set
Picture(ByVal NewValue As Picture)
  Set
cbPicture = NewValue
 
DrawIt BtnDepressed
 
PropertyChanged "Picture"
End Property

  


GETTING STARTED:

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:

Screen Shot - New Project Dialog

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.

Screen Shot - Toolbox 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:

 Property Name:  Set Value To:  Property Description:
AccessKeys BLANK Returns or sets the key(s) that will act as "Hot Keys" for your ActiveX Control.  If you set the value to "ab", the keys "A" and "B" will be hot keys for your control when combined with the <ALT> key and will set focus to your ActiveX Control.
Alignable False If set to TRUE, whomever is using your control will have the ability to align the control to the top, bottom, left, or right (similar to PictureBox controls).
Appearance 0 - Flat If set to "1 - 3D" and the "BorderStyle" property is set to "1 - FixedSingle" then when your control is placed on a form, it will look etched out like when you first place a PictureBox control on a form.
BackColor &H8000000F&
(ButtonFace)
Sets the color of the background area of the control.
BackStyle 0 - Transparent If set to "1 - Opaque", the background will be drawn for the control giving it a "solid" look.  If set to "0 - Transparent", the background will not be drawn, giving the control a "see through" look.
BorderStyle 0 - None If set to "1 - Fixed Single" then a border will be drawn around the control.  If the "Appearance" property is set to "0 - Flat", it will be a single black line.  If the "Appearance" property is set to "1 - 3D" then a 3D looking border will be drawn around the control.
CanGetFocus False If set to TRUE, the control can receive focus similar to a TextBox control.  Otherwise it can't... similar to a Label control.
ControlContainer False If set to True, the control itself can contain other controls (similar to a PictureBox or Frame control).
Enabled True Sets whether the control is enabled or disabled.
HasDC False If set to TRUE, the control will have a Device Context (DC) assigned to it's interface so that you can perform text and graphics printing directly to it (similar to a PictureBox).

This is not recommended unless it's designed to do that because the DC takes up unnecessary memory.

Height 450 Default height (in twips) of the control
InvisibleAtRuntime False If set to true, your control will be invisible at Run-Time (similar to a Timer control).
PropertyPage StandardFont
StandardColor
If you create a property page in your ActiveX Control property by right-clicking in the "Project Group" window and selecting "Add > Property Page", you can create a page that will come up when the user right clicks on your control in Design-Time and selects Properties.  You specify which property pages are in that dialog at Design-Time here.
Windowless False If set to TRUE, the control is assigned a Window Handle (hWnd) which can be used to do different operations on objects with the hWnd property available to them.

This is not recommended unless you want it to have a Window Handle because the hWnd takes up unnecessary memory.

        

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
' USERCONTROL EVENTS
'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX


Private Sub UserControl_Initialize()
  ' Set the default variable values
  p_TimeFormat = tf_HHMMSS_AMPM
  p_DateFormat = df_None
  p_FontName = "Courier New"
  p_FontSize = "10"
  p_FontBold = True
  p_FontItalic = False
  p_FontUnderline = False
  p_FontStrikeThru = False
  p_ForeColor = vbBlack
  p_BackColor = vbButtonFace
  p_Transparent = True
  p_Enabled = True
  p_Blink = True
  p_Paused = False
  lblDisplay.AutoSize = True
  lblDisplay.BackStyle = 0
  Timer1.Enabled = True
  Timer1.Interval = 500
End Sub

Private Sub UserControl_Resize()
  RenderDisplay
End Sub


'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
' TIMER EVENTS
'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX


Private Sub Timer1_Timer()
  RenderDisplay
End Sub


'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
' PUBLIC PROPERTIES
'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX


Public Property Get CurrentTime() As String
  CurrentTime = lblDisplay.Caption
End Property

Public Property Get Blink() As Boolean
  Blink = p_Blink
End Property
Public Property Let Blink(ByVal NewValue As Boolean)
  p_Blink = NewValue
  Timer1.Interval = 1000
  If p_Blink = True Then Timer1.Interval = 500
  PropertyChanged "Blink"
End Property

Public Property Get Enabled() As Boolean
  Enabled = p_Enabled
End Property
Public Property Let Enabled(ByVal NewValue As Boolean)
  p_Enabled = NewValue
  PropertyChanged "Enabled"
End Property

Public Property Get Paused() As Boolean
  Paused = p_Paused
End Property
Public Property Let Paused(ByVal NewValue As Boolean)
  p_Paused = NewValue
  PropertyChanged "Paused"
End Property

Public Property Get TimeFormat() As TimeFormats
  TimeFormat = p_TimeFormat
End Property
Public Property Let TimeFormat(ByVal NewValue As TimeFormats)
  p_TimeFormat = NewValue
  RenderDisplay
  PropertyChanged "TimeFormat"
End Property

Public Property Get DateFormat() As DateFormats
  DateFormat = p_DateFormat
End Property
Public Property Let DateFormat(ByVal NewValue As DateFormats)
  p_DateFormat = NewValue
  RenderDisplay
  PropertyChanged "DateFormat"
End Property

Public Property Get Font() As Font
  Set Font = lblDisplay.Font
End Property
Public Property Set Font(ByVal NewValue As Font)
  Set lblDisplay.Font = NewValue
  p_FontBold = lblDisplay.Font.Bold
  p_FontItalic = lblDisplay.Font.Italic
  p_FontName = lblDisplay.Font.Name
  p_FontSize = lblDisplay.Font.Size
  p_FontStrikeThru = lblDisplay.Font.Strikethrough
  p_FontUnderline = lblDisplay.Font.Underline
  RenderDisplay
  PropertyChanged "Font"
End Property

Public Property Get ForeColor() As OLE_COLOR
  ForeColor = p_ForeColor
End Property
Public Property Let ForeColor(ByVal NewValue As OLE_COLOR)
  p_ForeColor = NewValue
  RenderDisplay
  PropertyChanged "ForeColor"
End Property

Public Property Get BackColor() As OLE_COLOR
  BackColor = p_BackColor
End Property
Public Property Let BackColor(ByVal NewValue As OLE_COLOR)
  p_BackColor = NewValue
  RenderDisplay
  PropertyChanged "BackColor"
End Property

Public Property Get Transparent() As Boolean
  Transparent = p_Transparent
End Property
Public Property Let Transparent(ByVal NewValue As Boolean)
  p_Transparent = NewValue
  RenderDisplay
  PropertyChanged "Transparent"
End Property


'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
' PRIVATE FUNCTIONS
'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX


Private Function RenderDisplay() As Boolean
  
  Static strPreviousTime As String
  Static blnBlinkOn As Boolean
  
  Dim strTime As String
  Dim strDate As String
  
  ' If the control is disabled, exit
  If p_Enabled = False Then Exit Function
  
  ' Set the appearance settings according to the current property settings
  UserControl.BackColor = p_BackColor
  If p_Transparent = True Then
    UserControl.BackStyle = 0
  Else
    UserControl.BackStyle = 1
  End If
  lblDisplay.BackColor = p_BackColor
  lblDisplay.ForeColor = p_ForeColor
  lblDisplay.Left = 3
  lblDisplay.Top = (UserControl.ScaleHeight - lblDisplay.Height) / 2
  
  ' If the control has been paused, then exit out at this point
  If p_Paused = True Then Exit Function
  
  ' Get the time and date so we don't have to keep getting them
  strTime = Time
  strDate = Date
  
  ' Set the displayed time/date based on the settings
  lblDisplay.Caption = ""
  blnBlinkOn = Not blnBlinkOn
  If blnBlinkOn = True Then
    Select Case p_TimeFormat
      Case tf_HM_AMPM: lblDisplay.Caption = Format(strTime, "h:m AMPM")
      Case tf_HHMM_AMPM: lblDisplay.Caption = Format(strTime, "h:mm AMPM")
      Case tf_HMS_AMPM: lblDisplay.Caption = Format(strTime, "h:m:s AMPM")
      Case tf_HHMMSS_AMPM: lblDisplay.Caption = Format(strTime, "h:mm:ss AMPM")
      Case tf_HM: lblDisplay.Caption = Format(strTime, "h:m")
      Case tf_HHMM: lblDisplay.Caption = Format(strTime, "h:mm")
      Case tf_HMS: lblDisplay.Caption = Format(strTime, "h:m:s")
      Case tf_HHMMSS: lblDisplay.Caption = Format(strTime, "h:mm:ss")
    End Select
  Else
    Select Case p_TimeFormat
      Case tf_HM_AMPM: lblDisplay.Caption = Format(strTime, "h m AMPM")
      Case tf_HHMM_AMPM: lblDisplay.Caption = Format(strTime, "h mm AMPM")
      Case tf_HMS_AMPM: lblDisplay.Caption = Format(strTime, "h m s AMPM")
      Case tf_HHMMSS_AMPM: lblDisplay.Caption = Format(strTime, "h mm ss AMPM")
      Case tf_HM: lblDisplay.Caption = Format(strTime, "h m")
      Case tf_HHMM: lblDisplay.Caption = Format(strTime, "h mm")
      Case tf_HMS: lblDisplay.Caption = Format(strTime, "h m s")
      Case tf_HHMMSS: lblDisplay.Caption = Format(strTime, "h mm ss")
    End Select
  End If
  Select Case p_DateFormat
    Case df_MMDDYY: lblDisplay.Caption = lblDisplay.Caption & Format(strDate, "mm/dd/yy")
    Case df_DDMMYY: lblDisplay.Caption = lblDisplay.Caption & Format(strDate, "dd/mm/yy")
    Case df_MMDDYYYY: lblDisplay.Caption = lblDisplay.Caption & Format(strDate, "mm/dd/yyyy")
    Case df_DDMMYYYY: lblDisplay.Caption = lblDisplay.Caption & Format(strDate, "dd/mm/yyyy")
    Case df_YYMMDD: lblDisplay.Caption = lblDisplay.Caption & Format(strDate, "yy/mm/dd")
    Case df_YYDDMM: lblDisplay.Caption = lblDisplay.Caption & Format(strDate, "yy/dd/mm")
    Case df_YYYYMMDD: lblDisplay.Caption = lblDisplay.Caption & Format(strDate, "yyyy/mm/dd")
    Case df_YYYYDDMM: lblDisplay.Caption = lblDisplay.Caption & Format(strDate, "yyyy/dd/mm")
  End Select
  
  ' If the previous time isn't the same as the current time, fire the CHANGE event
  If strPreviousTime <> "" Then
    If strPreviousTime <> lblDisplay.Caption Then
      strPreviousTime = lblDisplay.Caption
      RaiseEvent Change(lblDisplay.Caption)
    End If
  Else
    strPreviousTime = lblDisplay.Caption
  End If
  
  ' Refresh the display
  UserControl.Refresh
  lblDisplay.Refresh
  
End Function
    

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:

Screen Shot - About Form

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

    

Screen Shot - Procedure Attribues 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
    Lists the properties, methods, and events defined. The property, method, or event in the Code window in which the
    cursor is currently positioned appears selected.

Description
    Displays the description of the property, method, or event that you want to show up in the Object Browser.

Project Help File
    Displays the path to the help file for the project that is specified on the General tab of the Project Properties dialog
    box. This option is read only.

Help Context ID
    Specifies the help context ID specified on the General tab of the Project Properties dialog box for the selected
    property or method.

OK
    Applies the options to the selected property or method on the user control and closes the dialog box.

Apply
    Applies the options to the selected property or method on the user control without closing the dialog box.

Advanced
    Expands the dialog box to include the following options:

Procedure ID
    Allows you to choose a standard member ID for the selected property, event, or method. The member ID is used to
    identify a property, method, or event as a standard type that control hosts may know about. A procedure whose
    ProcedureID is 0 acts as the default property or method for the control. It is also referred to as the Value property.

    Note  When you set a property, method, or event as a standard type, it does not change the behavior of the control.
    It tells a control container that the property, method, or event will behave in the well understood way but it does not
    create the behavior. It is up to you to make sure that the control behaves correctly.

    Caution  If you make a public property or method of a class the default member for that class, you cannot edit the
    declaration for the member and change Public to Friend or Private without first removing the default attribute. If you
    inadvertently edit the declaration, make the member Public, remove the Default attribute, and then reset the
    declaration to Friend or Public.

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
    chosen in the Property window. The property in the Property window is marked with a button and an ellipsis (…).
    When a user clicks the ellipsis, the Property Page specified is displayed.

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
    your own. When you type in a category, the property browser automatically creates a new level for the property.

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
    Allows you to set some standard behaviors of the selected property, method, or event.

  • Hide this member — Determines whether the property, method, or event will appear to the end user of the control. If checked, it is hidden and cannot be seen in a property browser or the Object Browser. You can still write code to access it but it does not appear in the user interface.

  • User Interface Default — Determines which property is highlighted in the property browser or which event is displayed in the Code window when you double-click the control. There can be only one User Interface default property and one User Interface default event. Not valid for methods.

  • Don't show in Property Browser — Determines if a property, method, or event will be hidden in the property browser. It will continue to appear in the Object Browser and you can continue to write code to access it. This box is cleared when you choose Hide this member.

Data Binding
    Determines whether a property can be bound or linked to a field in a database table.

  • Property is data bound — Determines if the property is a data bound property. If this option is selected, the property supports data binding and appears in the DataBindings collection. The box is cleared by default.

  • This property binds to DataField — Specifies whether the field to which the property is bound is specified in the DataField property. With this option, the end user does not have to use the Data Bindings collection and can use DataField. The box is cleared by default.

  • Show in DataBindings collection at design time — Determines if the property will appear in the user interface as bindable at design time. If selected, the property appears in the Bindings dialog box. If cleared, the property does not appear in the user interface as a bindable property but you can continue to write code to access it.

  • Property will call CanPropertyChange before changing — Tells the control container that the control will always call the CanPropertyChange method, and respect the return value, before changing a property value.

    The box is cleared by default.

So for the following properties, methods, and events, we set the following options in the Procedure Attributes dialog:

 Name:  Description:  Procedure ID:  Use this Page in
 Property Browser:
 Property Category:
BackColor Returns/sets the background color used to display the time/date text. BackColor StandardColor Appearance
Blink Returns/sets whether the dots in the time being displayed will blink every second or not. (None) (None) Appearance
Change This event occurs when the time/date beind displayed changes. (None) N / A N / A
Click This event occurs when the user presses and then releases a mouse button over this control. Click N / A N / A
CurrentTime Returns what the currently displayed time/date is on this control. (Default) (None) Data
DateFormat Returns/sets the format that the date should be displayed in. (None) (None) Data
DblClick This event occurs when the user presses and releases a mouse button and then presses and releases it again over the control. DblClick N / A N / A
Enabled Returns/sets a value that determines whether this control can respond to user-generated events. Enabled (None) Behavior
Font Returns/sets the font that is to be used to dispaly the time/date. Font StandardFont Appearance
ForeColor Returns/sets the foreground color used to display the time/date text. ForeColor StandardColor Appearance
MouseDown This event occurs when the user presses the mouse button while the cursor is over this control. MouseDown N / A N / A
MouseMove This event occurs when the user moves the mouse over this control. MouseMove N / A N / A
MouseUp This event occurs when the user releases the mouse button after the mouse button has been depressed while over this control. MouseUp N / A N / A
OLECompleteDrag Occurs at the OLE drag/drop source control after a manual or automatic drag/drop has been completed or canceled. (None) N / A N / A
OLEDragDrop Occurs when data is dropped onto the control via an OLE drag/drop operation, and OLEDropMode is set to manual. (None) N / A N / A
OLEDragOver Occurs when the mouse is moved over the control during an OLE drag/drop operation, if its OLEDropMode property is set to manual. (None) N / A N / A
OLEGiveFeedback Occurs at the source control of an OLE drag/drop operation when the mouse cursor needs to be changed. (None) N / A N / A
OLESetData Occurs at the OLE drag/drop source control when the drop target requests data that was not provided to the DataObject during the OLEDragStart event. (None) N / A N / A
OLEStartDrag Occurs when an OLE drag/drop operation is initiated either manually or automatically. (None) N / A N / A
Paused Returns/sets whether the display is paused (not showing changes in the time nor date). (None) (None) Behavior
ShowAbout Shows the About dialog for this control. AboutBox N / A N / A
TimeFormat Returns/sets the format that the time should be displayed in. (None) (None) Data
Transparent Returns/sets whether the time/date being displayed should have a background, or if it should be "see-through". (None) (None) Appearance

At this point, we are pretty much DONE!  Time to compile the OCX file and test it out!


COMPILING YOUR PROGRAM:

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.

Screen Shot - VB Project (TimeControl Events)

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