Tutorial 7: Building a Ticker ActiveX Control with the ActiveX Control Wizard

by Michael Rounding

This pages show you how to use the VB wizard to create a custom ticker control. It illustrates the VB wizard in detail, how properties can be dispatched to existing controls within your own control, and how completely new properties can be created. It also shows an interesting (even though its simple) new type of control! The actual ticker example is from Chapter 5 of Custom Controls Library by Rod Stephens (O'Reilly Press).

We assume that you're going to know how to start up VB and start a new project. Its probably best if you have already tried the HelloWorld example. 

Also, download the source if you want to see it work before you do it yourself. We will cover:


The Control Wizard

  1. Start up VB and start a new ActiveX Control project (as you see it in Figure 1, below).

Figure 1: new project dialog
  1. Set the project name to tickerControl (Figure 2):
    1. In the Project Menu, select Project1 Properties
    2. Change the Project Name to tickerControl

Figure 2: project properties dialog
  1. Set up the layout for the control as pictured below in Figure 3:
    1. Add a Timer to the control and call it tmrTicker.
    2. Size and position everything like you see it.
    3. select the control (your selected object should be "UserControl1") Change it's name to ctlTicker (this is what it will be called when you add it to another project.)

Figure 3: the control layout
  1. Start the ActiveX Control Interface Wizard (Figure 4) by:
    1. Go to the Project menu
    2. Select Add User Control from the menu
    3. You'll be presented with a dialog with a few options. One of the options is VB ActiveX Control Interface Wizard. Select that.

Figure 4: how to find the illusive VB ActiveX Control Interface Wizard
  1. Skip the introduction screen. The first real screen you see is the Select Interface Members dialog. You want to have the following items in your Selected names list. Modify it by movins stuff from and to the Available names list (check out Figure 5):
BackColor Property
BorderStyle Property
Click Event
DblClick Event
Enabled Property
Font Property
ForeColor Property


Figure 5: select interface members dialog
  1. The next dialog you get is the Create Custom Interface Members dialog. In it, you can add your own properties or methods that might not come as predefined things in VB. You want to add the following members (guess what? click the New button in Figure 6):
AutoSize Property
Caption Property
Interval Property
RepeatSpacing Property
XChange Property


Figure 6: create some custom interface members!
  1. In the next dialog, you get to map all of your properties and events (if you want) to specific controls and member methods / properties thereof. Below is a list of what you should map for this ticker control thing:
Public Name Maps to Control Maps to Member
AutoSize (none)  
BackColor UserControl BackColor
BorderStyle UserControl BorderStyle
Caption (none)  
Click UserControl Click
DblClick UserControl DblClick
Enabled (none)  
Font UserControl Font
ForeColor UserControl ForeColor
Interval tmrTicker Interval
RepeatSpacing (none)  
XChange (none)  


Figure 7: set mapping for the members of your control
  1. After adding your custom interface members, you are presented with the Set Attributes dialog, which lets you set the attributes for the members you just added. Fill'em in as follows. Note: you can fill in the Description field in the attributes of each member to provide someone using the control with a little bit of feedback about what that property is for.
Public Name Data Type Default Value Run Time Design Time Description
AutoSize Boolean 0 R/W R/W determines whether the control resizes itself to fit the text displayed
Caption String   R/W R/W the text displayed
Enabled Boolean 0 R/W R/W determines whether the control is enabled to scroll text
RepeatSpacing Integer 0 R/W R/W the distance in pixels between occurrences of the repeating caption
XChange Integer 0 R/W R/W the distance by which the text is moved when the interval expires


Figure 8: Set the attributes for the members of your control
  1. Hey, we are done! When you hit the finished button, it'll give you a report and give you an option to save that summary report to a file. It may come in handy as a reference, so save it to wherever you're saving the ActiveX project to. Next up: Code!

Figure 9: and we are done.

 


The Code

Okay, the complete code is spat out here. Here's how to read it:

Option Explicit

' Enumerated data values.
Enum tick_BorderStyle
    None_tick_BorderStyle = 0
    Fixed_Single_tick_BorderStyle = 1
End Enum

'Default Property Values:
Const m_def_RepeatSpacing = 4
Const m_def_XChange = 1
Const m_def_AutoSize = False
Const m_def_Caption = "Ticker..."
Const m_def_Enabled = False
'Property Variables:
Dim m_RepeatSpacing As Integer
Dim m_XChange As Integer
Dim m_AutoSize As Boolean
Dim m_Caption As String
Dim m_Enabled As Boolean
'Event Declarations:
Event Click() 'MappingInfo=UserControl,UserControl,-1,Click
Event DblClick() 'MappingInfo=UserControl,UserControl,-1,DblClick

Dim skip_redraw As Boolean

Dim X As Single
Dim CaptionWidth As Single

Public Property Get AutoSize() As Boolean
    AutoSize = m_AutoSize
End Property

Public Property Let AutoSize(ByVal New_AutoSize As Boolean)
    m_AutoSize = New_AutoSize
    PropertyChanged "AutoSize"

    SizeControl ' Resize if necessary.
End Property

'WARNING! DO NOT REMOVE OR MODIFY THE FOLLOWING COMMENTED LINES!
'MappingInfo=UserControl,UserControl,-1,BackColor
Public Property Get BackColor() As OLE_COLOR
    BackColor = UserControl.BackColor
End Property

Public Property Let BackColor(ByVal New_BackColor As OLE_COLOR)
    UserControl.BackColor() = New_BackColor
    PropertyChanged "BackColor"
    
    DrawText    ' Redraw.
End Property

'WARNING! DO NOT REMOVE OR MODIFY THE FOLLOWING COMMENTED LINES!
'MappingInfo=UserControl,UserControl,-1,BorderStyle
Public Property Get BorderStyle() As tick_BorderStyle
    BorderStyle = UserControl.BorderStyle
End Property

Public Property Let BorderStyle(ByVal New_BorderStyle As tick_BorderStyle)
    UserControl.BorderStyle() = New_BorderStyle
    PropertyChanged "BorderStyle"
    
    SizeControl ' Resize if necessary.
End Property
Public Property Get Caption() As String
    Caption = m_Caption
End Property

Public Property Let Caption(ByVal New_Caption As String)
    m_Caption = New_Caption
    PropertyChanged "Caption"
    
    CaptionWidth = TextWidth(m_Caption)
    If CaptionWidth > 0 And Ambient.UserMode Then
        tmrTicker.Enabled = m_Enabled
    Else
        tmrTicker.Enabled = False
    End If
    
    SizeControl ' Resize if necessary.
    DrawText    ' Redraw.
End Property

Private Sub DrawText()
    ' Do nothing if we're loading.
    If skip_redraw Then Exit Sub
    
    ' Start from scratch.
    Cls
    
    ' Do nothing if the text takes up no room.
    If CaptionWidth <= 0 Then Exit Sub
    
    ' Draw the text.
    CurrentX = X
    CurrentY = 0
    Do While CurrentX < ScaleWidth
        Print m_Caption;
        CurrentX = CurrentX + m_RepeatSpacing
    Loop
    
    ' Shift the starting position (if it's
    ' run time).
    If Not Ambient.UserMode Then Exit Sub
    X = X + m_XChange
    If m_XChange > 0 Then
        If X > 0 Then _
            X = -CaptionWidth - m_RepeatSpacing
    Else
        If X < -CaptionWidth Then _
            X = m_RepeatSpacing
    End If
End Sub
Private Sub SizeControl()
Static resizing As Boolean

    ' Do nothing if the caption is blank.
    CaptionWidth = TextWidth(m_Caption)
    If CaptionWidth <= 0 Then Exit Sub
    
    ' Do nothing unless AutoSize is True.
    If Not m_AutoSize Then Exit Sub
    
    ' Do not recurse.
    If resizing Then Exit Sub
    
    resizing = True
    Size ScaleX(TextWidth(m_Caption) - ScaleWidth, vbPixels, vbTwips) + Width, _
         ScaleY(TextHeight(m_Caption) - ScaleHeight, vbPixels, vbTwips) + Height
    resizing = False
    
    DrawText    ' Redraw.
End Sub
Private Sub tmrTicker_Timer()
    DrawText
End Sub
Private Sub UserControl_Click()
    RaiseEvent Click
End Sub

Private Sub UserControl_DblClick()
    RaiseEvent DblClick
End Sub

'WARNING! DO NOT REMOVE OR MODIFY THE FOLLOWING COMMENTED LINES!
'MappingInfo=UserControl,UserControl,-1,Font
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
    PropertyChanged "Font"

    SizeControl ' Resize if necessary.
    DrawText    ' Redraw.
End Property

Public Property Get Enabled() As Boolean
    Enabled = m_Enabled
End Property

Public Property Let Enabled(ByVal New_Enabled As Boolean)
    m_Enabled = New_Enabled
    PropertyChanged "Enabled"

    ' Enable or disable if it's run time.
    If Ambient.UserMode Then _
        tmrTicker.Enabled() = New_Enabled
End Property

'WARNING! DO NOT REMOVE OR MODIFY THE FOLLOWING COMMENTED LINES!
'MappingInfo=UserControl,UserControl,-1,ForeColor
Public Property Get ForeColor() As OLE_COLOR
    ForeColor = UserControl.ForeColor
End Property

Public Property Let ForeColor(ByVal New_ForeColor As OLE_COLOR)
    UserControl.ForeColor() = New_ForeColor
    PropertyChanged "ForeColor"

    DrawText    ' Redraw.
End Property

'WARNING! DO NOT REMOVE OR MODIFY THE FOLLOWING COMMENTED LINES!
'MappingInfo=tmrTicker,tmrTicker,-1,Interval
Public Property Get Interval() As Long
    Interval = tmrTicker.Interval
End Property

Public Property Let Interval(ByVal New_Interval As Long)
    tmrTicker.Interval() = New_Interval
    PropertyChanged "Interval"
End Property

'Initialize Properties for User Control
Private Sub UserControl_InitProperties()
    skip_redraw = True
    
    m_AutoSize = m_def_AutoSize
    m_Caption = m_def_Caption
    Set Font = Ambient.Font
    m_RepeatSpacing = m_def_RepeatSpacing
    m_XChange = m_def_XChange

    skip_redraw = False
    If Ambient.UserMode Then tmrTicker.Enabled = m_Enabled
    SizeControl ' Resize if necessary.
    DrawText    ' Redraw.
End Sub
Private Sub UserControl_Paint()
    DrawText
End Sub

'Load property values from storage
Private Sub UserControl_ReadProperties(PropBag As PropertyBag)
    skip_redraw = True

    m_AutoSize = PropBag.ReadProperty("AutoSize", m_def_AutoSize)
    UserControl.BackColor = PropBag.ReadProperty("BackColor", &H8000000F)
    UserControl.BorderStyle = PropBag.ReadProperty("BorderStyle", 0)
    m_Caption = PropBag.ReadProperty("Caption", m_def_Caption)
    Set Font = PropBag.ReadProperty("Font", Ambient.Font)
    m_Enabled = PropBag.ReadProperty("Enabled", m_def_Enabled)
    UserControl.ForeColor = PropBag.ReadProperty("ForeColor", &H80000012)
    tmrTicker.Interval = PropBag.ReadProperty("Interval", 0)
    m_RepeatSpacing = PropBag.ReadProperty("RepeatSpacing", m_def_RepeatSpacing)
    m_XChange = PropBag.ReadProperty("XChange", m_def_XChange)
    
    skip_redraw = False
    If Ambient.UserMode Then tmrTicker.Enabled = m_Enabled
    SizeControl ' Resize if necessary.
    DrawText    ' Redraw.
End Sub

Private Sub UserControl_Resize()
    SizeControl ' Resize if necessary.
End Sub

'Write property values to storage
Private Sub UserControl_WriteProperties(PropBag As PropertyBag)
    Call PropBag.WriteProperty("AutoSize", m_AutoSize, m_def_AutoSize)
    Call PropBag.WriteProperty("BackColor", UserControl.BackColor, &H8000000F)
    Call PropBag.WriteProperty("BorderStyle", UserControl.BorderStyle, 0)
    Call PropBag.WriteProperty("Caption", m_Caption, m_def_Caption)
    Call PropBag.WriteProperty("Font", Font, Ambient.Font)
    Call PropBag.WriteProperty("Enabled", m_Enabled, m_def_Enabled)
    Call PropBag.WriteProperty("ForeColor", UserControl.ForeColor, &H80000012)
    Call PropBag.WriteProperty("Interval", tmrTicker.Interval, 0)
    Call PropBag.WriteProperty("RepeatSpacing", m_RepeatSpacing, m_def_RepeatSpacing)
    Call PropBag.WriteProperty("XChange", m_XChange, m_def_XChange)
End Sub

Public Property Get RepeatSpacing() As Integer
    RepeatSpacing = m_RepeatSpacing
End Property

Public Property Let RepeatSpacing(ByVal New_RepeatSpacing As Integer)
    m_RepeatSpacing = New_RepeatSpacing
    PropertyChanged "RepeatSpacing"
    
    DrawText    ' Redraw.
End Property

Public Property Get XChange() As Integer
    XChange = m_XChange
End Property

Public Property Let XChange(ByVal New_XChange As Integer)
    m_XChange = New_XChange
    PropertyChanged "XChange"
End Property