Microsoft Common Controls via FlexCOM 2 - part 1

A Data Access Worldwide White Paper
by Vincent Oorsprong

August 12, 2003
Last Edited: January 23, 2004

This white paper is the first one in a series that was written to demonstrate the usage of FlexCOM2. To learn more about COM and Visual DataFlex a hands on training is available, contact your local Data Access representative. To demonstrate the usage of COM via FlexCOM, a connection to the Microsoft Windows Common Controls ActiveX library was made. The library contains a number of controls such as ListView, ImageList, ImageCombo, TabStrip, ToolBar, Treeview, ProgressBar, StatusBar, Slider, Animation, DateTimePicker, MonthView, FlatScrollBar, UpDown, CoolBar. In this white paper, we will concentrate on the ImageCombo control. Once you have read this white paper, you should have all the knowledge to implement it yourself.

Important! The ActiveX library used in this White Paper (mscomctl.OCX file) is installed with Microsoft Visual Basic 6.0. According to the information in the Microsoft Knowledge Base Article - 194784 (INFO: Controls Shipped in Visual Basic 6.0), which can be read in full at http://support.microsoft.com/default.aspx?scid=kb;en-us;194784,  you will need to have at least the Learning Edition of that product installed in order to be able to use the controls from that OCX file.

Contents

ImageCombo

The ImageCombo control is a ComboForm which has a list of selectable options, just like any other ComboForm, but in addition each list item has an image in front of the item and the data can be indented so that a tree can be shown. This makes the control in fact a combination of a Treeview and a ComboForm, where the ComboForm is already a combination of a List and a Form control. Below you will see how you can build the control and what special actions you have to be aware of.

First off, let's state that you may not have a need for this control. The usage is not very common in a database application like you are used to building with Visual DataFlex. Using ComboForms in a data entry application is quite limited by the static nature of a ComboForm. For Visual DataFlex programs we advise the use of a more dynamic way to select a record; the selection list (lookup).

Results


The above picture shows the end result of the steps outlined in this white paper. It includes the following components:

The ImageCombo is the control on the left that displays the treeview of Visual DataFlex sample workspaces. This control is one of the ActiveX controls imported into Visual DataFlex when you follow the steps described in the next section.

The three Form controls to the right are used to display the information about a workspace and will be updated while browsing through the workspaces in the ImageCombo control. 

The checkbox at the bottom, labeled "Lock the image Combo Control", is a toggle that has comparable results to the Entry_State property in the Visual DataFlex ComboForm and dbComboForm classes. The Entry_State property in these classes enables or disables entering information in the form part of the ComboForm. So, if Entry_State is False, you cannot enter new information; you can only select from existing values. The best default is to not allow entering new information, because you need to have a way to add the new information to the list part as well. That issue is more complex and outside the scope of this white paper.

The two non-visible controls -- an Array and an ImageList -- play important roll when using the ImageCombo (read more details in the next sections).

Steps to Create the View Shown Above

Follow the document and you should be able to create the view as shown in the results section.

Import the ActiveX control
The first action to complete is importing the ActiveX control into the Visual DataFlex Studio. This is done via the "Import ActiveX Control..." option, which can be found on the Tools pulldown menu. The following dialog will be shown:

The ImageCombo control is part of the Microsoft Windows Common Control 6.0 OCX library listed. While it is possible to remove some classes from the list of classes found, it is better to let FlexCOM generate code for all of them because:

  1. If you remove classes now and want to use them later, you still have to generate the package for the other ones and remove duplicated code manually.
  2. You will need some of the other classes for the next whitepaper on using FlexCOM.

Note: It is recommended that you generate the package (and the Studio subclass files) in the workspace subdirectories instead of the global area. When copying a workspace to a different computer, globally generated and registered classes tend to be forgotten quite easily by developers, and this would provide the application from compiling on the new computer.

The next step is to design the view and place the controls we need on it. At design time, the view should look like this:

Now you can see the two non-visible controls displayed -- the view has two more controls than the five controls shown in the first picture. 

Make sure you create all the objects from the correct class and name them as listed below:

Class / Type Name
Array oWorkspaces
cComImageList oImageList
cComImageCombo oImageCombo
Form oWorkspaceNameForm
Form oWorkspaceDescriptionForm
Form oWorkspacePathForm
Checkbox oLockedCheckbox

