Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

.NET

Visual Basic by Remote Control


SEP95: Visual Basic by Remote Control

Bob is an independent software developer who has implemented many Windows applications. He can be reached at 30 E. Huron, #1310, Chicago, IL 60611.


Visual Basic continues to establish itself as a popular development tool for Microsoft Windows, and, predictably, developers continue to push the envelope of what Visual Basic and its associated Control Development Kit (CDK) can do.

Recently, I needed to create a Visual Basic custom control that would mimic the standard Data control, but for a non-SQL, non-ODBC database. The control would have to communicate with the database via an API set, and communicate with other Visual Basic controls in order to build queries and display query results. I wanted all communication with the other controls to be handled automatically at the C level, so that the people adding my control to their Visual Basic forms would not have to do any Basic programming.

Communication with the database API was straightforward, but communication with the other controls turned out to be unexpectedly challenging. I finally got my control to work the way I wanted, but only after learning a lot about some unusual and incompletely documented Visual Basic features. I ended up writing a collection of functions to simplify access to control properties; I used these functions to write a diagnostic VBX that acts like a Spy program for Visual Basic controls, displaying a list of properties and their data types, flags, and values.

In this article, I'll describe how to access control properties at run time, and present my collection of access functions. I'll also describe my diagnostic control, CTRLINFO.VBX, which is available electronically; see "Availability," page 3.

Foreign-Control Properties

To make my custom control perform queries by example, I wanted it to poll all TextBox fields on the form, using their DataField property for column name and their Text property for column value. After the query result returned, I wanted my control to replace the Text property with the value for that column in the query result. This process required calling VBGetControlProperty() to get the Text and DataField properties to build the query, and then calling VBSetControlProperty() for the Text property to display the result.

The problem is that VBGetControlProperty() and VBSetControlProperty() require that you know the index of the property being accessed. Property indexes are created as #defines in a control's header file, and generally are not documented or otherwise made available to the outside world. (In particular, the index for a standard property is not the corresponding "IPROP_STD_" constant defined in VBAPI.H.) And even if you do happen to know a property index for a foreign control, the control's designers are free to change the index from one version of the control to the next.

The only reliable way to determine a property index for a foreign control is through direct access to the control's property array.

The Property Array

At design time, you define a control's properties by creating an array of 2-byte integers. The array entry for a custom property will be a "true" (near) address of a PROPINFO structure; the entry for a standard property will be a "fake" address defined in VBAPI.H. The array is part of the control's MODEL structure, which is passed to VBRegisterModel() when the control is created. Example 1 is a code fragment from a control's header file defining an array containing one standard and one custom property.

A property's index, as passed to VBGetControlProperty(), is actually its index in this property array. To get to the property array at run time, you first call VBGetModel() to obtain the address of the control's MODEL structure. The property array is one of the fields of this structure. Example 2 shows how to walk through the array.

Example 2 also reveals some subtleties in dealing with near and far pointers. All pointers obtained from the MODEL structure are near pointers, but to data residing in the MODEL structure's segment. An application needs to turn these near pointers into far pointers by using the MAKELONG() macro.

Getting Property Information

The last section showed how to obtain entries in a control's property array. The entry for a custom property is the address of a PROPINFO structure, which contains all the information you need about the property, including its name, data type, and flags. However, the entry for a standard property is just a constant defined in VBAPI.H, which your application has to interpret somehow. We need a simple method for obtaining a true PROPINFO structure for any property, either custom or standard; this would allow an application to deal uniformly with all properties.

Listing One contains my solution to this problem: I provide a lookup array containing the "PPROPINFO_STD_" constant and a corresponding PROPINFO structure for each of the 42 standard properties defined in VBAPI.H; the PROPINFO structure contains the property's name and data type and has the rest of its fields initialized to 0.

The data types of the standard properties are not specified in the Visual Basic documentation. Some are listed in a technical fax available from Microsoft; I had to discover the rest by trial and error. Some of the data types are surprising: For example, the x- and y-dimensional properties, which are always integers, store their values as floats rather than shorts or longs.

