Centura Development Products FAQ

Last updated June 2nd, 1999
Copyright © 1998-1999 Thomas Althammer. All rights reserved.
No part of this document should be reproduced, distributed or altered without my permission.

All Windows SDK definitions appearing on this page can be optained from 16/32-bit include files in the "Downloads" section.

Contents

Fast Facts
- If you are experiencing crashes or other obscure errors when using Centura Team Developer, take a look at the CTD Development Environment section.
- If there are problems with your database connection, make sure you have walked throught the basic steps explained in the paragraph
Configuring database connections.
- Too many timeouts? Check here.

General

Development environment

Algorithms & Functions

    Date/Time

    Number

    String

    Miscellaneous

User-defined Varibles

External functions

Controls

    Check Boxes

    Combo Boxes

    Data Fields

    Form Windows

    List Boxes

    MDI

    Multiline Field

    Pushbuttons
   
    QuickGraphs

    QuickTabs

    Radio Boxes

    Table Windows

    Miscellaneous

Messages

Database access

Report Windows

SQLWindows

Development environment

External functions

Miscellaneous

OLE

Team Developer

General

Development environment

Database Access

ActiveX

Miscellaneous

Online Help

Team Object Manager

 CDK

 Migrating from SQLWindows to SQLWindows/32

General

Database Access

DDE

XSal - SAL extensions

General

BKG - Background Images and Hotspots

CPT - Custom Painted Table Windows

TLB - Toolbars

TTP - ToolTips

UDV - User-defined variables


General

Where can I find information about Centura's development products and the Year 2000 problem?
The newest releases are all Year 2000 compliant. Check www.centurasoft.com/support/tech_info/bulletins/cli2000.htm for further information.
Even though SQLBase is Y2K compliant, your client-side applications that access SQLBase may not be. Use a utility available for download at ftp.centurasoft.com/products/utilities/y2ksqlb.zip to analyze your SQLBase databases to help with your Y2K efforts. Please read the ReadMe.txt and License.txt files after extracting them from the .ZIP file.

Should I build one huge application or split it up into a number of interlinked applications?
There are many applications deployed around the world that are fairly large in size. A typical "large" Centura application would contain hundreds of top level windows (form windows, dialog boxes etc.) and in excess of 100,000 lines of SAL code. Either of the two approaches (i.e., build everything into one application, versus split them into multiple applications) has been adopted into such applications. The governing factors for the choice are: Does the system being built contain independent modules that can run independent of each other? If so, then building separate apps for each module makes sense.
There really is only one reason, stated above, to warrant a system to be built as a set of applications that would be deployed as multiple EXEs at runtime. Other than that, Centura Team Developer has several built-in features that greatly aid building large applications:
- Team Object Manager takes care of version control, as well as concurrent check-in/check-outs using its visual diff/merge features.
- Dynalibs, the pre-compiled chunks of SAL code that lend themselves to faster compile times.
- Centura Team Developer, being a 32-bit product, shatters all limits imposed by the 16-bit architecture that manifested in SQLWindows like the maximum size of a section of an outline and the global symbol-table space. These were the limits that prevented SQLWindows apps from growing beyond a certain limit.
Once you built your system as a single application, you would reap the following benefits:
- You wouldn't have to worry much about protocols like DDE for enabling communication between different windows in the system. Simple windows messaging and function call interfaces will take care of that.
- Creating and launching another window within the system would be faster as compared to starting another app via SalLoadApp() function call.
- Windows resources consumed in creating a new window within the same app are much less than having to start another application. This point is rather moot in Windows 95 and Windows NT systems. However, if you are planning on deploying your Centura-built applications onto a Win16 platform, then the issue of free windows resources becomes critical.

How can I deliver help files along with my self-written applications?
Several third-party tools enable you to generate HLP and nowadays HTML files. The Microsoft Help Compiler will turn a RTF (richt text format) document with certain formatting and tags into a HLP file. More convenient is using products like DocToHelp, RoboHelp, or Help Magician that take care of necessary formatting and enable the user to generate online help and the application documentation from one source in different formats.

Which naming conventions should I use when developing with SQLWindows or SQLWindows/32?
The following table is just a recommendation and should only be used as a guideline.

Object type Prefix Example Alternatives
Boolean b bDebug  
Date d dBegin  
Time t tStart  
Date/Time dt dtEnd  
Number n nCount  
String s sName str
Long String ls lsName  
Window Handle hWnd hWndFom  
SQL Handle hSql hSqlDQL  
Functional Class cf cfUser rec ("record"); obj/o ("object"); udv ("user-defined variable")
       
Internal Class Variable m_ [+ datatype] m_bConstructed i_
Class Variable c_ [+ datatype] c_bShowError  
Global Variable g_ [+ datatype] g_bMayEdit  
Parameter p_ [+ datatype] p_nCustomerID  
       
Form Window frm frmName fw
Table Window tw tblName tbl
Dialog Box dlg dlgName  
MDI Window mdi mdiName  
Child table tbl tblCustomers  
Data Field df + Datatype dfnNumber, dfsName, dfdStart w/o datatype
Multiline Field ml mlErrorText  
Pushbutton pb pbStart  
Radio Button rb rbSelection  
Check Box cb cbShowColors  
Option Button ob obName  
List Box lb lbUsers  
Combo Box cmb cmbTitle  
Picture pic picImage  
Horizontal Scroll Bar hsb hsbRight  
Vertical Scroll Bar vsb vsbHeight  
Custom Control cc ccSpin  
Column col [+ datatype] colnNumber, colsName, coldStart w/o datatype; c

When I attempt to run the SETUP.EXE of a Centura product I get 'Installation Aborted", "Internal Error". How can I avoid this?
The Wise installer makes extensive use of the TEMP directory during any installation. Make sure that:
- The directory pointed to by the TEMP (or TMP) environment variable exists.
- Make sure that this directory is writable by the current user.
Sadly, there's no way to code around this in the installer, since the error occurs before the first line of code is executed.

How do I let a user select a value from a large set of values (30000+)?
The important point is that the user may not remember the exact "key" but if you show him a set of keys, he will know if the desired key is in that set and "select" it.
This is "universal" problem, not limited to any specific programming language. A combo-box is NOT the right solution, so here are some other suggestions:

1. If there are less than 20 entries, populate a combo-box. For 20+ entries, open a "selection dialog".

2. For 20+ to 80 entries the "selection dialog can show a grid (child table or a list view)" on which the user double-clicks.

For 80+ entries, a number of variations have to be tried:

3. Allow the user to enter QBE search criteria in the selection dialog, then populate (at the most 80) rows of the filtered entries in the grid. Ask the user to enter more QBE criteria if the qbe results in more than 80 rows.

4. Arrange the data in hierarchy and let the user open-up the tree-control. Users get fed-up after two levels of tree. Each level should not contain more than 20 entries - but this still allows you to select 1 entry out of 20x20x20 entries in few double-clicks. (...that users hate double clicks is another story).

5. Allow the user (or a user-group) to specify "his favourite 20 entries". Populate the combo-box with these 20 entries first. If the entry the user wants to select is not there then open the "Selection Dialog".

6. Allow the user (or a user-group) to specify "his favourite 100 entries". Populate the Selection Dialog grid with these 100 entries first. If the entry the user wants to select is not there then switch to QBE mode.

7. Keep a statistics of which entries get selected more frequently than others (separate statistics for each user or each user-group). Populate the Selection Dialog Grid with top 100 entries from this list. If the entry the user wants to select is not there then switch to QBE mode.

8. Like MS Index-server, create an index of "interesting" words from the description and code of the set of entries (trial-and-error required to prepare a list of words which are natural language specific). In Selection Dialog allow an index search to populate a list of all rows (top 80 only) containing the selected indexed word. Let the user select a row from it.

9. Allow AND/OR/NOT operations (boolean search expressions - of index words) in 8 above.


Development Environment

How do I change the standard file type settings in the open/save dialogs of the SQLWindows/Team Developer?
The default application file extension can be changed in the Preferences window. This is set to "APP" as Centura's standard source file type and can be replaced by any extension, for example "AP?" to display all files having an extension starting with "AP". However, as a drawback, you have to manually type the file extension of your file when saving.

I get the error "This application has exceeded the space available for string constants". What should I do?
What you need to do in your app is consolidate your string literals into constants. For example, you may have many calls to SalMessageBox( ) in your app and the title parameter passed to each call might be a hard coded string like the name of the application:
Call SalMessageBox( sText, 'Super App', MB_Ok )
Instead, you should define one string constant and use this string constant.
Other strategies you can use include storing lengthy messages in a text file or database table and load the messages at application startup. The Visual Toolchest string table functions can be great for this approach. Also, you could place code in internal functions and compile them with the object compiler.

Are constant names retained when making an executable?
In SQLWindows, when you make an EXE, named constants are replaced with their actual values. This means you can't use named constants with SalCompileAndEvaluate, including SAL system defined constants.
The workaround is to assign NUMBER_Null to a variable that you can later reference, for example:
On SAM_AppStartup
    Set gnNull=NUMBER_Null
and then you can call SalCompileAndEvaluate with
"Set nNumberVariable=' || 'gnNulll'".
In Centura Team Developer the runtime was changed so that the symbol is preserved in the EXE file and named constants can be used in SalCompileAndEvaluate.

Are there any problems I might face when working with Windows 98?
Centura Software Corporation is in the process of certifying its 32-bit products on Windows 98. Visit http://www.centurasoft.com/support/tech_info/bulletins/win98warn.html to obtain further information.

I keep getting messages about missing bitmaps in my source code that are not referenced anymore. How do I avoid this?
SQLWindows doesn't remove such references correctly, although the source will compile and run fine. To avoid it, just save as text, exit, and reload the file.

Whenever a new version of Centura's development environments comes out, do I have to recompile my source code or can I just replace the runtime DLLs?
You will need to recompile existing applications to deploy them with a new version's runtime files.  In general this is true of every major release of SQLWindows and SQLWindows/32. In 32-bits for example, all executables built with a given version of SQLWindows/32 are implicitly linked to cdlliXX.dll where XX is the major/minor release number, so your existing executables link to cdlli11.dll (which in turns links to many other xxxi11 DLLs), and all executables built with 1.5 link to cbi15.dll.
However, this does mean that you can deploy applications built with 1.5 alongside earlier ones built with 1.1.x.

Whenever an instance of a program developed with SQLWindows or SQLWindows/32 gets started, do new instances of deployment files get loaded or are these DLLs shared?
I Win16, DLLs detect if they are already loaded and will use the existing instace. Since each 32-bit application runs in it's own process, it loads its own copies of any DLLs required.

How do I initialise class variables?
For setting values for class variables, you don't need an existing instance of the class. So, you can just use the following code to initialise the default color of screen element:
On SAM_AppStartup
   Set cfScreenElement.c_mDefaultColor=COLOR_Grey

I am not able to compile my application because of an error stating that my outline is full. What should I do now?
You might first try to save everything as text, exit SQLWindows(/32) and reopen the files. If that does not help, try one or more of the following suggestions.
- partition your application in several smaller programs,
- compile parts of the outline into DLLs (Object Compiler),
- use dynalibs,
- examine your code for re-use possibilities, for example,
    - develop functions,
    - develop classes,
    - create screens with parameters if you have similar screens.

A comment in my code makes it look odd. How do I hide my comments?
Comments for Window Parameters and Window Variables can be easily hidden. Write the comment meant for the variable or parameter prefixing with "!". Select the comment line and press Alt + Right Arrow. The comment is now hidden. Now double click the variable to see the hidden comment associated with it. Likewise comments can be hidden anywhere in SAL outline code. And don't forget to mention some where "Double Click for Hidden Comments" in your code so that your colleagues find these hints.

How can I decipher a bitmask return code in Centura's development environments?
If the error is only one bit:
If ( nErrorCode & ERROR_CODE )
If the error is more than one bit:
If ( nErrorCode & ERROR_CODE )=ERROR_CODE


Algorithms & Functions

Date/Time