Note that the ImageList object is not a standard Visual DataFlex cImageList object, but instead derived from the cComImageList class (from the same ActiveX library that the ImageCombo comes from). We need the ImageList object to load pictures from disk to display in the list portion of the ImageCombo control.

The ImageList Object
Since the images are a very important part of the ImageCombo control (it is even in the name), we will focus on this class first. 

The cComImageList class can load images, like the standard VDF cImageList class, and store the information about the images for retrieval by another control. We cannot use the standard cImageList class because an object of this class does not have the iDispatch interface ID required by COM. Other differences between the COM variant and the standard cImageList control are:

The ImageList object visible in picture 3 is derived from the cComImageList class and can be dragged from Studio's Controls Palette to your view. Before we can load images to the object, we need to code an aggregated object in Code Explorer. 

The images of the cComImageList are loaded via a COM method (ComLoadPicture) in an object of the class cComListImages. If you ever want to address individual images you need to create an object of the cComListImage class. Since there is no need in this whitepaper to address the individual images we do not need an object of this class. The cComListImages and cComListImage are aggregated objects and thus will the COM interface be automatically created by the outer object. If we need to address one of the aggregated objects we need to create a DataFlex object, query the outer object for the COM dispatch id (there is no standard message/property so you need to examine documentation and/or class methods) and set the pvComObject property of the DataFlex object to this value.

Creating the Visual DataFlex counter part of the aggregated objects as children of their parent is not needed. The COM part has no idea how we handle it from Visual DataFlex. Nested creation may give a better overview of the nested structure in COM. But since the peNeighborhood property of the COM objects is set to nhPrivate you will find out that nesting of the Visual DataFlex objects will make addressing the objects more difficult. Instead of using just the object name you need to code the path to the object. For this whitepaper we address the aggregated object of the class cComListImages from within the cComImageList object and therefore the best place to add the following code is inside the oImageList object:

// This object is used to store the images in the image list
// it is a child object of cComImageList.
Object oListImages Is A cComListImages
End_Object // oListImages

The images must be loaded after the COM object has been instantiated. This means via the OnCreate event. Keeping procedures and functions small will make the programs easier to read and therefore we load the images in a method called DoLoadImages. In that method we set the properties that describe the size of the images in the OnCreate event. So we will add this code at the same location (oImageList object):

// This method will be used to initialize the image list. Properties of a
// COM control cannot be set before a Dispatch ID is retrieved.
// We set the size of each image to 16x16 pixels
// We set the transparent color to clFuchsia (in this control ALL bitmaps 
// do need to have the same transparency color)
Procedure OnCreate
    Forward Send OnCreate

    Set ComImageHeight To 16
    Set ComImageWidth To 16
    Set ComMaskColor To clFuchsia

    Send DoLoadImages
End_Procedure // OnCreate

The classes cComImageList and cComListImages do not have a function to load a graphic from disk so we need the FlexCOM method ComLoadPicture to load the pictures. This FlexCOM method first checks if the picture is embedded as resource in our compiled Visual DataFlex program and loads from there. If the picture is not embedded, the method will search the path in the runtime attribute DF_OPEN_PATH.. The method will return, on success, a dispatch ID to the loaded image. The result will be placed in a variable of the Variant data type, called vPicture in our program. The picture is loaded in the COM object via the ComAdd method, a member of the cComListImages class. If you want to address the just loaded picture, you can use the vListImage dispatch ID. 

 Add the following code to the cComImageList object below the green line:

// This method will first retrieve the dispatch ID for the child object that is used
// to store the images. When this is not 0, we will assign it to the child object. Then, we
// use the FlexCOM 2.0 message ComLoadPicture to load an image. This method respects the
// DF_OPEN_PATH attribute and also checks the include bitmap and icon resources in the Visual
// DataFlex EXE file. ComAdd seems not to support, like the Visual DataFlex cImageList,
// loading of multiple images at the same time cutting them in pieces by the height and the
// width.
Procedure DoLoadImages
    Variant vListImages vListImage vPicture
    Integer hoListImages

    Move oListImages To hoListImages

    Move (NullComObject ()) To vListImages
    Get ComListImages To vListImages
    If (Not (IsNullComObject (vListImages))) Begin
        Set pvComObject Of hoListImages To vListImages

        Move (NullComObject ()) To vPicture
        Get ComLoadPicture "WorkspaceGroup.Bmp" To vPicture
        If (Not (IsNullComObject (vPicture))) Begin
            Get ComAdd Of hoListImages Nothing Nothing vPicture To vListImage
        End
        Else Begin
            Error DFERR_OPERATOR "Cannot load WorkspaceGroup.Bmp"
        End

        Move (NullComObject ()) To vPicture
        Get ComLoadPicture "Workspace.Bmp" To vPicture
        If (Not (IsNullComObject (vPicture))) Begin
            Get ComAdd Of hoListImages Nothing Nothing vPicture To vListImage
        End
        Else Begin
            Error DFERR_OPERATOR "Cannot load Workspace.Bmp"
        End
    End
