Applies To:
  • CitectSCADA 5.xx
  • CitectHMI 5.xx

What are some of the most common questions about using ActiveX controls in CitectSCADA? 


1. Why doesn’t the example in KB article Q3229 sometime work on the customer’s machine?

The Cicode function CreateControlObject() can use the object’s human readable name, its ProgID, or its GUID to create an instance. To keep backward compatibility, the object’s ProgID and GUID are always the same between different versions. However, the “human readable name” will change when a service patch is applied. For example, “Microsoft ADO Data Control 6.0 (OLEDB)” will change to “Microsoft ADO Data Control 6.0 (SP4) (OLEDB)” after Service Pack 4 is installed. But its GUID - {67397AA3-7FB1-11D0-B148-00A0C922E820}, ProgID – “MSAdodcLib.Adodc.6” remain the same. Therefore, using ProgID or GUID to create an ActiveX control is a better programming practice.

2. Why do I have to reinsert the control each time I make a minor change to the control and rebuild the OCX in VB?

With VB to develop an ActiveX control, you have to maintain "Binary Compatibility" of the control. Otherwise every time you build a new version or rebuild, VB will automatically create a new GUID for the control. It ends up you have to re-insert the control to the Citect page. Please do the following

1. Go to the VB project properties page.

2. Click on "Component" tab.

3. Select "Binary Compatibility" option.

4. Use "Browse" button to point the control that has been inserted in the containers (In your case, the container is CitectSCADA).

Before you rebuild your control, you have to close Citect Explorer and shut down Citect runtime. It is noted that to keep binary compatibility, do not delete or alter any public interfaces (but new public interfaces are allowed). To remove the obsolete ActiveX entries from Registry, please refer to the KB article Q3809.

3. How to avoid reinserting the control if I have to change some interfaces of my control?

There are two ways to insert an ActiveX control on a CitectSCADA page.

1. At design time, use Graphics Builder to insert an ActiveX control on a page. With this method, you will be able to pre-configure all properties (if your control has the customised property page) and set up tag association. The configuration information of an ActiveX control is saved in CTF file. If the ActiveX control interface is modified after its instance is saved with a page, you may have to re-insert and re-configure it. Therefore, you have to document the configuration including tag associations to ensure that the control is configured correctly each time it is inserted on the page.

2. At runtime, use CreateControlObject(sClass, sName, x1, y1, x2, y2, sEventClass) to create an instance and use _ObjectSetProperty(hObject, sProperty, vValue) to set up properties. If you want to associate CitectSCADA tags with the control properties, use ObjectAssociatePropertyWithTag (sObject sPropertyName sTagName sOnChangeEvent). To make them work for you, you have to put all these standard functions into your own function and call it on the "On Page Entry" event. Please refer to the on-line help for how to use these functions. This is a very flexible approach that allows you to send only two files to your customer, OCX and Cicode files each time the control interface is modified. Your customer will simply copy and paste the files, of course the OCX has to be re-registered. The downside of this approach is that you may observe that the page is a little bit slower opening compared to the pre-configured approach with Graphics Builder.

Note: Currently, the ActiveX configuration is stored in the CTG as a blob of data. It would be good if there was some way to export and import this data, so that it can be backed up and restored if the ActiveX object is having problems with a new version. We are seeking a solution to this problem.

4. Why are my associated tags not updated when the values of their corresponding properties are changed?

By default, the tag association is unidirectional. Whenever tag values change, their associated properties will be updated. However, if ActiveX properties change, the associated tags won’t be updated as CitectSCADA doesn’t know whether the properties have changed. To have CitectSCADA automatically update the associated tags whenever the properties change, you need to specify an ActiveX event to each tag association.

However, there is a problem with this bi-directional updating. If an associated tag is in the status of “Read Pending”, its write will be ignored. This occurs intermittently and depends on timing between “read” and “write” queues of associated tags. It is confirmed that there is a problem “Update association on” in “Tag Association” in V550 and earlier versions. The fix will be included in SPA of V542 and V550.

