Library sample chapters
ActiveX controls
Refining the Control
Adding a UserControl object to the current project and placing some constituent controls on it is just the first step toward the creation of a full-fledged, commercial-quality ActiveX control. In this section, I'll show you how to implement a robust user interface, add binding capabilities and property pages, create user-drawn controls, and prepare your controls for the Internet.
Custom Properties
You've already seen how you can add custom properties using pairs of property procedures. This section explains how to implement some special types of properties.
Design-time
and run-time properties
Not all properties are available both at design time and at run time, and it's interesting to see how you write the code in the UserControl module to limit the visibility of properties. The easiest way to create a run time-only property, such as the SelText property of a TextBox or the ListIndex property of a ListBox, is by ticking the Don't Show In Property Browser option in the Attributes section of the Procedure Attributes dialog box. (You can access this dialog box by choosing it from the Tools menu.) If this check box is selected, the property doesn't appear in the Properties window at design time.
The problem with this simple approach, however, is that it also hides the property in the other property browser that Visual Basic provides, namely the Locals window. To have the property listed in the Locals window at run time but not in the Properties window, you must raise an error in the Property Get procedure at design time, as this code demonstrates:
Public Property Get SelText() As String
If Ambient.UserMode = False Then Err.Raise 387
SelText = Text1.SelText
End Property
Error 387 "Set not permitted" is the error that by convention you should raise in this case, but any error will do the trick. If Visual Basic-or more generally, the host environment-receives an error when reading a value at design time, the property isn't displayed in the properties browser, which is precisely what you want. Creating a property that's unavailable at design time and read-only at run time is even simpler because you need merely to omit the Property Let procedure, as you would do with any read-only property. Visual Basic doesn't show such a property in the Properties window because it couldn't be modified in any way.
Another common situation concerns properties that are available at design time and read-only at run time. This is similar to the MultiLine and ScrollBars properties of the Visual Basic TextBox control. You can implement such properties by raising Error 382 "Set not supported at runtime" in their Property Let procedures, as shown in the following code:
' This property is available at design time and read-only at run time.
Public Property Get ScrollBars() As Integer
ScrollBars = m_ScrollBars
End Property
Public Property Let ScrollBars(ByVal New_ScrollBars As Integer)
If Ambient.UserMode Then Err.Raise 382
m_ScrollBars = New_ScrollBars
PropertyChanged "ScrollBars"
End Property
When you have design-time properties that are read-only at run time, you can't call the Property Let procedure from within the ReadProperties event procedure because you would get an error. In this case, you're forced to directly assign the private member variable or the constituent control's property, or you have to provide a module-level Boolean variable that you set to True on entering the ReadProperties event and reset to False on exit. You then query this variable before raising errors in the Property Let procedure. You can also use the same variable to skip an unnecessary call to the PropertyChanged method, as in this code example:
Public Property Let ScrollBars(ByVal New_ScrollBars As Integer)
' The ReadingProperties variable is True if this routine is being
' called from within the ReadProperties event procedure.
If Ambient.UserMode
And Not ReadingProperties Then Err.Raise 382
m_ScrollBars = New_ScrollBars
If Not ReadingProperties Then
PropertyChanged "ScrollBars"
End Property
Enumerated properties
You can define enumerated properties using either Enum blocks in code or Visual Basic's own enumerated types. For example, you can modify the code produced by the wizard and improve the MousePointer property as follows:
Public Property Get MousePointer() As MousePointerConstants
MousePointer = Text1.MousePointer
End Property
Public Property Let MousePointer(ByVal New_MousePointer _
As MousePointerConstants)
Text1.MousePointer() = New_MousePointer
PropertyChanged "MousePointer"
End Property
Enumerated properties are useful because their valid values appear in the Properties window in a combo box, as shown in Figure 17-6. Keep in mind, however, that you should always protect your ActiveX control from invalid assignments in code, so the previous routine should be rewritten as follows:
Public Property Let MousePointer(ByVal New_MousePointer _
As MousePointerConstants)
Select Case New_MousePointer
Case vbDefault To vbSizeAll, vbCustom
Text1.MousePointer() = New_MousePointer
PropertyChanged "MousePointer"
Case Else
Err.Raise 380 ' Invalid Property Value error
End Select
End Property
Figure 17-6.
Use enumerated properties to offer a list of valid values in the Properties
window.
There's a good reason for not defining properties and arguments using Visual Basic and VBA enumerated constants, though: If you use the control with environments other than Visual Basic, these symbolic constants won't be visible to the client application.
|
Sometimes you might want to add spaces and other symbols inside an enumerated value to make it more readable in the Properties window. For example, the FillStyle property includes values such as Horizontal Line or Diagonal Cross. To expose similar values in your ActiveX controls, you have to enclose Enum constants within square brackets, as in the following code: Enum MyColors
Black = 1
[Dark Gray]
[Light Gray]
White
End Enum
Here's another idea that you might find useful: If you use an enumerated constant name whose name begins with an underscore, such as [_HiddenValue], this value won't appear by default in the Object Browser. However, this value does appear in the Properties window, so this trick is especially useful for enumerated properties that aren't available at design time. |
Picture and
Font properties
Visual Basic deals in a special way with properties that return a Picture or Font object. In the former instance, the Properties window shows a button that lets you select an image from disk; in the latter, the Properties window includes a button that displays a Font common dialog box.
When working with Font properties, you should keep in mind that they return object references. For example, if two or more constituent controls have been assigned the same Font reference, changing a font attribute in one of them also changes the appearance of all the others. For this reason, Ambient.Font returns a copy of the parent form's font so that any subsequent change to the form's font doesn't affect the UserControl's constituent controls, and vice versa. (If you want to keep your control's font in sync with the form's font, you simply need to trap the AmbientChanged event.) Sharing object references can cause some subtle errors in your code. Consider the following example:
' Case 1: Label1 and Text1 use fonts with identical attributes. Set Label1.Font = Ambient.Font Set Text1.Font = Ambient.Font ' Case 2: Label1 and Text1 point to the *same* font. Set Label1.Font = Ambient.Font Set Text1.Font = Label1.Font
The two pieces of code look similar, but in the first instance the two constituent controls are assigned different copies of the same font, so you can change the font attributes of one control without affecting the other. In the latter case, both controls are pointing to the same font, so each time you modify a font attribute in either control the other one is affected as well.
It's a common practice to provide all the alternate, old-styled Fontxxxx properties, namely FontName, FontSize, FontBold, FontItalic, FontUnderline, and FontStrikethru. But you should also make these properties unavailable at design time, and you shouldn't save them in the WriteProperties event if you also save the Font object. If you decide to save individual Fontxxxx properties, it's important that you retrieve them in the correct order (first FontName, and then all the others).
One more thing to keep in mind when dealing with font properties: You can't restrict the choices of the programmer who's using the control to a family of fonts- for example, to nonproportional fonts or to printer fonts-if the Font property is exposed in the Properties window. The only way to restrict font selection is to show a Font Common Dialog box from a Property Page. See the "Property Pages" section later in this chapter for details about building property pages.
Font properties pose a special challenge to ActiveX control programmers. If your control exposes a Font property and the client code modifies one or more font attributes, Visual Basic calls the Property Get Font procedure but not the Property Set Font procedure. If the Font property delegates to a single constituent control, this isn't usually a problem because the control's appearance is correctly updated. Things are different in user-drawn ActiveX controls because in this case your control gets no notification that it should be repainted. This problem has been solved in Visual Basic 6 with the FontChanged event of the StdFontobject. Here's a fragment of code taken from a Label-like, user-drawn control that correctly refreshes itself when the client modifies an attribute of the Font property:
Private WithEvents UCFont As StdFont
Private Sub UserControl_InitProperties()
' Initialize the Font property (and the UCFont object).
Set Font = Ambient.Font
End Sub
Public Property Get Font() As Font
Set Font = UserControl.Font
End Property
Public Property Set Font(ByVal New_Font As Font)
Set UserControl.Font = New_Font
Set UCFont = New_Font ' Prepare to trap events.
PropertyChanged "Font"
Refresh ' Manually perform the first refresh.
End Property
' This event fires when the client code changes a font's attribute.
Private Sub UCFont_FontChanged(ByVal PropertyName As String)
Refresh ' This causes a Paint event.
End Sub
' Repaint the control.
Private Sub UserControl_Paint()
Cls
Print Caption;
End Sub
Object properties
You can create ActiveX controls with properties that return objects, such as a TreeView-like control that exposes a Nodes collection. This is possible because ActiveX control projects can include PublicNotCreatable classes, so your control can internally create them using the New operator and return a reference to its clients through a read-only property. Object properties can be treated as if they were regular properties in most circumstances, but they require particular attention when you need to make them persistent and reload them in the WriteProperties and ReadProperties procedures.
Even if Visual Basic 6 does support persistable classes, you can't save objects that aren't creatable, as in this case. But nothing prevents you from manually creating a PropertyBag object and loading it with all the properties of the dependent object. Let me demonstrate this technique with an example.
Suppose that you have an AddressOCX ActiveX control that lets the user enter a person's name and address, as shown in Figure 17-7. Instead of many properties, this AddressOCX control exposes one object property, named Address, whose class is defined inside the same project. Rather than having the main UserControl module save and reload the individual properties of the dependent object, you should create a Friend property in the PublicNotCreatable class. I usually call this property AllProperties because it sets and returns the values of all the properties in one Byte array. To serialize the properties into an array, I use a private stand-alone PropertyBag object. Following is the complete source code of the Address class module. (For the sake of simplicity, properties are implemented as Public variables.)
' The Address.cls class module
Public Name As String, Street As String
Public City As String, Zip As String, State As String
Friend Property Get AllProperties() As Byte()
Dim PropBag As New PropertyBag
PropBag.WriteProperty "Name", Name, ""
PropBag.WriteProperty "Street", Street, ""
PropBag.WriteProperty "City", City, ""
PropBag.WriteProperty "Zip", Zip, ""
PropBag.WriteProperty "State", State, ""
AllProperties = PropBag.Contents
End Property
Friend Property Let AllProperties(value() As Byte)
Dim PropBag As New PropertyBag
PropBag.Contents = value()
Name = PropBag.ReadProperty("Name", "")
Street = PropBag.ReadProperty("Street", "")
City = PropBag.ReadProperty("City", "")
Zip = PropBag.ReadProperty("Zip", "")
State = PropBag.ReadProperty("State", "")
End Property
Rather than saving and reloading all the individual properties in the WriteProperties and ReadProperties event procedures of the main AddressOCX module, you simply save and restore the AllProperties property of the Address object.
Figure 17-7.
An AddressOCX ActiveX control that exposes each of the Address properties
as an individual Address, PublicNotCreatableobject.
' The AddressOCX code module (partial listing)
Dim m_Address As New Address
Public Property Get Address() As Address
Set Address = m_Address
End Property
Public Property Set Address(ByVal New_Address As Address)
Set m_Address = New_Address
PropertyChanged "Address"
End Property
Private Sub UserControl_ReadProperties(PropBag As PropertyBag)
m_Address.AllProperties = PropBag.ReadProperty("Address")
End Sub
Private Sub UserControl_WriteProperties(PropBag As PropertyBag)
Call PropBag.WriteProperty("Address", m_Address.AllProperties)
End Sub
All the individual constituent controls must refer to the corresponding property in the Address object. For example, this is the code in the Change event procedure of the txtName control:
Private Sub txtName_Change()
Address.Name = txtName
PropertyChanged "Address"
End Sub
The ActiveX control should also expose a Refresh method that reloads all the values from the Address object into the individual fields. Alternatively, you might implement an event that the Address object raises in the AddressOCX module when any of its properties is assigned a new value. This problem is similar to the one I described in the "Forms as Object Viewers" section of Chapter 9.
Related articles
Related discussion
-
VB6 Runtime error 381 subsript out of range Error
by Uncle (2 replies)
-
passing and reading parameters from using Shell
by jigartoliya (0 replies)
-
Convert C++ code to VB6
by mawcot (4 replies)
-
listbox scrollbar
by Dennijr (10 replies)
-
Can you describe Above simple VB6 code?
by pramodmca09 (0 replies)
I also looking for the control for years, but still can't get a good one, some control only make a form transparent instead of a control like picturebox, some control only copy the picture of background under it, isn't real transparent. some controls have transparent background but the transparent area is a hole that you can click controls behind it in z-order. I ever tried the following code in a form:
Public Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
Global Const GWL_EXSTYLE = (-20)
Global Const WS_EX_TRANSPARENT = &H20&
add a textbox on the form and a picturebox named picturebox1 over it, in form_load event add this code:
SetWindowLong picturebox1.hwnd, GWL_EXSTYLE, WS_EX_TRANSPARENT
the problems are, only when content of textbox change(by code), the picturebox was changed to transparent, second, when then picturebox resized, the picturebox was changed back to white.usually.
dear sir, if you have got a ideal method to make this control, would you please send me a copy? thank a lot.
i want to create a transperent picturebox behind it i will put internet control / webbrowser.
user will able to see the webpage and unable to click on web page ,
and he would not be able to select text / image .
is it possible to create such a activx picture box . i need it urgently . please reply.as soon as possible
In .NET it is possible to show properties to users of comoponents during runtime. Is there a method to show PropertyPages during Runtime of a VB 6.0 ActiveX control?
Thanks
In .NET it is possible to show properties to users of comoponents during runtime. Is there a method to show PropertyPages during Runtime of a VB 6.0 ActiveX control?
Thanks
Does anyone know how can I convert a *.vbp (visual basic project) into an activex control to use it on a Web Page?
Is it possible to digitally sign an already packaged Active X control or does the code have to be signed.
I want to sign MsRdpClient so we can use it on our intranet without changing IE security settings in the GPO.
Thanks
Hi
very thanx from givig imediate reply.
I have a property page for ActiveX control & if i right click on that control in client design view its show popup menu & in that option is property after clicking it shows the property page what ever i made But the same proprprty page I wan tto display at run time . cos i m developing active control like Rational Rose. so that user ca draw the flow digram & at that time (run time) user can be able to change the property at run time its Background , style etc so it is possible to show the property page at run time
Plz comments
Thanx
Regards
Mahesh
Hi
I want to show the property page at run time for a activeX control so that user can change the backgroun color & style of drawing .
any buddy knows how to show ?
plz comment
Thanx
Regards
adadf
Deep and clear. It was just I was looking for. Great! Thank you.
This thread is for discussions of ActiveX controls.