End_Procedure // DoLoadImages

The above code initializes the variant variables that will contain the iDispatch IDs. Always do this when there is the possibility for  the call to a COM method to fail. If you do not do this and the call fails, the variant will have its initial status. Since that is not the same as an iDispatch ID, the compare with IsNullComObject would fail with an error telling you that you have done an invalid data type conversion. 

As mentioned, if the ComLoadPicture is successful, the vPicture variant variable contains a handle that we can use with the ComAdd method defined in the cComListImages class. That method takes 3 parameters and the first 2 are optional. 

The first parameter can be used to insert the image at a specific location in the image list and, if omitted (we pass Nothing to indicate this), will add the image to the end of the list. 

The second argument makes it possible to give the image a unique string identifier (key). You can search on this keyvalue if you need. If you start using it, you need to pass a unique value or you will receive an error while loading. Because this unique string identifier also applies to the later described cComImageCombo, it is important that you know about this and also about the fact that it needs to be a string and may not start with a number (undocumented but generating an error). 

The Pictures
The two pictures mentioned above can be found - combined into one - in the bitmaps directory of your Visual DataFlex development set. You need to open the bitmap file with a tool like paint and store each of the pictures as individual files in the bitmaps directory of your workspace. Store the blue "W" as WorkspaceGroup.Bmp and the red "W" as Workspace.Bmp.