For now, a workaround could be used to avoid this problem, which is to use an event handler to update tag values in Cicode. For example, you want to update Tag1’s value with Property “Value1” on Event Click. Instead of using “Update association on” – Click, you should use


Integer Value;

Value = _ObjectGetProperty(ObjectByName("ANXX"), "Value1");

Tag1 = Value;


5. Why can’t I set an array type property of An ActiveX control in Cicode?

CitectSCADA doesn’t support the array properties in Cicode. If you want to use the control has array type properties, you have to wrap up this control with VB and add new interfaces that are supported by Cicode. For example, you are attempting to use the MS Flex grid ActiveX control with CitectSCADA. The ColWidth property of this control is the array type property. In VB, this property can be set using the syntax MSFlexGrid1.ColWidth(n), where n is the column number (0, 1, 2, etc). To make it work with CitectSCADA, you should do the following.

In VB, the wrapper should have a method, say,

Public Sub SetColWidth(Index As Long, Value As Long)

MSFlexGrid1.ColWidth(Index) = Value

End Sub

In Cicode, you should call this method to set MSFlexGrid1.ColWidth(0) = 100 using

_ObjectCallMethod(ObjectByName("An35"), "SetColWidth", 0, 100);

6. Why can’t I set an array type property of An ActiveX control in CiVBA?

It is confirmed that there is a problem in CiVBA. Take ListBox control of Microsoft Form 2.0 as an example. Set ColumnCount = 3, create a 6x3 array and fill it with values. Results from Excel and CitectSCADA are shown in the following figures. The Excel result is correct as expected. However, CiVBA is not functional correctly although it fills the control with the three column values.

If you encounter the problem of this kind, you have to wrap up the control in the same way as described in the above article.

Figure 1. Result from Excel VBA

Figure 2. Result from CiVBA

7. Why do I sometime receive hardware alarms when I go to my ActiveX page?

The most common cause to this problem is that the initialisation code for the ActiveX control is called in “On page entry” event. This is the timing issue between an inserted ActiveX control and CitectSCADA. If the initialisation code is called before an instance of the control is created on the page, CitectSCADA will generated the hardware alarm "Unrecognized object class or properties" or '355 - Object has no interface - ANXX'. To avoid this problem, you need to raise an event, say, OnShow in UserControl_Show (assume the control written in VB). When this event fires, it will guarantee the instance of has been created. On the CitectSCADA side, you need to implement this event. By default, the Cicode function looks like

FUNCTION PgaeName_ANXX_OnShow(This OBJECT, your event arguments)

Add your initialisation code here……..



Otherwise, the user error handling must be implemented in your initialisation Cicode.

In version 6, a new page event is introduced, <On page shown>. This event will be fired after all objects on the page have been instantiated including ActiveX controls. Now it is safe to put your initialisation code in this event command field.

8. Why do I receive #COM when I access my ActiveX control page?

The most common cause to this problem is the initialisation code for the ActiveX. A typical example is to use ADODB.Connection to connect to a remote MS SQL server in UserControl_Initialize() in your VB ActiveX control. If it takes a long time to get connected or times out, it will cause #COM. The reason is that ActiveX controls are treated as foreground objects. A long running function call in the ActiveX control will impact on the CitectSCADA main thread and block everything else. So in this case, it is better to use asynchronised connection option to connect MS SQL server. As the asynchronised connection uses a different thread to the ActiveX control, CitectSCADA won’t be affected by this long running “task”. To use the asynchronised connection,

1. Declare the connection object with keyword “WithEvents”.

2. Use AsyncConnect option for connection open.

3. Add the code for populating recordset in Event


If you still want to use synchronised connection, consider to reduce the connection timeout. By default, the property of “ConnectionTimeout” is 15 seconds.

9. Why do I receive ODBC errors when I use CiRecipe control with DBF files?

It appears that the ODBC driver for DBF has a problem with releasing Windows resources (Handles). This problem occurs when you frequently open and close the page on which the instance of CiRecipe is running. When the page containing CiRecipe control is closed, the control resources should be released on termination. However, on this occasion, if you start Windows Task Manager and select the “Handle Count” column on the “Process” tab, you will notice that the handle count will increase under Citect32 process. If you have to use CiRecipe with DBF files, don’t close the CiRecipe page and just hide it when you navigate to other pages.

