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

Windows 3.0 Application Development


NOV90: WINDOWS 3.0 APPLICATION DEVELOPMENT

WINDOWS 3.0 APPLICATION DEVELOPMENT

A single tool just won't cut it anymore

Walter Knowles

Walt is a programmer for Asymetrix Corp. and can be reached at 110 110th Avenue N.E., Suite 717, Bellevue, WA 98004.


For users, Windows 3.0 has significantly increased the visibility of Graphical User Interfaces (GUIs). For programmers, however, creating sophisticated Windows applications has proven to be no mean feat. Even the simplest Windows programs require the overhead of message reception and dispatch functions, and demand that programmers master new interfaces. To go beyond the "simple" dialog-box/built-in control level of interface, programmers must often write their own code for manipulating text, identifying the location clicked on by the mouse, and displaying graphics.

Furthermore, Windows, and indeed any GUI, pays a penalty in speed and program size for its user friendliness, and programmers usually need to compensate by making data storage as compact and efficient as possible. This was certainly the case with the checking account manager which I describe in this article. This application provides cash-based accounting for an individual or small business by letting the user balance multiple checkbooks and distribute transactions across multiple categories of expenses.

This checking account manager consists of setup screens -- for accounting information, names and addresses of check recipients, recurring entries, and so on. The part of this application that I'll focus on is a general check entry screen that uses data from these setup screens (see Figure 1). Key to this user interface are the combobox-style selectors which let the user select transaction types, check payees, and distribution accounts. These comboboxes allow the user direct access to the database, based on either the name or code order. Another very significant element is the visual representation of the checkbook register that lets the user actively select which check to edit.

The Development Process

Our development team consisted of an accountant (who did the specification research), a User-Interface (UI) designer, two ToolBook programmers, and a Windows/C programmer. Although ours was a five-member team, there's no reason why one or two broadly disciplined individuals couldn't have accomplished what we did.

ToolBook was the front-end for the application because of its graphical richness, programming language, and relatively straightforward interface to Windows Dynamic Link Libraries (DLLs). For the database engine, we chose db_VISTA III Windows DLL because of its relatively small size and significant speed advantage over the more traditional relational or flat-file manager. For optimizing our ToolBook/db_VISTA application, we used the Windows SDK and Microsoft's C 5.1 compiler. We also used the SDK reference material to guide our interface to the Windows kernel functions for interfacing ToolBook and db_VISTA.

After we had defined the basic functionality for the program, the accountant sketched the screens using ToolBook's drawing tools. Because our main "user" developed the basic components of the UI, we avoided the usual cycles of capturing the user's requirements and refining the idea.

Because ToolBook provides a functional flat-file database with its record field object, we took the UI designer's screens, put some minimal functionality behind them, and had a functional application prototype up and running in slightly more time than it would have taken using a slide show demo program. We then tried out the program on "real" users to test our general conceptions and data flows.

While the UI designer was busy polishing the screens and menus, the programming team focused on implementing the database in its final form. ToolBook's internal object-oriented database was ideal for the prototype, but, like all OODBMSs, the overhead of using ToolBook's database for a traditional "data processing" database appeared to be too high.

As part of its OpenScript language, ToolBook provides an interface that allows links to external database engines which can store data outside the ToolBook OODBMS. (Additionally, the interface allows manipulation of the Windows functions, including Windows memory management.) To use this interface, the external functions must be in a Windows-compatible DLL and must not need to call back into the instance that calls them. This task is made significantly easier if the external functions use the Pascal calling convention (as is typical of Windows functions).

In reality, a checking account manager is a hierarchically-structured complex accounting system. Although this structure is more clearly modeled with a hierarchical database than a relational one, we needed more flexibility than a traditional hierarchical database provides. We also needed some relational features (keys, for example), and some common features of small, fast datasets with multiple owners. And, given our time constraints (we had to prove the technology and deliver the application to the client on schedule) we needed a working Windows 3.0-compatible DLL. The db_VISTA database engine provided this.

Building the Database

Our basic database design goal was to make the performance fast. To compensate for the overhead display of Windows and ToolBook with their complex graphics (for example, ToolBook pages take up more disk space than db_VISTA records, and disk access is understandably slower), we knew we could optimize data access so that the users would feel the system was responsive. Because the fastest way to access data in a hierarchical database is through the set pointers, we defined a database that has more small sets than might be usual for an application of this size.

Ordered sets, because they are doubly-linked lists, degrade linearly with the number of entries in the set. We thus designed the database so that the typical maximum number of members of an ordered set would be 150, and the average number of members would stay below 50. This necessitated making some of the sets "sets-of-sets."

We then used keys for access across multiple sets. Because keys are used primarily for reporting rather than for transaction processing, we were willing to have slightly slower access. The end result was the design shown in Figure 2. We then coded this database in db_VISTA's data definition language. (See Listing One, page 136.) The first part of the definition defines three database files and a key file. db_VISTA could have combined all records into one file, but to minimize disk usage, we chose to separate them. The second section of the definition defines the record structures in a rather C-like language; the third defines the sets we are using. We used two orderings of sets:

  • Ascending, which orders the set from lowest to highest in either the numeric or text collation sequence. This ordering can exact a penalty in insertion because it forces a linear search of the link list for the insertion point.
  • Next, which inserts the new record into the set sequence directly after the current record. This "ordering" is very fast on the insertion, but not readily searchable.
db_VISTA provides three other orderings: first, last, and descending. Sets can be ordered (for example in payor_accountcode) by more than one field, as long as the fields are in the same record.

Coupling db_VISTA and ToolBook

Once the database was designed, we had to hook up db_VISTA with ToolBook. ToolBook's OpenScript language provides three means for linking to and using a DLL: LinkDLL, function calls, and pointers and pointer conversions.

LinkDLL identifies the functions in the DLL that ToolBook will call and specifies the DLL which contains them. Functions are then prototyped using the data types for which ToolBook has built-in coercion. (See Table 1.)

Table 1: DLL functions called by ToolBook

  BYTE     unsigned char
  INT      signed int
  WORD     unsigned int
  LONG     signed long
  DWORD    unsigned long
  FLOAT    float
  DOUBLE   double
  POINTER  void far *.   STRING   LPSTR, or void far **.

* ToolBook displays a pointer as segment, offset

**ToolBook requires a string to be zero terminated.


Listing Two (page 136) is the Link-DLL structure for the check manager application where we link to four DLLs: The Windows 3.0 kernel for memory management functions; ToolBook's tbkfile.dll, to check the drive and directory; db_VISTA's vista.dll for database management (we use about half the functions of the DLL in this application); and chekmate.dll, our own helper.DLL.

Once a function has been linked in a LinkDLL structure, the function is called just like an OpenScript intrinsic function. If we call db_VISTA's find first member of set function in C, we use something like error = dt_findfm (PAYEE_ACCOUNT, hCurrentTask, nDBnum); In ToolBook, it's much the same: set vError to dt_findfm(20002, svhCurrentTask, svDBnum).

Most DLLs pass data back and forth by reference as well as by value; db_VISTA is no exception. OpenScript provides a pointer dereferencing function for each of its coercible data types. For example, the function for converting to and from a LONG is pointer-LONG. To dereference the second element of a db_VISTA database address array (they are longs declared as DBA payee[20];), we would set the second element to pointerLong(4,payee) and the third to get pointerLong(8, payee-,39421).

