A step by step article on how to make an Active X control. In my eyes, I am not an expert but somebody asked me to write this (past comment on another submission) so I did. If you follow it, in the end you will have your own Custom PictureBox control that will have a property to assign a URL to an image to use for its picture along with an event to know when it completed its download. Hope you all enjoy. I did not realize it would take me so long to write.
How to build an Active X Control (Basic Tutorial)
I cannot believe I am going to try to attempt to teach since I never thought of myself as a teacher, but in one of my previous postings, a comment asked if I could post a tutorial on how to make an Active X Control because that person liked the way I explain things. So here I go. I would like to request that if I mistake anything or call something by it's wrong name that you do not flame me. Feel free to comment and let others know of my mistake, but please, be nice :) Ok, here goes.
This tutorial is going to walk you through step by step of how to create a new PictureBox control that will have a new property to supply a URL to an image on the web to use as it's picture (without the use of Winsock or Internet controls). I think everyone would find a use for this type of control.
Instructions assume you are using Visual Basic 6.0
Open VB
Choose to start a New Active X Control Project:
Default Naming:
Easy enough right. Ok, first things first, we need to name a few things and set some project properties. Click on the Project Explorer Tree on the Project itself (PROJECT1) and then down in the properties window, rename it to: WEBPIC. This name is going to become the name of your OCX (duh). Then click on the user control branch and rename it to: WebPictureBox. This name is the name it will be known as inside of VB (the tool tip on the tool when you put it in your available components later on for new projects that use it)
Now go up to your menu and Choose Project. Then Choose WebPic Properties. In Project Description enter Web Picture Box. This is the name it will be listed as when you pull up the list of available controls to add to a project. You want to keep it english like so you can tell what it is. I hate those who make controls bet never set this and then it will default to the name of the OCX which most of the time is some abbreviated name that does not make too much sense when you are just skimming though. Anyhow, that is a different story. Go ahead and set all the other properties you want about the project, Company, Copyright etc. I personally like to set auto increment on the version tab. Click Ok.
Start of Coding:
Ok, before we get started, Save your work (however you like to save where ever you want)
Place a Picture Box on the UserControl (any where you like, code with handle it's position later). Name it: picBOX.
Double Click on an empty spot on the User Control. This should take you to UserControl_Initialize(). In that Sub type:
Private Sub UserControl_Initialize()
With UserControl
.picBOX.Move 0, 0, .ScaleWidth, .ScaleHeight
End With
End SubThis code make the picture box match the size of the control when it is first placed on a form later.
Note, instead of ME, you say UserControl when referring to your object. Me refers it to its exposed methods and properties that we will put in soon.
Now we need to code for when the user control gets resized. Go to the Resize Event for the UserControl and type:
Private Sub UserControl_Resize()
If m_privateResize = False Then
With UserControl
.picBOX.Move 0, 0, .ScaleWidth, .ScaleHeight
End With
End If
End SubPretty much the same as before, but this just keeps the picture box the same size as the user control always. However, I added a bit to check to see if code told it to resize or did the user do it. Later down I have code resizing the control and I don't want this to fire. Up in the General Declarations you need to defind m_privateResize as Boolean, up top type:
Private m_privateResize As Boolean
Adding Events:
Now lets just put in some basic events that our control will have. You can add more later after you see how this is done. I am going to add the Click, DblClick, MouseUp, MouseMove, MouseDown, and Resize events to my control.
At the top of your code for the control in the General Declarations section type:
Event Click()
Event DblClick()
Event MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single)
Event MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single)
Event MouseUp(Button As Integer, Shift As Integer, X As Single, Y As Single)
Event Resize()These are now events that you can raise later in your code. Which we will program now. Pretty much, we want to say in our control when somebody clicks on the picture box (or moves etc) we want to raise those events out of our control. So in your code type:
Private Sub picBOX_Click()
RaiseEvent Click
End Sub
Private Sub picBOX_DblClick()
RaiseEvent DblClick
End Sub
Private Sub picBOX_MouseDown(Button As Integer, _
Shift As Integer, X As Single, Y As Single)
RaiseEvent MouseDown(Button, Shift, X, Y)
End Sub
Private Sub picBOX_MouseMove(Button As Integer, _
Shift As Integer, X As Single, Y As Single)
RaiseEvent MouseMove(Button, Shift, X, Y)
End Sub
Private Sub picBOX_MouseUp(Button As Integer, _
Shift As Integer, X As Single, Y As Single)
RaiseEvent MouseUp(Button, Shift, X, Y)
End SubPretty simple, really just raising our event when the corresponding events occur within our control.
Now I want to add the code for when the control itself gets resized.
Go back to your UserControl_Resize code you typed earlier and add the RaiseEvent Resize line so it look like this:
Private Sub UserControl_Resize()
If m_privateResize = False Then
With UserControl
.picBOX.Move 0, 0, .ScaleWidth, .ScaleHeight
End With
End If
RaiseEvent Resize
End SubProperties:
Now we need some basic properties, really the same ones as the picture box, I will skip some just to keep this quick.
We are going to add the: Appearance, BackColor, BorderStyle, AutoRedraw, AutoSize, and Picture property to our control.
In your code type:
Public Property Get Appearance() As Integer
Appearance = picBOX.Appearance
End Property
Public Property Let Appearance(ByVal New_Appearance As Integer)
picBOX.Appearance() = New_Appearance
PropertyChanged "Appearance"
End Property
Public Property Get BackColor() As OLE_COLOR
BackColor = picBOX.BackColor
End Property
Public Property Let BackColor(ByVal New_BackColor As OLE_COLOR)
picBOX.BackColor() = New_BackColor
PropertyChanged "BackColor"
End Property
Public Property Get BorderStyle() As Integer
BorderStyle = picBOX.BorderStyle
End Property
Public Property Let BorderStyle(ByVal New_BorderStyle As Integer)
picBOX.BorderStyle() = New_BorderStyle
PropertyChanged "BorderStyle"
End Property
Public Property Get AutoRedraw() As Boolean
AutoRedraw = picBOX.AutoRedraw
End Property
Public Property Let AutoRedraw(ByVal New_AutoRedraw As Boolean)
picBOX.AutoRedraw() = New_AutoRedraw
PropertyChanged "AutoRedraw"
End Property
Public Property Get AutoSize() As Boolean
AutoSize = picBOX.AutoSize
End Property
Public Property Let AutoSize(ByVal New_AutoSize As Boolean)
picBOX.AutoSize() = New_AutoSize
PropertyChanged "AutoSize"
End Property
Public Property Get Picture() As Picture
Set Picture = picBOX.Picture
End Property
Public Property Set Picture(ByVal New_Picture As Picture)
Set picBOX.Picture = New_Picture
PropertyChanged "Picture"
End Property
Private Sub UserControl_ReadProperties(PropBag As PropertyBag)
picBOX.Appearance = PropBag.ReadProperty("Appearance", 1)
picBOX.BackColor = PropBag.ReadProperty("BackColor", &H8000000F)
picBOX.BorderStyle = PropBag.ReadProperty("BorderStyle", 1)
picBOX.AutoRedraw = PropBag.ReadProperty("AutoRedraw", False)
picBOX.AutoSize = PropBag.ReadProperty("AutoSize", False)
Set Picture = PropBag.ReadProperty("Picture", Nothing)
End Sub
Private Sub UserControl_WriteProperties(PropBag As PropertyBag)
Call PropBag.WriteProperty("Appearance", picBOX.Appearance, 1)
Call PropBag.WriteProperty("BackColor", picBOX.BackColor, &H8000000F)
Call PropBag.WriteProperty("BorderStyle", picBOX.BorderStyle, 1)
Call PropBag.WriteProperty("AutoRedraw", picBOX.AutoRedraw, False)
Call PropBag.WriteProperty("AutoSize", picBOX.AutoSize, False)
Call PropBag.WriteProperty("Picture", Picture, Nothing)
End Sub
Ok, to explain all that, in general you just made properties to your control that when they get set, will then in turn set properties of the picture box inside of your control. The ReadProperties and WriteProperties as subs that will save these properties to the property bag of the control so it remembers what you set even after you close. As you typed those lines (if you did not copy/paste) then you would have noticed what each of those arguments are, Name, Value, Default. Actually pretty easy to understand I think.
Tweaking:
Ok, with that added, we need to tweak a few things now. One thing that stands out is the AutoResize Event. Right now our control is coded that if the developer resizes the user control, we resize the picture box to fit. But what happens when AutoSize is set to true and a new picture gets assigned. The picture box will change size. Therefore, we need to code to make the usercontrol match back to the size of the new picture loaded. So, in the "Public Property Set Picture" sub, we need to add some code to make it look like this:
Public Property Set Picture(ByVal New_Picture As Picture)
Set picBOX.Picture = New_Picture
If Me.AutoSize = True Then
With UserControl
m_privateResize = True
.Width = .picBOX.Width
.Height = .picBOX.Height
m_privateResize = False
End With
End If
PropertyChanged "Picture"
End PropertyThere, that takes care of that. You may find other areas to tweak, but I am just building this the same time I am typing so I have not thought of any yet.
Custom Properties:
Time to add our custom properties which makes our new version of a picture box different from the default one. We need a new property named PictureURL that will contain a string to a fully qualified URL to an image on the web. Because this property does not correspond back to some other property already of the picture box, we need a place to store it when it gets set. So up in the General Declarations section type:
Const m_def_PictureURL = ""
Private m_PictureURL As StringNow in code type:
Public Property Get PictureURL() As String
PictureURL = m_PictureURL
End Property
Public Property Let PictureURL(ByVal New_PictureURL As String)
m_PictureURL = New_PictureURL
PropertyChanged "PictureURL"
End Property
Private Sub UserControl_InitProperties()
m_PictureURL = m_def_PictureURL
End SubThis is the statements to read and write to this property. It will save and read from the m_PictureURL variable we defined above and use the m_def_PictureURL constant as default the first time this control is initialized.
However, now we need to go back to our ReadProperties and WriteProperties to make sure we tell the property bag to remember what ever gets set here in design time.
Private Sub UserControl_ReadProperties(PropBag As PropertyBag)
picBOX.Appearance = PropBag.ReadProperty("Appearance", 1)
picBOX.BackColor = PropBag.ReadProperty("BackColor", &H8000000F)
picBOX.BorderStyle = PropBag.ReadProperty("BorderStyle", 1)
picBOX.AutoRedraw = PropBag.ReadProperty("AutoRedraw", False)
picBOX.AutoSize = PropBag.ReadProperty("AutoSize", False)
Set Picture = PropBag.ReadProperty("Picture", Nothing)
m_PictureURL = PropBag.ReadProperty("PictureURL", m_def_PictureURL)
End Sub
Private Sub UserControl_WriteProperties(PropBag As PropertyBag)
Call PropBag.WriteProperty("Appearance", picBOX.Appearance, 1)
Call PropBag.WriteProperty("BackColor", picBOX.BackColor, &H8000000F)
Call PropBag.WriteProperty("BorderStyle", picBOX.BorderStyle, 1)
Call PropBag.WriteProperty("AutoRedraw", picBOX.AutoRedraw, False)
Call PropBag.WriteProperty("AutoSize", picBOX.AutoSize, False)
Call PropBag.WriteProperty("Picture", Picture, Nothing)
Call PropBag.WriteProperty("PictureURL", m_PictureURL, m_def_PictureURL)
End SubNote the last lines in each sub. We are saving and reading the private variable we defined and storing that. Once again, the property bag is what remembers what you set in the property window when you design a form and place controls on it. If you do not use the property bag, no matter what you set on the property window later will never be saved.
Ok now, CLICK SAVE. You do not want to lose what you have done so far.
Finishing our Custom Properties:
Ok, now we need to add the bit that gets an image from the web. We need to go back to our "Public Property Let PictureURL" sub and add some code. This code will get the image from the web for us. Type to make our "Public Property Let PictureURL" sub look like this:
Public Property Let PictureURL(ByVal New_PictureURL As String)
m_PictureURL = New_PictureURL
If (New_PictureURL <> "") Then
AsyncRead m_PictureURL, vbAsyncTypePicture, "PictureURL", vbAsyncReadForceUpdate
End If
PropertyChanged "PictureURL"
End PropertyThis uses the AsyncRead method to get an image from the web. Pretty simple huh :) The vbAsyncReadForceUpdate argument tells the AsyncRead to always get the picture from the web and ignore any cached copy. Maybe later you can change this and provide some new property to have this as a setting. (nice upgrade to practice with)
Ok almost done. The code above just starts the download. Now we need to get the picture when it is done. For this we use the AsyncReadComplete event of our user control. Go ahead and type:
Private Sub UserControl_AsyncReadComplete(AsyncProp As AsyncProperty)
On Error Resume Next
Select Case AsyncProp.PropertyName
Case "PictureURL"
Set Me.Picture = AsyncProp.Value
Case Else
End Select
End SubThe user controls AsyncReadComplete event is fired when the download is done. So we ready the AsyncProp object to determine what was just downloaded. In the earlier code when we started the download, we supplied a name "PictureURL" as the name of the download (not the file name, but the name associated with the download. Just like you name controls when you code). We check to see if this downloaded file is the one we requested, if it is, assign it to the picture property of the picture box. It is written this way to help you see that you can add more capability to this if you wish and provide multiple downloads and what not.
Last Bit Of Code:
Ok, we are pretty much done, but I think it would be nice to add an event to our control to tell the user/developer using it, that the download is done. So back up in the General Declarations type:
Event DownloadComplete()
Then back in the "Private Sub UserControl_AsyncReadComplete" sub, add a line to make it look like this:
Private Sub UserControl_AsyncReadComplete(AsyncProp As AsyncProperty)
On Error Resume Next
Select Case AsyncProp.PropertyName
Case "PictureURL"
Set Me.Picture = AsyncProp.Value
Case Else
End Select
RaiseEvent DownloadComplete
End SubThere, the code side of things is done. Click Save. Ok, go for the first compile. Fix any typos and try to compile again until you get a good compile. Now go back to your menu, choose Project, Choose WebPic Properties. Click the component tab. Turn on the option for Binary Compatibility (it should be defaulted pointing to the ocx you just made)
The reason for this is to make it that when you make changes to your control it will make it so programs already compiled with earlier versions of your control will still work. However, depending on your changes, it may warn you that you are breaking compatibility. If you break it, you should consider compiling under a new name if other programs exist using your older version that are already compiled and released. If there are no other programs released, you can break it and then try not to again. Breakage occurs when you alter the declaration of an exposed method or property that already existed in the older version. For example, if you right now have the PictureURL property but later decided to call it just URL, it will break compatibility. But if you add new properties or methods, or just alter code inside existing functions or subs, it will not break.
Finishing up:
Now go back to your design view of your user control. I suggest sizing the user control down to a better size. Remember, the size you set here will become the default size of the control when it later gets placed on a form. Also, for the properties of the user control, there is a property named: ToolBoxBitmap. Here is where you assign an image to use as the image that will appear in the toolbox later on. For best results make a BMP 16x15. Note it will attempt to read (I believe the bottom left pixel, or top left I forget) to determine what color it will use as its transparent color. I normally just keep a one pixel border around the image I make and have the background color set to LIME green or something to stay out of trouble. Feel free to make it what ever you want or you can do it later.
Testing:
Ok, time to test. Yeah. Don't close this project but I do suggest closing all design and code windows of the control (in your playing around later you will find out why), just go up to File, then choose Add Project. Choose Standard EXE. Click Ok.
Now over in your project explorer tree right click on the project that just got added and choose Set As Start Up.
Rename the project to whatever you want. Like WebPicTest. Then go to the form and rename it to something like frmTEST.
In the toolbox you should see either the image you made for your control, or the default generic image if you did not. If you cannot tell, just mouse move over the controls listed as the bottom until the tooltip of one reads the name of your control. Go ahead and click it and add it to your form.
Presto, your control is on a form. Go ahead and resize it a bit to make sure our resize code works.
Yeah it does (at least for me). Over in the properties for it, check AutoResize to true.
Then in the picture property go and browse for an image from your hard drive to test the Picture Property.
Yeah, it worked and it resized right.
Ok, now the real test. Remove the image from the Picture Property. What we coded really is setup for us to use either Picutre, or PictureURL but not really both, it won't crash, but just adds a little confusion. Anyhow, delete the previous image from the picture property then in the PictureURL property type: http://microsoft.com/library/homepage/images/init_windows.gif and press enter. If you left the other picture there, all that would happen is the new web downloaded image would replace it. Ok, time for testing of the events and calling properties in code. First lets delete what we typed in the PictureURL property. Then for the Picture Property go ahead and browse and choose an image from your hard drive. The on the code for this test form have it say:
Option Explicit
Private Sub WebPictureBox1_Click()
Me.WebPictureBox1.PictureURL = _
"http://microsoft.com/library/homepage/images/init_windows.gif"
Debug.Print "Click"
End Sub
Private Sub WebPictureBox1_DblClick()
Debug.Print "DblClick"
End Sub
Private Sub WebPictureBox1_DownloadComplete()
Debug.Print "Download Complete"
End Sub
Private Sub WebPictureBox1_MouseDown(Button As Integer, _
Shift As Integer, X As Single, Y As Single)
Debug.Print "MouseDown"
End Sub
Private Sub WebPictureBox1_MouseMove(Button As Integer, _
Shift As Integer, X As Single, Y As Single)
Me.Caption = X & " : " & Y
End Sub
Private Sub WebPictureBox1_MouseUp(Button As Integer, _
Shift As Integer, X As Single, Y As Single)
Debug.Print "MouseUp"
End Sub
Private Sub WebPictureBox1_Resize()
Debug.Print "Resize"
End SubRun your test project.
Click on your control
Did it download the image. Did you get all your debug.prints?
I did. yeah.
Code Listing Reference:
Ok, here at the end is a listing of all the code for the user control so you can just copy and paste from here if you had any problems:
WebPictureBox (Code)
Option Explicit
Event Click()
Event DblClick()
Event MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single)
Event MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single)
Event MouseUp(Button As Integer, Shift As Integer, X As Single, Y As Single)
Event Resize()
Event DownloadComplete()
Const m_def_PictureURL = ""
Private m_PictureURL As String
Private m_privateResize As Boolean
Private Sub picBOX_Click()
RaiseEvent Click
End Sub
Private Sub picBOX_DblClick()
RaiseEvent DblClick
End Sub
Private Sub picBOX_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single)
RaiseEvent MouseDown(Button, Shift, X, Y)
End Sub
Private Sub picBOX_MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single)
RaiseEvent MouseMove(Button, Shift, X, Y)
End Sub
Private Sub picBOX_MouseUp(Button As Integer, Shift As Integer, X As Single, Y As Single)
RaiseEvent MouseUp(Button, Shift, X, Y)
End Sub
Private Sub UserControl_Initialize()
With UserControl
.picBOX.Move 0, 0, .ScaleWidth, .ScaleHeight
End With
End Sub
Private Sub UserControl_Resize()
If m_privateResize = False Then
With UserControl
.picBOX.Move 0, 0, .ScaleWidth, .ScaleHeight
End With
End If
RaiseEvent Resize
End Sub
Public Property Get Appearance() As Integer
Appearance = picBOX.Appearance
End Property
Public Property Let Appearance(ByVal New_Appearance As Integer)
picBOX.Appearance() = New_Appearance
PropertyChanged "Appearance"
End Property
Public Property Get BackColor() As OLE_COLOR
BackColor = picBOX.BackColor
End Property
Public Property Let BackColor(ByVal New_BackColor As OLE_COLOR)
picBOX.BackColor() = New_BackColor
PropertyChanged "BackColor"
End Property
Public Property Get BorderStyle() As Integer
BorderStyle = picBOX.BorderStyle
End Property
Public Property Let BorderStyle(ByVal New_BorderStyle As Integer)
picBOX.BorderStyle() = New_BorderStyle
PropertyChanged "BorderStyle"
End Property
Public Property Get AutoRedraw() As Boolean
AutoRedraw = picBOX.AutoRedraw
End Property
Public Property Let AutoRedraw(ByVal New_AutoRedraw As Boolean)
picBOX.AutoRedraw() = New_AutoRedraw
PropertyChanged "AutoRedraw"
End Property
Public Property Get AutoSize() As Boolean
AutoSize = picBOX.AutoSize
End Property
Public Property Let AutoSize(ByVal New_AutoSize As Boolean)
picBOX.AutoSize() = New_AutoSize
PropertyChanged "AutoSize"
End Property
Public Property Get Picture() As Picture
Set Picture = picBOX.Picture
End Property
Public Property Set Picture(ByVal New_Picture As Picture)
Set picBOX.Picture = New_Picture
If Me.AutoSize = True Then
With UserControl
m_privateResize = True
.Width = .picBOX.Width
.Height = .picBOX.Height
m_privateResize = False
End With
End If
PropertyChanged "Picture"
End Property
Private Sub UserControl_ReadProperties(PropBag As PropertyBag)
picBOX.Appearance = PropBag.ReadProperty("Appearance", 1)
picBOX.BackColor = PropBag.ReadProperty("BackColor", &H8000000F)
picBOX.BorderStyle = PropBag.ReadProperty("BorderStyle", 1)
picBOX.AutoRedraw = PropBag.ReadProperty("AutoRedraw", False)
picBOX.AutoSize = PropBag.ReadProperty("AutoSize", False)
Set Picture = PropBag.ReadProperty("Picture", Nothing)
m_PictureURL = PropBag.ReadProperty("PictureURL", m_def_PictureURL)
End Sub
Private Sub UserControl_WriteProperties(PropBag As PropertyBag)
Call PropBag.WriteProperty("Appearance", picBOX.Appearance, 1)
Call PropBag.WriteProperty("BackColor", picBOX.BackColor, &H8000000F)
Call PropBag.WriteProperty("BorderStyle", picBOX.BorderStyle, 1)
Call PropBag.WriteProperty("AutoRedraw", picBOX.AutoRedraw, False)
Call PropBag.WriteProperty("AutoSize", picBOX.AutoSize, False)
Call PropBag.WriteProperty("Picture", Picture, Nothing)
Call PropBag.WriteProperty("PictureURL", m_PictureURL, m_def_PictureURL)
End Sub
Public Property Get PictureURL() As String
PictureURL = m_PictureURL
End Property
Public Property Let PictureURL(ByVal New_PictureURL As String)
m_PictureURL = New_PictureURL
If (New_PictureURL <> "") Then
AsyncRead m_PictureURL, vbAsyncTypePicture, "PictureURL", vbAsyncReadForceUpdate
End If
PropertyChanged "PictureURL"
End Property
Private Sub UserControl_InitProperties()
m_PictureURL = m_def_PictureURL
End Sub
Private Sub UserControl_AsyncReadComplete(AsyncProp As AsyncProperty)
On Error Resume Next
Select Case AsyncProp.PropertyName
Case "PictureURL"
Set Me.Picture = AsyncProp.Value
Case Else
End Select
RaiseEvent DownloadComplete
End Sub
I hope this all works out. I also hope you learned something. At least you got a new control. One final thing I would do is back in the WebPic project. I would open the Object Browser, then for each of the properties of our new control, define tips to display in the property window (the area on the bottom of the property window that tells you what a property does) and also pick what event I would want as my default event. While this option is nice and make a control more professional, it is just too much to explain here and requires another lesson.
Feel free to email if you have any questions:
-Clint LaFever
http://lafever.iscool.net or http://vbaisc.iscool.net