How do I round up durations to a certain granularity?
The following is a simple expression that will get you the nearest half hour, given a duration expressed in decimal hours.
This expression takes a value such as 2.36 hours from nHourDuration, doubles it to 4.72 half-hours and adds 0.99999 to make this round up to 5.71999. SalNumberTruncate( ) reduces this down to five half-hours, and the division by 2 yields 2.5 hours.
SalNumberTruncate( 2 * nHourDuration + 0.99999, 10, 0 ) / 2
You can go from minutes to hours at half-hour granularity by using
SalNumberTruncate(nMinuteDuration / 30 + 0.99999, 10, 0 ) / 2
This can be easily modified to any time period you want to use.

What is the forumla for calculating a week number?
The number of a week can be determined by
Set nWeekNumber=( SalDateWeekBegin( dtDate ) - SalDateWeekBegin( SalDateConstruct( SalDateYear( SalDateWeekBegin( dtDate ) + 3 ), 1, 4, 0, 0, 0 ) ) ) / 7 + 1

Where can I get information about calendars?
Check the Calendar FAQ at www.pip.dknet.dk/~pip10160/calendar.html. It contains an extensive overview of the Christian, Hebrew, and Islamic calendar and even covers some historical background.

Given that I have two date/time values, how do I calculate the elapsed time between them?
If you subtract two date/time values from each other to get the elapsed time, the resulting value is given in number of days. When you are dealing with a matter of minutes or even a smaller time interval, the results of the subtraction aren't very useful. In order to convert this value to a more meaningful one, calculate how many of the given time units occur in one day. Then, multiply the difference you calculated by the number of units in a day to get the number of units in your elapsed time.For example, there are 24 * 60 * 60 seconds in a day, or 86400 seconds. If subtracting two date/time values results in a value of 0.0416666666666667, then multiply 0.0416666666666667 * 86400=3600 seconds. (This corresponds to one hour.)

Number

How do I combine low and high values into one number?
Use the following code fragment:
Set nCombinedValue=nHighValue * 65536 | nLowValue
Instead, one can use VisNumberMakeLong( ), a function part of the Visual Toolchest (VT.APL/VTMISC.APL).

How do I accomplish hexadecimal conversions with Centura's development products?
Function: NumberToHex
Description:
Returns
  String:
Parameters
  Number: p_nValue
Static Variables
Local variables
  Number: nRemainder
  String: sHex
Actions
  Set p_nValue=SalNumberAbs( p_nValue )
  If p_nValue > 15
   Set sHex=NumberToHex( SalNumberTruncate( p_nValue/16, 18, 0 ) )
  Set nRemainder=SalNumberMod( p_nValue, 16 )
  If nRemainder > 9
   Set nRemainder=nRemainder + 7
  Set sHex=sHex || SalNumberToChar( nRemainder + 48 )
  Return sHex

Function: HexToNumber
Description:
Returns
  Number:
Parameters
  String: p_sHex
Static Variables
Local variables
  Number: nDec
  Number: nLen
  Number: nLower
Actions
  Set p_sHex=SalStrUpperX( SalStrTrimX( p_sHex ) )
  If SalStrLeftX( p_sHex, 2 )='0X'
   Set p_sHex=SalStrRightX( p_sHex, SalStrLength( p_sHex ) - 2 )
  Set nDec=SalStrLop( p_sHex )
  Set nLen=SalStrLength( p_nHex )
  If (nDec > 47) and (nDec < 58)
   Set nDec=nDec - 48
  Else If (nDec > 64) and (nDec < 71)
   Set nDec=nDec - 55
  Else
   Set nDec=NUMBER_Null
  If (nLen > 0) and (nDec !=NUMBER_Null)
   Set nLower=HexToNumber( p_sHex )
   If nLower !=NUMBER_Null
    Set nDec=nDec * SalNumberPower( 16, nLen ) + nLower
   Else
    Set nDec=NUMBER_Null
  Return nDec

String

How do I concatenate strings in Centura's development products?
String can be combined with two vertical lines like sString1 || sString2 .

Miscellaneous

Where can I get the documentation of the CStruct library available with SQLWindows and Team Developer?
The CStruct functions are rarely documented, however, there is a .WRI file available that was originally written for SQLWindows. The functions are almost the same, thus the information provided counts for CTD as well. Download SWCStruc.ZIP.

In which order does SalLoadApp( ) search for the correct path to be used if I didn't specify one?
SQLWindows' SalLoadApp() uses WinExec() directly.  The rules for finding executables with WinExec() are as follows:
1. The directory from which the application loaded.
2. The current directory.
3. The Windows system directory. The GetSystemDirectory() function retrieves the path of this directory.
4. The Windows directory. The GetWindowsDirectory() function retrieves the path of this directory.
5. The directories listed in the PATH environment variable.

When running on NT or Win95 then WinExec actually uses CreateProcess() internally (and CTD uses CreateProcess directly instead of WinExec)  The rules for finding exectuables on Win95 and NT are as follows:
1. The directory from which the application loaded.
2. The current directory for the parent process.
3. Windows 95: The Windows system directory. Use the GetSystemDirectory() function to get the path of this directory.
    Windows NT: The 32-bit Windows system directory. Use the GetSystemDirectory() function to get the path of this directory.
    The name of this directory is SYSTEM32.
4. Windows NT: The 16-bit Windows system directory. There is no Win32 function that obtains the path of this directory, but it is searched. The name of this directory is SYSTEM.
5. The Windows directory. Use the GetWindowsDirectory() function to get the path of this directory.
6. The directories that are listed in the PATH environment variable.
Note that on NT there is a 'system' path that is prepended to any user supplied path.   This is settable in Control Panel->System->Environment.

How do I use the function SalCompileAndEvaluate to evaluate an IF-ELSE statement?
1) Put your If-Else statement into a internal function and call this function with SalCompileAndEvaluate( ). This is of course only possible if your If-Else statement is known at design-time.
2) Transform your If-Else statement to an expression. For example:
If <condition>
  <statement1>
else
  <statement2>
...can also be written as...
(<condition> AND <statement1>) & 0 OR (Not <condition> AND <statement2>)
This works because Centura only evaluates as much of the AND-OR expressions as is needed to decide the final outcome. Hence if <condition> is true, then <statement1> must be evaluated to decided whether the first AND is True or FALSE. If <condition> is false, then <statement1> does not need to be evalueded and therefore is never executed.  The same (but reverse) is true for the second AND statement. The "&0 OR" part in the middle is there to force Centura to evaluate both AND statements.
Additionally, you might consider constructing an expression that contains VisStrChoose( ) or VisNumberChoose( ) to be evaluated by SalCompileAndEvaluate( ).

How can I open/print a file/document with its default application?
This can easily be done with the Windows SDK function "ShellExecuteA" (leave out the "A" in 16-bit SQLWindows). Example:
Call ShellExecuteA( hWndForm, "open", "C:\example.doc", STRING_Null, STRING_Null, SW_SHOWNORMAL )
Call ShellExecuteA( hWndForm, "print", "C:\example.doc", STRING_Null, STRING_Null, 0 )
To avoid the appearance of a new window, specify "SW_HIDE" as the last parameter. Other SW_* parameters are defined in the SDK libraries.
It is possible to send eMails with the above function call. Just pass "open" and "mailto:Centura_FAQ@gmx.net" in the call to ShellExecute(A).

How can I close an external application from within SQLWindows or SQLWindows/32 (for example the local database engine)?
Just use the following function:
Call SalPostMsg(  SalAppFind( sAppliationName, FALSE ), SAM_Close, 0, 0 )
sApplicationName could be set to "DBNT1SV.EXE".

How do I use SalAppFind on NT, it works on all other platforms?
You have to run a specific OS installation routine which is located in your Deploy directory. See http://www.centurasoft.com/support/tech_info/knowledge_base/wintdist.htm for further information

