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.
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
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