Because ToolBook has its own internal values system, you can't directly pass by reference. When your DLL requires pointers instead of values (and db_VISTA does), you need to allocate Windows global memory that both ToolBook and your DLL can use.

Memory Management Calls

ToolBook is "just" a Windows program, and db_VISTA is "just" a Windows DLL. That means that all memory has to be manipulated through Windows' kernel.EXEs memory management functions. The five key functions are listed in Table 2.

Table 2: Memory management functions

  Function       Description
--------------------------------------------------------------------------

  GlobalAlloc    Returns a handle to memory.  Handles are put in a table,
                 so that memory can be moved around unless locked
                 down.  You can't directly use the allocated memory unless
                 you "lock it down" and get a pointer to it.

  GlobalReAlloc  Resizes a memory block.  This function returns a new
                 handle which may be the same as the old one.

  GlobalLock     Takes a handle and returns a pointer to fixed
                 memory.  Because Windows is multitasking, you don't want
                 to lock memory for any longer than absolutely necessary.

  GlobalUnLock   Unlocks the memory so that it can move.  Because the
                 memory can move, this function invalidates the pointers
                 to the block.

  Global Free    Frees the memory so that it can be reused.

Because all of these calls can fail under low-memory conditions, check that your calls were executed and recover if not. We haven't included this error checking in the examples, but Listing Three (page 138) shows how it could be done. InitGlobals allocates and locks the three global memory blocks we use as sharable, zero initialized memory. FreeGlobals does the cleanup. It is called when the system shuts down. The ToolBook system variables, which point to the Windows globals, are set to null so that the ToolBook coercion system will trap uninitialized memory pointers for us, rather than sending them off to Windows to cause general-protection violations.

Global Memory Blocks and Communication with the DLL

Because ToolBook's values are not directly available to the DLL world, we constructed two primary global blocks. The first is a simple 255-byte buffer that we can pass back and forth between ToolBook and db_VISTA. You'll see it in the listings as svptrDbBuffer. Since ToolBook is a protected-mode only application, leaving the memory locked makes no difference to Windows use.

The other main block is a structure which is mainly an array of handles to Windows memory. To maximize performance, we needed to buffer a number of relatively small blocks of data, particularly the combobox selectors for accounts and vendors. The structure definition for this control block is in Listing Four (page 138). The code and name array handles point to these selectors and the database addresses for each of these selector fields.

We could access this block by using the pointer function when we need it, but it's better to centralize setting and getting values, particularly when the structure is changing. Listing Five (page 138) is the accessor code for the ToolBook side of this control block. The first to get handler, or ToolBook function to get hControl, returns the value of the individual element by getting the offset through the ControlOffset function and dereferencing the structure. The other handler in this example is orthogonal to the get function to set hControl and uses the same ControlOffset function to get the offset before using the second form of the pointer functions to set values in the control block.

Shell Calls to db_VISTA

The db_VISTA engine, which was designed to be called from a C program, provides several header files to make the interface in a multiple-tasking environment such as Windows simple and clean. db_VISTA requires the calling program to specify both a pointer to an instance control block and the database identifier. In order to emulate the simplicity, we began to shell calls to db_VISTA so that we only needed to pass the relevant parameters inside our main program. We also used to set and to get handlers to provide parallelism in the function structure. Because of space constraints, the actual shell code for general-database access is not included with this article but is available electronically.

Optimizing

While ToolBook, like any other general-purpose programming environment does many things well, there are areas that, either for speed or ease of maintenance, require optimization. We identified the key areas, then applied a good dose of "vitamin C." The most important step in any optimization is to identify what really needs to be optimized. We identified three key areas where optimization would give us noticeable performance benefits:

  • Putting the text and database addresses of the combobox selectors in global memory
  • Building and maintaining the check register image in global memory
  • Maintaining account balances
The combobox selectors are on the check-image page and on various other pages. We could have left them there, but they need to be updated throughout the system, and this takes time. We elected to keep these in global memory and copy them into ToolBook fields when needed.

By keeping the database addresses available, we can access the account and payee information quickly; we can also connect records quickly without having to actually retrieve them from the database. It is significantly faster to dereference the 25th element of an array than to get the 25th item of a ToolBook list. Therefore, we put this information in global memory. We first implemented this optimization using ToolBook's OpenScript. (For those of you who are interested, this code is also available online and on disk.)

After we completed all optimizations, we came back and optimized our ToolBook optimization in MS C 5.1 as shown in Listing Seven on page 139. (The original ToolBook version appears in Listing Six, page 138.) This code demonstrates one of the best uses of ToolBook as a prototyping tool. Except for breaking CreateSelectorList into two functions, there is a direct correspondence in the C code and the OpenScript code. By prototyping even those functions, which we knew would be recoded in C, we saved several compile-link-test cycles in ToolBook's interactive environment.

We had planned from the start to manage the register and its balances in a helper.DLL. While computation in ToolBook is no slouch, we could get a substantial increase in speed by using integer arithmetic and array manipulation in the helper.DLL rather than ToolBook's floating-point arithmetic and string manipulation.

Product Information

ToolBook v. 1.0 Asymetrix Corp. 110 110th Ave. N.E., Ste. 717 Bellevue, WA 98004 206 462-0501

db_VISTA III v. 3.15 Raima Corp. 3245 146th Pl. S.E. Bellevue, WA 98007 206-747-5570

Windows 3.0 Windows 3.0 SDK Microsoft C 5.1 Microsoft Corp. One Microsoft Way Redmond, WA 98052-6399 800-227-4679