How do I open a file with long file/path name and SalLoadApp( )?
You have to submit additional quotes like
Call SalLoadApp( '"C:\\Program Files\\Microsoft Office\\Winword.exe"'

How do I specify multiple file extensions in one file type selection row of SalDlgOpenFile( )?
Just use the following code as an example:
Set saFilters[0]='Images Files'
Set saFilters[1]='*.bmp; *.gif; *.jpg; *.tif; *.wmf; *.ico'
Set nNumFilters=2
Call SalDlgOpenFile( hWndOwner, sTitle, saFilters, nNumFilters, nIndex, sFile, sPathedFile )


What is SOUNDEX and how can I use it?
SOUNDEX is an algorithm that converts strings to a 4-byte code. It can be used to encrypt a surname and do searching only on the code. This will eliminate most variations of different spelling.
Example: Meyer=M600, Meier=M600, Mayer=M600, Smith=S530, SMYTHE=S530.
The function listed below generates the soundex code for a string that is passed to the function.
Function: Soundex
Description:
Returns
  String:
Parameters
  String: p_sOrgString
  Static Variables
Local variables
  String: s
  String: result
  String: PrevChar
  String: Ch
  Number: i
Actions
  Set s=SalStrUpperX( SalStrTrimX( p_sOrgString ) )
  If s !=STRING_Null
   Set PrevChar=STRING_Null
   Set result=SalStrLeftX( s, 1 )
   Set i=1
   While i < SalStrLength( s )
    If SalStrLength( result )=4
     Break
    Set Ch=SalStrMidX( s, i, 1 )
    If Ch !=PrevChar
     If SalStrScan( 'BPFV', Ch ) >=0
      Set result=result || '1'
     Else If SalStrScan( 'CSKGJQXZ', Ch ) >=0
      Set result=result || '2'
     Else If SalStrScan( 'DT', Ch ) >=0
      Set result=result || '3'
     Else If Ch='L'
      Set result=result || '4'
     Else If SalStrScan( 'MN', Ch ) >=0
      Set result=result || '5'
     Else If Ch='R'
      Set result=result || '6'
     Set PrevChar=Ch
   Set i=i + 1
  While SalStrLength( result ) < 4
   Set result=result || '0'
  Return result


Is it possible to use a timer that has longer intervals than those provided by SalSetTimer( )?
A: I would suggest to set some date/time-variable to the current time, then call SalSetTimer() with a value 1 minute (you should specify less, if you need more accurate timing, common rule is you get a maximum error of +/- time_specified/2). Then in the message actions of SAM_Timer you have to check your date/time-variable against the current-time and see if the needed amount of time has elapsed (remember you get values in days if you compute the difference between dates, that means 0.000001157407.. or 1/86400 are one second.
Alternatively, you could use the SDK function SetTimer( ).


User-defined variables

How do I search an array of UDV?
You can use the standard VisArrayFindNumber(), VisArrayFindString(), and VisArrayFindDateTime(). The search is performed on the first member of the UDV. When the UDV is derived from another UDV, the first member is also the one from the base class.
Functional Class: cTestClass
Instance Variables:
    String: m_sName
    Number: m_nAge
...
cTestClass: ObjArray[*]
...
Set ObjArray[0].m_sName="Tom"
Set ObjArray[1].m_sName="Sam"
Set ObjArray[2].m_sName="Pam"
...
Set nIndex=VisArrayFindString( ObjArray, "Pam" )     ! nIndex will be set to "2"
VisArray* functions defined in VT.APL (SW) and VTARRAY.APL (CTD).

How do I set up an UDV as a receive parameter in Centura's development environment?
User-defined variables are always passed by reference (as a receive parameter). That is why SQLWindows and SQLWindows/32 do not differentiate between normal and receive paramters with functional classes.


External functions

How do I define an external function?
External functions usually reside in dynamically linked libraries (DLLs). A function has to be identified by SQLWindows or Team Developer. If an export ordinal is supplied, Centura will use it to identify the function uniquely. Otherwise, the development environment uses the function name.
It is possible to set up one function several times with different function names. In this case, you have to enter the correct export ordinal each time. If the function name is spelled correctly, set the export ordinal to "0".
It is recommended to use the correctly spelled function name in 32-bit, because there are some differences between Windows 95 and Windows NT in the export tables of DLLs.

What is an export ordinal?
An export ordinal uniquely defines a function within a dynamically linked library (DLL). Upon building the DLL, the export ordinal is defined in the project's definition file (*.DEF). Some DLLs specify a certain base ordinal that has to be added to each definition.

How do I determine the export ordinal of a function?
There are a couple of utilities that allow you do view the structure of a DLL. If you are using a 16-bit development environment, you can determine a function's export ordinal by running MAPDLL.EXE (supplied with SQLWindows) or EXEHDR.EXE  (comes with Visual C++). Team Developer does not ship with such a tool. Windows 95 and Windows NT 4 include a utility named "QuikView" which is added to the explorer's context menu. Just right-click on a DLL and scroll down until you see the list with exported functions ("Export table"). Mind that the "base" (in the header of the "Export table") has to be added to each value.

Is there a limit to the number of parameters that can be passed to an external function?
Yes, there is a limit to the number of parameters that can be passed to an external function. It is not actually a fixed number, but a limit that is reached when the compiler cannot compile the function call because of it's complexity. It was found that this usually occurs at 15 or more parameters. The solution is to pass an array or UDV handle to the external DLL. If it is one you have written, if not, you may have to consider writing another DLL using arrays or UDVs which then calls the DLL you want.

Can I use non-indexed DLLs (those that have no export ordinal numbers)?
SQLWindows 5.0 as well as Team Developer can handle DLL's that export functions without ordinal numbers. SQLWindows 4.1 requires ordinals.

How do I deal with CTD string handles in Borland's Delphi?
Centura Team Developer has its own string handling mechanisms. Strings are managed internally and only string handles can be seen from the outside. The APIs used to accomplish this task reside in CDLLI1x.DLL and are included as external functions. Furthermore, there are three wrapper functions that make life somewhat easier:
- SWinCreateHString (create a new Centura string)
- SWinSetHString (replace the content of a Centura string)
- SWinGetHString (retrieve the content of a Centura string)
Find the necessary Delphi unit below:
unit HStrings;
interface

uses
  Windows;

type
  HSTRING=integer;

// creates a new string handle and copies s
function  SWinCreateHString (s:PChar) : HSTRING;

// replaces the contents associated with the string handle with s
procedure SWinSetHString (hs:HSTRING; s:PChar);

// converts a string handle to a pascal string
function  SWinGetHString (StringHandle:HSTRING) : String;

implementation
uses
  SysUtils;

const
  DLLName='CDLLI11';

function  SWinStringGetBuffer (StringHandle:HSTRING; var Len:integer) :
PChar;
  stdcall; external DLLName;
function  SWinInitLPHSTRINGParam (var StringHandle:HSTRING; Len:integer)
: BOOL;
  stdcall; external DLLName;

function  SWinCreateHString (s:PChar) : HSTRING;
var
  hs : HSTRING;
  Len : integer;
begin
  hs :=0; // handle=0 tells CTD to create a new string
  Len :=strlen (s) + 1; // length of the string plus zero-byte
  if SWinInitLPHSTRINGParam (hs, Len) then
    StrCopy (SWinStringGetBuffer (hs, Len), s); // set the content
  result :=hs; // return the newly created handle
end;

procedure SWinSetHString (hs:HSTRING; s:PChar);
var
  Len : integer;
begin
  // handle is not zero, which tells CTD to replace the string
  Len :=strlen (s) + 1; // length of the string plus zero-byte
  if SWinInitLPHSTRINGParam (hs, Len) then
    StrCopy (SWinStringGetBuffer (hs, Len), s); // set the content
end;

function  SWinGetHString (StringHandle:HSTRING) : String;
var
  Len : integer;
begin
  result :=StrPas (SWinStringGetBuffer (StringHandle, Len));
end;

end.

How can I execute a batch-file from within Centura's development environemnts?
This can easily be done with the Windows SDK function "ShellExecuteA" (leave out the "A" in 16-bit SQLWindows). Example:
Call ShellExecuteA( hWndForm, "open", "C:\example.bat", STRING_Null, STRING_Null, SW_ShowNormal)
To avoid the appearance of a new window, specify "SW_HIDE" as the last parameter.

My application crashes when I compile it with a self-written DLL using MFC. How can I fix that?
SQLWindows(/32) loads/unloads the DLL to check the correctness of external references during compilation. Experiments have shown that the function call
Sleep( 50 );
placed inside 'InitInstance' and 'ExitInstance' will fix the observed error.

What is the difference between the external data types LPSTR and LPCSTR?
It's mostly the same to CTD. The benefit is for you is that it is somewhat documenting. For a string you can map it to an LPSTR or an LPCSTR, but for a receive string you can only map it to an LPSTR. Similarly, a Number can be mapped to a DWORD or a HANDLE. The HANDLE is more documenting about how the parameter is used.

Where can I find documentation about the various functions which exist in Windows DLLs like USER.EXE/USER32.DLL, etc.?
These functions are documented in the Windows SDK, available with development products like VC++, C++ Builder, VB, or the Microsoft Developer Network. The information is also available online at www.microsoft.com/msdn/.

How can I get the address of a string-variable?
Use the following code:
nAddress=SWinStringGetBuffer( sString, lLength )
Library name: CDLLI11.DLL
    Function: SWinStringGetBuffer
        Description:
        Export Ordinal: 0
        Returns
            Number: DWORD
        Parameters
            String: HSTRING
            Receive Number: LPLONG


How do I pass parameters that are part of a pointed array of structures?
There are two ways to go about it. The simple way requires that you always pass an array of the same size. If this works for you, than your declaration would look something like this:
Function: DPtoLP
Returns
  Boolean: BOOL
Parameters
  Number: HANDLE
  structPointer
   Receive Number: LONG
   Receive Number: LONG
   Receive Number: LONG
   Receive Number: LONG
   Receive Number: LONG
   Receive Number: LONG
  Number: INT
This declarations presumes that you're always going to pass three POINTs. You'd have to call it something like this:
Call DPtoLP( hDC, x1, y1, x2, y2, x3, y3, 3 )
If that doesn't work for you and you need dynamic arrays, then you could declare the function like this:
Function: DPtoLP
Returns
  Boolean: BOOL
Parameters
  Number: HANDLE
  String: LPVOID
  Number: INT
With this declaration, you'd "stuff" the POINTs into a string variable first using CStructPutLong from the CSTRUCTL.APL library. The call would look something like:
! assume na[ ] is a 2D array of point data.
Set nPointSize=8 ! 2 longs
Set nNumPoints=5 ! just for this example
Call SalStrSetBufferLength( sBuffer, nPointSize * nNumPoints )
Set n=0
While n < nNumPoints
  Call CStructPutLong( sBuffer, n * nPointSize, na[n, 0] ) ! first long
  Call CStructPutLong( sBuffer, n * nPointSize + 4, na[n, 1] ) ! second long
  Set n=n + 1
Call DPtoLP( hDC, sBuffer, nNumPoints )


Controls

How can I avoid the empty areas when I resize a toplevel window?
This can be accomplished by trapping the windows message WM_SIZE and aligning the child windows properly on the form window. Table windows and list boxes should extend their size, pushbuttons will have to be placed accordingly, radio and check boxes moved to the right or bottom. A sample on how this can be accomplished is available for download: RESIZE.ZIP

Is it possible to determine the window handle of a status bar in order to place controls in it?
The identifier of a status bar is hard-coded into SQLWindow/Team Developer and equals the hex value 0x7FF1. By calling the SDK function GetDlgItem( hWnd, 0x7FF1 ), the window handle of the status bar is returned. hWnd must be the parent of a form window, so from within the message actions section of normal toplevel window you would have to use the following code:
On SAM_Create
    Set hWndStatus=GetDlgItem( GetParent( hWndForm ), 0x7FF1 )
Controls can be easily placed there with a call to SetParent( ).
Alternatively, you could create a small form window and place all the controls on it, that you want to appear on the status bar. Keep in mind, that height and width of the status bar is limited.
Call SalCreateWindowEx( frmDummy, hWndStatus, 0.0, 0, nFormWidth, nFormHeight, CREATE_AsChild )

In which order are child controls created at runtime?
Except for lines, frames, and pictures, all child items of a container window are created in the order they appear in your outline. This behaviour will ensure the "z order", meaning that background texts will not appear to be on top of data field, for example.

How can I set the tab-order dynamically at runtime?
This can be accomplished with the SDK funktion SetWindowPos( ). Alternatively, VisWinSetTabOrder( ) as defined in VT.APL/VTWIN.APL will work also.

I'm searching for items in the toolbar and SalGetFirstChild() and SalGetNextChild() don't seem to work?
First, you have to look for the window handle of the toolbar (which is not the same as your form, respectively hWndForm). Afterwards, you can get acquire the window handles of the controls with SalGetFirst/NextChild( ).
Set hWndToolbar=SalGetFirstChild( GetParent( hWndForm ), TYPE_FormToolBar )
Set hWndToolbarChild=SalGetFirstChild( hWndToolbar, TYPE_Any
)

How do I get the handle of a window I only know the name of?
Find the handle using SalFindWindow( hWndParent, sWindowName ) for SQLWindows/32 or SWinFindWindow( hWndParent, sWindowName ) for SQLWindows. Errors have been reported with these functions when there are instances of the cOutlineComboBox placed on a form. In such a case, you might want to try the interal function below:
Function: __FindWindow
Returns
    Window Handle:
Parameters
    Window Handle: hWndContainer
    String: sTemplate
Local variables
    Window Handle: hWnd
    Window Handle: hWndChild
    Number: nTypeMask
    String: sName
Actions
    Set hWnd=  SWinFindWindow( hWndContainer, sTemplate )
    If hWnd=hWndNULL
        Set nTypeMask=TYPE_Any
        Set hWndChild=   SalGetFirstChild (   hWndContainer, nTypeMask )
        Loop
            If SalGetItemName ( hWndChild, sName )
                If sName=sTemplate
                    Return hWndChild
            Set hWndChild=SalGetNextChild ( hWndChild, nTypeMask )
            If hWndChild=hWndNULL
                Break
   Return hWnd


How can I detect/prevent that the user shuts down Windows?
When the user tries to shut down Windows, WM_QUERYENDSESSION is sent to all applications. If you want to allow the termination return TRUE. If you return FALSE, Windows will not be closed.

How do I get window handle of a Background Text?
Window handle of a Background Text can be captured by setting SQLWindows bStaticsAsWindows parameter.
Before creating a Form Window which contains the Background Text, use statement "Set bStaticsAsWindows=TRUE". Use SalGetFirstChild and SalGetNextChild functions to extract the window handles of the Background Text's you are interested in.

When trying to place a cCalendarDropDown in a Toolbar, I get GPFs. How can I fix that?
When placed on a Tool Bar, the object GPFs when setting m_lpControl in the VTM_ControlCreate message. It appears that the window objects aren't "there" yet, so if  you post the same message to itself, the control works beautifully.
Original Message Actions
On VTM_ControlCreate
    Set m_lpControl=lParam
Workaround Message Actions
On VTM_ControlCreate
    If wParam
        Set m_lpControl=lParam
    Else
        Call SalPostMsg(hWndItem,VTM_ControlCreate, TRUE, lParam)


How can I hide a group box?
You can find the handle of a groupbox on a form by using SalGetFirstChild() with TYPE_GroupBox. The following example shows or hides a groupbox identified by it's title:
Function: Switch
 Description:
 Returns
  Boolean: bSucceeded
 Parameters
   String: p_sGroupboxTitle
  Boolean: p_bMakeVisible
 Static Variables
 Local variables
  Window Handle: hWnd
  String: sText
 Actions
  Set hWnd=SalGetFirstChild( hWndForm, TYPE_GroupBox )
   While hWnd !=hWndNULL
   Call SalGetWindowText( hWnd, sText, 256 )
   If sText=p_sGroupboxTitle
    If p_bMakeVisible
     Call SalShowWindow( hWnd )
    Else
     Call SalHideWindow( hWnd )
    Return TRUE
   Set hWnd=SalGetNextChild( hWnd, TYPE_GroupBox )
  Return FALSE

Check Boxes

Is it possible to have the text (label) of a check box appear on the left?
To have the label of a check box switch sides, the button style "BS_LEFTTEXT", defined in the Windows SDK, has to be assigned which can be done with the following code (remove the "A" in the *WindowLong functions when using SQLWindows):
On SAM_Create
  Call SetWindowLongA( cbCheck, GWL_STYLE, GetWindowLongA( cbCheck, GWL_STYLE ) | BS_LEFTTEXT )
To assign this style using the Visual Toolchest enhancements (defined in VT.APL/VTWIN.APL), call
On SAM_Create
  Call VisWinSetStyle( cbCheck, BS_LEFTTEXT, TRUE )

Combo Boxes

How do I determine the window handle of the data field part of a combo box?
By making use of the Windows SDK, the handle of the editing part of a combo box can be returned with
GetWindow( cmb1, GW_CHILD)

When I disable a combo box, the combo box text is hard to read. How can I make it appear in the original black font?
The trick is to disable the combo box, but enable the contained data field. This can be done by adding a global function to your class library:
Function: BlackDisabledCombo
    Description: Disables a combo box, but leaves text black, rather than hard-to-read "disabled gray" color.
    Returns
Boolean:
    Parameters
        Window Handle: p_hWndCombo
    Static Variables
    Local variables
Boolean: bOk
        Window Handle: hWndEditPortion
    Actions
        Set bOk=TRUE
        Set bOk=bOk AND SalGetType( p_hWndCombo )=TYPE_ComboBox
        Set bOk=bOk AND SalDisableWindow( p_hWndCombo )
If bOk
            Set hWndEditPortion=GetWindow( p_hWndCombo, GW_CHILD )
    Set bOk=bOk AND hWndEditPortion !=hWndNULL
        Set bOk=bOk AND SalEnableWindow(hWndEditPortion)
        Set bOk=bOk AND SalSendMsg( hWndEditPortion, EM_SETREADONLY, TRUE, 0 )

Return bOk
However, the above function does only work for combo boxes having the "editable" style set. If you still need to prevent the user from altering the text contained in the combo box, use the following (in the SAM_CreateComplete message of its parent, for example):
Call SalEnableWindow( hWndCombo )
Call SalSendMsg( GetWindow( hWndCombo ), GW_CHILD), EM_SETREADONLY, TRUE, 0 )

Check the August 1998 issue of Centura Pro for further information.

Data Fields

Is there a way to avoid disabled datafields appear grayed out on Win32s platforms?
This can be accomplished with setting the attribute "Editable" to "No" in the object's customizer. Then, when the field gets disabled at runtime using SalDisableWindow( ), add the call SalSetColor( dfDatField, COLOR_IndexWindow, COLOR_White - 1 ). There is a limitation in Windows causing disabled fields to refuse appearing in COLOR_White.
Alternatively, you could just make the control read only instead of disabling it. This can be done with sending the message EM_SETREADONLY to the control, passing TRUE in wParam.

Form Windows

How do I create a window without the system menu?
Just add the following code to the windows message actions section:
On WM_NCCREATE
    Call SetWindowLongA( hWndForm, GWL_STYLE, GetWindowLongA( hWndForm, GWL_STYLE ) - WS_SYSMENU )
In 16-bit SQLWindows, remove the "A" from the function definitions.

How do I trap that a window gets minimized?
The WM_SIZE message gets send when the user clicks the "minimize" button in the title bar or chooses this function from the system menu. The type of sizing is passed in the wParam variable as shown below:
On WM_Size
   If wParam=SIZE_MINIMIZED

How do I prevent scroll bars to appear when resizing?
Set the form page width and height to 0.01" at design time.

List Boxes

MDI Windows

I add a dynamically built menu to my MDI Window. Why does it get lost when switching to another MDI child?
Menus are built depending on the current MDI child. Whenever the user switches to another child, the menu set up for that form becomes the current menu of the MDI parent. If you want to build a dynamic menu in a MDI Window, you have to rebuild your dynamic menu every time the current MDI child changes. In order to do this, trap the WM_MDIACTIVATE on the MDI Child (the Form Window, not the MDI Parent Window), and recreate the menu if wParam is TRUE. This parameter indicates that the window get activated, not de-activating.
Alternatively, you can return FALSE when receiving WM_MDIACTIVATE to avoid updating the menu.

Multiline Fields

How do I append text to a multiline field?
The easiest solution is using
Set mlField=mlField || sNewString
This is not a useful technique when the new text portion should be scrolled into view as in console windows or progress notificators. The following technique is recommended by Microsoft and can be easily wrapped up in a global function, FieldAppendText( ), for example:
Call SalSendMsg( mlField, EM_SETSEL, -1, -1 )
Call SendMessageA( mlField, EM_REPLACESEL, 0, CRLF || sStringToAdd )
In 16-bit SQLWindows, use SendMessage( ) instead of SendMessageA( ).

How do I position the cursor in a multiline field?
This can be achieved by using the EM_SETSEL message as defined in the SDK libraries available for download.
The following example shows how to move the cursor to the 25th character:
Call SalSendMsg( mlField, EM_SETSEL, 25, 25 )
Additionally, if the following ten characters should be selected, use the following function call:
Call SalSendMsg( mlField, EM_SETSEL, 25, 35 )


I try to display text with some page breaks in my multiline field, but all breaks are displayed as double concatenate-chars ("||"), why does that happen?
When retrieving text from external sources sometimes you get the paragraph breaks as CR/LF (DOS/Windows) and sometimes as CR (mostly on Unix). Centura (like other Windows-programs, e.g. Notepad) expects that there is the CR/LF-combination, therefore if there is only CR it doesn't display page-breaks. You must convert the single CRs in your text to CR/LF.
CR is ASCII-code 0x0a
LF is ASCII-code 0x0d

Pushbuttons

QuickGraphs

How do I spool a Quick Graph to the printer?
QG.ZIP is a simple sample that show how to use the Quick Graph with SAL. That is, that graph is not linked directly to a datasource. You control the graph programmatically. QG.ZIP also include two options for printing the graph, one using a .WMF file, and another showing how to print the QG by linking it to detail lines in a .QRP report. The sample is using the BUDGET table from the sample database. QG.ZIP include QG.SQL, SW_QG.APT, CTD_QG.APT and two .QRP's. QG.ZIP sample work with both SQLWindows and CTD higher than 1.0.0. Click here to jump to the downloads section.

QuickTabs

How do I set up a control to have no association to any of the tab pages at all?
Enter the "Associate with tab" dialog from the context menu. When you click on the last row, just hold done the shift-key or use the space bar to deselect the last row.

SalHideWindow doesn't work with QuickTabs. How do I keep controls hidden?
Whenever you activate a tab, it goes through its business of making visible the associated child objects. To keep a child object hidden at that moment of time, you'll need to tap into the functionality provided by the QuickTab class library. Here's what you need to do:
In the parent form window, code the following window function:
Function: TabActivateStart
    Description: Indicates that a tab has been activated.
                 This function is called before the child windows have been shown
    Returns
    Parameters
        Window Handle: hWnd
        Number: nTab
    Static Variables
    Local variables
    Actions
        If nTab=2     ! 2 is the # of tab in which df1 exists
            Call picTabs.HideWindow( df1 )

df1 is the data field that you need to hide. What's really happening is that TabActivateStart is a late-bound function call in the class library. It allows you to hook into the process of a tab being activated when the user clicks on it. At that point in time, calling this special flavor of "HideWindow" function does the trick.
To hide an object on the default tab, you'll need to plug in another late-bound function call, TabCreate(), as a window function of the form window. This is because the default tab won't need to call the TabActivateStart() function until the time the user tabs away to another tab and revisits subsequently.
Function: TabCreate
    Description: Indicates that a tab has been created.
                       This function is called when the tab control receives SAM_Create.
    Returns
    Parameters
        Window Handle: hWnd
    Static Variables
    Local variables
    Actions
        Call picTabs.HideWindow( df1 )
And you can use picTabs.ShowWindow ( hWnd ) to make the object visible.

When I edit the base class which includes cQuickTabsDialog or cQuickTabsForm I lose all the information in the derived Windows! Can I avoid this?
Some information is stored in picTabs, which is replaced if something changes and therefore this gets lost. You can work around this problem if you remember all the tabs in the QuickTabs editor and recreate them with the equal names after changing your classes. The child windows will be on the same tabs as before, because they reference the page on which they are to be shown by named properties.

How do I hide individual tab pages of a cQuickTab?
Hiding is not directly supported in the cQuickTab interface. However, you can delete a tab and recreate it later using the same tab name (and label if you want). All associated child windows will still appear as configured at design time on the correct page. Keep in mind that the index will change when deleting pages at the beginning, i. e. the second tab page will become the first one if the with Index=0 gets erased.

I am trying to figure out how to associate a child window on a tab. How can I do this?
If you look at properties from the "Outline" tab of SQLWindows/32, you will not find "Associate with tab" over there. But if you switch to "Layout" mode and look at control properties the "Associate with tab" will appear (if the form/dialog is derived from cQuickForm or cQuickDialog class).

I've derived a new class from the Tab QuickObject. Now, I cannot access the tab properties anymore. How can this be fixed?
You need to configure the editor settings for the new class. Select your new class in the outline and do the following:
1) Click on cQuickTabsParentForm(/Dialog) or cQuickTabs and go to the "Component" menu, select QuickObject editor.
2) Write all of the entries down for that class, especially application and dialog name.
3) Pick your derived class from the outline.
4) Enter the entries taken from the tab class in the QuickObject editor for your self-written class.