This problem may be caused by Borland Database Engine (BDE) when the ODBC driver for dBase is going through BDE. The following registry setting can be used to disabled BDE. This has been tested on Windows XP SP2 and the handle stops leaking.



10. Why do I need to consider if I use VB6 based ActiveX controls with CitectSCADA?

VB6 is the single thread programming language. CitectSCADA as an ActiveX container will treat ActiveX controls as foreground objects. Thus a running method of ActiveX control will impact on the CitectSCADA main thread. For this reason, it is not recommended that an ActiveX control be running on CitectSCADA IO Servers or critical Citect units, such as trend servers. For example, if you had a control on a Citect trend server that has a long running method, you might experience missing trend samples.

When you develop your ActiveX controls using VB6, don’t forget release references in UserControl_Terminate() event. Moreover, you also need to consider using methods and events to enhance communication between your control and CitectSCADA. For example, you can raise an event in UserControl_Show() to tell CitectSCADA that the control’s window has been added on a page. Once CitectSCADA catches this event, you can call a method to tell your control to change its background colour or draw a picture.

UserControl.Extender.Name is used to identify the Control's current instance, but CitectSCADA doesn’t support the use of this property. So do not use this property in your control.

11. Why can’t I change my ActiveX control’s font in Cicode?

Before v5.41 Cicode doesn’t support the font object as a property in ActiveX control. The example code given in Cicode in the CitectSCADA help doesn’t work. The workaround is to use CiVBA. This bug has been fixed in V5.51 (MegaWatt).


12. Why can't I use tabbed controls in CitectSCADA?

CitectSCADA graphics is based on a single view per page' architecture, and while you can insert tabbed ActiveX controls onto a page in graphics builder with no problems, there is no way to draw graphics onto the individual tabbed pages themselves.

In order to simulate a tabbed dialog in CitectSCADA, you can draw the graphics as required on a single page in Graphics Builder, group them together and then set the visibility property of all the objects in each group using a PageGetInt() call. Create buttons that look like tabs above your graphics that will change the value of the int to make the appropriate graphics visible and all the rest invisible. The only problem with this is that it will be a bit messy in the Graphics Builder because all the pages will need to be stacked on top of each other.

13. Why can't I change the default identification of an ActiveX control in Graphics Builder with CiVBA?

CitectSCADA allows the default identification of an ActiveX control to be changed at design time. But it only works if Cicode is used to refer the control. This is because the identification mapping is designed for runtime only and any access to ActiveX properties and methods by Cicode is through Cicode functions.

As for CiVBA, it uses the dot extension to access ActiveX properties and methods. Since the identification mapping is not started yet, CitectSCADA compiler will still look for the default identification of an ActiveX control and compile errors “Undefined function ……” will be generated.

It is confirmed that there is a problem in CitectSCADA compiler in this regards.

14. How can I generate a Crystal Report on a graphics page?

You can use “Crystal Report Viewer Control” (crviewer.dll) in combination with “Crystal Runtime Application” (craxdrt.dll). Here is the example code that shows you how to generate a Crystal report using CiVBA. Note that this example uses version of crviewer.dll that is shipped with Crystal Report 8.

  1. Insert “Crystal Report Viewer Control” on a graphics page
  2. Add a button on the page
  3. In the command field, type


CreateReport "C:\Citect\Data\Reports\OperatorInput.rpt"

  1. Save the page as “CRViewer”
  2. Add the following code to your project.

Sub CreateReport(Byval sReportTemplate As string)
Dim crpApp As Object
Dim crpReport As Object

    'Clear the source, or “Memory full” error if func called twice
CRViewer_AN35.ReportSource = crpReport

    'Create an instance of crystal runtime application
Set crpApp = CreateObject("CrystalRuntime.Application")

    'Open Report Template and create a report object