There are three columns in a check register: deposits, withdrawals, and balances. We stored this as a 3-by-n array of longs (longs give us +/- $21 million, definitely large enough for most people's checkbooks!). Because the balances are never stored in the database, we calculate them on-the-fly from the registerNumbers array. (You can get the balance maintenance code online or on disk.)

We planned to maintain the check register image and its database address array in global memory as part of the initial design. The check register, as you can see in Figure 1, is an array of the check number, date, description, whether or not the check has been reconciled, the deposit, withdrawal, and balance amounts. We build each line for a given month in the helper.DLL and once all of the lines are created, we copy the image into a ToolBook field. An important part of this optimization is the same technique which we used with the combobox text: We maintain an array of database addresses which corresponds to the register. This allows us to directly access a check by its database address. (The code for building the register is also available online.)

Conclusion

By using development tools where they have their greatest strengths--ToolBook for implementing a graphically sophisticated interface, db_VISTA for handling raw data, and C for optimization--programmers can bring an application from conception through implementation in much less time than is required for typical GUI applications. Even with the overhead of a large development and delivery environment such as ToolBook, we were able to provide solid performance by carefully choosing where to optimize and gaining the greatest advantage from our optimization effort.

Acknowledgments

The C code examples to this article are the effort of the Windows developer on our team, Leslie Gardenswartz, and some of the best aspects of the checkbook manager's internal design are due to his efforts.

_WINDOWS 3.0 APPLICATION DEVELOPMENT_ by Walter Knowles

[LISTING ONE]

<a name="0245_0011">

/********************************************************
 * FILE: chekmate.ddl
 * DESCRIPTION: db_Vista database definition language schema
 *********************************************************/

database chekmate {
/* File definition section */
    data file checks1 = "chekmate.d01" contains system, payor, payee,
                                                tranPayee, account;
    data file checks2 = "chekmate.d02" contains transaction, actual;
    data file checks3 = "chekmate.d03" contains budget, distribution;

    key file checkKey = "chekmate.k01" contains strPayorName, strAccountName,
                                                strAccountCode, strPayeeCode,
                                                strPayeeName;
 /* Ewcord definition section   */
    record  payor {
        unique key  char    strPayorName   [35];
                    char    strPayorAddr1  [35];/* single element fields */
                    char    strPayorAddr2  [35];/* are easier in toolbook */
                    char    strPayorCity   [20];
                    char    strPayorState  [3];
                    char    strPayorZip    [10];
                    char    strPayorCtry   [10];
                    char    strPayorTel    [15];
                    char    cPayorRegAdd;   /* values are (a)D(d) and */
                    char    cPayorAccAdd;  /*(a)S(k) on exception processing */
                    char    cPayorPayeeAdd;
                    char    cPayorAccess;  /* values are C(ode) and N(ame) */
                    int     nPayorYear;
                    db_addr dbaPayorCurrBank;
                    int     nPayorNextRecur;
    }

    /*  Account covers both balance sheet and income statement accounts. */
    record  account {
               key  char strAccountName   [35];
               key  char strAccountCode   [15];
                    char strAccountNumber [15];
                    char cAccountType;          /* A L E I E */
                    int  bAccountTax;           /* T= tax related */
                    int  bAccountIsRegister;
    }
    /*  Payee */
    record  payee       {
               key  char    strPayeeName  [35];
               key  char    strPayeeCode  [15];
                    char    strPayeeAddr1 [35];
                    char    strPayeeAddr2 [35];
                    char    strPayeeCity  [20];
                    char    strPayeeState [3];
                    char    strPayeeZip   [10];
                    char    strPayeeCtry  [10];
                    char    strPayeeTel   [15];
                    /* default account is a set */
                    char    strPayeeMemo  [35];
                    char    strPayeeType  [10];
    }
    /*  Budget records are owned by the account. */
    record  budget {
                    int     nBudgetMonth;       /* number from 1 to 12 */
                    long    lBudgetAmount;
    }
    /*  Actuals are maintained in order; speeds process of updating register.*/
    record  actual {
                    int     nActualMonth;
                    long    lActualAmount;
    }
    /*  Transactions are headers for distributions. */
    record  transaction {
                    char    cTranType;          /* check, deposit, etc */
                    char    strTranMemo [35];
                    int     bTranClear;         /* T = cleared */
                    int     nTranNumber;        /* Check number */
                    long    lTranDate;          /* Transaction date as a
                                                   "COBOL" date: ccyymmdd   */
                    long    lTranAmount;
    }
    /*  Distributions are the balancing entries for transactions. */
    record  distribution {
                    long    lDistrAmount;
    }
    /* Set definition section -- Sets with system as parent */
    set system_payor    {
            order   ascending;
            owner   system;
            member  payor by strPayorName;
    }
    set system_payeecode    {
            order   ascending;
            owner   system;
            member  payee by strPayeeCode;
    }
    set system_payeename    {
            order   ascending;
            owner   system;
            member  payee by strPayeeName;
    }
    set system_lastData {
            order   next;
            owner   system;
            member  lastData;
    }
    /* Sets with payor as parent    */
    set payor_accountcode   {
            order   ascending;
            owner   payor;
            member  account by strAccountCode;
    }
    set payor_accounttypecode   {
            order   ascending;
            owner   payor;
            member  account by cAccountType, strAccountCode;
    }
    set payor_accountname   {
            order   ascending;
            owner   payor;
            member  account by strAccountName;
    }
    /* Sets with account as parent */
    set account_budget {
            order   ascending;
            owner   account;
            member  budget      by nBudgetMonth;
    }
    set account_actual {
            order   ascending;
            owner   account;
            member  actual      by nActualMonth;
    }
    set account_distribution {
            order   next;
            owner   account;
            member  distribution;
    }
    /*  account_payee implements default distribution account for payees. */
    set account_payee   {
            order   next;
            owner   account;
            member  payee;
    }
    /*  sets with payee as owner */
    set payee_transaction   {
            order   next;
            owner   payee;
            member  transaction;
    }
    /*  sets with actual as owner. */
    set actual_transaction_date  {
            order   ascending;
            owner   actual;
            member  transaction     by lTranDate;
    }
    set actual_transaction_number   {
            order   ascending;
            owner   actual;
            member  transaction     by nTranNumber;
    }
    set actual_transaction_type {
            order   ascending;
            owner   actual;
            member  transaction     by cTranType, lTranDate;
    }
    set actual_distribution {
            order   next;
            owner   actual;
            member  distribution;
    }
    /*  sets with transaction as owner  */
    set transaction_distribution    {
            order   next;
            owner   transaction;
            member  distribution;
    }
    set transaction_tranPayee   {
            order   next;
            owner   transaction;
            member  tranPayee;
    }

}



<a name="0245_0012"><a name="0245_0012">
<a name="0245_0013">
[LISTING TWO]
<a name="0245_0013">

to handle LINKDLLS

   -- use the Windows kernel for memory management
    linkdll "kernel.exe"
        word globalAlloc (word,dword)
        word globalFree (word)
        pointer globalLock (word)
        word globalReAlloc(word,dword,word)
        dword globalSize(word)
        word globalUnlock(word)
    end

   --use ToolBook's file dll for file system access
    linkdll "tbkfile.dll"
        string getCurrentDirectory(string)
        string getCurrentDrive()
    end

   -- use Raima's db_VISTA dll for database functionality
    linkdll "vista.dll"
        --except for close,closetask, and opentask, functions are as
        --in the db_vista docs (dt_ = d_). The last args are *currenttask,dbn

        int dt_close    (pointer)
        int dt_closetask(pointer)
        int dt_connect  (int,pointer,int)
        int dt_crget    (pointer,pointer,int)

        --other calls ommitted here

        int dt_setro    (int,pointer,int)
    end

   --chekmate.dll is the helper dll for the checking account system
    linkdll "chekmate.dll"
        --other calls ommitted here
        word dbv_EditRegister (word,int,long,int,word,int)
        word dbv_GetRegister (word,int,word)
    end

end



<a name="0245_0014"><a name="0245_0014">
<a name="0245_0015">
[LISTING THREE]
<a name="0245_0015">

to handle InitGlobals
    system svhControl,  svPtrControl
    system svhDBBuffer, svPtrDBBuffer
    system svhCurrTask, svPtrCurrTask
    set svhControl to  GlobalAlloc (66,64) --GHND is 66, size is 64 bytes
    set svhDBBuffer to GlobalAlloc (66,256)
    set svhCurrTask to GlobalAlloc (66,64)
    if svhControl <= 0 or svhDBBuffer <= 0 or svhCurrTask <= 0
        --clean up, since allocation failed
        send FreeGlobals
        send Exit  -- shutdown the system
    else
        --get pointers
        set svPtrControl  to GlobalLock (svhControl)
        set svPtrDBBuffer to GlobalLock (svhDBBuffer)
        set svPtrCurrTask to GlobalLock (svhCurrTask)
    end
end
to handle FreeGlobals
    system svhControl,  svPtrControl
    system svhDBBuffer, svPtrDBBuffer
    system svhCurrTask, svPtrCurrTask
    if svhControl is not null and svhControl > 0
        get GlobalUnlock (svhControl)
        get GlobalFree   (svhControl)
    end
    if svhDBBuffer is not null and svhDBBuffer > 0
        get GlobalUnlock (svhDBBuffer)
        get GlobalFree   (svhDBBuffer)
    end
    if svhCurrTask is not null and svhCurrTask > 0
        get GlobalUnlock (svhCurrTask)
        get GlobalFree   (svhCurrTask)
    end
    --clean up globals to make ToolBook suspend rather than GP Fault
    set svhControl      to null
    set svhDBBuffer     to null
    set svhCurrTask     to null
    set svPtrControl    to null
    set svPtrDBBuffer   to null
    set svPtrCurrTAsk   to null
end



<a name="0245_0016"><a name="0245_0016">
<a name="0245_0017">
[LISTING FOUR]
<a name="0245_0017">

typedef struct Control {
   HANDLE  hCurrentTask;        //current task structure (DB_TASK)
   HANDLE  hDataBaseBuffer;     //db_Vista's control buffer

   // Offscreen image / database address pairs
   HANDLE  hRegisterImage;      //offscreen image of the register field
   HANDLE  hRegisterDBAArray;   //array of database addresses, 1 for each line
                                //in the register image
   // Selector fields storage
   HANDLE  hPayeeCodeArray;     //offscreen image of the payee selector field
   HANDLE  hPayeeCodeDBAArray;  //array of database addresses - payee selector
   HANDLE  hPayeeNameArray;     //offscreen image of the payee selector field
   HANDLE  hPayeeNameDBAArray;  //array of database addresses - payee selector
   HANDLE  hAccountCodeArray;   //offscreen image of account selector field
   HANDLE  hAccountCodeDBAArray;//array of database addresses - accts. selector
   HANDLE  hAccountNameArray;   //offscreen image of account selector field
   HANDLE  hAccountNameDBAArray;//array of database addresses - accts selector
   HANDLE  hRegisterNumbers;    //array of deposit, payments, balances

   // Database addresses for active records
   DB_ADDR   dbaCurrPayor;     //address of the current payor record
   DB_ADDR   dbaCurrRegister;  //address of the current active register record
   DB_ADDR   dbaCurrRegisterActual; //address of current month's register

   // Database number for concurrency operations
   int   nDatabaseNumber;     //normally 0
   } CONTROL;



<a name="0245_0018"><a name="0245_0018">
<a name="0245_0019">
[LISTING FIVE]
<a name="0245_0019">

--------------------------
--CONTROL BLOCK ACCESSOR--
--------------------------
to get hControl fField
    system svPtrControl
    set vOffset to ControlOffset(fField)
    set retval to -1
    conditions
    when char 1 of fField is "h" --handles
        set retval to pointerWord(vOffset,svPtrControl)
    when chars 1 to 3 of fField is "dba" --db_addrs
        set retval to pointerlong(vOffset,svPtrControl)
    when char 1 of fField is "n"
        set retval to pointerint(vOffset,svPtrControl)
    end
    return retval
end

to set hControl fField to fVal
    system svPtrControl
    set vOffset to ControlOffset(fField)
    conditions
    when char 1 of fField is "h" --handles
        get pointerWord(vOffset,svPtrControl,fVal)
    when chars 1 to 3 of fField is "dba" --db_addrs
        get pointerlong(vOffset,svPtrControl,fVal)
    when char 1 of fField is "n"
        get pointerint(vOffset,svPtrControl,fVal)
    end
end

to get ControlOffset fField
    conditions
    when char 1 of fField is "h" --handles
        conditions
        when fField is hCurrentTask
            set vOffset to 0
        when fField is hDatabaseBuffer
            set vOffset to 1
        when fField is hRegisterImage
            set vOffset to 2
        when fField is hRegisterDBAArray
            set vOffset to 3
        when fField is hRegisterCodeArray
            set vOffset to 4
        when fField is hRegisterCodeDBAArray
            set vOffset to 5
        when fField is hRegisterNameArray
            set vOffset to 6
        when fField is hRegisterNameDBAArray
            set vOffset to 7
        when fField is hPayeeCodeArray
            set vOffset to 8
        when fField is hPayeeCodeDBAArray
            set vOffset to 9
        when fField is hPayeeNameArray
            set vOffset to 10
        when fField is hPayeeNameDBAArray
            set vOffset to 11
        when fField is hAccountCodeArray
            set vOffset to 12
        when fField is hAccountCodeDBAArray
            set vOffset to 13
        when fField is hAccountNameArray
            set vOffset to 14
        when fField is hAccountNameDBAArray
            set vOffset to 15
        when fField is hRegisterNumbers
            set vOffset to 16
        end
        return vOffset*2
    when chars 1 to 3 of fField is "dba" --db_addrs
        set vbase to 34 -- max(vOffset*2)+2
        conditions
        when fField is dbaCurrPayor
            set vOffset to 0
        when fField is dbaCurrRegister
            set vOffset to 1
        when fField is dbaCurrRegisterActual
            set vOffset to 2
        end
        return vbase+vOffset*4
    when char 1 of fField is "n"
        set vbase to 46 --vbase + dbaentries * 4
        conditions
        when fField is "nDatabaseNumber"
            set vOffset to 0
        end
        return vbase+vOffset*2
    end
end




<a name="0245_001a"><a name="0245_001a">
<a name="0245_001b">
[LISTING SIX]
<a name="0245_001b">


-- Fill selector loads selector text from global memory into selector field

to handle FILLSELECTOR fComboName,fObjectName
    set vCurdba to dbv_currentrecord()
    set vHText to hControl("h" & fComboName & "Array")
    if vhText = 0
        get createSelectorList (fComboName)
        set vHText to hControl("h" & fComboName & "Array")
    end
    get globalLock (vHText)
    if fObjectName is null
        set fObjectName to fComboName
    end
    set text of (pListID of group fObjectName) to\
        pointerString(0, it)
    get globalUnLock (vHText)
    set dbv_currentrecord() to vCurdba
end
-- createSelectorList loads global memory block with values from appropriate
-- set member fields
to get createSelectorList fArray
    system svPtrDbBuffer, svPtrCurrTask
     -- first determine what all the database constants are
    conditions
    when fArray is "RegisterName"
        get dt_findfm(20000,svPtrCurrTask,0)
        set vSet to 20006
        get dt_setom(vSet,20000,svPtrCurrTask,0)
        set vField to 1000
    -- cases for "RegisterCode", "PayeeCode", "PayeeName", "AccountCode",
    -- and "AccountName" are similar
    else
        return false
    end
    set vArray to "h" & fArray
    --check if the memory is already allocated
   if hControl(vArray & "Array") = 0
        set vhTextBuffer to globalAlloc(66,1000)
        set vhDBABuffer to globalAlloc(66,500)
        set hControl(vArray & "Array") to vhTextBuffer
        set hControl(vArray & "DBAArray") to vhDBABuffer
    else
        set vhTextBuffer to hControl(vArray & "Array")
        set vhDBABuffer to hControl(vArray & "DBAArray")
    end

    set vPtrTextBuffer to globalLock(vhTextBuffer)
    set vPtrDBABuffer to globalLock(vhDBABuffer)
    set vDBAOffset to 0
    set vcharCount to 0
    get dt_findfm(vSet,svPtrCurrTask,0)
    while it = 0
     --build the text buffer
        get dt_crread(vField,svPtrDbBuffer,svPtrCurrTask,0)
        set vline to pointerstring(0,svPtrDbBuffer)
        put CRLF after vLine
        get pointerstring(vCharCount,vPtrTextBuffer,vLine)
        increment vCharCount by charcount(vLine)
      --build the db_addr buffer
        get dt_crget(svPtrDbBuffer,svPtrCurrTask,0)
        get pointerlong(0,svPtrDbBuffer)
        get pointerlong(vDBAOffset,vPtrDBABuffer,it)
        increment vDBAOffset by 4 -- because DBAs are longs
      --get next record
        get dt_findnm(vSet,svPtrCurrTask,0)
    end
    return true
    get globalunLock(vhTextBuffer)
    get globalunLock(vhDBABuffer)
end



<a name="0245_001c"><a name="0245_001c">
<a name="0245_001d">
[LISTING SEVEN]
<a name="0245_001d">

/***************************************************************************
 * dbv_CreateSelectorList--Purpose: Obtains list of available selections for
 *  a particular chekmate field and save them along with database addresses
 *  in the chekmate control block.
 * Parameters: hControl, HANDLE to the database control block; nSetID, numeric
 *  identifier for set containing selection list; lField, LONG database field
 *  number; nHandleOffset, integer offset into the Control block of handle for
 *  memory where data should be stored.
 * Return Value: 0, if no errors; -n, if errors reading database
 */
extern WORD FAR PASCAL dbv_CreateSelectorList(
      HANDLE   hControl,         // Control Block
      int      nSetID,           // database set identifier
      LONG     lField,           // database field number
      int      hHandleOffset)    // offset in Control Block for memory handle
{
   int      i;
   int             iError=-1;       // error return code
   LPCONTROL       lpControl=NULL;  // control block
   DB_TASK DB_FAR *lpTask=NULL;     // task pointer
   LPHANDLE        lpHandle;        // handle pointer
   if (NULL==hControl)
   {
      return -1;  // control block not initialized
   }

   // Lock control block so task block can be locked for database call.
   lpControl  = (LPCONTROL) GlobalLock (hControl);
   lpTask     = (DB_TASK DB_FAR *) GlobalLock (lpControl->hCurrentTask);

   // point to handle in control block for list text.
   lpHandle   = ((LPHANDLE) lpControl) + hHandleOffset;
   iError = LoadSelectorList (lpHandle,lpHandle+1,nSetID,lField,lpTask,
                                                   lpControl->nDatabaseNumber);
CleanUp:
   GlobalUnlock (lpControl->hCurrentTask);
   GlobalUnlock (hControl);
   return (iError);
}

/***************************************************************************
 * LoadSelectorList--Purpose: Reads database for specified set and transfer
 *   data from lField into text buffer. Each record a separate text line in
 *   buffer and database addresses for each record will also be saved.
 * Parameters: lphListText, handle to memory for list text; lphListDBA, handle
 *   to memory for list database addresses; nSetID, numeric identifier for
 *   database set containing the selection list; lField, LONG database field
 *   number; lpTask, pointer to database task; nDatabase, database number
 * Return Value: 0, if no errors; -n,  if errors reading database
 */
int PASCAL LoadSelectorList (
         LPHANDLE    lphListText,      // handle to memory for list text
         LPHANDLE    lphListDBA,       // memory handle for list database adr.
         int         nSetID,           // database set ID
         LONG        lField,           // database field number for list text
         DB_TASK DB_FAR *lpTask,       // database task
         int         nDatabase)        // database number
{
   int          iError=-1;    // error return code
   int          nDBA=0;       // number of DBA's
   int          nMaxDBA=0;    // maximum number of DBA's used
   int          nMaxBytes=0;  // maximum bytes allowed
   LPSTR        lpText=NULL;  // current text line
   DB_ADDR FAR *lpDBA=NULL;   // database address value
   HANDLE       hMem;         // handle to reallocated memory block
   DB_ADDR      lCurDBA;      // current database record

   // save the current database record
   dt_crget ((DB_ADDR FAR *)&lCurDBA,lpTask,nDatabase);

   // initialize the text and DBA memory blocks.
   if (*lphListText == NULL)
   {
      *lphListText = GlobalAlloc (DLL_ALLOC,SELECTOR_TEXT_SIZE);
   }
   if (*lphListDBA == NULL)
   {
      *lphListDBA = GlobalAlloc (DLL_ALLOC,(LONG)(SELECTOR_DBA_COUNT*sizeof
                                                                  (DB_ADDR)));
   }
   if (*lphListText == NULL || *lphListDBA == NULL)
   {
      goto CleanUp;
   }

   // initial allocations to set the maximum values and lock the memory blocks.
   nMaxDBA   = GlobalSize (*lphListDBA) / sizeof(DB_ADDR);
   nMaxBytes = GlobalSize (*lphListText);

   lpText = GlobalLock (*lphListText);
   lpDBA  = (DB_ADDR FAR *) GlobalLock (*lphListDBA);

   // read the database and fill in the text values
   for (iError = dt_findfm (nSetID,lpTask,nDatabase);
        iError==S_OKAY;
        iError = dt_findnm (nSetID,lpTask,nDatabase))
   {
      if (nMaxBytes<=MIN_SELECTOR_TEXT_SIZE)
      {
         // need to allocate more text memory.
         lpText = NULL;
         GlobalUnlock (*lphListText);
         hMem = GlobalReAlloc (*lphListText,
                               GlobalSize(*lphListText)+SELECTOR_TEXT_SIZE,
                               GMEM_ZEROINIT);
         if (hMem==NULL)
         {
            iError = -2;
            goto CleanUp;  // not enough memory
         }
         *lphListText = hMem;  // new handle
         lpText       = GlobalLock (*lphListText);
         nMaxBytes    = GlobalSize(*lphListText) - lstrlen (lpText);
         lpText      += lstrlen(lpText);
      }
      // read the field contents into the text buffer
      dt_crread(lField,lpText,lpTask,nDatabase);
      lstrcat (lpText,"\r\n");
      lpText += lstrlen(lpText);

      // save the DBA of the record
      if (nDBA >= nMaxDBA)
      {
         // need to allocate more DBA memory.
         lpDBA = NULL;
         GlobalUnlock (*lphListDBA);
         hMem = GlobalReAlloc (*lphListDBA,
                    GlobalSize(*lphListDBA)+SELECTOR_DBA_COUNT*sizeof(DB_ADDR),
                                                               GMEM_ZEROINIT);
         if (hMem==NULL)
         {
            iError = -2;
            goto CleanUp;  // not enough memory
         }
         *lphListDBA = hMem;  // new handle
         lpDBA       = (DB_ADDR DB_FAR *)GlobalLock (*lphListDBA);
         nMaxDBA     = GlobalSize(*lphListDBA) - nDBA;
         lpDBA      += nDBA;
      }
      dt_crget   (lpDBA,lpTask,nDatabase);
      lpDBA++;
      nDBA++;
   }
CleanUp:
   // restore the address of the current record
   dt_crset ((DB_ADDR far *)&lCurDBA,lpTask,nDatabase);
   if (lpText!=NULL)
   {
      GlobalUnlock (*lphListText);
   }
   if (lpDBA!=NULL)
   {
      GlobalUnlock (*lphListDBA);
   }
   if (iError==S_EOS)
   {
      iError = S_OKAY;
   }
   return (iError);
}



[BALANCE MAINTENACE CODE]

typedef struct RegisterNumbers {
   long  lDeposit;   //value of the deposit (may be 0)
   long  lPayment;   //value of the payment (may be 0)
   long  lBalance;   //value of the balance
   } REGISTERNUMBERS;

/***************************************************************************
 * UpdateRegisterNumbers
 *
 *    Purpose:
 *    This routine will update the register payments, deposits and balance
 *    arrays.  Additonally it will copy the information into the register
 *    text arrays and add the final zero byte to the register text
 *
 *    Parameters:
 *       lpControl      pointer to the control block
 *
 *    Return Value:
 *       0  if no errors
 *
 */
int PASCAL UpdateRegisterNumbers(
         LPCONTROL   lpControl)  // pointer to the Control block
{
   int i;
   char cPayment[20], cDeposit[20], cBalance[20];
   LONG              lBegBal;       // begining balance
   LPREGISTERNUMBERS lpNumbers;     // register numbers
   LPREGISTERIMAGE   lpImage;       // register image block
   LPSTR             lpLine;        // register line image
   LONG              lPrevBal;      // previous balance

   lpImage      = (LPREGISTERIMAGE)   GlobalLock (lpControl->hRegisterImage);
   lpNumbers    = (LPREGISTERNUMBERS) GlobalLock (lpControl->hRegisterNumbers);
   lpLine       = (LPSTR) &(lpImage->Text[0]);

   // we first go thru the list of numbers and set the balances and then
   // we convert the values to strings and place them into the register
   //  line.  Note the register lines are assumed to be blank filled and
   //  terminated with a CRLF.

   for (i=0,lPrevBal=lpNumbers->lBalance;i<lpImage->nLines;i++,lpNumbers++)
   {
      lPrevBal  =
         lpNumbers->lBalance = lPrevBal +
                            lpNumbers->lDeposit - lpNumbers->lPayment;

      // convert the values to zero terminated strings
      Long2Money (lpNumbers->lPayment,(LPSTR) &(cPayment[0]));
      Long2Money (lpNumbers->lDeposit,(LPSTR) &(cDeposit[0]));
      Long2Money (lpNumbers->lBalance,(LPSTR) &(cBalance[0]));
      wsprintf (lpLine+50," %10s   %10s %10s",
                (LPSTR) &(cPayment[0]),
                (LPSTR) &(cDeposit[0]),
                (LPSTR) &(cBalance[0]));

      // now add the CRLF to the lines the current zero byte placed in
      // the lpLine will be overwritten and no zero byte will be used.
      lpLine += lstrlen(lpLine);  // positioned at the zero byte
      *lpLine++ = '\r';
      *lpLine++ = '\n';
      // lpLine now positioned correctly for begining of next line
   }
   // add the final empyt line and a zero byte to terminate the register text field
   lstrcat (lpLine,"\r\n");

   GlobalUnlock (lpControl->hRegisterImage);
   GlobalUnlock (lpControl->hRegisterNumbers);
   return (0);
}


/***************************************************************************
 * Long2Money
 *
 *    Purpose:
 *    convert a long number into a money text string. The numeric value
 *    is in terms of cents.
 *
 *    Parameters:
 *       lValue   LONG value to be converted
 *       lpText   LPSTR to the text string
 *
 *    Return Value:
 *       LPSTR    pointer to the text string
 *
 */
LPSTR PASCAL Long2Money(
   LONG  lValue,           // long value to be converted
   LPSTR lpDecimalText)    //
{
   LPSTR lpStr = lpDecimalText;

   // Take care of the sign of the number so that the conversion
   // will only have to deal with positive values.
   if (lValue <0)
   {
      lValue = -lValue;
      *lpStr++ = '-';
   }
   // convert the number to characters - note if the value is less
   // than 100 then we will want the number to be be converted as
   // 0.nn  Therefore 100 will be added to the value and then later
   // the '1' will be replaced with a '0'.

   wsprintf (lpStr,"%li",(LONG) ((lValue<100)?lValue+100:lValue));

   // now make room for the decimal point
   lpStr += lstrlen(lpStr);       // lpStr points to the end of the string
   *(lpStr+1) = *lpStr; lpStr--;  // terminating character
   *(lpStr+1) = *lpStr; lpStr--;  // units digit
   *(lpStr+1) = *lpStr;           // tens digit
   *lpStr--   = '.';              // add in the decimal point
   if (lValue < 100)
   {
      *lpStr = '0';  // replace the '1' with a '0' forces leading zero
   }
   return (lpDecimalText);
}

[REGISTER MAINTENANCE CODE]

/****************************************************************************
 * dbv_GetRegister
 *
 *    Purpose:
 *    This routine will read the transactions associated with the current
 *    account for a given month and generates a checkbook register image.
 *    The image is saved in memory and also set into the toolbook fields
 *    specified in hFldTable.
 *
 *    Parameters:
 *       hControl          HANDLE to the database control block
 *       nSetID            numeric identifier for database set
 *       hFldTable         HANDLE to the ToolBook field table list
 *
 *    Return Value:
 *       0  if no errors
 *       1  if error in setting field
 *      -1  control block not initialized
 *
 */
extern WORD FAR PASCAL dbv_GetRegister(
      HANDLE   hControl,         // Control Block
      int      nSetID)           // database set identifier
{
   int i;
   int             iError=0;        // error return
   LPCONTROL       lpControl=NULL;  // control block
   int             nDatabase;       // database number
   LPREGISTERIMAGE lpImage=NULL;    // pointer to the register image block

   // Lock the control block so that the task block and the database
   // buffer can be obtained.  The database buffer will be used to contain
   // the individual field values for the current record.

   if (NULL==hControl)
   {
      return -1;  // control block not initialized
   }

   // Lock the control block so that the task block can be locked for the
   // database call.  Then lock the field table so that we can access
   // which ToolBook fields are to be set.

   lpControl  = (LPCONTROL) GlobalLock (hControl);
   lpFldTable = (LPFLDTABLE) GlobalLock(hFldTable);

   // Initialize the memory blocks that will be used for the register
   // information.
   if (0!=(iError=InitRegisterImage(lpControl)))
   {
      goto CleanUp;
   }

   // Now we load the register image
   if (0!=(iError=LoadRegisterImage (lpControl,nSetID)))
   {
      goto CleanUp;
   }

   GlobalUnlock (lpControl->hRegisterImage);


CleanUp:

   // all done - now unlock all the allocations
   GlobalUnlock (lpFldTable->hBookName);
   GlobalUnlock (hFldTable);
   GlobalUnlock (hControl);
   return 0;
}

/***************************************************************************
 * InitRegisterImage
 *
 *    Purpose:
 *    This routine will initialize the memory blocks that will contain the
 *    chekmate register image, the database addresses and the array of the
 *    deposits, payments and balances for the register.
 *
 *    Parameters:
 *       lpControl      pointer to the control block
 *
 *    Return Value:
 *       0  if no errors
 *      -1  if error allocating the memory
 *
 */
int PASCAL InitRegisterImage(
         LPCONTROL   lpControl)  // pointer to the Control block
{
   LPREGISTERIMAGE lpImage;      // pointer to the register image

   // if the handles are currently pointing to memory blocks then we free
   // them and re-allocate. This is done just to make life a little easier

   if (NULL!=lpControl->hRegisterImage)
   {
      GlobalFree (lpControl->hRegisterImage);
      lpControl->hRegisterImage = NULL;
   }
   if (NULL!=lpControl->hRegisterDBAArray)
   {
      GlobalFree (lpControl->hRegisterDBAArray);
      lpControl->hRegisterDBAArray = NULL;
   }
   if (NULL!=lpControl->hRegisterNumbers)
   {
      GlobalFree (lpControl->hRegisterNumbers);
      lpControl->hRegisterNumbers = NULL;
   }

   // now allocate the space for the register image array
   lpControl->hRegisterImage = GlobalAlloc (DLL_ALLOC,
                                   (LONG) sizeof(REGISTERIMAGE)+
                                   (INITIALREGISTERLINES*REGISTERLINESIZE));
   if (NULL==lpControl->hRegisterImage)
   {
      return -1;
   }
   // now allocate the database address lists
   lpControl->hRegisterDBAArray = GlobalAlloc (DLL_ALLOC,
                                      ((long) sizeof(DB_ADDR))*((long) INITIALREGISTERLINES));
   if (NULL==lpControl->hRegisterDBAArray)
   {
      return -1;
   }

   // now allocate the register payments, deposits and balance columns
   lpControl->hRegisterNumbers = GlobalAlloc (DLL_ALLOC,
                                      ((long) sizeof(REGISTERNUMBERS))*((long)
INITIALREGISTERLINES));
   if (NULL==lpControl->hRegisterNumbers)
   {
      return -1;
   }

   // initialize the image structure to contain the header information
   lpImage = (LPREGISTERIMAGE) GlobalLock (lpControl->hRegisterImage);
   lpImage->nMaxLines = INITIALREGISTERLINES;
   lpImage->nLines    = 0;

   GlobalUnlock (lpControl->hRegisterImage);
   return (0);
}



/***************************************************************************
 * LoadRegisterImage
 *
 *    Purpose:
 *    This routine will load the register data from the database.
 *
 *    Parameters:
 *       lpControl      pointer to the control block
 *       nSetID         transaction set id
 *
 *    Return Value:
 *       0  if no errors
 *      -1  if error reading the database
 *
 */
int PASCAL LoadRegisterImage(
         LPCONTROL   lpControl,  // pointer to the Control block
         int         nSetID)     // set containing the transaction members
{
   int i;
   int               iError=0;      // error return
   DB_ADDR           lCurDBA;       // current database address
   actual            Actual;        // current months value
   payee             Payee;         // current transactions payee
   transaction       Transaction;   // current transaction
   int               nDatabase;     // database number
   DB_ADDR           lTranDBA;      // transaction dba
   DB_TASK DB_FAR   *lpTask=NULL;   // task pointer
   DB_ADDR FAR      *lpDBA;         // database address array
   LPREGISTERNUMBERS lpNumbers;     // register numbers
   LPREGISTERIMAGE   lpImage;       // register image block
   LPSTR             lpLine;        // line for the register image

   // lock the task, the register image, the database addresses array
   // and the register number array.

   lpTask    = (DB_TASK DB_FAR *)  GlobalLock (lpControl->hCurrentTask);
   nDatabase = lpControl->nDatabaseNumber;

   // save the current database record
   dt_crget ((DB_ADDR FAR *)&lCurDBA,lpTask,nDatabase);

   // now we load up the register data.  The fist line in the register is
   // the begining balance.  We "Fake out" the first line by creating
   // a psudeo transaction record for the actual month record.

   dt_crset ((DB_ADDR far *)&(lpControl->dbaCurrRegisterActual),lpTask,nDatabase);
   dt_setor (nSetID,lpTask,nDatabase);
   if (S_OKAY!=(iError=dt_recread((DB_ADDR FAR *)&Actual,lpTask,nDatabase)))
   {
      goto CleanUp;
   }
   lpImage   = (LPREGISTERIMAGE)   GlobalLock (lpControl->hRegisterImage);
   lpDBA     = (DB_ADDR FAR *)     GlobalLock (lpControl->hRegisterDBAArray);

   lpNumbers = (LPREGISTERNUMBERS) GlobalLock (lpControl->hRegisterNumbers);

   Transaction.cTranType   = BBAL;     // begining balance
   Transaction.lTranDate   = 19000001 + (Actual.nActualMonth)*100; // date
   Transaction.nTranNumber = 0;        // transaction number
   Transaction.bTranClear  = 0;
   Transaction.lTranAmount = Actual.lActualAmount;
   lpNumbers->lBalance     = Actual.lActualAmount;
   dt_crget (lpDBA++,lpTask,nDatabase);
   GenerateRegisterLine ((LPTRANSACTION) &Transaction,(LPPAYEE) &Payee,&(lpImage->Text[0]));
   lpImage->nLines++;
   lpNumbers++;

   GlobalUnlock (lpControl->hRegisterImage);
   GlobalUnlock (lpControl->hRegisterDBAArray);
   GlobalUnlock (lpControl->hRegisterNumbers);

   // now step thru the database reading the members of the set and
   // generating register lines for them.
   for (iError=dt_findfm (nSetID,lpTask,nDatabase),i=1;
        iError==S_OKAY;
        iError=dt_findnm (nSetID,lpTask,nDatabase),i++)
   {
      dt_crget   ((DB_ADDR FAR *) &lTranDBA,lpTask,nDatabase);
      EditRegisterImage (lpControl,i,lTranDBA,nSetID,ADD_TRAN);
   }

   // now fill in the payment, deposit and balance fields
   UpdateRegisterNumbers (lpControl);

 CleanUp:
   // restore the address of the current record
   dt_crset ((DB_ADDR far *)&lCurDBA,lpTask,nDatabase);

   GlobalUnlock (lpControl->hCurrentTask);

   return (((iError==S_EOS)?S_OKAY:iError));

}


[DB_VISTA SHELL CODE]

-- composite functions for adding and editing records

to get dbv_AddRecord fHDbControl, ftbFields, fRecordID
    system svPtrCurrTask,svPtrDbBuffer

    get dt_fillnew(fRecordID,svPtrDbBuffer,svPtrCurrTask,0)
    get last char of fRecordID
    set vStartingField to it *1000 --starting fields of VISTA records are on 1000 boundries
    return dbv_EditRecord(fHDbControl, fTbFields, vStartingField)
end


to get dbv_EditRecord fHDbControl, ftbFields, fStartingField
    system svPtrCurrTask,svPtrDbBuffer
    set vFldCount to itemcount( fTbFields )
    step i from 1 to vFldCount
        get item i of fTbFields
        get pointerstring(0,svPtrDbBuffer,text of field id it)
        get dt_crwrite(fStartingField+i,svPtrDbBuffer,svPtrCurrTask,0)
    end
    return true
end


to get dbv_GetTBFieldArray fhDBControl, fSet, fFieldID, fTbFields
    system svPtrDbBuffer,svPtrCurrTask, svCurrAccountDBA

    set vCurDBA to dbv_currentrecord()
    get dt_findfm(fSet,svPtrCurrTask,0)
    if it = 0
        set vFldCount to itemcount( fTbFields)
        step i from 1 to vFldCount
            get dt_crread(fFieldID,svPtrDbBuffer,svPtrCurrTask,0)
            set vID to item i of fTbFields
            set text of field id vID\
                to pointerLong(0,svPtrDbBuffer)/10
            format text of field id vID as "0.00"
            if dt_findnm(fSet,svPtrCurrTask,0) <> 0
                break step
            end
        end
    end
    set dbv_currentrecord() to vCurDBA
    return true
end

to get dbv_EditTBFieldArray fhDBControl, fhFldTable, fSet, fTbFields
    system ,svPtrDbBuffer,svPtrCurrTask, svCurrAccountDBA

    set vCurDBA to dbv_currentrecord()
    get dt_findfm(fSet,svPtrCurrTask,0)
        if it = 0
        set vFldCount to itemcount( fTbFields )
        step i from 1 to vFldCount
            get text of field id (item i of fTbFields)
            format it as "0"
            get pointerLong(0,svPtrDbBuffer,it)
            get dt_crwrite(fFieldID,svPtrDbBuffer,svPtrCurrTask,0)
            if dt_findnm(fSet,svPtrCurrTask,0) <> 0
                break step
            end
        end
    end
    set dbv_currentrecord() to vCurDBA
    return true
end

to get dbv_field fType,fFieldNumber
    system svhControl,svPtrCurrTask,svPtrDbBuffer
    get dt_crread(fFieldNumber,svPtrDbBuffer,svPtrCurrTask,0)
    if fType is "CHAR"
        set vVal to pointerByte(0,svPtrDbBuffer)
        set vVal to ansitochar(vVal)
    else
        execute "set vVal to pointer" & fType & "(0,svPtrDbBuffer)"
    end
    return vVal
end

to set dbv_field  fType,fFieldNumber to fVal
    system svhControl,svPtrCurrTask,svPtrDbBuffer
    if ftype is "CHAR"
        get chartoansi(fVal)
        get pointerbyte (0,svPtrDbBuffer,it)
    else
        execute "get pointer" & fType & "(0,svPtrDbBuffer,fVal)"
    end
    get dt_crwrite(fFieldNumber,svPtrDbBuffer,svPtrCurrTask,0)
end




-- db_vISTA shell functions

to get dbv_connectSet fSet
    system svPtrCurrTask
    get dt_connect(fSet,svPtrCurrTask,0)
end

to get dbv_currentOwnerField fSet, fType, fFieldNumber
    system svhControl,svPtrCurrTask,svPtrDbBuffer
    get dt_csoread(fSet,fFieldNumber,svPtrDbBuffer,svPtrCurrTask,0)
    if fType is "CHAR"
        set vVal to pointerByte(0,svPtrDbBuffer)
        set vVal to ansitochar(vVal)
    else
        execute "set vVal to pointer" & fType & "(0,svPtrDbBuffer)"
    end
    return vVal
end

to set dbv_currentOwnerField fSet, fType, fFieldNumber to fVal
    system svhControl,svPtrCurrTask,svPtrDbBuffer
    if ftype is "CHAR"
        get chartoansi(fVal)
        get pointerbyte (0,svPtrDbBuffer,it)
    else
        execute "get pointer" & fType & "(0,svPtrDbBuffer,fVal)"
    end
    get dt_csowrite(fSet,fFieldNumber,svPtrDbBuffer,svPtrCurrTask,0)
end

to get dbv_memberCount fSet
    system svPtrCurrTask,svPtrDbBuffer
    get dt_members(fSet,svPtrDbBuffer,svPtrCurrTask,0)
    return (pointerLong(0,svPtrDbBuffer))
end

to get dbv_findFirstMember fSet
    system svPtrCurrTask
    return dt_findfm(fSet,svPtrCurrTask,0)
end

to get dbv_setOwnerToCurrRec fSet
    system svPtrCurrTask
    return dt_setor(fSet,svPtrCurrTask,0)
end


to get dbv_currentOwner fSet
    system svPtrCurrTask,svPtrDbBuffer
    set verror to dt_csoget(fSet,svPtrDbBuffer,svPtrCurrTask,0)
    set vdba to pointerlong(0,svPtrDbBuffer)
    set sysError to vError
    return vdba
end

to set dbv_currentOwner fSet to fDBA
    system svPtrCurrTask,svPtrDbBuffer
    if fDBA is null or fDBA is 0
    get dbv_currentOwner(fSet)
    if sysError = 0
            get dt_discon(fSet,svPtrCurrTask,0)
        end
    else
        get pointerlong(0,svPtrDbBuffer,fDBA)
        get dt_csoset(fSet,svPtrDbBuffer,svPtrCurrTask,0)
    end
end



to set dbv_connectOwner fSet to fDBA
    system svPtrCurrTask,svPtrDbBuffer
    set vCurrDBA to dbv_currentRecord()
    if dt_ismember(fSet,svPtrCurrTask,0) = 0
            get dt_discon(fSet,svPtrCurrTask,0)
    end
    if not(fDBA is null or fDBA is 0 )
        get pointerlong(0,svPtrDbBuffer,fDBA)
        get dt_csoset(fSet,svPtrDbBuffer,svPtrCurrTask,0)
        get dbv_currentmember(fSet)
        get dbv_currentrecord()
        get dbv_currentType()
        get dbv_currentOwner(fset)

        --get dt_csmset(fSet,)
        get dt_connect(fSet,svPtrCurrTask,0)
    end
end

to get dbv_currentMember fSet
    system svPtrCurrTask,svPtrDbBuffer
    set verror to dt_csmget(fSet,svPtrDbBuffer,svPtrCurrTask,0)
    set vdba to pointerlong(0,svPtrDbBuffer)
    set sysError to vError
    return vdba
end

to get dbv_currentType
    system svPtrCurrTask,svPtrDbBuffer
    get dt_crtype(svPtrDbBuffer,svPtrCurrTask,0)
    return pointerint(0,svPtrDbBuffer)
end

to get dbv_currentRecord
    system svPtrCurrTask,svPtrDbBuffer
    set verror to dt_crget(svPtrDbBuffer,svPtrCurrTask,0)
    set vdba to pointerlong(0,svPtrDbBuffer)
    set sysError to vError
    return vdba
end

to set dbv_currentRecord to fDBA
    system svPtrCurrTask,svPtrDbBuffer
    get pointerlong(0,svPtrDbBuffer,fDBA)
    get dt_crset(svPtrDbBuffer,svPtrCurrTask,0)
end


--dbv_findKey finds the key that matches fKeyVal, or the next higher key that
--contains fKeyVal

to get dbv_findkey  fField, fKeyVal, fType
    system svPtrDbBuffer,svPtrCurrTask
    execute ("get pointer" & fType & "(0,svPtrDbBuffer,fKeyVal)")
    get dt_keyfind(fField, svPtrDbBuffer, svPtrCurrTask,0)
    if it <> 0
        get dt_keynext(fField,svPtrCurrTask,0)
        if it <> 0
            return -2
        end

        execute ("get pointer" & fType & "(0,svPtrDbBuffer)")
        if fKeyVal is in it
            return 0
        else
            return -1
        end
    end
end

-- dbv_findrecord navigates through a set to find the nearest member of a set
-- that matches or exceeds fVal
to get dbv_findrecord fSet,fField,fVal,fType
    system svPtrDbBuffer,svPtrCurrTask
    set vCurrDBA to dbv_currentrecord()
    set sysError to -1
    set retval to 0
    set dbv_currentowner (fSet) to vCurrDBA
    get dt_findfm(fSet,svPtrCurrTask,0)
    if it = 0
        get dt_crread(fField, svPtrDbBuffer, svPtrCurrTask, 0)
        execute ("set vdbVal to pointer" & fType &"(0,svPtrDbBuffer)")
        while vdbval <= fVal as text
            if vdbval is fval
                set retval to dbv_currentrecord()
                break while
            end
            get dt_findnm(fSet,svPtrCurrTask,0)
            if it <> 0
                break while
            end
            get dt_crread(fField, svPtrDbBuffer, svPtrCurrTask, 0)
            set it to "set vdbVal to pointer" & fType &"(0,svPtrDbBuffer)"
            execute it
        end
    end
    set dbv_currentrecord() to vCurrDBA
    return retval
end












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.