Radio Boxes

Is it possible to have the text (label) of a radio button appear on the left?
To have the label of a radio button switch sides, the button style "BS_LEFTTEXT", defined in the Windows SDK, has to be assigned which can be done with the following code (remove the "A" in the *WindowLong functions when using SQLWindows):
On SAM_Create
  Call SetWindowLongA( rbRadio, GWL_STYLE, GetWindowLongA( rbRadio, GWL_STYLE ) | BS_LEFTTEXT )
To assign this style using the Visual Toolchest enhancements (defined in VT.APL/VTWIN.APL), call
On SAM_Create
  Call VisWinSetStyle( rbRadio, BS_LEFTTEXT, TRUE )

Table Windows

How do I change the background color for individual cells of a table window?
Unfortunately, this is not directly supported as it is possible to change the text color with SalTblSetCellTextColor( ). However, it can be accomplished by manually painting table windows or by placing controls on top of it. These techniques got explorered by Gianluca Pivato in Centura Pro's July 1997 issue, "Super-flexible Table Windows", and the December 1997 copy, namely "Custom Painted TableWindows". The functions covered in the latter article are available as a 3rd-party product at www.pivato.com wrapped into a DLL .
Additionally, a similar table window enhancement called "TableGDI" is available at www.primeardour.co.nz/primeardour.

How can I reset the style of a column to normal (a function like SalTblDefineNormalColumn)?
Such a function doesn't exist in CTD. However, it can be easily implemented by including the following global function in your code:
Function: SalTblDefineStandardColumn
    Description:
    Returns
        Boolean:
    Parameters
        Window Handle: p_hWndColumn
    Static Variables
    Local variables
        Boolean: bRetVal
        Window Handle: hWndParentTable
        Number: nColID
    Actions
        Set bRetVal=FALSE
        If SalGetType( p_hWndColumn )=TYPE_TableColumn
            Set hWndParentTable=SalParentWindow( p_hWndColumn )
            Set bRetVal=SalSendMsg( hWndParentTable, WM_USER + 122, SalTblQueryColumnID( p_hWndColumn ) - 1, 0 )
        Return bRetVal


Is it possible to implement sorting in ascending/descending order by clicking on the column header of a table window?
The table window must be set up to capture for a click on a column header (gray area at top of each column). This is accomplished by setting a table flag on (TRUE); usually this is done at the time the child table window is created, but it could be done at any time you specify. At the Message Action section of the table window:
On SAM_CreateComplete
  Call SalTblSetTableFlags(hWndForm, TBL_Flag_SelectableCols, TRUE)

Now that the table is able to capture for a click on a column header, you may use another message at the table window's Message Action section. When the customer clicks on a column header, it will be directed to this block of code that contains a call to a function named ColumnSort(), for example. This function is expecting a number as a parameter, and conveniently enough, wParam in this case contains the column's window handle in the form of a number. (Window Handle is a unique address given to each window object such as a push button, data field, column and so on when they are created—Windows takes care of assigning this for you):
On SAM_ColumnSelectClick
  Call ColumnSort(wParam)