Set crpReport = crpApp.OpenReport(sReportTemplate, 1)

    'Discard any saved data

    'Set report source, CRViewer_AN35 is the default instance name
CRViewer_AN35.ReportSource = crpReport

Set crpApp = Nothing
Set crpReport = Nothing

End Sub

To extend the above functionality, add the following function to your code and a button on the page calling this function. Now you should be able to navigate folders and RPT files, and display a selected Crystal Report.

FUNCTION OpenCrystalReport()
sFile = FormOpenFile("Open", "*.RPT", "Report Files (*.RPT)|*.RPT|");
IF FileExist(sFile) THEN
VbCallRun(VbCallOpen("CreateReport", sFile));

Please note that a care must be taken when implementing Crystal Report functionality in CitectSCADA. As the viewer is regarded as a foreground object, don’t implement it on a critical CitectSCADA box, such as alarm, trend or IO servers. Do not draw significant data to the Crystal Report viewer, using parameters to limit the reported data.

To customise the appearance of the Crystal Report Viewer control, please refer to its documentation shipped with Crystal Report package.

15. How can I print out a Crystal Report without user prompt?

In this application, only the “Crystal Runtime Application” (craxdrt.dll) is required in code. Here is the example code that shows you how to do it.

Sub PrintReport(Byval sReportTemplate As string)
Dim crpApp As Object
Dim crpReport As Object

'Create an instance of crystal runtime application
Set crpApp = CreateObject("CrystalRuntime.Application")
'Open Report Template and create a report object
Set crpReport = crpApp.OpenReport(sReportTemplate, 1)
    'Discard any saved data

'Prints the report to default printer
'PrintOut([promptUser],[numberOfCopy],[collated],[startPageN], [stopPageN])
crpReport.PrintOut False, 1, False, 1, 1

Set crpApp = Nothing
Set crpReport = Nothing
End Sub

The above example is to print the first page of the report on the default printer. Note that if your report has parameters to be provided, you have to implement the “ParameterFields” property of report object and pass the desired values; otherwise the user prompt for parameter entries will pop up. For details, please refer to the documentations shipped with Crystal Report, version

It is also possible to automate report printout using a CitectSCADA event. To do so, you have to call this VB function via a Cicode function as the event engine cannot call VB functions directly. For example, your Cicode function is defined as

FUNCTION PrintCrystalReport()
VbCallRun(VbCallOpen("PrintReport", "C:\Reports\Inventory.rpt"));

Now you can add function “PrintCrystalReport” in the action field in your report trigger event.

16. How can I use the Microsoft Text Voice control in CitectSCADA to convert text to speech?

The “Text Voice” ActiveX control is used to convert text to voice like a “document reader”. It is also very useful in the alarm system with speakers.

The control (VText.dll) is located in C:\Windows\Speech for WindowsXP and C:\Winnt\Speech for Windows2000. For Windows NT, you have to download this control from the Microsoft website. The example code is given here to show how to implement it in Cicode.


    sClassID = "{2398E32F-5C6E-11D1-8C65-0060081841DE}";
    //ProgID "Vtext.Vtext.1"
goVText = CreateObject(sClassID);

// Function converts text to voice
_ObjectCallMethod(goVText, "Speak", sText);

Add VTextInit in the start up code of your project. As goVText is declared as a global variable, the object will remain in existence until the variable either has another object assigned or is set to NullObject.

It is noted that this functionality cannot be implemented in CiVBA as the module variables are not supported in CiVBA.

17. Why can’t I see some properties on the property list for tag association?

This will depend on how attributes of these properties are set up in the ActiveX control. For example, a property can be hidden from Property Browser, but it can still be accessed at runtime. If VB6 is used to develop a control, use Procedure Attributes to enable the “Don’t show in Property Browser” attribute for a selected property.

Some generic properties are deliberately removed from the property list as they are not supported by CitectSCADA, such as “Visible” and “Left”. If you use Cicode to toggle this “Visible” property, you will get a hardware alarm of “invalid parameter”. However, if you use CiVBA to set this property, the error will be logged in the Kernel main window saying “Class does not support Automation or does not support expected interface”. It is noted that Cicode and CiVBA are different code processes and some error handlings may not be aligned yet.