The Array Object
An array object is used in this example to store the enumerated information from the workspaces.ini file. This way we do not need to retrieve the data over and over again from the INI file. For each ImageCombo item, we will store 3 items in the array. Therefore, add the following method to the array object (which you created by dragging the Array control from the Studio's control palette)  below the green line.

// This method stores the workspace key (e.g. "DAW.Sample Applications.Order.ws"), the
// Description (read from the .WS file) and the path to the .WS file. Each 3 items of the
// array are seen as one element. The method returns the item number of the first value
Function AddWorkSpaceInfo String sFullWorkspaceKey String sDescription String sPath Returns Integer
    Integer iWorkspaceId

    Get Item_Count To iWorkspaceId

    Set Array_Value Item iWorkspaceId To sFullWorkspaceKey
    Set Array_Value Item (iWorkspaceId + 1) To sDescription
    Set Array_Value Item (iWorkspaceId + 2) To sPath

    Function_Return iWorkspaceId
End_Function // AddWorkSpaceInfo

The Form Objects
The color of the Form objects will give the user the idea that the controls have their Enabled_State set to false. However, if you set the Enabled_State to false, the objects will refuse getting the focus, and we don't want this --  we need to give them the focus in order to enable the select and copy functions of these objects. 

So, we need to set the Forms' colors to clBtnFace and their Entry_State to false. Using this combination of property settings, the user cannot enter a value in the Form object while selecting and copying the information is still possible. 

To set the Forms' colors to clBtnFace, use the Properties panel and change the Color property. After that, we need to set  the Entry_State to false. To do this, add the following code to each of the Form objects below the green line:

// Do not allow the user to enter text. We use this property instead
// of Enabled_State to make it possible to scroll through the data and
// select a value to copy. Note that the color of the control is set
// to the usual color for disabled controls.
Set Entry_State Item 0 To False

The Entry_State needs the item parameter otherwise it will not disable data entry.

The Checkbox Object
The checkbox is a regular Visual DataFlex Checkbox object, but when checked, it will try to change a property of the cComImageCombo control that can best be compared with the Visual DataFlex Entry_State property. Because we want to use the cComImageCombo for selecting a workspace and do not want the user to enter new information, the default will be "locked". The property ComLocked can only be executed if the COM portion of the oImageCombo object has been created, hence the checking for the validity of the vImageCombo variant contents.

We will represent this by setting the Checked_State property of the Checkbox object. So, add the following code to the Checkbox object below the green line:

// To reflect the default of the "locked" property we set in the activating
// method of oImageCombo via the checked_state. At this moment we cannot
// query the COM control because it is not yet instantiated.
Set Checked_State To True

// When the user clicks the checkbox, the "locked" property of the
// combo will be set to true/false.
Procedure OnChange
    Boolean bChecked
    Integer hoImageCombo
    Variant vImageCombo

    Get Checked_State To bChecked
    Move oImageCombo To hoImageCombo
    Get pvComObject Of hoImageCombo To vImageCombo
    If (Not (IsNullComObject (vImageCombo))) Begin
        Set ComLocked Of hoImageCombo To bChecked
    End
End_Procedure // OnChange

Note: Since the method OnChange is automatically added to a checkbox object when you create one with drag and drop from the controls palette, it is easy to forget to add the code to set the default appearance of the checkbox object to match with the setting done in OnCreate of the ImageCombo object.

The ImageCombo Object
Finally, the ImageCombo object. For those readers that jump directly to this description part, the above code contains important information as well.

The cComImageCombo object has an aggregated object which can be derived from the cComComboItems class. The object contains the methods to address each individual combo item, both for accessing existing items and for adding new items. The individual items are addressed via a different aggregated object of the cComComboItem class. So add the following code to the cComImageCombo object below the green line:

// The ComboForm has a child object with the combo items. This
// object is used to add combo items.
Object oComboItems Is A cComComboItems
End_Object // oComboItems

// The combo items object has a child object to individually
// address the combo items. In this sample this object is used to retrieve
// the key value to display the workspace information.
Object oComboItem Is A cComComboItem
End_Object // oComboItem

The pvComObject property of the oComboItems object will be set in the procedure DoFillWithData described below. The pvComObject to "talk" to individual combo items will be set each time we do address the comboitem, for example, in the method OnComClick.

The OnCreate Event
Usually, you set runtime properties and load data (see the cComImageList class above) in the OnCreate event. However, if you do this with the cComImageCombo object  you will never see data in the list portion of the control. As far as I can see, this is a failure in the control, since no error is given when loading data in the OnCreate event. Instead of OnCreate, we use the Activating event, which is sent -- like OnCreate, only once, but on a later moment in time (when the object is activated for the first time). Add the following code to the cComImageCombo object under the object listed above:

// When this control is activated, we will make the control not accept any
// user entered data ("locked" property) and we will read in the data.
Procedure Activating
    Forward Send Activating

    Set ComLocked To True

    Send DoFillWithData
End_Procedure // Activating

Filling the Object with Data
As written earlier in this paper, the data shown in this example are the workspaces from the central file named workspaces.ini. This is done because the grouping feature of workspaces makes it an ideal example for using the ImageCombo control in the Visual DataFlex environment. 

The routine in this example to load the data is called, as you can see in the Activating event, DoFillWithData. The code below should be added to the cComImageCombo object before the Activating method:

// This method reads the file workspaces.ini (usually this file is found in the BIN directory)
// into an object instantiated from cIniFile. All the sections of the INI file (the information
// between the square brackets) are stored, via ReadSections, in an array object. After this
// enumeration all the items of the array are sorted. This is done to make sure that the
// workspaces with the same path are listed in the same order (we can say that is true when the
// path is equal to the last path we have a path value we do not have to store). For each
// workspace, the description and the path to the .WS file is retrieved. This information is
// stored in the oWorkspaces array. The paths part of the each workspace is stored via the
// DoAddWorkspacePath method and the name of the workspace is stored here. Each workspace
// gets a unique ID (the item number of the oWorkspaces array) and this is used to fill
// the key property of the combo item. This property does not like numeric values and thus,
// we place the constant string "WS" in front of it. Furthermore, the key property must have
// a unique value or be empty. So a value for the groups like "GROUP" is not ok.
Procedure DoFillWithData
    String sWorkspace sFileName sWorkspacePath
    String sPreviousWorkspacePath sFullWorkspaceKey
    String sDescription sPath sWorkspaceId
    Integer hoIniFile hoArray hoComboItems iWorkspaceId
    Integer iItem iItems iSeparatorPos iIndentCount hoWorkspaces
    Variant vComboItems vComboItem vImageList

    Move oComboItems To hoComboItems
    Move oWorkspaces To hoWorkspaces

    Get ComComboItems To vComboItems
    If (Not (IsNullComObject (vComboItems))) Begin
        Set pvComObject Of hoComboItems To vComboItems

        Get pvComObject Of oImageList To vImageList
        Set ComImageList To vImageList

        Get Create U_cIniFile To hoIniFile
        Get Create U_Array To hoArray

        Move "Workspaces.Ini" To sFileName
        Get_File_Path sFileName To sFileName
        If (sFileName <> "") Begin
            Set psFileName Of hoIniFile To sFileName
            Send ReadSections Of hoIniFile hoArray
        End

        Send ComClear Of hoComboItems
        Send Delete_Data Of hoWorkspaces
        Send Sort_Items Of hoArray

        Move (Item_Count (hoArray) - 1) To iItems
        For iItem From 0 To iItems
            Get String_Value Of hoArray Item iItem To sWorkspace
            Get ReadString Of hoIniFile sWorkspace "Description" "" To sDescription
            Get ReadString Of hoIniFile sWorkspace "Path" "" To sPath
            Move sWorkspace To sFullWorkspaceKey
            Get LastSeparatorPos sWorkspace To iSeparatorPos
            If (iSeparatorPos > 0) Begin
                Move (Left (sWorkspace, iSeparatorPos)) To sWorkspacePath
                If (sWorkspacePath <> sPreviousWorkspacePath) Begin
                     Move sWorkspacePath To sPreviousWorkspacePath
                     Send DoAddWorkspacePath sWorkspacePath hoComboItems
                End
                Move (Right (sWorkspace, Length (sWorkspace) - iSeparatorPos)) To sWorkspace
                Get WorkspaceLevels sWorkspacePath To iIndentCount
            End
            Else Begin
                Move 0 To iIndentCount
            End
            Get AddWorkSpaceInfo Of hoWorkspaces sFullWorkspaceKey sDescription sPath To iWorkspaceId
            Move ("WS" - String (iWorkspaceId)) To sWorkspaceId
            Get ComAdd Of hoComboItems Nothing sWorkspaceId sWorkspace 2 2 iIndentCount To vComboItem
        Loop

        Send Destroy Of hoIniFile
        Send Destroy Of hoArray
    End
End_Procedure // DoFillWithData

Let us examine the above code. The first 2 MOVE lines retrieve the object ID of the aggregated oComboItems object and the array object that we use to store the workspace data. 

In the next step, we dynamically create an object of the cIniFile class and an object of the Array class using the Create method. The cIniFile object is used to open and enumerate the workspace names found in the workspace.ini file. If we examine the workspaces.ini file, we will see data like:

[DAW.Examples.Windows.Big]
Description=Big Sample Application
Path=c:\program files\visual dataflex 9.1\Examples\Windows\Big\Programs\

[DAW.Examples.Windows.Contact]
Description=Contacts Sample Application
Path=c:\program files\visual dataflex 9.1\Examples\Windows\Contact\Programs\

The above shows 2 sections from the workspaces.ini file. Each section can have a number of values. The number of values is always 2: the workspace description and the path to the .WS file. The enumerated workspace sections from the INI file are stored in the array object whose ID is passed to the ReadSections function call. After reading all the section names, we will sort the values so that all workspaces with equal portions are put together. 

The sorting and erasing of existing data from the COM control will be followed by the loop that gets the section names one by one and reads the information values (description and path). In order to determine the correct number of indents for the list portion of the cImageCombo object, we determine the position of the last dot in the workspace name. This is done in the following function (add it to the cComImageCombo object, right before DoFillWithData method):

// This method searches for the last separator in the passed string. In workspace
// keys this is a dot (as in "DAW.Sample Applications.Order"). This method will
// be called to split the string into substrings.
Function LastSeparatorPos String sWorkspace Returns Integer
    Integer iLength

    Move (Length (sWorkspace)) To iLength
    While (iLength > 0)
        If (Mid (sWorkspace, 1, iLength) = ".") Begin
            Function_Return iLength
        End
        Decrement iLength
    End

    Function_Return 0
End_Function // LastSeparatorPos

Since all workspace names are sorted, the information in the array object should look something like:

DAW.Examples.Advanced Components.COMSamples
DAW.Examples.Advanced Components.DataDefinition
DAW.Examples.Advanced Components.INetTransfer
DAW.Examples.Advanced Components.MAPI
DAW.Examples.Advanced Components.OrgTreeView
DAW.Examples.Advanced Components.XML-Sample
DAW.Examples.Advanced Components.Embedded SQL
DAW.Examples.Web.WebAppSample91
DAW.Examples.Windows.Big
DAW.Examples.Windows.Contact
DAW.Examples.Windows.Order
DAW.Examples.Windows.Wine
DAW.Utilities.CodeMnt
DAW.Utilities.VDFOEMAnsi

The shared part of each value shown above is the portion to the left of the last dot. As soon as anything in this portion changes the workspace is part of another grouping and the number of dots inside the workspacepath need to be counted again. This information must be added to the cComComboItems (oComboItems) object. The number of dots is counted via the function call WorkspaceLevels. The code of this function is (add this one also to the cComImageCombo object right after the LastSeparatorPos method):

// This method counts the number of dots in the string. This
// way we know how many levels there are.
Function WorkspaceLevels String sWorkspacePath Returns Integer
    Integer iLength iPos iLevels

    Move (Length (sWorkspacePath)) To iLength
    For iPos From 1 To iLength
        If (Mid (sWorkspacePath, 1, iPos) = ".") Begin
            Increment iLevels
        End
    Loop

    Function_Return iLevels
End_Function // WorkspaceLevels

// This method will add each part of the workspace path. The key property of the
// comboitem has no value for us so we pass a nothing. The imagelist contains two
// pictures and we use image number 1 for the path part.
Procedure DoAddWorkspacePath String sWorkspacePath Integer hoComboItems
    Integer iIndentCount iSeparatorPos
    String sWorkspace
    Variant vComboItem

    Move 0 To iIndentCount

    While (sWorkspacePath <> "")
        Move (Pos (".", sWorkspacePath)) To iSeparatorPos
        If (iSeparatorPos > 0) Begin
            Move (Left (sWorkspacePath, iSeparatorPos - 1)) To sWorkspace
            Move (Right (sWorkspacePath, Length (sWorkspacePath) - iSeparatorPos)) To sWorkspacePath
        End
        Else Begin
            Move "" To sWorkspace
            Move "" To sWorkspacePath
        End
        If (sWorkspace <> "") Begin
            Get ComAdd Of hoComboItems Nothing Nothing sWorkspace 1 1 iIndentCount To vComboItem
            Increment iIndentCount
        End
    End
End_Procedure // DoAddWorkspacePath

The information of each workspace is added to the oWorkspaces object via the AddWorkspaceInfo method. This method returns the item number in the array where the data starts. This item number is coded into a key value for the combo item by appending this number to the constant string "WS". This is done because the COM object does not allow us to use a number as key value -- the value of the key is used to find the workspace information when we point at an item. 

The ComAdd function call finally adds the data to the COM object. The first parameter tells the COM object whether it should insert the data rather than append the data. If no value is specified, as in our code, the data is appended. If you want to insert it, you need to specify here where you want to do the insert. The 2 parameters passed to ComAdd are used to tell which image from the image list needs to be used for the value. The first tells the COM object which image to use for normal display and the second tells it what image to use when the user cursor is sitting on the item.

Now that we have loaded the COM object with data, the user needs to start using it. When the user opens the list and browses through the items or when the list is closed and the arrow up/down keys are pressed, the COM object notifies us about this event by sending the OnComClick message. So our OnComClick method (add it to the cComImageCombo object right after the Activating method) will be:

// When the user selects a combo item with the mouse or the keyboard this
// event occurs. In this method we find out if the user select a workspace
// or one of the branches. This is done via the value retrieved from the
// Key property. When there is a value, we will get the number out of this
// value. The control does not allow to store a number as key, it has to be
// a string. The keys are WS<nnn> (just an enumerated number). The combo
// items that are used to store the branches do not have a key and are empty.
Procedure OnComClick
    Variant vComboItem
    Integer iWorkspaceInfoId hoWorkspaces
    String sWorkspaceInfoId sWorkspaceName sWorkspaceDescription sWorkspacePath

    Get ComSelectedItem To vComboItem
    If (Not (IsNullComObject (vComboItem))) Begin
        Set pvComObject Of oComboItem To vComboItem
        Get ComKey Of oComboItem To sWorkspaceInfoId

        If (sWorkspaceInfoId <> "") Begin
            Move (Right (sWorkspaceInfoId, Length (sWorkspaceInfoId) - 2)) To iWorkspaceInfoId

            If (iWorkspaceInfoId >= 0) Begin
                Move oWorkspaces To hoWorkspaces

                Get String_Value Of hoWorkspaces Item iWorkspaceInfoId To sWorkspaceName 
                Get String_Value Of hoWorkspaces Item (iWorkspaceInfoId + 1) To sWorkspaceDescription 
                Get String_Value Of hoWorkspaces Item (iWorkspaceInfoId + 2) To sWorkspacePath

                Set Value Of oWorkspaceNameForm To sWorkspaceName 
                Set Value Of oWorkspaceDescriptionForm To sWorkspaceDescription 
                Set Value Of oWorkspacePathForm To sWorkspacePath
            End
        End
        Else Begin
            Set Value Of oWorkspaceNameForm To ""
            Set Value Of oWorkspaceDescriptionForm To ""
            Set Value Of oWorkspacePathForm To ""
        End
    End
End_Procedure // OnComClick

The code starts with the retrieval of the selected item. The returned value is not a number but an iDispatch ID, which we will assign to the oComboItem object. After that assignment we can get information from the item. 

We have stored the workspace key name, the description and path in an array object and we keep track of the starting item by the value in the Key property of the oComboItem object. The ComKey function call returns something like "WS1" or "WS99" and we need to strip off the "WS" part. We do this by taking the right part of the key minus 2 characters (the length of "WS"). The result should be the item number to read from. If so, we will get the data from the array object and display it in the 3 Form objects.

Final Steps
Now that we have created all the objects and inserted the code shown in this paper, we can compile the sample and see if it all works. 

The source code for the view is not available for download,  this will make you go through all the steps at least once and it should help you learn how to implement a control like ImageCombo. The next white paper in this series will be about another Microsoft Windows common control.

Credits
Special thanks to my colleagues Dennis Piccioni and Marcia Ferreira for proof reading and making sure you can succesfully implement this control.

Additional Reading

Contacting Data Access Worldwide

Data Access Worldwide
14000 SW 119 Ave
Miami, FL 33186
305-238-0012
Domestic Sales: 800-451-3539
Fax: 305-238-0017
email: sales@dataaccess.com
Newsgroup Server: news.dataaccess.com
Internet: http://www.dataaccess.com

Data Access Worldwide - Asia Pacific
Suite 5, 333 Wantirna Road, Wantirna VIC 3152 Australia
Phone: +61 3 9800 4233 f: +61 3 9800 4255
Sales: asiapacific@DataAccess.com
Support: support.asiapacific@DataAccess.com
Internet: http://www.DataAccess.com.au

Data Access Worldwide - Brasil
Av.Paulista, 1776 - 21st.Floor
São Paulo -SP - Brazil
CEP 01310-921
Phone: 5511-3262-2000
Fax 5511-3284-1579
Sales: info@dataaccess.com.br
Support: suporte@dataaccess.com.br
Internet: http://www.dataaccess.com.br

Data Access Worldwide - Europe
Lansinkesweg 4
7553 AE Hengelo
The Netherlands
Telephone: +31 (0)74 - 255 56 09
Fax: +31 (0)74 - 250 34 66
Sales: info@dataaccess.nl
Support: support@dataaccess.nl
Internet: http://www.dataaccess.nl

Data Access Technical Support
800-451-3539 / 305-232-3142
email: support@dataaccess.com
Visit our Support Home page to see all of our Support options: http://www.dataaccess.com/support

Data Access Technical Knowledge Base
http://www.dataaccess.com/kbase
Many answers to technical problems can be found online in the Data Access Technical Knowledge Base. Here, you can access the same live data that Data Access Worldwide technical support and development staff use to enter and track technical articles.

Copyright Notice
This document is property of Data Access Corporation. With credit to Data Access Corporation for its authorship, you are encouraged to reproduce this information in any format either on paper or electronically, in whole or in part. You may publish this paper as a stand alone document within your own publications conditional on the maintenance of the intent, context, and integrity of the material as it is presented here.

DataFlex is a registered trademark of Data Access Corporation.

NO LIABILITY FOR CONSEQUENTIAL DAMAGES
To the maximum extent permitted by applicable law, in no event shall Data Access Corporation be liable for any special, incidental, indirect, or consequential damages whatsoever (including, without limitation, damages for loss of business profits, business interruption, loss of business information, or any other pecuniary loss) arising out of the use of or inability to use any information provided in this document, even if Data Access Corporation has been advised of the possibility of such damages. Because some states and jurisdictions do not allow the exclusion or limitation of liability for consequential or incidental damages, the above limitation may not apply to you.