This should be defined as a local function within the table window:
Function: ColumnSort
    Description:
    Returns
    Parameters
        Number: nColHandle
    Static Variables
        Boolean: bSortOrder
        Number: nPreviousColumn
    Local variables
        Window Handle: hWndClickedCol
        Number: nIdClickedCol
    Actions
        Set hWndClickedCol =SalNumberToWindowHandle( nColHandle )
        Set nIdClickedCol=SalTblQueryColumnID( hWndClickedCol )
        ! the sort order is a numeric value of either 0 or 1.  The constants are
        ! TBL_SortIncreasing (1) and TBL_SortDecreasing (0).
        If nIdClickedCol=nPreviousColumn
            Set bSortOrder=NOT bSortOrder
            ! Don't use FALSE - the code would not toggle between Ascending and Descending.
        Else
            Set bSortOrder=TRUE
        Call SalTblSortRows( hWndForm, nIdClickedCol, bSortOrder )
        Set nPreviousColumn=nIdClickedCol

This code is well suited to migration into a class library. This will allow all your table windows to have this type of functionality and you don't ever have to think about this code again.

How do I get around the problem of check box columns displaying incorrect values?
There is a mysterious error in check box columns. To get around this anomaly, just insert an invisible column (preferably as the first one) with the check box style. It does not need to contain any data - just the existance solved all problems with this column style.

After populating a table window with TBL_FillNormal, blank rows appear if one tries to scroll up or down. How can avoid this anomaly?
Try one of the following:
- The table needs to have its property "Discardable" changed from the default of YES to NO.
- Make sure that the "Max Rows in Memory" value in the table's properties is large enough.
- Ensure that the SQL handle used with SalTblPopulate( ) is not disconnected before the table window gets destroyed.

You may want to review the table window chapter in the docs to understand how the table window manages rows.
If a table window's Discardable attribute is set to yes, then the table window expects that it can discard unmodified rows freely. Usually you use this setting if the data is in a persistent result set that you can go back to for refetching the rows. With this setting, the table window's Max Rows in Memory attribute governs how many rows it will cache in memory at a time. If you have a Discardable table window with 200 Max Rows in Memory, then it will only cache 200 rows at a time. As you scroll the table window, it will discard previously fetched rows once the cache is full to accomodate new rows. With Discardable set to yes, a SAM_CacheFull message means all the rows being cached have been modified so there's no space left to refetch rows. Use discardable table windows when you want the table window to be a "window" into a larger result set. You need to use
discardable table windows if you're working with a result set that is greater than than the maximum Max Rows in Memory, which in SQLWindows is 32767 rows.
If a table window's Discardable attribute is set to no, then the table window retains all refetched rows and doesn't discard any rows. Usually you use this setting if you need the table window to contain the whole result of a query because you're not using a database result set (possibly not even using a SQL database). With this setting, Max Rows in Memory still governs how many rows will be managed by the table window. If you have a non-Discardable table window with 200 Max Rows in Memory, then you'll only be able to fetch the first 200 rows of a database query result, you'll get a SAM_CacheFull message if you try fetch past this point.
So you have to decide what works for your particular application. If your using result sets enabled, then you could leave your table window as discardable, and set Max Rows in Memory to a suitably high setting. If you're not using result sets, you could still have a high Max Rows in Memory setting. In fact, developers usually use a table window class as the basis of
their table windows that defines Max Rows in Memory as 32750. This is acceptable because row memory is dynamically allocated as needed.

How do I avoid SQLBase error 203 appearing occasionally when filling a table with SalTblPopulate( )?
This error usually appears when the SQL handle specified for populating a table window is used at other locations during the existance of the table window, for example to insert or update data. The table's result set built internally with Prepare/Execute gets destroyed and the table window is unable to fetch rows not contained in the table window cache anymore. Read the online help for further information on that topic.

I have a table window column and some code under SAM_Validate where I try to clear the "Field Edit" flag with SalTblSetRowFlags( tblTest, nRow, ROW_Edited, FALSE), and it just doesn't work. How can I get it to work?
When leaving SAM_Validate with VALIDATE_Ok, then the Edit-Flag is set, so in addition to unsetting it with SalTblSetRowFlags(), return also VALIDATE_OkClearFlag out of SAM_Validate as shown in the following example
On SAM_Validate
    If <Clear EditFlag but otherwise ok>
        Call SalTblSetRowFlags( tblTest, SalTblQueryContext( tblTest), ROW_Edited, FALSE)
        Return VALIDATE_OkClearFlag
    Else
        Return VALIDATE_Ok


I have created the column with SalTblCreateColumn( ). How can I refer to it as a bind or into variable?
You can use ‘#’ character as a separator between the table window name and the column number to refer to automatically created columns.
tblMain#1 - first column of tblMain
frm1.tbl1#3 - third column of tbl1, a child table of frm1

How do I return the number of columns in a table window?
In lieu of a SalTblQueryColumnCount function, here's a way to do it in SAL:
Function: QueryColumnCount
    Returns
        Number:
    Parameters
        Window Handle: p_hWndTbl
    Static Variables
    Local Variables
        Number: nColCount
        Number: nWindowType
        Window Handle: hWndCol
    Actions
        Set nWindowType=SalGetType( p_hWndTbl )
        If nWindowType=TYPE_TableWindow OR nWindowType=TYPE_ChildTable
            Set nColCount=0
            Set hWndCol=SalGetFirstChild( p_hWndTbl, TYPE_TableColumn )
            While hWndCol !=hWndNULL
                Set nColCount=nColCount + 1
                Set hWndCol=SalGetNextChild( hWndCol, TYPE_TableColumn )
        Else
            Set nColCount=-1
        Return nColCount


How can I hide a table window row?
You can use the function SalTblSetRowFlags( ) with the ROW_Hidden constant. See the help file for further information.

How do I get the handle of the listbox element in columns with the "drop down" style set?
Put the following code in your column class:
On SAM_DropDown
  ! Find out the drop down handle
  Set hWndDropDown=SalNumberToWindowHandle( SalSendMsg( hWndForm, 0x0400+119, SalTblQueryColumnID( hWndItem )-1, 0 ) )
You could for example use LB_SETHORIZONTALEXTENT to show an horizontal scroll bar in the list box.

How many rows can be kept in the cache of a table window?
In SQLWindows/16 this used to be 32753 rows. In CTD the table window cache can theoretically address 2,147,423,632 rows, providing the operating system can supply the required resources.

Miscellaneous

How do I make the Visual Toolchest splitter bar display in 3D?
The cSplitterWindow has a flat style and doesn't fit nicely with other controls displayed etched or in 3D. This appearance can be changed when the following code is added to a splitter bar's message actions section.
On WM_NCCREATE
    Call VisWinSetStyle( hWndItem, WS_DLGFRAME, TRUE )
On WM_NCHITTEST
    Return HTCAPTION
On WM_MOVE
    Call ResizeObjects( )
VisWin* is defined in VT.APL (SW) and VTWIN.APL (CTD).

Is it possible to replace the text displayed in the Visual Toolchest cMeter control?
Yes, just set the Windows style to 0x00000010 and then call SalSetWindowText( ) to set the text of the meter. You can pass an empty string if there shouldn't be any notification displayed at all.

When using the cOutline* classes, the function GetItemHandle() does not seem to work sometimes. Is this correct?
GetItemHandle() works only after ShowOutline() function is called.  If any item is not 'visible' in the outline, then GetItemHandle() returns a NULL or 0.  But if all the items are 'visible' in the outline, then GetItemHandle() returns a proper value.

I encounter crashes when using the function Expand( ) with the cOutline* classes. How can this be avoided?
This does happen if the Expand( ) function is called on a leaf node. Just check if the specified item can get expanded, as soon shown in the following code:
If GetItemFlags( hItem ) & ITEM_CanExpand
Call Expand( hItem )

This has been confirmed by Centura and will get fixed in upcoming releases.


Messages

What is the difference between SalSendMsg( ) and SalPostMsg( ) and when should which one be used?
SalPostMsg( ) will cause a message to be placed in the addressed window's message queue. This message queue is queried once a thread enters idle state, that is, when nothing else has to be executed. SalSendMsg( ) works just like a normal function. The code in the receiver's message actions of the specified message is immediately executed. You can return a numeric value when calling SalSendMsg( ).
Mostly, SalSendMsg( ) is used. However, there are situations where posting a message is necessary, for example when re-setting focus upon receiving SAM_KillFocus.

 


Database access

How do I implement Query By Example in the QuickObjects architecture?
One of the good things about QuickObjects is that it's an extensible framework. For example, when the data source (the child table window) goes about its business of formulating the SQL statement for retrieving data, it gives you an opportunity to plug in your own WHERE clause or your own ORDER BY clause. In the QuickObjects class code (in QCKDVC.APL), if you look at the function cQuickTable.GetSqlSelect, you will notice a late-bound function called "GetSelectWhere ()". All you need to do is define a function of the same name in the instance of cQuickTable in your form window, and take care of supplying the QBE-driven WHERE clause. Here's the pseudo-code you would use:
Loop through all the objects of the form window
(SalGetFirst/NextChild)
         If the object participates in the QBE
             If the object is not null (sContents)
                - Check the database item name of this object
                - SalWindowGetProperty (hWndItem, 'ITEM', sItem)
                - Append to WHERE clause
                - sItem || "LIKE " || sContents || "%"
See 'What does "QBE" stand for?' for further information.

How can I get around the error 163 ("Result sets are not active")?
Centura provides native connectivity routers (and ODBC access) to all popular RDBMS brands like Oracle, Sybase, SQLServer, Informix, DB/2, etc. One of the features provided by those routers is "front end result sets" (FERS). Every time data is read from the backend, a copy of the data is written to the PC's disk. This enables backward scrolling capability, a feature that is normally missing in the backend. SQLBase, by the way, supports this as a native feature (by formulating a scrollable result set on the backend). FERS enables the following function calls to be made from the app:
- SqlFetchNext
- SqlFetchRow
- SqlGetResultSetCount
The FERS files are written to the TEMP directory on the PC. These files are named as FRSn, where n is an integer. The router takes care of purging these files as soon as they are not required anymore. There are certain instances, however, when these files may linger "orphaned" -- typically under abnormal termination conditions (affectionately known as "GPF" in Windows parlance). Consequently, there may come a time when the TEMP directly could have hundreds of these FERS files. Under some circumstances, the router could have trouble creating a new file in the same directory -- even DOS has a limit to the number of files that can be created in a directory. Or the disk itself may run out of space. This is when you start noticing error 163 -- the router is informing you that it could not create a result set file for you.
The solution is simple: every time you boot your PC, clean up your TEMP directory. Better still, put the cleanup into your AUTOEXEC.BAT (echo Y | del C:\Windows\Temp). On a more strategic note though, most of your application could do without using FERS altogether! You see, when you populate a listbox, you don't need this feature. And a table window can contain up to 32K rows anyway, which should be more than sufficient in most cases. So, the trick is to use FERS with DISCRETION. This feature can be turned off globally by the following code:
On SAM_AppStartup
      Set SqlResultSet=FALSE

FERS can also be set on a per cursor basis:
If SqlConnect ( hSql)
      Call SqlSetResultSet ( hSql, FALSE )

For complete information on these functions, check your on-line help.

How can I quickly count the number of rows in a table?
The easist solution can be wrapped in a function:
Function: SqlRowCount
    Description:
    Returns
        Number:
    Parameters
        Sql Handle: p_hSql
        String: p_sTableName
    Static Variables
    Local variables
        Number: nRowCount
    Actions
        If p_hSql !=hWndNULL
            If SqlPrepareAndExecute( p_hSql, 'ROWCOUNT ' || sTableName )
                If SqlGetModifiedRows( p_hSql, nRowCount )
                    Return nRowCount
        Return -1
Alternatively, you could call the function SQLGNR from the SQLBase C/API. It is defined for SQLWindows (16-bit) in library SQLAPIW.DLL and for Team Developer (32-bit) in library SQLWNTM.DLL.
Function: sqlgnr
    Description: Determines the rows in a database table
    Export Ordinal: 0
    Returns
        Number: SHORT
    Parameters
        Number: USHORT
        String: LPSTR
        Number: USHORT
        Number: ULONG

Call sqlgnr( SqlGetCursor( hSql ), sTable, SalStrLength( sTable ), nRowCountTable )
Keep in mind that this function only works with SqlBase and therefore break portability to another database platform. However, there might be similar functions that return the same information.