Three data types in my array are left as 0, for "unknown." The None property really doesn't have a data type, because it only acts as a place holder for a property that was dropped from the array since a prior version of the control. The Name and DataSource properties, which really ought to have useful values, have data types of zero because their values as returned by VBGetControlProperty() don't seem to be meaningful. (A call to Microsoft's technical-support line confirmed that these property values are not available to a VBX at run time.)

Listing One contains three functions to access property information. GetPropertyArray() is called once and returns the address of the control's property array. GetProperty() is called for each desired property, and always returns a far pointer to a PROPINFO structure. In the case of a custom property, this pointer is the address of the "real" structure; in the case of a standard property, the pointer is the address of a structure in my lookup array. IsStandardProperty() can be called to determine whether a property is standard or custom.

Finding a Property by Name

My original interest in control properties was to get or set their values using VBGetControlProperty() and VBSetControlProperty(). As discussed previously, these functions require a property's index, which is generally unknown for a foreign control. What is known, however, is the property's name. So as a practical application of Listing One, Listing Two contains a function called GetPropertyIndex(), which returns the index of a named property.

GetPropertyIndex() makes one call to GetPropertyArray() and then uses GetProperty() to examine the name of every property in the array. It returns either the index of the property with the desired name, or -1 if no such property is found.

Note that the near/far pointer problem is still here, but that the segments have changed. The property name is a near pointer to a string living in the same segment as the (far) property address returned by GetProperty(). MAKELONG() is used again, to make a far pointer with the correct segment.

The CTRLINFO Diagnostic Control

Getting and setting foreign-control properties really isn't practical without a diagnostic tool for determining the properties available, their data types, the values they take, and how and when these values are set. As a more extensive application of the functions in Listing One, I wrote a diagnostic custom control called "CTRLINFO.VBX."

This control is added to a Visual Basic form at design time. At run time, it creates a dialog box displaying model information and a list of property names for any control under the cursor; double-clicking on one of the listed property names brings up a second dialog box showing in-depth information about the property: whether it is custom or standard, its data type (and flags, for a custom property), and its value. The display is similar to that of Microsoft's Spy program. The code for this VBX is available electronically (see "Availability," page 3).

Conclusion

Accessing foreign-control properties is an underused but extremely effective technique. The utilities in Listings One and Two and the CTRLINFO.VBX diagnostic tool make this technique simple and practical for anyone writing a custom control. The results can be rewarding. With a lot less work than you think, you can give your existing API-based resources a healthy new life as Visual Basic controls.

Example 1: Defining a property array containing one standard and one custom property.

// PROPINFO structure for custom property
PROPINFO Property_Session =
{
        "Session",
        DT_HSZ | PF_fGetData | PF_fSetData,
        OFFSETIN(MYCONTROL, Session), 0, 0, NULL, 0
};
// property indices
#define IPROP_MYCONTROL_NAME      0
#define IPROP_MYCONTROL_SESSION   1
// property array
PPROPINFO MyControl_Properties[] =
{
         PPROPINFO_STD_NAME,     // fake address
         &Property_Session,      // real address
         NULL
};
Example 2: Processing a control's property array.
void ProcessPropertyArray (HCTL hctl)
{
        MODEL FAR     *lpModel;
        PPROPINFO FAR *lppPropInfo; // far ptr to array of near pointers
        LPPROPINFO     lpPropInfo;
        lpModel = VBGetControlModel(hctl);
        lppPropInfo = (PPROPINFO FAR*) MAKELONG(
                              lpModel->npproplist, (_segment)lpModel);
        for (; *lppPropInfo; lppPropInfo++)
        { 
               if (*lppPropInfo >= PPROPINFO_STD_LAST)
               {
                     // process *lppPropInfo as a fake address
                     // for a standard property defined in VBAPI.H
                }
                else
                {
                     lpPropInfo = (LPPROPINFO) MAKELONG(
                                       *lppPropInfo,                                                       (_segment)lpModel);
                     // process lpPropInfo as a real address of a
                     // PROPINFO structure
                }
        }
}

Listing One

/************************************************************************
 *  CtrlProp.cpp -- Utilities for getting information about Visual Basic
 *  controls' properties                             by Bob Sardis, 1995
 ************************************************************************/
#include <windows.h>
#include <vbapi.h>
#include "CtrlProp.h"
// StandardPropery -- holds information for a 'PPROPINFO_STD_' property
typedef struct
{
        PPROPINFO FakeAddress;
        PROPINFO  PropInfo;
} StandardProperty;
StandardProperty StandardProperties[] =
{
        PPROPINFO_STD_NAME,              {"Name",          0},
        PPROPINFO_STD_INDEX,             {"Index",         DT_SHORT},
        PPROPINFO_STD_HWND,              {"Hwnd",          DT_SHORT},
        PPROPINFO_STD_BACKCOLOR,         {"BackColor",     DT_COLOR},
        PPROPINFO_STD_FORECOLOR,         {"ForeColor",     DT_COLOR},
        PPROPINFO_STD_LEFT,              {"Left",          DT_XPOS},
        PPROPINFO_STD_TOP,               {"Top",           DT_YPOS},
        PPROPINFO_STD_WIDTH,             {"Width",         DT_XSIZE},
        PPROPINFO_STD_HEIGHT,            {"Height",        DT_YSIZE},
        PPROPINFO_STD_ENABLED,           {"Enabled",       DT_BOOL},
        PPROPINFO_STD_VISIBLE,           {"Visible",       DT_BOOL},
        PPROPINFO_STD_MOUSEPOINTER,      {"MousePointer",  DT_ENUM},
        PPROPINFO_STD_CAPTION,           {"Caption",       DT_HSZ},
        PPROPINFO_STD_FONTNAME,          {"FontName",      DT_HSZ},
        PPROPINFO_STD_FONTBOLD,          {"FontBold",      DT_BOOL},
        PPROPINFO_STD_FONTITALIC,        {"FontItalic",    DT_BOOL},
        PPROPINFO_STD_FONTSTRIKE,        {"FontStrikeThru",DT_BOOL},
        PPROPINFO_STD_FONTUNDER,         {"FontUnderline", DT_BOOL},
        PPROPINFO_STD_FONTSIZE,          {"FontSize",      DT_REAL},
        PPROPINFO_STD_TABINDEX,          {"TabIndex",      DT_SHORT},
        PPROPINFO_STD_PARENT,            {"Parent",        DT_LONG},
        PPROPINFO_STD_DRAGMODE,          {"DragMode",      DT_ENUM},
        PPROPINFO_STD_DRAGICON,          {"DragIcon",      DT_SHORT},
        PPROPINFO_STD_BORDERSTYLEOFF,    {"BorderStyleOff",DT_ENUM},
        PPROPINFO_STD_TABSTOP,           {"TabStop",       DT_BOOL},
        PPROPINFO_STD_TAG,               {"Tag",           DT_HSZ},
        PPROPINFO_STD_TEXT,              {"Text",          DT_HSZ},
        PPROPINFO_STD_BORDERSTYLEON,     {"BorderStyleOn", DT_ENUM},
        PPROPINFO_STD_CLIPCONTROLS,      {"ClipControls",  DT_BOOL},
        PPROPINFO_STD_NONE,              {"None",          0},
        PPROPINFO_STD_HELPCONTEXTID,     {"HelpContextID", DT_SHORT},
        PPROPINFO_STD_LINKMODE,          {"LinkMode",      DT_ENUM},
        PPROPINFO_STD_LINKITEM,          {"LinkItem",      DT_HSZ},
        PPROPINFO_STD_LINKTOPIC,         {"LinkTopic",     DT_HSZ},
        PPROPINFO_STD_LINKTIMEOUT,       {"LinkTimeout",   DT_SHORT},
        PPROPINFO_STD_LEFTNORUN,         {"LeftNoRun",     DT_XPOS},
        PPROPINFO_STD_TOPNORUN,          {"TopNoRun",      DT_YPOS},
        PPROPINFO_STD_ALIGN,             {"Align",         DT_ENUM},
        PPROPINFO_STD_IMEMODE,           {"ImeMode",       DT_BOOL},
        PPROPINFO_STD_DATASOURCE,        {"DataSource",    0},
        PPROPINFO_STD_DATAFIELD,         {"DataField",     DT_HSZ},
        PPROPINFO_STD_DATACHANGED,       {"DataChanged",   DT_BOOL},
        NULL,                            {"",              0},
};
PROPINFO UnknownStdProp =                {"UNKNOWN_STD",       0};
/*****************************************************************
 *  Function:     GetPropertyArray()
 *  Description:  Gets property array for a Visual Basic control
 *  Parameters:   hctl -- handle to control
 *  Returns:      far pointer to property array
 *****************************************************************/
 PPROPINFO FAR * GetPropertyArray (HCTL hctl)
{
        PPROPINFO FAR *lppPropInfo = NULL;
        MODEL FAR *lpModel = VBGetControlModel(hctl);
        if (lpModel)
        {
             lppPropInfo = (PPROPINFO FAR*)
                    MAKELONG(lpModel->npproplist, (_segment)lpModel);
        }
        return lppPropInfo;
}
/*****************************************************************
 *  Function:     GetProperty()
 *  Description:  gets specified property of a Visual Basic control
 *  Parameters:   PropertyArray -- array returned by GetPropertyArray()
 *                index         -- index of control, from 0
 *  Returns:      far pointer to a PROPINFO structure;  for a standard
 *                property, this will be a pointer into StandardProperties[]
 *****************************************************************/
LPPROPINFO GetProperty (PPROPINFO FAR * PropertyArray, short index)
{
        PPROPINFO FAR *lppPropInfo = PropertyArray + index; // offset addr
        if (*lppPropInfo == NULL)
           return NULL;
        if (IsStandardProperty(*lppPropInfo))
        {
               // 'standard' property pointers are not real addresses;
               // need to search StandardProperties[] list
               StandardProperty *pStdProp;
               for ( pStdProp = StandardProperties; 
                     pStdProp->FakeAddress; 
                     pStdProp++)
               {
                    if (*lppPropInfo == pStdProp->FakeAddress)
                        return &(pStdProp->PropInfo);
               }
               return &UnknownStdProp; // standard property not found
        }
        else
        {
               return (LPPROPINFO) MAKELONG(
                                 *lppPropInfo, (_segment)PropertyArray);
        }
}
/*****************************************************************
 *  Function:     IsStandardProperty()
 *  Description:  determines whether a property is a standard property,
 *                as defined in VBAPI.H
 *  Parameters:   lpPropInfo -- pointer to PROPINFO structure
 *  Returns:      TRUE if property is standard, FALSE otherwise
 *****************************************************************/
BOOL IsStandardProperty (LPPROPINFO lpPropInfo)
{
        StandardProperty *pStdProp;
        NPPROPINFO pPropInfo = (NPPROPINFO)LOWORD(lpPropInfo); // near ptr
        
        if (pPropInfo >= PPROPINFO_STD_LAST)
              return TRUE;    // pPropInfo is a 'fake' address in VBAPI.H
           
        for (pStdProp = StandardProperties; pStdProp->FakeAddress; pStdProp++)
        {
             if (pPropInfo == &(pStdProp->PropInfo))
                return TRUE; //pPropInfo is contained in StandardProperties[]
        }   
        if (pPropInfo == &UnknownStdProp)
             return TRUE;    // pPropInfo is an 'unknown' standard property
        return FALSE;        // property is not standard
}   

Listing Two

/*****************************************************************
 *  Function:     GetPropertyIndex()
 *  Description:  Gets index of named Visual Basic control property
 *  Parameters:   hctl         -- handle to control
 *                lpszPropName -- property name
 *  Returns:      index of property; returns -1 if property not found
 *****************************************************************/
short GetPropertyIndex (HCTL hctl, LPSTR lpszPropName)
{
       PPROPINFO FAR * PropertyArray;
       LPPROPINFO lpPropInfo;
       short i;
       LPSTR lpsz;
       PropertyArray = GetPropertyArray(hctl);
       if (!PropertyArray)
              return -1;
       for (i = 0; ; i++)
       {
              lpPropInfo = GetProperty(PropertyArray, i);
              if (!lpPropInfo)
                     break;  // have reached end of PropertyArray[]
              lpsz = (LPSTR) MAKELONG(
                           lpPropInfo->npszName, (_segment)lpPropInfo);
              if (!lstrcmp(lpsz, lpszPropName))
                     return i;
       }
       return -1;


Copyright © 1995, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.