tags:

views:

621

answers:

2

Does anyone know if it is possible to generate ActiveX properties at run-time?

I only need to be able to get and set these properties from Visual Basic.

My ActiveX control is coded in C++ and I already know how to create properties by implementing hard-coded C++ get and put functions. However I have potentially a large set of properties for this ActiveX control and ideally the property set exposed will change depending on the internal state of the ActiveX control.

I am hoping there is a way to generate properties from data, such as the following XML:

<Properties>
 <Property>
  <Name>SomeProperty</Name>
  <Type>Int</Type>
  <DefaultValue>10</DefaultValue>
 </Property>

 ...

<Properties>

Thanks

A: 

First it is technically possible but involves mucking around with memory and vtables. Not for the faint at heart and definitely not for anything you want to maintain in the long term. COM is designed so that once a interface is defined it is invariant for that revision. Which is why it is vital to update the revision numbers when you add to the interface.

We had to deal with this same issue in creating the costing module for our metal cutting software. Microsoft Scripting's Dictonary was too inflexible so we created a similar set of objects called property. It allows to dynamically add properties to our classes.

Option Explicit

Private priName As String
Private priValue As Variant

Private priProperties As PropertyList

Public Property Get Properties() As PropertyLis
    Set Properties = priProperties
End Property

Public Function Assign(ByVal RHS As Property)
    priName = RHS.Name
    priValue = RHS.Value
    priProperties.Assign RHS.Properties
End Function

Public Function Clone() As Property
    Dim nObject As Property

    Set nObject = New Property

    nObject.Name = priName
    nObject.Value = priValue

    If Not priProperties Is Nothing Then
        nObject.Properties.Assign priProperties
    End If

    Set Clone = nObject

    Set nObject = Nothing
End Function

Public Property Get Name() As String
    Name = priName
End Property

Public Property Let Name(ByVal RHS As String)
    priName = RHS
End Property

Public Property Get Value() As Variant
    Value = priValue
End Property

Public Property Let Value(ByVal RHS As Variant)
    priValue = RHS
End Property

Private Sub Class_Initialize()
    mPropertyType = PropertyTypeEnum.ptVariant
    mPropertyList = New PropertyList
End Sub

PropertyList. Define Item as your default property and NewEnum as your enumerator property.

Option Explicit

Private mCol As Collection

Public Sub Assign(nObject As PropertyList)
    Dim I As Long

    Me.Clear
    For I = 1 To nObject.Count
        Me.AddMember nObject(I).Clone
    Next

End Sub

Public Function Clone() As PropertyList
    Dim nObject As PropertyList
    Dim I As Long

    Set nObject = New PropertyList

    For I = 1 To Count
        nObject.AddMember Me(I).Clone
    Next I

    Set Clone = nObject

    Set nObject = Nothing
End Function

Public Sub AddMember(Item As Property)
    mCol.Add Item, Item.Name
End Sub

Public Sub Add(Name As String, Optional Value As Variant)
    Dim Item As Property
    If Not Defined(Name) Then
        Set Item = New Property

        Item.Name = Name

        If Not IsMissing(Value) Then N.Value = Value
        AddMember N
    Else
        If Not IsMissing(Value) Then Me(Name).Value = Value
    End If
End Sub

Private Sub Class_Initialize()
    Set mCol = New Collection
End Sub

Private Sub Class_Terminate()
    Set mCol = Nothing
End Sub

Public Sub Clear()
    Set mCol = New Collection
End Sub

Public Property Get Count()
    Count = mCol.Count
End Property

Public Property Get Defined(Index As Variant) As Boolean
    Dim Item As Property

    On Error Resume Next

    If IsNumeric(Index) Then
        Set Item = mCol(Index)
    Else
        Set Item = mCol(UCase(Index))
    End If

    If Err.Number <> 0 Then
        Defined = False
    Else
        Defined = True
    End If

End Property

Public Property Get Item(Index As Variant) As Property
    If IsNumeric(Index) Then
        Set Item = mCol(Index)
    Else
        Set Item = mCol(UCase(Index))
    End If
End Property

Public Property Get NewEnum() As IUnknown
    Set NewEnum = mCol.[_NewEnum]
End Property

Our version is more sophiscated than this with sorting and formatting capabilities which is why we didn't use the Microsoft Scripting Dictonary. What this allows us is to dynamically create a hierarchical set of properties. Which has proven useful in implementing a costing module to our software. As we continually add or change field in response to customer input.

This should prove useful in dealing with the flexibility of XML fields. You can use the Vartype function get the exact type of a variant. Optionally you can opt to store strings as VB6, like most BASIC variants, can freely convert between numbers and strings.

RS Conley
+1  A: 

This can be very easy or somewhat difficult depending on the syntax you require.

One way would be to create your own name/values collection in the ActiveX control. You could add just two methods:

HRESULT GetPropery([in] BSTR name, [out,retval] VARIANT value);
HRESULT SetPropery([in] BSTR name, [in] VARIANT value);

Basically you would have ONE property on the control that would contain a collection of all the others. This is the most straight-forward way.

You could instead create a com collection (link assumes ATL, but theres generic info about com collections) property of variants. Make the Item() call of the collection accept strings. Accessing it would be like (the collection is named "Properties"):

myValue = myControl.Properties("Name")

I'm not sure how you could set values like this?

myControl.Properties("Name") = newValue

That may require the collection to return not variants but COM objects with a "default" property. I don't even remember the much of the details of default properties - but I think VB6 clients support them well and all you had to be was set some attributes in your IDL/ODL file.

Both ideas require the callers have that little bit of indirection of a method (Get/SetProperty) or use of the collection property (myobject.Properties.XXXX). If you MUST have syntax like this:

x = myControl.MyDynamticProperty

You'll need to write your own implementation of IDispatch's GetIDsOfName and Invoke. I've done this awhile ago, it was ugly. Thankfully this was all removed since we went a different direction with that part of the application. You'd have to force the callers to use the non-vtable IDispatch interface (and be late-bound) - I suppose this could be easy or hard depending on the calling language. My callers were always VB script so this was not a problem.

Aardvark
Thanks. The first option you mentioned sounds good. IDispatch sounds interesting but I don't really want to get bogged down implementing it... it sounds like a real time sink.
Ashley Davis
The IDispatch thing could be a huge time sink. But I did learn a lot about how IDispatch works. Which still comes in handy in debugging - well ummm OK once it did... ;)
Aardvark