Is it possible to change the user name appearing on the SQLBase-Server screen programmatically?
It can be done with the following function call:
Call SqlSetParameterAll( hSQl, SQLPCLN, 0, 'NewUserName', FALSE )
This is useful when only one SQL.INI is used for a group of workstations in the network or one seat is shared by several users. The definition of SQLPCLN can be looked up in SQL.H, found in the Centura installation directory.

I receive the message "SQL Error: No SQL Cursors remaining. Halt application?". What can I do to avoid this?
This appears if 100 cursors are opened simultaneously. Check your application to disconnect all SQL handles not needed anymore and don't connect an already connected handle a second time. You should also check to disconnect all previously connected handles if in case of errors you exit with an error code.

Is it possible to stop stop the execution of a command sent to the database with SqlPrepare(), SqlExecute(), or SqlImmediate()?
From within SQLWindows and SQLWindows/32 it is currently not possible to kill or stop the execution of a SQL statement. SqlPrepare/SqlExecute will return control to the application when the specified SQL processing is finished. To stop SqlPrepare/SqlExecute( ) before control is returned to the application, you would need external software that watches the duration of SQL processing inside your application (DLL) or a development environment being capable to manage multiple threads.
What you can do is controlling fetching data. For SalTblPopulate( ) this can be done with returning TBL_NoMoreRows on SAM_FetchRow. Normal fetching can be interrupted after each SqlFetch* call. The corresponding code would look
like this:
Function: Populate
    ! ... Disable form here (except pbCancel)
    Set bOk=bOk AND SalYieldStartMessage( pbCancel )
    While bOk AND NOT bCancel AND SqlFetchNext( hSql, nFetch )
        ! ...
        ! ... (for example SalTblInsertRow( )
        ! ...
    Set bOk=bOk AND SalYieldStopMessage( pbCancel )

Pushbutton: pbCancel
  On SAM_Click
    Set bCancel=TRUE

I experience problems while trying to connect to the database. What am I doing wrong?
There are some steps you should check thoroughly:
1. Are you using one and only one SQL.INI? Try to have only one SQL.INI in the Windows-PATH and directories that could be referenced by your application, better in the whole system to make sure that your application is using the correct one. Having several versions might produce unexpected errors.
2. Some DLLs from Centura get installed in various places. Check if you have the correct versions of them (right click in Windows-Explorer and then Properties...), because Centura applications can behave unpredictable wrong if there are mixed versions being used. You should especially look after SQLWNTM.DLL and SQLNGCI.DLL which are installed into the Windows system folder, and the communication DLLs from the SQLBase or development directory. Some of the important ones (depending on your setup) are SQLAPIPE.DLL, SQLAPIW.DLL, SQLWSOCK.DLL, SQLWS32.DLL, SQLWSSPX.DLL, SQLORA.DLL, SQLORA32.DLL, SQLODBC.DLL, SQLODB32.DLL.
3. Go step by step when trying to get things like connections to foreign database systems working! That means, set the foreign client up first and use the client tools
(ISQL/W, SqlPlus, MSQuery for ODBC-Connections, ...) to see if the pure foreign database connection works! Then you can configure your SQL.INI and use SQLTalk first to see if this works. Look in "Help-About" to see if the correct file versions were loaded (the PTF level is displayed there as well). Once you are able to establish a connection through SQLTalk, there should not be a problem with the database connection from your application.
Check the Database Products FAQ for further information about installing and configuring database server and clients.

After a call to SqlVarSetup( ) the flow of execution suddenly jumps somwhere else and reports mysterious errors. What is going on?
This behaviour can sometimes be observed when SqlVarSetup( ) is placed before SqlPrepare( ). Just move it done until you actually execute the prepared statement, thus, right before SqlExecute( ).

How can I export a binary object (e.g. a BMP) from a SQL-Database to a file?
You'll need to SELECT you BMP INTO a long string-variable in SQLWindows/Centura Builder, then use SalFileWrite() to write the file to disk. If the BMP had been compressed at INSERT time, you'll need to uncompress before the write.

I'd like to periodically check whether the cancel button has been pressed during long operations. How can I accomplish this?
Take a look at the SalYield* functions. If the form window/dialog box gets disabled before the beginning of the transaction, please don't forget to enable the Cancel button.

I would like to know how to use the SQLBase C/API. I am interested in doing UNLOAD, LOAD and REORGANIZE. How do I get going?
Starting from the release of SQLBase 6.00 and the new functions sqlunl() and sqlldp() this can be done from within a SQLWindows/16 and SQLWindows/32 (CTD) program. In fact you do not need to to use the C/API all the way as SqlPrepareAndExecute() now support UNLOADING and LOADING. Download SBCAPI.ZIP for further information and sample code.

My application eats up the available memory and eventually crashes when running long database processes. How do I avoid this?
First thing you can do is to eliminate all calls of SqlImmediate(), because this function eats memory with every call. Second step is to close sql handles which are kept after SqlFetchNext()-calls with the SqlClose()-function regardless if ResultSetMode is on or off for this handle. This will free memory allocated for this resultset and prevents your application from consuming more and more memory. With these two things in mind people have succesfully implemented very long
running transaction (36 hours on Oracle) with many selects, inserts and updates.

How do I reduce the number of timeouts in my application?
- The default mode for SQLWindows when it connects to SQLBase is RR - Read Repeatability.   When you connect your cursors, you should change the the isolation level to RL - Release Locks.  This alone may reduce your Timeouts dramatically - if you are not doing this now.
- Make sure that all Transactions ( update/insert/delete ) are handled quickly and a commit ( or rollback ) is done at the end of the Transaction before ANY user display or interaction is allowed.  After all, modification statements will put an Exclusive Lock on the data, and will increase the likelyhood of Timeouts until commited or rolledback.
- Alter the Timeout period after you connect the cursors.  The timeout period should be set to at lease 30 - 40 seconds - something reasonable. 
- You may wish to implement RO - Read Only isolation mode for cursors that are just going to be used for reading the database.  Please read about ReadOnly and see if you would like to use it.  There may be a performance hit when using RO.
- Do NOT use SqlImmediate( ... ) or SqlExists( ... ) in your SQLWindows code! These commands will lead to unnecessary timeouts! You can easily write your own SqlImmediate command by defining your own cursor, etc.  And you will have MUCH more control as to what is happening in your code.  I can NOT be too emphatic about not using these two commands.
- It is possible to increase the PCTFREE parameter in a table definition. For more information about this issue, consult the online books. This will probably increase your database size quite significantly, though.


Report Windows

I am having problems printing from ReportWindows on a HP Laserjet 5 printer. How can I get around this problem?
There are several problems with HP printer drivers in conjunction with Centura products. Usually, the LaserJet 4 driver supplied with Windows 95 works correctly. Instead of using the drivers supplied with the DeskJet 6xx and 8xx series, use a standard DJ 500 or 550 printer driver.

A landscape report saved as a RTF file loses its orientation in Word, how can I fix that?
When printing landscape report to RTF-file with SalReportPrintToFile( ) function, paper size is generated properly but the "\landscape" RTF command is not generated as it should be. This leads to the problem that Word interprets report in landscape size but orientation is portrait. Printing such a document causes incorrect result. "\landscape" defines that the whole document is in landscape orientation. This means that you have to do function which opens RTF-file after print, add "\landscape" control word to where other paper properties are set (\paperhN\paperwN and so on). So the correct control word series could be like:
\landcape\paperw16833\paperh11908
(A4 landscape, width and height are in twips).

Pictures are not displayed correctly in RTF files generated with ReportWindows. How do I have them display correctly?
When printing to RTF-file with SalReportPrintToFile-function, bitmaps are formatted as windows metafiles. For example original picture size is 5.2" x 1.2" and it is scaled to 1.56" x 0.36" in report template. The SalReportPrintToFile( ) function generates the following command series in RTF files:
\pict\wmetafile8\picw1814\pich439\picwgoal7488\pichgoal1728 \picscalex30\picscaley30 
So, if the picture should show correctly in Word 6, 7, and 8, the RTF commands "\picw" and "\pich" should have following values:
\picw13210\pich3050 or \picw0\pich0
both of these result the same. This means that you have to do function which opens RTF-file after print, search all the "\picw" and "\pich" commands and set the correct values for them. Please note that calculated values may differ a bit in the RTF-file.

RTF command Meaning
\pict This control word defines the picture beginning
\wmetafileN This control word sets the picture type to a Windows metafile.
N specifies the metafile type. This parameter must be 8 (MM_ANISOTROPIC)
\picwN This control word contain an optional suggested picture size in MM_HIMETRIC units. This is the control word that causes picture to be shrinked in width. It is not known why SalReportPrintToFile-function generates this value as it does, but anyhow it shows correctly on Word 6 and 7 but not in Word 8. This value should be original picture size in MM_HIMETRIC units (one logical unit is mapped to 0.01millimeters) or just zero.
For example, original picture height is 5.2" which is about 13,21 cm -> 132,1mm -> 13210 in MM_HIMETRIC units.
\pichN This command contain an optional suggested picture size in MM_HIMETRIC units. This is the control word that causes picture to be shrinked in height. It is not known why SalReportPrintToFile-function generates this value as it does, but anyhow it shows correctly on Word 6 and 7 but not in Word 8. This value should be original picture size in MM_HIMETRIC units (one logical unit is mapped to 0.01 millimeters) or just zero.
For example, original picture height is 1.2" which is about 3,05 cm -> 30,5mm -> 3050 in MM_HIMETRIC units.
\picwgoalN This specifies the picture height set in ReportWindows. N specifies the desired height, in twips (1\1440 part of inch)
For example, width 5.2 inches=5.2*1440 twips=7488 twips
\pichgoalN This specifies the picture height set in ReportWindows. N specifies the desired height, in twips (1\1440 part of inch).
For example, height 1.2 inches=1.2*1440 twips=1728 twips
\picscalexN Horizontal scaling value.
For example N=30, 5.2" x 0.30=1.56"
\picscaleyN Vertical scaling value.
For example N=30, 1.2" x 0.30=0.36"

For more information on RTF, visit www.microsoft.com to view the RTF specifications online.

How do I develop a report that is able to print multiple detail tables?
While handling multiple sets of data with RW you need to retrieve all information for the detail tables as you skip through your master table. There are several techniques for doing this. The sample code shows two methods: One fetching detail records into the .QRP one by one, another one is using a technique reading the data into tabbed strings.

How do I avoid corrupted reports when exiting ReportWindows (CTD 1.5)?
There is a bug that got introduce in version 1.5 of Centura's development environment. Check the downloads section for a small utility that recovers corrupted QRP files.


SQLWindows

Development environment

Is SQLWindow certified for Windows 95/NT?
SQLWindows 5.0.2 is the first certified version which you can use with Windows 95, beginning with 5.0.3 PTF4, SQLWindows has the NT compliant certificate.

Is it possible to run SQLWindows 5.x without using all the components that are in the deploy catalog?
Yes this is possible, however note that you are on your own risk while doing that. The tips you need are all documented in a paper called "SQLWindows 5.x minimized deploy catalog". Click here to go to the downloads section. The file is named SWDEPLOY.ZIP.


External functions

How do I determine the version of the operating system?
This can be done with a call to GetVersion( ), however, the information returned will only be different between Windows 95 and any other Windows platform (return value "3.1"). To get the "real" version, check out the file WIN16VER.APT which uses file version functions with KRNL386.EXE. Look up returned values in the table given below. "WOW" stands for "Windows on Win32".

Platform

GetVersion( ) "FileVersion" "WOW Version"
Windows 3.10 3.1 3.1 N/A
Windows 3.11 3.1 3.11 N/A
Windows 95 3.95 4.0095 or 4.00.1111 (OSR2) N/A
Windows 98 3.98 (?) 4.?? (?) N/A
Windows NT 3.1 3.1 3.1 3.1
Windows NT 3.5 3.1 3.1 3.5
Windows NT 3.51 3.1 3.1 3.51
Windows NT 4.0 3.1 3.1 4.0

 


Miscellaneous

I would like to integrate ToolTip support in my applications. How do I do this?
ToolTips are part of the XSal extensions, written by Gianluca Pivato.


OLE - Object Linking and Embedding

How can I avoid the "OLE - Invalid class" error message?
In SQLWindows, when one tries to add a QuestWindow in the form window, the following error message is displayed. The application does not stop, but tables in the database are not listed and one just can not use QuestWindow at this point. You get an error saying "OLE - Invalid Class".
While loading Quest, during the various activity initialization, one may get following messages in a sequence related to corrupted OLE registration database: "Cannot Update Application Registration Database", "Sorry, Unable to register the Activity Library"
After receiving these messages, Table and Query activities are disabled and can not be used.
Windows 3.1:
Windows maintains the OLE registration database called REG.DAT. One can edit it using the registration database editor called REGEDIT.EXE. These files usually reside in the directory where MS Windows is installed, most typically in C:\WINDOWS directory. Sometimes, the database might get corrupted due to number of reasons such as bad sectors on hard disk, etc. There are 2 solutions to this problem.
- Load REGEDIT.EXE. It lists all registered file types. Select Quest related file types from the list. Choose "Delete File Type" option from Edit menu and delete these entries. Exit the editor. Load Quest and it should be running OK.
- Quit Windows and delete REG.DAT (might want to back up the file too), and restart Windows. Run Registration Information Editor and select Merge Registration File option from File menu. Select SETUP.REG (resides in the Windows SYSTEM directory) from the file name list in the dialog box.
Windows NT:
When one logs on to newly-installed Windows NT computer first time, the system migrates REG.DAT and portions of WIN.INI from the previous version of Windows to the Registry in Windows NT. This Registry file is called REGISTRY.INF. The status of each step in the migration is logged in the Application Log, which can be viewed with Event Viewer.
The Registry is structured as a set of four subtrees of keys that contain per-computer and per-user databases. One of the subtree is called HKEY_CLASSES_ROOT which contains Object Linking and Embedding and file-class association data. Each individual key can contain data items called value entries and can also contain subtrees. Following are the steps to resolve this problem on Windows NT:
Run Registry editor called REGEDT32.EXE. Under Windows menu, select HKEY_CLASSES_ROOT. It will list keys called QuestQueryServer, QuestTableServer, etc. among other keys. Select Delete option from Edit menu and delete these Quest related keys. Run Quest. It should work OK now. One may have to exit Windows NT and reload it if Quest does not work properly at this point.

Why do I face problems when trying to use OCX controls whose methods have variant datatypes (SQLWindows/pre CTD 1.5)?
All versions of SQLWindows and SQLWindows/32 before Centura Team Developer 1.5 do not contain support for variant datatypes. That is the reason why the OLE Class Wizard refuses to generate such classes.


Team Developer

General

What is the difference between "SQLWindows/32" and Centura Team Developer?
The latest release of Centura's 32-bit application development tool is Centura Team Developer. One of the components of Centura Team Development is the development environment. In previous versions of Centura Team Developer, this component was known as Centura Builder. In homage to their groundbreaking 16-bit development tool, they decided that in the Centura Team Developer 1.5, they would rename the development environment to SQLWindow/32.
To state is another way: The product is still Centura Team Developer. Within the product, the development environment is now called SQLWindows/32.

Is it possible to have both 1.5 and 1.1.x installed simultaneously on the same machine?
It should be easy to set up your machine to use both 1.5 and 1.1.x.  You should even be able to use them at the same time.
The only problem with having two versions of CTD-SQLWindows/32 on the same machine stems from the practice of not incorporating version numbers into the names of APL files.   If we did, you would have to fix up every APP you wrote with the new name when you migrated to a new release, something I'm sure you would rather not have to do. The steps used to attempt to locate APLs are as follows:
- Look in the directories listed in the Application Path (SQLWin/32 1.5 only)
- Look in the directories listed in the Global Path
- Look in the directories listed in the PATH environment variable
Most people encounter problems because they rely on the environment PATH for all of the built-in APLs shipped with CTD, although we do include the directories which hold those files in the initial Global Path on installation.
However, there was a problem with the 1.5 installer: it prepends the registry setting of an earlier version of CTD - if there is on on the machine - to the path constructed for the 1.5 version.  Using the Directories tab of the Prefeences dialog you should remove any 1.1.2 directories from the Global Path for 1.5, as the first step to making the two versions coexist.
The next step is to make sure that the Global Path includes every directory from the appropriate installation which that version should use to locate Centura APLs (and also any third party APLs which link to Centura DLLs).  If that is done, then SQLWindows/32 will never resort to looking at the environment PATH to locate APLs.
This means that you can leave directories of both installations in the environment PATH so that DLLs can be located at compile- and run-time. Since the DLLs (Centura's, in any case) do have version numbers, there will be no conflict.
Important: Please keep in mind that the Object Compiler is version dependent. You cannot use DLLs generated with version 1.1.x in conjunction with CTD 1.5!


Development environment

I do often get "Application errors" when trying to open APP files. What should I do?
Try to save this file as "Text" or "Indented Text", close CTD, and reopen this file.

Centura Team Developer crashes upon opening a source code file. How can I recover the APP?
If CTD doesn't even let you open the file, use a CDK utility which can be downloaded at ftp.centurasoft.com/products/utilities/cvt2apt.zip or a program called CBFIX.EXE which is available for download at www.metex.com/Products/cbfix.

I've created an APP with CTD 1.5 (Eiger) and would like to open this file with a previous version. I've saved as text, but apparently, it doesn't work. What should I do?
If you need go backwards, and open a CTD 1.5 app into CTD 1.1.2, the following will work:
1) Save the app as text
2) use a text editor to change the outline version to 4.0.26
3) Delete lines pertaining to ActiveX features, including the ActiveX line in Default Classes.
Then you shouldn't have any problems opening the app in CTD 1.1.2.