18. How do I hide, resize and reposition my control at runtime?

The interfaces for resize, visibility and movement of controls are not implemented in CitectSCADA as an ActiveX container. So you cannot use properties of an ActiveX control, “Visible”, “Width” and “Height” to manipulate its visibility or size.

Graphically, an ActiveX control is treated as a CitectSCADA object. So it is possible to use the object properties to achieve these functionalities. For example, add a tag to Object Properties - “Scaling (Vertical / Horizontal)”, and change its value to resize the control. Similarly use a digital tag to toggle the control visibility, and use “Movement (Vertical / Horizontal)” properties to change the control’s location on a page.

The “Movement” properties are equivalent to the control’s “Top” and “Left” properties. The “Scaling” properties are equivalent to the control’s “Width” and “Height” properties and the “Visibility” property is equivalent to the control’s “Visible” property.

See also KB Article Q3899 and Q3900

19. Why can't I ignore optional arguments of an Active control method in Cicode?

CitectSCADA does not support optional arguments of a method in Cicode. So you have to provide each optional argument required by a method when _ObjectCallMethod is used. Take the ListBox control of Microsoft Form 2.0 as an example. Method AddItem has two arguments and both of them are optional as shown below.

Sub AddItem([pvargItem], [pvargIndex])
Member of MSForms.ListBox

If you use Microsoft Object Browser, it can be seen that any arguments enclosed by [ ] indicate they are optional. Here is an example how to use this method in Cicode. Assume ListBox is inserted at AN35 on a graphics page and 10 Items are to be added with two leading 0 in the index.

INT Index;
STRING sItemName;
    FOR Index = 0 TO 9 DO
        sItemName = "Item " + Index:#00;
        _ObjectCallMethod(ObjectByName("AN35"), "AddItem", sItemName, Index);

The result is shown in the following Figure. Please note that the ListBox index is zero based and must be continuous.

20. Why doesn’t enabling the “Persist ActiveX Data between Page Transitions” option for the MS Forms ListBox control perform the way it implies?

There is a misunderstanding about the <Persist ActiveX Data between Page Transitions> option implemented in CitectSCADA V6. This option is used to save the current settings of an ActiveX control through public interfaces to a temporary data file which will then be reloaded to the ActiveX control when the page is re-opened. To achieve persistence of ActiveX control’s data, the control must have public properties and they should be

  1. Read / Write.
  2. Single value
  3. CitectSCADA supported data type

How the control behaviors after the saved properties have been reloaded will depend on the control itself. In the example of ListBox control, the populated data is cached in its own memory and will be lost after the control is destroyed on page change. If the data is not implemented through property interfaces, CitectSCADA cannot retrieve the data and persist it. However, if the ListBox control is implemented in the way that all of its items are loaded from a data source, such as a file or SQL table, then a property of this control could be used to reload the items from the data source on the property change event. In this case, the data of ListBox control will be persisted.

In summary, the <Persist ActiveX Data between Page Transitions> option will only persist data of the properties that meet the above requirement.

21. Why does one of my ActiveX control methods work differently after having upgraded from V5.42 to V6.00?

It appears to be the problem of automation interfaces.

Prior to version 6, we did not check type of parameters passed to an ActiveX control method. So data are always copied back to the passed parameters regardless. But increasing demands for richer and better performing ActiveX controls have meant that this policy has had to be strictly enforced from version 6 onwards, as the performance increases are, in relative terms, reasonably large. For these reasons, we do not copy data after the automation call unless the parameter is marked with the [out] in the interface type information. Similarly if marked as [out] but not [in] we do not bother copying data before the automation call.

If your ActiveX control is written in VB6, you have to clearly define the parameter as ByRef in the method of your ActiveX control if the method makes any changes to this parameter. It is noted that if you don’t declare the type, arguments or parameters in Visual Basic are ByRef by default. So if data of parameters need to be passed in, the parameters must be declared as ByVal.