How can I easily check out the values of named properties at design time?
Next to the browse capabilities of some 3rd-party applications and directly looking at source code saved as text, Team Developer lets you print the named properties set up for a specific item. Just go to the Page Settings window and check the option "Include Named Properties".

I've converted my app from SqlWindows to CTD 1.1. The application compiles, but during execution I get an application error. How can I avoid this problem?
Make sure you step through the application. Often, 16-bit Windows API functions got not converted correctly. Check your function definitions and replace them according to the Win32 SDKdefintions, for example WORD to DWORD.

How do I avoid a general protection fault "Unhandled exception 0x000c5"?
There can be many causes for such untimely exits of CBI11.EXE; sometimes it is because your code has 'tripped' over a bug in the CTD runtime system which causes an unhandled exception, and sometimes it is because the parameters passed to an external function (a Sal function, or a Visual Toolchest method which has a C++ implementation, for example) are incorrect, and attempts to interpret them are not 'safe'.
The first, and often fastest, thing you can do to track down this problem is to run your application with "Fast Animate" turned on, with the windows layed out on the screen so that you can see what Sal code is being executed when the program terminates.   You can then re-run with a breakpoint set at that line, and examine the parameters to see if they have correct and legitimate values.
Check to see where it occurs and contact Centura support if it seems to be a bug in the development environment. Often, this error appears due to incorrect set up of external function calls.

Using version 1.0 of Centura Team Developer, I experience a lot of crashes, especially on Windows NT machines. What's wrong?
Version 1.0 is known to be very unstable. Consider switching to the next version, i. e. 1.1 or 1.5.

When there are two applications referencing the same Dynalib, are they loaded once or twice into memory?
Each program loads its own copy of the dynalib.

According to the documentation, CenturyDefaultMode behaviour should be in CTD 1.1.0. However, I get wrong results. Can this be fixed?
CenturyDefaultMode behavior was targeted for CTD 1.1.0, but introduced until CTD 1.1.0 PTF 1.
CTD 1.0.0 and vanilla CTD 1.1.0 do not have CenturyDefaultMode behavior. All releases of CTD from CTD 1.1.0 PTF 1 onwards should exhibit CenturyDefaultMode behavior.

When I load my application into SQLWindows/32, I get error messages about "Invalid class size at line xxx". Can I avoid this error?
This is a small, not critical error in Centura Team Developer. This might appear when you change the initial object sizes of certain classes. Save the file, exit and reopen it. The errors should go away.

Is Centura Team Developer certified with NT 4.0?
Yes. With CTD 1.1.0 and higher.

 


Database Access

I have heard about SqlContext*( ) functions but can't find them in the documentation. What are they used for?
This is quoted from the SQLWindows 5 online help:

bOk=SqlContextClear ( hSql )
Clears the context set by SqlContextSet. SQLWindows evaluates the bind and INTO variables associated with the specified Sql Handle in the local context. For new applications, call SqlVarSetup instead of this function.
Parameters
   hSql    Sql Handle. A handle that identifies a database connection.
Return Value
   bOk is TRUE if the function succeeds and FALSE if it fails.

bOk=SqlContextSet ( hSql )
Sets the context for future processing (for example, calls to SqlPrepare, SqlFetchNext, SqlFetchPrevious, and SqlFetchRow). Sql* functions you call after SqlContextSet behave as if they are in the window identified by hWndForm. Call this function in a class to perform SQL processing for the current window without fully qualifying bind and INTO variables. This function is also useful for global functions. Important: After you call SqlContextSet, the context for bind variables and INTO variables is always hWndForm. If you call a Sql* function in an internal function, window function, or class function after calling SqlContextSet, SQLWindows does not recognize local variables or Parameters that you use as bind variables and INTO variables. For new applications, call SqlVarSetup instead of this function.
Parameters
   hSql    Sql Handle. A handle that identifies a database connection.
Return Value
   bOk is TRUE if the function succeeds and FALSE if it fails.

bOk=SqlContextSetToForm ( hSql, hWndMyForm)
This function is like SqlContextSet, except for an additional parameter: SqlContextSet sets the context of the Sql Handle to the window identified by hWndForm; SqlContextSetToForm sets the context of the Sql Handle to the window you specify in the second parameter. Call this function from a child table window when you want to set the context to the parent form window; in this situation hWndForm refers to the child table window, not to the parent form window. For new applications, call SqlVarSetup instead of this function.

 


ActiveX

How can I use MTS-published COM objects in CTD 1.5?
Install the MTS development SDK on your machine. You'll notice a few MTS-related type libraries appear in the ActiveX Wizard.  Use the Wizard to generate classes for them, and use those to access MTS context interfaces, etc... Apart from that, it's just a case of talking to the COM objects via Automation as usual -- CTD 1.5 should be able to take advantage of this interface like any ActiveX interface.

 


Miscellaneous

How do I acquire the file information (such as version number, vendor, description) stored in Win32 files?
Certain version information can be stored in Win32 file images (not available in 16-bit Windows file images). Things like version number, vendor name, and copyright notice can be accessed with functions part of the file installation library defined in VERSION.DLL. To access such resources, read the Win32 SDK and use the code snippet below in conjuction with the definitions provided in the WinSDK32.APT.
Set dwLen=GetFileVersionInfoSizeA( dfsFile, dwLen )
Set bOk=bOk AND dwLen > 0
Set lpData=GlobalAlloc( GMEM_FIXED, dwLen )
Set bOk=lpData !=0
Set bOk=bOk AND GetFileVersionInfoA( sFile, 0, dwLen, lpData )
Set bOk=bOk AND VerQueryValueA( lpData, '\\StringFileInfo\\040904b0\\FileDescription', lplpBuffer, puLen )
Set bOk=bOk AND SalStrSetBufferLength( sDesc, puLen )
Set bOk=bOk AND CStructCopyFromFarMem( lplpBuffer, sDesc, puLen )
Set bOk=bOk AND ( 0=GlobalFree( lpData ) )
Please don't forget to include CSTRUCTL.APL. The above function calls will return the description (English [U.S.]) of a Win32 file image (passed through "sFile") in "sDesc". 

I would like to integrate ToolTip support in my applications. How do I do this?
There is a library called QCKTTIP.APL in the samples directory of CTD which provides an easy to use interface. If the QuickObject framework is not needed, the underlying TTMNGR.APL file can be used directly.
Also, ToolTips are part of the XSal extensions, written by Gianluca Pivato.

 


Online Help

How can I implement context sensitive help with a question mark button in the title bar of a window?
In the Win32, the extended window style WS_EX_CONTEXTHELP got added for dialogs which causes the help button to get displayed. To apply this style to a dialog box add the following code in the message actions section:
On WM_NCCREATE
    Call SetWindowLongA( hWndForm, GWL_EXSTYLE, WS_EX_CONTEXTHELP | GetWindowLongA( hWndForm, GWL_EXSTYLE ) )
This is partially true. 
It is only possible to assign the style WS_EX_CONTEXTHELP to form windows that are not maximizable and not minimizable. On normal form windows, this does not work because there is no such style. In Microsoft Word and similar applications, this is usually accomplished by placing a button in the window’s toolbar. Add the following code to the button’s message actions:
On SAM_Click
    Call SalSendMsg( GetParent( hWndForm ), WM_SYSCOMMAND, SC_CONTEXTHELP, 0 )
A call to GetParent( ) is necessary when the button resides in a window's toolbar.
Each child that should react to the context help function needs the following code in its message actions section:
On WM_HELP
    Call WinHelpA( hWndForm, "Helpfile.hlp", HELP_CONTEXTPOPUP, nContextID )

 


Team Object Manager

Can a script to create a database be generated from a TOM Data Model?
Unfortunately, the TOM data modeler does not support generating schemas, only reading them in from existing databases.
The screen shot with such an option in the manual slipped through in earlier releases of the documentation. For the curious, the original developers of TOM, Jarrah/OEC Australia/Borland Australia/Inprise Australia, did plan to support Entera generation in TOM, but the feature was withdrawn before the 1.0 release.

When trying to import a new project with the Wizard, the "Finish" button is disabled for no obvious reason. How can I fix that?
This is a known bug in Centura Team Developer 1.1.1 that has been fixed in subsequent releases. If you're determined to stick with 1.1.1, there is a hack that'll get you going. The hack is to use a tool like Spy++ to get the window handle of the disabled Finish button. Once you've got it you can send it a SAM_Click message from another app. Just start up Builder and code something like:
On SAM_AppStartup
    Call SalSendMsg( SalNumberToWindowHandle( 0xXXXX ), SAM_Click, 0, 0 )
and execute it.

 


Component Development Kit

How do I determine the library file an item is included from?
There is an undocumented function that will do the job:
HITEM SalOutlineGetIncludingItem( HOUTLINE hOutline, HITEM hItem ) .

Is it possible to compile sources externally, for example with a "mass compiler"?
External compilation is one feature available with the command line options of SQLWindows and SQLWindows/32. With passing either "-b" or "-B", Centura compiles and save the application as an executable file. "-x" and "-X" will cause the development to exit immediately after the compilation is done.

Where can I obtain a copy of the CDK for SQLWindows 5?
Unlike the CDK for Centura Team Developer, which is freely downloadable, the SQLWindows CDK was an add-on product which was not free and delivered on traditional media.
Unfortunately, the SQLWindows CDK has been discontinued. You may be able to purchase a "previously owned" copy by posting on the news groups.

I have recently upgraded my version of CTD and my CDK is causing problems. How can I avoid this?
The CDK interacting with CTD is very version specific. The need to be exactly the same version. Make sure that you are using the latest version.

 


Migrating from SQLWindows to SQLWindows/32

For migration issues, check the following sites:
http://www.centurasoft.com/support/tech_info/migrate_wizard.html
http://www.centurasoft.com/products/development/white/ctdguide.html

General

What are the SalIdle* functions used for?
The SalIdle* functions are general purpose. Some examples for situations in which they might be useful:
- refresh the UI such as enabling/disabling toolbar buttons,
- periodic commits when using SQLBase to release log files when no transaction has occured,
- populate the rest of a table window that has not been filled completely.

With 16-bit SQLWindows I used SWCSTRUC.DLL. Now we moved to 32-bit and I can't seem to find the equavilent library?
It is now named STRCI11.DLL, the name of the include APL received the suffix "L". The general functionality did not get changed.

I would like to call C/API functions directly but it does not seem to work. What happened to SQLAPIW.DLL?
SQLAPIW.DLL is the 16-bit SQLBase API, the 32-bit version is SQLWNTM.DLL which has to be used with Centura Team Developer.

Why do I get GPFs during compile/run time with the 32-bit version after the migration?
Be careful of the definition of external functions.
- Almost all ordinal numbers are different in external DLLs, like USER.EXE vs. USER32.DLL. Check all external functions you use and try to remove ordinal numbers whereever possible.
- Parameters may be different. For instance, some parameters which are WORD in 16-bit version may have to be changed to DWORD, depending on the function used.

I switched to 32-bit and get the error 'Cannot find function within external library' when compiling. How can I fix that?
This can occurs with external functions that have string parameters. In Win32, there are two different functions for the same purpose, one for ASCII, the other one for Unicode. These functions are typically marked with an "A" and a "W". For example, the 32-bit equivalents for GetWindowText are GetWindowTextA( ), 8 bit characters (ASCII) and GetWindowTextW( ), 16 bit characters (Unicode). Centura Team Developer does only work with ASCII functions, thus you'll have to modify your external declarations, and add the 'A' to some of the function names.

When converting from SQLWindows to Team Developer 1.5, do I have to migrate via 1.1 or can I directly use the newest version?
You should be able to go straight to 1.5. It is recommended that you open your APL files first in CTD 1.5 and save them, then open your APP files and save them.

The "Select From" with library files does not seem to work anymore, am I doing something wrong?
This feature is not implemented in SQLWindows/32 (the CTD package).

 


Database Access

The function SqlConnectTransaction( ) doesn't seem to work. Is this a bug?
This function was used in SQLWindows 5.x to connect named transactions. This concept is not supported in Centura Team Developer, thus, the functions won't work anymore. You have to rewrite your code.

I can't find the SqlContext* functions in the documentation. Can they still be used?
Centura Software Corp. has confirmed that they continue to offer SqlContext* functions and preserve compatibility with existing code. It is recommended for new applications to use SqlVarSetup where necessary.

 


Dynamic Data Exchange (DDE)

The SalDDEPost( ) function doesn't seem to work anymore. What is going on?
The function definition has changed.
Call SalDDEPost( hWndDDE, UMSG_DDESearch, frmMain, SalNumberLow(vnNr), SalNumberHigh(vnNr) )
doesn't function any more (lParam is 0). Therefore you should use
Call SalPostMsg( hWndDDE, UMSG_DDESearch, 0, vnNr).

 


XSal - SAL extensions

The XSal package is available at www.pivato.com.

General

I get the runtime violation message when I use the runtime version of my XSal extension on NT, why?
The first builds of the XSal extensions were testing the file name for the ".EXE" suffix. NT keeps the file name the way it's written, therefore instead of ".EXE", the suffix is ".exe" and the runtime extension fails to initialize. The solution is to contact support@pivato.com to receive a later build or to change the executable name to uppercase.

 


BKG - Background Images and Hotspots

Can I use the BKG in the toolbar?
Yes, you have to create a dialog box with the BKG as a child of the toolbar using SalCreateWindowEx():
Set hWndToolbar=SalGetFirstChild( hWndMDI, TYPE_FormToolBar )
Call SalGetWindowSize( hWndToolbar, nToolbarWidth, nToolbarHeight )
Set hWndToolbarBkg=SalCreateWindowEx( dlgToolbarBkg, hWndToolbar, 0, 0, nToolbarWidth, nToolbarHeight, CREATE_AsChild )
Then, in case you want to make the dialog box always fit the toolbar, use WM_SIZE (0x0005) to resize the dialog:
On WM_SIZE
  Call SalSetWindowSize( hWndToolbarBkg, SalPixelsToFormUnits( hWndForm,SalNumberLow( lParam ), FALSE ), nToolbarHeight )

 


CPT - Custom Painted Table Windows

Can I show custom controls in a TableWindow cell?
Yes, you can do it putting your custom control/s or any other type of child window in a dialog box and the create the dialog box as a child of the table windows hooking the handle to the TableWindow cell using the CPT function XSalTblSetCellCustom(). Then you can make sure the form (or forms, since you can do that for as many cells as you like) is shown inside the cell using the SAM_DrawCell message:
Set nCustom=SalWindowHandleToNumber( SalCreateWindowEx( dlgTest, hWndTable, 0,0,0,0,CREATE_AsChild ) )
Call XSalTblSetCellCustom( hWndColumn, nCustom )
Add the following code to the table window's message actions section:
On SAM_DrawCell
    If XSalTblGetCustomInfo( lParam, hdc, nCol, nRow, nValue, nLeft, nTop, nRight, nBottom )
        Set hWndChild=SalNumberToWindowHandle( nValue )
        Call SalSetWindowLoc( hWndChild, SalPixelsToFormUnits( hWndForm, nLeft, FALSE ), SalPixelsToFormUnits( hWndForm, nTop, TRUE ) )
        Call SalSetWindowSize( hWndChild, SalPixelsToFormUnits( hWndForm, nRight-nLeft, FALSE ), SalPixelsToFormUnits( hWndForm, nBottom-nTop, TRUE) )

Can I show bitmaps in a TableWindow cell from a database?
Yes, if the image in the database column is a bitmap save with the same format of a bitmap file or from a picture control using SalPicGetString(). You have to get the image handle using XSalImageFromString() (part of the IMG extensions) and set the image handle in the cell using XSalTblSetCellImage() (CPT). Remember to free the image using XSalImageClose() to release the resources.

 


TLB - Toolbars

Can I have pushbuttons with the bitmap on the side?
Yes, use the TLB function XSalToolboxMakeFlat( pbButton, TLBS_LEFTIMAGE or TLBS_RIGHTIMAGE ).

Can I have pushbuttons only with a thin frame without making them flat?
Yes, in XSalToolboxMakeFlat(), just use the TLBS_THINFRAME flag.

Sometimes my flat buttons get "stuck". What can I do?
It happens when you execute a function that release the mouse capture and notifies the "wrong" window. In those cases you can release the "stuck" flat button sending the WM_CANCELMODE (0x001F) message to the button.

When a flat toolbar button gets clicked, SAM_Validate is not sent to the control loosing the focus. How can this be fixed?
When you press buttons that have been modified using XSalToolboxMakeFlat( ), they behave like menu items. The focus doesn't really change and thus, SAM_Validate does not get sent. You can either program the focus change with SalSetFocus( ) or, more elegantly, make a call to SalSendValidateMsg( ) from within the SAM_Click message portion of toolbox buttons. Once implemented in a class, you don't have to further worry about it.

 


TTP - ToolTips

How do I show tooltips for TableWindows' columns/rows/cells?
Detect the mouse movement using WM_MOUSEMOVE (0x0200) and save the column/row/cell using
SalTblObjectsFromPoint( hWndItem, SalNumberLow(lParam), SalNumberHigh(lParam), nRow, hWndColumn, nFlags ).
When the column/row/cell is different from the previous one, use XSalTooltipShow( hWndItem, sTooltipText ).

Can I receive a notification when a tooltip is shown or hidden?
You can use SAM_TooltipSetText to know when the tooltip gets shown. For SAM_TooltipClose you have to wait the next version: TTP2.0.

I've noticed that tooltips are shown even when the window is inactive, can I decide to show the tooltips only when the window is active?
You can use the following function instead of XSalTooltipSetText():
Function: XSalTooltipSetTextActive
Parameters
    Number: p_nLParam
    String: p_sText
Actions
    If GetActiveWindow(  )=hWndForm OR GetParent( hWndForm )=GetActiveWindow(  )
        Return XSalTooltipSetText( p_nLParam, p_sText )
Next version (TTP2.0) will have a setting for this plus another setting for showing tooltip on disabled windows.

The tooltips don't seem to work although I included the library and added SAM_TooltipSetText to some controls. Is this a bug?
No, it is not a bug. You have to call any tooltip function to make SQLWindows32 load the DLL. SQLWindows loads DLLs at startup, SQLWindows32 doesn't. That's why the DLL doesn't get initialized. Just call XSalTooltipSetColors() and the tooltip system will get enabled.

 


UDV - User-defined variables

Can I pass an object (UDV or functional class) by sending a a message?
Yes, but only by value, not by reference. You can convert the object in a string using XSalUdvToString() (UDV) and send the string in a message using SalHStringToNumber(). The received of the message can duplicate the object locally using XSalUdvFromString() and SalNumberToHString().