RTR logo

BBC BASIC for Windows

Library Routines



Introduction to libraries

The INSTALL statement allows you to load a library containing functions and procedures which can be called from within your program, without them appearing in the program listing. If you, or somebody else, has written a number of useful functions which you might want to call from several different programs, this provides a convenient way of packaging and distributing them.

Because the procedures and functions do not form part of your program the size of its file is reduced, and if a bug is found in one of the functions it can be corrected by updating the library without having to modify each of the programs in which it is used.

You can build up your own set of such files, but BBC BASIC for Windows is supplied with the following libraries:

These can be found in the LIB sub-directory (folder) in the main BBC BASIC directory.

Array and matrix functions

The ARRAYLIB library contains a set of procedures and functions for performing arithmetic and matrix operations on 1- and 2-dimensional arrays. These include adding two matrices together, multiplying two matrices, transposing a matrix and inverting a matrix.

The library should be loaded from your program using the command:

INSTALL @lib$+"ARRAYLIB"

The functions contained are:

In BBC BASIC for Windows version 3.00a and later several of these operations are incorporated within the interpreter. Using the built-in operations will be considerably faster than using the library routines; see the Array arithmetic section for details. The library routines which are not supported as built-in operations are PROC_transpose, PROC_invert and FN_det.

PROC_add(A(), B)

PROC_add adds a scalar value B to all the elements of 1D or 2D (numeric) array A(), and returns the result in A():
DIM N(3)
N() = 1, 2, 3, 4
PROC_add(N(), 5)
PRINT N(0),N(1),N(2),N(3)
When executed this program will print:
         6         7         8         9

PROC_mul(A(), B)

PROC_mul multiplies all the elements of 1D or 2D (numeric) array A() by the scalar value B, and returns the result in A():
DIM N(3)
N() = 1, 2, 3, 4
PROC_mul(N(), 2)
PRINT N(0),N(1),N(2),N(3)
When executed this program will print:
         2         4         6         8

PROC_sum(A(), B())

PROC_sum adds 1D or 2D (numeric) arrays A() and B() together, and returns the result in A(). A() and B() must have the same dimensions.
DIM N(3), S(3)
N() = 1, 2, 3, 4
S() = 5, 6, 7, 8
PROC_sum(N(), S())
PRINT N(0),N(1),N(2),N(3)
When executed this program will print:
         6         8        10        12

PROC_dot(A(), B(), C())

PROC_dot multiplies 2D matrices A() and B() together and returns the result in C(). The number of columns of A() must equal the number of rows of B(), the number of columns of C() must equal the number of columns of B() and the number of rows of C() must equal the number of rows of A().
DIM N(0,2), S(2,1), D(0,1)
N() = 1, 2, 3
S() = 4, 5, 6, 7, 8, 9
PROC_dot(N(), S(), D())
PRINT D(0,0) D(0,1)
When executed this program will print:
        40        46

PROC_transpose(A(), B())

PROC_transpose transposes 2D matrix A() and returns the result in B(). The number of columns of A() must equal the number of rows of B() and the number of rows of A() must equal the number of columns of B().
DIM N(1,2), T(2,1)
N() = 1, 2, 3, 4, 5, 6
PROC_transpose(N(), T())
PRINT T(0,0) T(0,1)
PRINT T(1,0) T(1,1)
PRINT T(2,0) T(2,1)
When executed this program will print:
         1         4
         2         5
         3         6

PROC_invert(A())

PROC_invert inverts square matrix A() and returns the result in A().
DIM M(2,2)
M() = 2,0,6,8,1,-4,0,5,7
PROC_invert(M())
PRINT M(0,0) M(0,1) M(0,2)
PRINT M(1,0) M(1,1) M(1,2)
PRINT M(2,0) M(2,1) M(2,2)
When executed this program will print:
   0.09184   0.10204  -0.02041
  -0.19048   0.04762   0.19048
   0.13605  -0.03401   0.00680

FN_mod(A())

FN_mod returns the modulus (the square-root of the sum of the squares of all the elements) of a 1D or 2D array.
DIM M(2,2)
M() = 2,0,6,8,1,-4,0,5,7
PRINT FN_mod(M())
When executed this program will print:
13.96424

FN_det(A())

FN_det returns the determinant of a square array.
DIM M(2,2)
M() = 2,0,6,8,1,-4,0,5,7
PRINT FN_det(M())
When executed this program will print:
294

Toolbars and status bars

The WINLIB library contains a set of procedures and functions for creating and controlling toolbars and status bars. These allow you to make your BBC BASIC for Windows programs look more like genuine Windows™ programs, with a proper Graphical User Interface.

The library should be loaded from your program using the command:

INSTALL @lib$+"WINLIB"

The functions contained are:

The WINLIBU library contains an identical set of functions except that those receiving a text string assume it to be in Unicode (UTF-8) format, allowing non-ANSI (e.g. foreign language) characters to be incorporated.

FN_createstatusbar(text$)

This function creates a status bar, and returns its window handle. A single string parameter must be supplied, which contains the text (if any) to be displayed by default in the status bar. The returned value can be used for manipulating the status bar using Windows™ API functions (see below).

The following program segment creates a status bar containing the string "Press F1 for Help":

hstat% = FN_createstatusbar("Press F1 for help")
Note that BASIC's output window is not automatically made smaller to accommodate the status bar. You should use the VDU 24 and/or VDU 28 commands to reduce the size of the graphics and/or text windows to suit.

If you want the status bar to resize itself automatically when the size of the main window changes (e.g. as the result of the user dragging a corner) you should forward the size-change message as follows:

ON MOVE SYS "PostMessage",hstat%,@msg%,@wparam%,@lparam% : RETURN
You should remove the status bar by calling PROC_removestatusbar before your program exits or returns to immediate mode. It is good practice to trap errors (using ON ERROR ) and to remove the status bar if an unexpected error occurs.

Once you have created a basic status bar, you can use Windows™ API functions to divide it into a number of parts and write different text in each part. The following program segment illustrates how to split the status bar into three parts; hstat% is the value returned from the FN_createstatusbar function.

SB_SETTEXT = 1025
SB_SETPARTS = 1028
nparts% = 3
DIM edges%(nparts%-1)
FOR N% = 0 TO nparts%-1
  READ edges%(N%)
NEXT N%
SYS "SendMessage", hstat%, SB_SETPARTS, nparts%, ^edges%(0)
FOR N% = 0 TO nparts%-1
  READ text$
  SYS "SendMessage", hstat%, SB_SETTEXT, N%, CHR$9+text$
NEXT N%
DATA 440, 540, 640
DATA Part 1, Part 2, Part 3
The first DATA statement determines the width of each part, by specifying the position of its right-hand edge (in pixels). The second DATA statement determines the text which will appear in each part; the CHR$9 character causes the text to be displayed centrally.

FN_createstatusbarex(text$, style%)

This function is similar to FN_createstatusbar except that it takes an additional style% parameter. This parameter may be set to SBARS_SIZEGRIP (256) to cause a 'size grip' to be displayed at the extreme right-hand end of the status bar. This is appropriate when the parent window can be resized by the user (by dragging a side or corner).

PROC_removestatusbar

This procedure removes a status bar previously created with FN_createstatusbar or FN_createstatusbarex. You should always remove the status bar before your program exits or returns to immediate mode.

FN_createtoolbar(nbutts%,button%(),buttid%())

This function creates a toolbar, and returns its window handle. Three parameters must be supplied: the number of buttons in the toolbar, an integer array of button types and an integer array of button identifiers. The returned value can be used for manipulating the toolbar using Windows™ API functions (see below). To create a floating toolbar see the FN_createfloatingtoolbar function in the WINLIB3 library.

The following program segment creates a toolbar containing three buttons: a cut button, a copy button and a paste button:

nbutts% = 3
DIM button%(nbutts%-1), buttid%(nbutts%-1)
button%() = 0, 1, 2
buttid%() = 100, 101, 102
htool% = FN_createtoolbar(nbutts%,button%(),buttid%())
The button% array contains the button types and the buttid% array contains the button identifiers (which are used to identify the buttons, and to determine which button has been clicked). If the button identifier is zero this signifies a separator rather than a button; the corresponding entry in the button% array contains a value (normally zero) by which the width of the separator is increased, in pixels.

In version 1.7 and later of the WINLIB library you can specify a negative identifier value. This creates an auto toggle button which alternates between the 'pressed' and 'unpressed' states when you click on it. In this case the actual identifier value allocated to the button is minus the value specified in the buttid% array.

The available button types are as follows:

Value   Button
0Cut
1Copy
2Paste
3Undo
4Redo
5Delete
6New
7Open
8Save
9Find
10Print Preview
11Help
12Find
13Replace
14Print
15no image
Note that BASIC's output window is not automatically made smaller to accommodate the toolbar. You should use the VDU 24 and/or VDU 28 commands to reduce the size of the graphics and/or text windows to suit.

If you want the toolbar to resize itself automatically when the size of the main window changes (e.g. as the result of the user dragging a corner) you should forward the size-change message as follows:

ON MOVE SYS "PostMessage",htool%,@msg%,@wparam%,@lparam% : RETURN
You should remove the toolbar by calling PROC_removetoolbar before your program exits or returns to immediate mode. It is good practice to trap errors (using ON ERROR ) and to remove the toolbar if an unexpected error occurs.

Once you have created a toolbar, clicking on the buttons will cause messages to be sent to your program which can be detected with ON SYS, in exactly the same way as a selection from a menu bar. The @wparam% value will be the button identifier you chose. Alternatively you can automate the process and avoid the need to use ON SYS in your code by allocating the identifier using FN_setproc in WINLIB5.

You can use Windows™ API functions to determine the appearance of the buttons. The following program segment illustrates how to cause a button to be disabled (greyed out); htool% is the value returned from the FN_createtoolbar function:

TB_SETSTATE = 1041
state% = 0
SYS "SendMessage", htool%, TB_SETSTATE, buttid%, state%
Some of the possible button states are:
0   Disabled
4Enabled
6Pressed
8Hidden
You can discover the current state of a button as follows:
TB_GETSTATE = 1042
SYS "SendMessage", htool%, TB_GETSTATE, buttid%, 0 TO state%
In the case of an auto toggle button which has been toggled to the 'pressed' state, the returned value is one greater than those shown. So 5 signifies that it has been toggled to the 'pressed' state and 7 that it is also being pressed by the user.

FN_createtoolbarex(nbutts%,button%(),buttid%(),resid%,style%)

This function is similar to FN_createtoolbar except that it takes additional resid% and style% parameters. resid% should be set to IDB_STD_SMALL_COLOR (0) for small toolbar buttons and IDB_STD_LARGE_COLOR (1) for large toolbar buttons. style% can be set to one or more of the toolbar styles, such as TBSTYLE_WRAPABLE (512) to cause the toolbar to wrap onto multiple lines if its width is insufficient.

PROC_removetoolbar

This procedure removes a toolbar previously created with FN_createtoolbar or FN_createtoolbarex. You should always remove the toolbar before your program exits or returns to immediate mode.

FN_custombutton(htool%,bmpfile$,buttid%)

This function allows you to customise a button image to suit your particular application. Three parameters must be supplied: the handle of the toolbar (as returned from FN_createtoolbar), the name of a Windows Bitmap file containing the required button image (it is automatically scaled to fit the button) and the identifier of the button you wish to change.

The function returns TRUE if the button image was successfully changed, and FALSE otherwise (most likely because the specified file could not be read or has an invalid format). You can customise as many buttons as you like by calling this function multiple times with different values of buttid%; alternatively you can customise multiple buttons from a single bitmap file using the FN_custombuttons function to be found in the WINLIB3 library.

The following program segment illustrates how to customise a button image:

ok% = FN_custombutton(htool%, "\pictures\bmp\owl.bmp", 102)
When initially creating the toolbar set the button 'type' of any custom buttons to 15 (no image). Also ensure that the background colour of your bitmap file is R=192, G=192, B=192 (&C0C0C0). These measures will ensure that your custom images are displayed correctly.

PROC_addtooltips(htool%,nbutts%,buttip$(),buttid%())

This procedure allows you to add tool tips to your toolbar. A tool tip is a text string which appears in a small yellow box when you 'hover' the mouse over the toolbar button for a short while. Adding tool tips to your toolbar gives your program an even more authentic Windows™ interface.

Four parameters must be supplied: the handle of the toolbar (as returned from FN_createtoolbar), the number of buttons for which you want to provide tips, an array of strings containing the tips to be displayed and an integer array of identifier values for the appropriate buttons.

The following program segment illustrates how to add tooltips to the toolbar created in the earlier example:

nbutts% = 3
DIM buttip$(nbutts%-1), buttid%(nbutts%-1)
buttip$() = "Cut", "Copy", "Paste"
buttid%() = 100, 101, 102
PROC_addtooltips(htool%,nbutts%,buttip$(),buttid%())
The buttid% array may often be the same as that passed to FN_createtoolbar.

Dialogue boxes

The WINLIB2 library contains a set of procedures and functions for creating and controlling custom dialogue boxes. These allow you to adopt the preferred Windows™ method of requesting input from the user.

The library should be loaded from your program using the command:

INSTALL @lib$+"WINLIB2"
The functions contained are: The WINLIB2B library contains an identical set of functions but additionally forwards to your program any WM_HELP and WM_NOTIFY messages generated by the dialogue box controls. These messages can be intercepted using *SYS 1 to allow your program to provide, for example, context-sensitive help for your dialogue box. WINLIB2B also suppresses the automatic closing of the dialogue box when you click on its close button, or press Escape.

The WINLIB2U library contains an identical set of functions to WINLIB2B except that those receiving a text string assume it to be in Unicode (UTF-8) format, allowing non-ANSI (e.g. foreign language) characters to be incorporated.

The supplied example program DLGEDIT.BBC can simplify the creation of dialogue boxes by providing a 'visual' editor and generating some of the required BBC BASIC code automatically.

FN_newdialog

Before you can use a custom dialogue box, you must first define its position, size and title. This should be done only once, typically in an initialisation routine, for each dialogue box your program contains (the dialogue box can subsequently be displayed as many times as you like):
dlg% = FN_newdialog(title$,x%,y%,cx%,cy%,font%,size%)
The string title$ specifies the title of the dialogue box, which is displayed in its title bar. The values x% and y% specify the initial position of the dialogue box, with respect to the top left corner of your program's window. The values cx% and cy% specify the width and height, respectively, of the dialogue box.

The value font% specifies the size, in points, of all the text strings in the dialogue box, and it also determines the size of the units used to specify the position and size of the box. Therefore, if you change the size of the text all the other dimensions of the dialogue box are scaled to suit. A common value to use is 8.

The value size% specifies the number of bytes of memory to allocate for the dialogue box template. As a rule of thumb this can be set to approximately the number of individual items within the dialogue box multiplied by 50; if the value is too small you will receive the error message No room for dialogue template when the items are created. There is no harm, apart from wasted memory, in making it too big.

The returned value (dlg% in this case) identifies the dialogue box, and must be stored for use in the subsequent procedures.

Normally a dialogue box has a title bar. This can be eliminated by modifying the style after the call to FN_newdialog as follows:

dlg% = FN_newdialog("",x%,y%,cx%,cy%,font%,size%)
WS_BORDER = &800000
dlg%!16 AND= NOT WS_BORDER
Note, however, that if you do this the user won't be able to move the dialogue box, so you may need to set its position more carefully.

Make sure you only call FN_newdialog once (for each dialogue box); if you need to re-open a dialogue box that has already been opened, simply call PROC_showdialog. Preferably place all your calls to FN_newdialog in an initialisation routine that is only executed once.

PROC_pushbutton

Once you have created the basic dialogue box, you must define its contents. The common items (pushbuttons, edit boxes etc.) each have their own procedure. Again, the contents of the dialogue box must be defined just once, during your program's initialisation phase.

All dialogue boxes should have at least one pushbutton, labelled OK, which enables the user to confirm that his input is complete:

PROC_pushbutton(dlg%,text$,id%,x%,y%,cx%,cy%,style%)
The value dlg% identifies the dialogue box, and is the value returned from FN_newdialog. The string text$ specifies the text to appear inside the button (e.g. "OK"), the values x% and y% specify the position of the button within the dialogue box and the values cx% and cy% specify the size of the button (in dialogue box units).

The value id% is a unique identifier of the pushbutton; all the items within a particular dialogue box must have different values. You can choose any value (within reason) but the values 1 and 2 are reserved for the OK and Cancel buttons respectively so should only be used for that purpose. Alternatively you can set this parameter to a value returned from FN_setproc (in WINLIB5 or WINLIB5A) which will cause the specified procedure to be called when the button is clicked.

The value style% can be zero, but other values allow you to modify the appearance or behaviour of the pushbutton. Setting it to 1 (BS_DEFPUSHBUTTON) causes the pushbutton to be the default button: it is displayed with a thicker border and pressing the Enter key has the same effect as pressing that button. There should only be one button in each dialogue box with this style; typically you would use it for the OK button. Setting style% to &20000 (WS_GROUP) causes the button to be the first item in a new group; this affects navigation of the dialogue box using the cursor keys. Setting style% to &80 (BS_BITMAP) indicates that the button will display a bitmap image (see Initialising the contents of a dialogue box); in this case text$ should be empty. The values can be combined.

PROC_checkbox

A check box is a small square which can either be checked (contain a tick mark) or not. When the user clicks on the box, it (usually) toggles between being checked and being unchecked. A check box is used to select one of two states (e.g. off or on).
PROC_checkbox(dlg%,text$,id%,x%,y%,cx%,cy%,style%)
The value dlg% identifies the dialogue box, and is the value returned from FN_newdialog. The string text$ specifies the text to appear alongside the check box, the values x% and y% specify the position of the check box within the dialogue box and the values cx% and cy% specify the size of the check box and its associated text (in dialogue box units). The value id% is a unique identifier for the check box.

The value style% can be zero, but other values allow you to modify the appearance or behaviour of the check box. Setting it to &20 (BS_LEFTTEXT) causes the associated text to appear to the left of the check box rather than to the right. Setting it to &20000 (WS_GROUP) causes the check box to be the first item in a new group; this affects navigation of the dialogue box using the cursor keys. The values can be combined.

PROC_radiobutton

A radio button is a small circle which can either be checked (contain a central spot) or not. Radio buttons are used in groups of two or more, where only one of the buttons is checked at any one time. When the user clicks one of the buttons it becomes checked, and all the other buttons in the group become unchecked. A radio button is used to select one of two or more states.
PROC_radiobutton(dlg%,text$,id%,x%,y%,cx%,cy%,style%)
The value dlg% identifies the dialogue box, and is the value returned from FN_newdialog. The string text$ specifies the text to appear alongside the radio button, the values x% and y% specify the position of the radio button within the dialogue box and the values cx% and cy% specify the size of the radio button and its associated text (in dialogue box units). The value id% is a unique identifier for the radio button.

The value style% can be zero, but other values allow you to modify the appearance or behaviour of the radio button. Setting it to &20 (BS_LEFTTEXT) causes the associated text to appear to the left of the radio button rather than to the right. Setting it to &20000 (WS_GROUP) causes the radio button to be the first item in a new group; this has special significance for radio buttons, since only one radio button in a group can be checked at any one time. The values can be combined.

PROC_groupbox

A group box is a rectangle which is used to enclose a number of items within the dialogue box, thus emphasising that they are grouped together.
PROC_groupbox(dlg%,text$,id%,x%,y%,cx%,cy%,style%)
The value dlg% identifies the dialogue box, and is the value returned from FN_newdialog. The string text$ specifies the text to appear in the top edge of the group box, the values x% and y% specify the position of the group box within the dialogue box and the values cx% and cy% specify the size of the group box (in dialogue box units). The value id% is a unique identifier for the group box.

The value style% may be zero, but will often have the value &20000 (WS_GROUP) signifying that the group box is the first item in a new group.

PROC_editbox

An edit box is a rectangular field into which the user can type textual or numeric input.
PROC_editbox(dlg%,text$,id%,x%,y%,cx%,cy%,style%)
The value dlg% identifies the dialogue box, and is the value returned from FN_newdialog. The string text$ specifies the initial text (if any) to appear in the edit box, the values x% and y% specify the position of the edit box within the dialogue box and the values cx% and cy% specify the size of the edit box (in dialogue box units). The value id% is a unique identifier for the edit box.

The value style% can be zero, but other values allow you to modify the appearance or behaviour of the edit box. Setting it to &80 (ES_AUTOHSCROLL) causes the contents of the edit box to scroll horizontally, if necessary. Setting it to &2000 (ES_NUMBER) causes the edit box to accept only numeric input. Setting it to &20000 (WS_GROUP) causes the edit box to be the first item in a new group. Setting it to &1004 (ES_WANTRETURN + ES_MULTILINE), along with an appropriate vertical size, creates a multi-line edit box. The values can be combined.

PROC_static

A static item is a rectangular area containing (usually) a text string or an image. This may be used to label another item or be simply informative.
PROC_static(dlg%,text$,id%,x%,y%,cx%,cy%,style%)
The value dlg% identifies the dialogue box, and is the value returned from FN_newdialog. The string text$ specifies the required text (if any), the values x% and y% specify the position of the rectangle within the dialogue box and the values cx% and cy% specify the size of the rectangle (in dialogue box units). The value id% is a unique identifier for the static item.

The value style% may be zero, but other values allow you to modify the appearance of the static item. By default text is left-justified within the rectangle but setting style% to 1 (SS_CENTER) causes the text to be centred within the rectangle and setting it to 2 (SS_RIGHT) causes the text to be right-justified within the rectangle. Setting style% to &E (SS_BITMAP) indicates that the item will contain a bitmap image to be loaded later (see Initialising the contents of a dialogue box); in this case text$ should be empty.

PROC_listbox

A list box displays a list of two or more items from which the user can select one.
PROC_listbox(dlg%,"",id%,x%,y%,cx%,cy%,style%)
The value dlg% identifies the dialogue box, and is the value returned from FN_newdialog. The text string is unused and should be set to an empty string, the values x% and y% specify the position of the list box within the dialogue box and the values cx% and cy% specify the size of the list box (in dialogue box units). The value id% is a unique identifier for the list box.

The value style% can be zero, but other values allow you to modify the appearance or behaviour of the list box. For example setting it to &20000 (WS_GROUP) causes the list box to be the first item in a new group. To disable automatic sorting of the listbox contents subtract 2 (LBS_SORT) from the style% value you would otherwise have used.

The items to be displayed in the list box must be written as a separate exercise once the dialogue box has been displayed. See Initialising the contents of a dialogue box for details. If the height of the list box is insufficient for the number of items to be displayed, a vertical scroll bar is automatically generated. Adding &100200 (WS_HSCROLL + LBS_MULTICOLUMN) to style% instead results in the list box having a horizontal scroll bar, and the items are presented in columns.

PROC_combobox

A combo box consists of a list and a selection field. The list presents the options a user can select and the selection field displays the current selection.
PROC_combobox(dlg%,"",id%,x%,y%,cx%,cy%,style%)
The value dlg% identifies the dialogue box, and is the value returned from FN_newdialog. The text string is unused and should be set to an empty string, the values x% and y% specify the position of the combo box within the dialogue box and the values cx% and cy% specify the size of the combo box (in dialogue box units). The value id% is a unique identifier for the combo box.

The value style% can be zero, but other values allow you to modify the appearance or behaviour of the combo box. Setting it to 3 (CBS_DROPDOWNLIST) creates a drop down list, where the list of items from which the selection can be made is only displayed when the user clicks on the arrow button (note particularly that in this case cy% is the dropped down height of the box). Setting it to &100 (CBS_SORT) causes the items in the list to be sorted into alphabetical order. Setting it to &20000 (WS_GROUP) causes the combo box to be the first item in a new group. The values can be combined.

The items to be displayed in the combo box list must be written as a separate exercise once the dialogue box has been displayed. See Initialising the contents of a dialogue box for details. In the case of a drop-down list the height of the combo box should be made sufficient for the list when it is displayed.

PROC_dlgitem

PROC_dlgitem can be used to create items other than those for which dedicated procedures are provided.
PROC_dlgitem(dlg%,text$,id%,x%,y%,cx%,cy%,style%,class%)
The value dlg% identifies the dialogue box, and is the value returned from FN_newdialog. The values x% and y% specify the position of the item within the dialogue box and the values cx% and cy% specify the size of the item (in dialogue box units). The values of text$, style% and class% depend on the type of item. The value id% is a unique identifier for the item.

PROC_dlgctrl

PROC_dlgctrl is similar to PROC_dlgitem except that a class name is specified rather than a numeric value:
PROC_dlgctrl(dlg%,text$,id%,x%,y%,cx%,cy%,style%,class$)
This allows you to embed in your dialogue box standard Windows™ controls for which dedicated procedures are not provided. Examples of such controls are up-down controls, trackbars and progress bars:

Adding an up-down control

You can add an up-down control (a pair of arrows used for incrementing and decrementing a numeric value in an edit box) using the following program segment:
PROC_dlgctrl(dlg%,"",id%,0,0,0,0,&50000096,"msctls_updown32")
This item must immediately follow a numeric edit box in which the value controlled by the up and down arrows appears. The up-down control automatically positions itself at the right-hand end of the edit box (the position values are unused and are set to 0,0).

To set the range of the up-down control (which must be done after the PROC_showdialog):

UDM_SETRANGE = 1125
SYS "SendDlgItemMessage", !dlg%, id%, UDM_SETRANGE, 0, (min% << 16) + max%

Adding a trackbar

You can add a trackbar to a dialogue box using the following program segment:
PROC_dlgctrl(dlg%,"",id%,x%,y%,cx%,cy%,&50000000,"msctls_trackbar32")
The above example creates a horizontal trackbar with no tick-marks. To alter the style of the trackbar add one or more of the following values to the &50000000:
StyleNameEffect
1TBS_AUTOTICKSShow tick marks
2TBS_VERTVertical trackbar
4TBS_LEFTTick marks to top or left (default is bottom or right)
8TBS_BOTHTick marks on both sides
To set the range of the trackbar (which must be done after the PROC_showdialog):
TBM_SETRANGE = 1030
SYS "SendDlgItemMessage", !dlg%, id%, TBM_SETRANGE, 1, (max% << 16) + min%
To set the current position of the trackbar:
TBM_SETPOS = 1029
SYS "SendDlgItemMessage", !dlg%, id%, TBM_SETPOS, 1, position%
To read the current position of the trackbar:
TBM_GETPOS = 1024
SYS "SendDlgItemMessage", !dlg%, id%, TBM_GETPOS, 0, 0 TO position%

Adding a progress bar

You can add a progress bar to a dialogue box using the following program segment:
PROC_dlgctrl(dlg%,"",id%,x%,y%,cx%,cy%,&50000000,"msctls_progress32")
The above example creates a horizontal progress bar. To alter the style of the progress bar add one or more of the following values to the &50000000:
StyleNameEffect
1PBS_SMOOTHSmooth (rather than segmented) progress bar
4PBS_VERTICALVertical progress bar
To set the range of the progress bar (which must be done after the PROC_showdialog):
PBM_SETRANGE = 1025
SYS "SendDlgItemMessage", !dlg%, id%, PBM_SETRANGE, 0, (max% << 16) + min%
To set the current position of the progress bar:
PBM_SETPOS = 1026
SYS "SendDlgItemMessage", !dlg%, id%, PBM_SETPOS, position%, 0
To 'step' the progress bar:
PBM_STEPIT = 1029
SYS "SendDlgItemMessage", !dlg%, id%, PBM_STEPIT, 0, 0

PROC_showdialog

Once the position, size and contents of the dialogue box have been defined, it may be displayed on the screen. Whilst the creation of the dialogue box and the definition of its contents should be done just once, the box may subsequently be displayed as many times as you like:
PROC_showdialog(dlg%)
The value dlg% identifies the dialogue box, and is the value returned from FN_newdialog. Once the dialogue box has been displayed, you can send messages to it to affect its contents, and request information about its current contents.

Initialising the contents of a dialogue box

In many cases the initial contents of the dialogue box are defined when the items within it are created. For example, the initial contents of an edit box can be specified in the PROC_editbox procedure call. However you may want to change the contents at other times, and in the case of list boxes and combo boxes, which contain multiple items, you must use other methods to initialise the contents. Note that this initialisation must take place after the call to PROC_showdialog.

To change the text associated with a dialogue box item (which may for example be the contents of an edit box or a label for a check box) you can use the SetDlgItemText API call:

SYS "SetDlgItemText", !dlg%, id%, text$
The value !dlg% (note the exclamation mark) is the handle of the dialogue box, which is contained in memory at the address returned from FN_newdialog. The value id% is the identifier for the item in question and text$ is the new text string to be associated with the item.

If the item is an edit box used for numeric entry, the value displayed in the box can be set with SetDlgItemInt:

SYS "SetDlgItemInt", !dlg%, id%, value%, signed%
The value !dlg% is the handle of the dialogue box, id% is the identifier for the item in question, value% is the new value to display and signed% determines whether the value should be interpreted as signed (1) or unsigned (0).

If the value is controlled by an up-down control you can set the allowed range as follows:

UDM_SETRANGE = 1125
SYS "SendDlgItemMessage", !dlg%, id%, UDM_SETRANGE, 0, (min% << 16) + max%
where id% is the identifier for the up-down control, min% is the lowest value permitted and max% is the highest value permitted.

To load a bitmap image into a static item or a pushbutton you should use the following program segment. In the case of a pushbutton change STM_SETIMAGE (370) to BM_SETIMAGE (247):

LR_LOADFROMFILE = 16
STM_SETIMAGE = 370
SYS "LoadImage", 0, bmpfile$, 0, cx%, cy%, LR_LOADFROMFILE TO hbitmap%
SYS "SendDlgItemMessage", !dlg%, id%, STM_SETIMAGE, 0, hbitmap%
The value bmpfile$ is the name of a Windows™ bitmap file containing the image, cx% and cy% are the dimensions of the image in pixels, !dlg% is the handle of the dialogue box and id% is the identifier for the static item or pushbutton in question. Once you have finished with the dialogue box (but not before) delete the bitmap handle as follows:
SYS "DeleteObject", hbitmap%

To enter the list of strings into a list box you should do the following:

LB_ADDSTRING = 384
SYS "SendDlgItemMessage", !dlg%, id%, LB_ADDSTRING, 0, "Listbox item 0"
SYS "SendDlgItemMessage", !dlg%, id%, LB_ADDSTRING, 0, "Listbox item 1"
SYS "SendDlgItemMessage", !dlg%, id%, LB_ADDSTRING, 0, "Listbox item 2"
etc.
where the value id% is the identifier for the list box. The list box will (by default) sort the strings into alphabetical order, so the order in which they are sent is not important. To disable sorting subtract 2 (LBS_SORT) from the style% value you would otherwise have used (see PROC_listbox).

To empty a list box of its contents do:

LB_RESETCONTENT = 388
SYS "SendDlgItemMessage", !dlg%, id%, LB_RESETCONTENT, 0, 0

To enter the list of strings into a combo box you should do the following:

CB_ADDSTRING = 323
SYS "SendDlgItemMessage", !dlg%, id%, CB_ADDSTRING, 0, "Combobox item 0"
SYS "SendDlgItemMessage", !dlg%, id%, CB_ADDSTRING, 0, "Combobox item 1"
SYS "SendDlgItemMessage", !dlg%, id%, CB_ADDSTRING, 0, "Combobox item 2"
etc.
where the value id% is the identifier for the combo box. In this case the items are not sorted (by default), so they must be sent in the order in which they should appear. The initial selection from the list should be made as follows:
CB_SETCURSEL = 334
SYS "SendDlgItemMessage", !dlg%, id%, CB_SETCURSEL, index%, 0
The value index% determines which of the items is initially selected (starting at 0).

To empty a combo box of its contents do:

CB_RESETCONTENT = 331
SYS "SendDlgItemMessage", !dlg%, id%, CB_RESETCONTENT, 0, 0

To initialise the state of a set of radio buttons, you can use the CheckRadioButton API call:

SYS "CheckRadioButton", !dlg%, first%, last%, id%
Here first% is the identifier of the first radio button in the group, last% is the identifier of the last radio button in the group and id% is the identifier of the button you want to be checked.

To initialise the state of a check box, you can use the CheckDlgButton API call:

SYS "CheckDlgButton", !dlg%, id%, state%
Here id% is the identifier of the button you want to affect and state% is the state you wish it to have: 0 signifies unchecked and 1 signifies checked.

Disabling and enabling dialogue box items

You may wish to disable one or more items in a dialogue box, for example you may want to disable a button if it is not appropriate to click it in the current circumstances. An item which is disabled is shown in grey.

To disable an item you can use the EnableWindow API call:

SYS "GetDlgItem", !dlg%, id% TO h%
SYS "EnableWindow", h%, 0
Here id% is the identifier of the button you want to affect. To re-enable the item change the zero to a one:
SYS "GetDlgItem", !dlg%, id% TO h%
SYS "EnableWindow", h%, 1
Note that, as with initialisation, these routines must be executed after the call to PROC_showdialog.

Reading the contents of a dialogue box

Since the whole purpose of a dialogue box is to receive user input, it is vital that the current contents can be determined, particularly when the OK button is pressed.

To read the text associated with a dialogue box item (which may for example be the contents of an edit box or the current selection of a combo box) you can use the GetDlgItemText API call:

DEF FNgetdlgtext(dlg%, id%)
LOCAL text%
DIM text% LOCAL 255
SYS "GetDlgItemText", !dlg%, id%, text%, 255
= $$text%
The parameter dlg% is the value returned from FN_newdialog and the parameter id% is the identifier for the item in question. The maximum length of the string is 255 characters.

In the case of a multi-line edit box the maximum length should be set to an appropriate value. To save the returned data to a file you can do the following:

DEF PROCsavetofile(dlg%,id%,filename$)
LOCAL text%, Len%
DIM text% LOCAL 65535
SYS "GetDlgItemText", !dlg%, id%, text%, 65535 TO Len%
OSCLI "SAVE """+filename$+""" "+STR$~text%+"+"+STR$~Len%
ENDPROC
The data returned from a multi-line edit box consists of lines of text separated by CRLF (CHR$13+CHR$10) sequences. If you want to process the data you can split it into individual lines as follows:
P% = text%
REPEAT
  A$ = $P%        : REM. get line of text from memory
  PRINT A$        : REM. print the text (for example)
  P% += LEN(A$)+2 : REM. advance pointer to next line
UNTIL P% >= (text%+Len%)
If the item is an edit box used for numeric entry, the current value can be read with GetDlgItemInt:
SYS "GetDlgItemInt", !dlg%, id%, 0, signed% TO value%
The value !dlg% is the handle of the dialogue box, id% is the identifier for the item in question and signed% determines whether a negative value should be accepted (1) or not (0).

To determine which item (if any) is selected in a list box you can do the following:

LB_GETCURSEL = 392
SYS "SendDlgItemMessage", !dlg%, id%, LB_GETCURSEL, 0, 0 TO sel%
where the value id% is the identifier for the list box. The returned value sel% gives the index (starting at 0) of the currently selected item. If no item is selected, −1 is returned. Note that if the listbox contents have been sorted, which is the default behaviour, knowing the index alone is of little value. However you can use it to discover the selected text as follows:
DEF FNgetlistboxtext(dlg%, id%, sel%)
LOCAL text%
DIM text% LOCAL 255
LB_GETTEXT = 393
SYS "SendDlgItemMessage", !dlg%, id%, LB_GETTEXT, sel%, text%
= $$text%
To determine which item is selected in a combo box do the following:
CB_GETCURSEL = 327
SYS "SendDlgItemMessage", !dlg%, id%, CB_GETCURSEL, 0, 0 TO sel%
To discover the current state of a check box or radio button, you can use the IsDlgButtonChecked API call:
SYS "IsDlgButtonChecked", !dlg%, id% TO state%
where the value id% is the identifier for the check box or radio button. The value state% is set to 0 if the button is not checked and to 1 if it is checked.

Determining when a button is clicked

You must be able to determine when the user has clicked on a button, so that (for example) when the OK button is clicked the contents of the dialogue box can be read and the box removed from the screen. The simplest method is probably to use the FN_setproc function when the button is created; see PROC_pushbutton for more details. In that case once you've shown your dialogue box all you need to do is enter an 'idle loop':
REPEAT
  WAIT 1 
UNTIL !dlg% = 0
PROC_closedialog(dlg%)
Alternatively you can use a polling technique to monitor button presses and other events handled by ON SYS. The following code segment can be used to wait for the OK or Cancel button to be clicked, and then take appropriate action:
Click% = 0
ON SYS Click% = @wparam% : RETURN
REPEAT WAIT 1 
  click% = 0
  SWAP click%,Click%
UNTIL click% = 1 OR click% = 2 OR !dlg% = 0
ON SYS OFF
IF click% = 1 THEN
  PRINT "OK pressed"
  REM. process contents of dialogue box here
ELSE
  PRINT "Cancel pressed"
ENDIF
PROC_closedialog(dlg%)
Since clicking the close button of a dialogue box (or floating toolbar) always produces the same ID code (2) you cannot directly tell from ON SYS which dialogue box or toolbar was closed. If your program has more than one open at the same time this could be a problem. You can determine which are still open (and therefore, by a process of elimination, which was closed) by examining the window handles (!dlg% in the above example). If the handle is non-zero the window is still open, and if it is zero it has been closed.

PROC_closedialog

When the user has clicked on OK, and the contents of the dialogue box have been processed, the box should (generally) be removed from the screen:
PROC_closedialog(dlg%)
The dialogue box template remains in memory, so you can display it again at any time by calling PROC_showdialog.

The dialogue box should also be removed whenever your program returns to immediate mode (for example if an error occurs or the END statement is executed) or when your program's window is closed by the user. This can be achieved by executing the following statements immediately after the call to PROC_showdialog:

ON CLOSE PROC_closedialog(dlg%):QUIT
ON ERROR PROC_closedialog(dlg%):PRINT'REPORT$:END
Because the dialogue box uses space on the heap, it is essential that you remove it before executing a CLEAR, CHAIN or RUN statement. Failure to do so is very likely to crash BBC BASIC for Windows.

Trackbars and progress bars

The WINLIB3 library contains a set of procedures and functions for creating and controlling floating toolbars, trackbars and progress bars. The library should be loaded from your program using the command:
INSTALL @lib$+"WINLIB3"

The functions contained are:

FN_createfloatingtoolbar(nbutts%,button%(),buttid%(),xpos%,ypos%,title$)

This function creates a floating toolbar, and returns a pointer to its window handle. It works in a similar way to FN_createtoolbar except that it needs three extra parameters: the initial horizontal and vertical coordinates of the toolbar (measured, in dialogue box units, from the top-left corner of BASIC's output window) and a string to display in the toolbar's title bar. Unlike FN_createtoolbar, the toolbar is not actually displayed until you call PROC_showfloatingtoolbar; this allows you to remove and restore the toolbar without having to re-create it each time (avoiding the memory wastage that would involve).

Normally the floating toolbar has a title bar. This can be eliminated by modifying the style after the call to FN_createfloatingtoolbar as follows:

ftb% = FN_createfloatingtoolbar(nbutts%,button%(),buttid%(),50,50,"")
WS_BORDER = &800000
ftb%!16 AND= NOT WS_BORDER
Note, however, that if you do this the user won't be able to move the toolbar, so you may need to set its position more carefully.

PROC_showfloatingtoolbar(ftb%)

This procedure displays a previously created floating toolbar. It requires one parameter, being the value returned from FN_createfloatingtoolbar. The following program segment creates and displays a floating toolbar containing three buttons: a cut button, a copy button and a paste button:
nbutts% = 3
DIM button%(nbutts%-1), buttid%(nbutts%-1)
button%() = 0, 1, 2
buttid%() = 100, 101, 102
ftb% = FN_createfloatingtoolbar(nbutts%,button%(),buttid%(),50,50,"Floating")
PROC_showfloatingtoolbar(ftb%)
See FN_createtoolbar for more details. If you want to send messages to the toolbar (for example to control the appearance of the buttons) you can do so using the means described under FN_createtoolbar, but note that the value returned from FN_createfloatingtoolbar is a pointer to the toolbar handle not the handle itself. For example, to cause a button to be disabled (greyed out):
TB_SETSTATE = 1041
SYS "SendMessage", !ftb%, TB_SETSTATE, buttid%, 0
Note that you can only send messages to a floating toolbar after it has been displayed with PROC_showfloatingtoolbar.

PROC_removefloatingtoolbar

This procedure removes a floating toolbar previously created with FN_createfloatingtoolbar. You should always remove the toolbar before your program exits or returns to immediate mode:
PROC_removefloatingtoolbar(ftb%)
Because FN_createfloatingtoolbar reserves space on the heap, it is essential that you remove the toolbar before executing a CLEAR, CHAIN or RUN statement. Failure to do so is very likely to crash BBC BASIC for Windows.

FN_custombuttons(htool%,bmpfile$,nbutt%,buttid%())

This function is very similar to FN_custombutton except that it allows you to customise the images of multiple buttons from a single bitmap file. Four parameters must be supplied: the handle of the toolbar (e.g. as returned from FN_createtoolbar), the name of a Windows Bitmap file containing the required button images (left to right), the number of button images in the file and an array of identifiers of the buttons you wish to change.

The function returns TRUE if the button images were successfully changed, and FALSE otherwise (most likely because the specified file could not be read or has an invalid format). You can customise as many buttons as you like by providing a bitmap with suitable dimensions.

Note that when used with a floating tool bar the value returned from FN_createfloatingtoolbar is a pointer to the toolbar handle not the handle itself. So to customise all the buttons in a floating toolbar:

ok% = FN_custombuttons(!ftb%, "birds.bmp", nbutts%, buttid%())
When initially creating the toolbar set the button 'type' of any custom buttons to 15 (no image). Also ensure that the background colour of your bitmaps is R=192, G=192, B=192 (&C0C0C0). These measures will ensure that your custom images are displayed correctly.

FN_createtrackbar(hwnd%,xpos%,ypos%,width%,height%,style%)

This function creates a trackbar (sometimes called a slider). A trackbar allows the user to control a parameter by dragging the slider to the wanted position. Six parameters must be supplied: the window handle of the trackbar's parent window (usually @hwnd% unless you need to place the trackbar in a child window), the horizontal and vertical coordinates of the trackbar (in pixels, measured from the top-left corner of the bar's parent window), the width and height of the trackbar (in pixels) and the trackbar style. Possible values for the style are as follows:
StyleNameEffect
1TBS_AUTOTICKSShow tick marks
2TBS_VERTVertical trackbar
4TBS_LEFTTick marks to top or left (default is bottom or right)
8TBS_BOTHTick marks on both sides
The values can be combined.

PROC_showtrackbar(tb%,max%)

This function displays a trackbar created with FN_createtrackbar. It requires two parameters: the value returned by FN_createtrackbar and the maximum value to which the slider can be moved. If tick marks are shown, the total number (including both ends) will be max%+1.

To create and display a horizontal trackbar with tick marks, suitable for selecting a value between zero and ten:

tb% = FN_createtrackbar(@hwnd%, 100, 200, 20, 300, 1)
PROC_showtrackbar(tb%, 10)

FN_trackbarpos(tb%)

This function returns the current position of the trackbar (between zero and the value of max% supplied to PROC_showtrackbar):
trackpos% = FN_trackbarpos(tb%)
Normally the trackbar is moved by the user, but if you want your program to move it to a specific position you can do that as follows:
TBM_SETPOS = 1029
SYS "SendMessage", !tb%, TBM_SETPOS, 1, trackpos%
(note the exclamation mark in !tb%).

PROC_removetrackbar(tb%)

This procedure removes a trackbar previously created with FN_createtrackbar. You should always remove the trackbar before your program exits or returns to immediate mode:
PROC_removetrackbar(tb%)
Because FN_createtrackbar reserves space on the heap, it is essential that you remove the trackbar before executing a CLEAR, CHAIN or RUN statement. Failure to do so is very likely to crash BBC BASIC for Windows.

FN_createprogressbar(hwnd%,xpos%,ypos%,width%,height%,style%)

This function creates a progress bar which is typically used to inform the user of the progress of a time-consuming operation. Six parameters must be supplied: the window handle of the progress bar's parent window (usually @hwnd% unless you need to place the progress bar in a child window or status bar), the horizontal and vertical coordinates of the progress bar (in pixels, measured from the top-left corner of the bar's parent window), the width and height of the progress bar (in pixels) and the progress bar style. A style value of zero selects a horizontal progress bar with separate 'blocks'. Other values are as follows:
StyleEffect
1Progress 'blocks' are contiguous
4The progress bar is vertical
The values may be combined.

PROC_showprogressbar(pb%,max%)

This function displays a progress bar created with FN_createprogressbar. It requires two parameters: the value returned by FN_createprogressbar and the maximum value which can be indicated. For example if the progress is shown in percent, max% should be 100.

To create and display a progress bar, suitable for showing a value between zero and ten:

pb% = FN_createprogressbar(@hwnd%, 100, 200, 20, 300, 0)
PROC_showprogressbar(pb%, 10)

PROC_stepprogressbar(pb%, step%)

This procedure advances the progress bar by the number of steps indicated by step% (which can be negative). If the end result exceeds the value of max% (supplied to PROC_showprogressbar), or is negative, it wraps around. To advance a progress bar by one step:
PROC_stepprogressbar(pb%, 1)
You can also set the progress bar to an absolute value as follows:
PBM_SETPOS = 1026
SYS "SendMessage", !pb%, PBM_SETPOS, progress%, 0

PROC_removeprogressbar(pb%)

This procedure removes a progress bar previously created with FN_createprogressbar. You should always remove the progress bar before your program exits or returns to immediate mode:
PROC_removeprogressbar(pb%)
Because FN_createprogressbar reserves space on the heap, it is essential that you remove the progress bar before executing a CLEAR, CHAIN or RUN statement. Failure to do so is very likely to crash BBC BASIC for Windows.

Property sheets and wizards

The WINLIB4 library contains a set of procedures and functions for creating and controlling property sheets and wizards. The library should be loaded from your program using the command:
INSTALL @lib$+"WINLIB4"

The functions contained are:

The WINLIB4U library contains an identical set of functions except that those receiving a text string assume it to be in Unicode (UTF-8) format, allowing non-ANSI (e.g. foreign language) characters to be incorporated.

Property sheets and wizards are very similar, both effectively consisting of multiple dialogue box pages within a single window (only one page being displayed at a given time). They differ principally in the way the individual pages are selected: in a property sheet the pages can be selected in any order (each has a tab which can be clicked by the user), whereas in a wizard the pages must be selected sequentially by means of Next and Back buttons.

Because property sheets and wizards are like multiple dialogue boxes, this is exactly how they are created in BBC BASIC for Windows. Each page must be created using the FN_newdialog function in the WINLIB2 library. However, unlike conventional dialogue boxes, you should not include OK, Apply or Cancel buttons in the individual pages.

Instead of displaying the individual dialogue boxes with PROC_showdialog they are grouped together as a property sheet or wizard using FN_newpropsheet and displayed using PROC_showpropsheet.

FN_newpropsheet(caption$,npages%,initial%,style%,dlg%())

This function groups together multiple dialogue boxes to form a single property sheet or wizard. Five parameters must be supplied: a caption for the title bar, the number of dialogue box pages, the page which should be initially displayed (where the first page is numbered zero), a window style (usually 0 for a property sheet or &20 for a wizard) and an array of dialogue box pointers (each being the value returned from FN_newdialog).

The following program segment creates a property sheet containing three dialogue box pages:

pages% = 3
DIM page%(pages%-1)
page%(0) = FN_newdialog("First page", 32, 32, 288, 128, 8, 650)
REM create the contents of the first page here
page%(1) = FN_newdialog("Second page", 32, 32, 288, 128, 8, 1100)
REM create the contents of the second page here
page%(2) = FN_newdialog("Third page", 32, 32, 288, 128, 8, 1100)
REM create the contents of the third page here
psh% = FN_newpropsheet("Property sheet",pages%,0,0,page%())
Following each FN_newdialog call the contents of the relevant page are created using the procedures provided in WINLIB2, e.g. PROC_static, PROC_editbox etc. If a wizard rather than a property sheet is wanted, the last line would be:
psh% = FN_newpropsheet("Property sheet",pages%,0,&20,page%())

PROC_showpropsheet(psh%,hdlg%())

This procedure displays the property sheet or wizard. Two parameters must be supplied: the pointer to the property sheet returned by FN_newpropsheet and an empty array to receive the dialogue box handles. This array should be DIMensioned to have at least as many elements as there are pages in the property sheet or wizard, and on return from PROC_showpropsheet will contain the handles of each dialogue box page. These will be needed in order to initialise and interrogate the contents of the individual pages.

The following program segment displays the property sheet or wizard created above:

DIM hdlg%(pages%-1)
PROC_showpropsheet(psh%,hdlg%())

Initialising the contents of a property sheet

As with a dialogue box, the initial contents of the property sheet pages may be defined when the items within them are created. For example, the initial contents of an edit box can be specified in the PROC_editbox procedure call. However you may want to change the contents at other times, and in the case of list boxes and combo boxes, which contain multiple items, you must use other methods to initialise the contents.

The methods for doing this are identical to those listed in Initialising the contents of a dialogue box except that whenever the dialogue box handle is needed you must supply an element from the hdlg%() array returned from PROC_showpropsheet rather than the value pointed to by FN_newdialog. For example, to change the text associated with an item in the first page of the property sheet or wizard:

SYS "SetDlgItemText", hdlg%(0), id%, text$
You should normally ensure that the item identifier id% is unique rather than being used in two or more different pages. If the specified item is not in the page corresponding to the specified dialogue box handle, the call will fail.

Determining when a property sheet button is pressed

You must be able to determine when the user has clicked on the OK or Finish button, so that the contents of the property sheet or wizard can be read and the window removed from the screen. When any item in a property sheet is clicked, a message is sent (similar to those from a menu or a toolbar) which can be detected with ON SYS.

The following code segment can be used to wait for the OK, Finish or Cancel button to be clicked, and then take appropriate action:

Click% = 0
ON SYS Click% = @wparam%  : RETURN
REPEAT WAIT 1
  click% = 0
  SWAP click%,Click%
UNTIL click% = 1 OR click% = 2
ON SYS OFF
IF click% = 1 THEN
  PRINT "OK or Finish pressed"
  REM process contents of property sheet here
ELSE
  PRINT "Cancel pressed"
ENDIF
PROC_closepropsheet(psh%)

Monitoring property sheet page changes

Under some circumstances you may need to know when the user has changed the property sheet or wizard page. This is particularly true of a wizard, since it is the responsibility of the user's program to replace the Next button by a Finish button when the last page of the wizard is displayed (and also to inhibit the Back button when the first page is displayed).

You can do this by monitoring the current page handle, which can be determined by means of an API call. For example, the loop for monitoring which buttons are pressed can be modified to check also for page changes as follows:

PSM_SETWIZBUTTONS = 1136
PSM_GETCURRENTPAGEHWND = 1142
Click% = 0
ON SYS Click% = @wparam% : RETURN
oldhdlg% = 0
REPEAT
WAIT 1
  click% = 0
  SWAP click%,Click%
  SYS "SendMessage", !psh%, PSM_GETCURRENTPAGEHWND, 0, 0 TO hdlg%
  IF hdlg%<>oldhdlg% THEN
    oldhdlg% = hdlg%
    CASE hdlg% OF
      WHEN hdlg%(0):
        SYS "SendMessage", !psh%, PSM_SETWIZBUTTONS, 0, 2 : REM Next only
      WHEN hdlg%(1):
        SYS "SendMessage", !psh%, PSM_SETWIZBUTTONS, 0, 3 : REM Back and Next
      WHEN hdlg%(2):
        SYS "SendMessage", !psh%, PSM_SETWIZBUTTONS, 0, 5 : REM Back and Finish
    ENDCASE
  ENDIF
  CASE click% OF
    WHEN 1: PRINT "Finish pressed"
    REM Process contents of wizard here
    WHEN 2: PRINT "Cancel pressed"
  ENDCASE
UNTIL hdlg% = 0
PROC_closepropsheet(psh%)
The above example is for a wizard with three pages. According to which page is currently displayed the Back, Next and Finish buttons are displayed appropriately.

Reading the contents of a property sheet

Since the whole purpose of a property sheet or wizard is to receive user input, it is vital that the current contents can be determined, particularly when the OK or Finish button is pressed.

The methods for doing this are identical to those listed in Reading the contents of a dialogue box except that whenever the dialogue box handle is needed you must supply an element from the hdlg%() array returned from PROC_showpropsheet rather than the value pointed to by FN_newdialog. For example, to read the text associated with an item in a property sheet or wizard:

DEF FNgetproptext(hdlg%(), page%, id%)
LOCAL text%
DIM text% LOCAL 255
SYS "GetDlgItemText", hdlg%(page%), id%, text%, 255
= $$text%

PROC_closepropsheet(psh%)

When the contents of the property sheet or wizard have been processed, it should be removed from the screen:
PROC_closepropsheet(psh%)
The property sheet templates remain in memory, so you can display it again at any time by calling PROC_showpropsheet.

The property sheet or wizard should also be removed whenever your program returns to immediate mode (for example if an error occurs or the END statement is executed) or when your program's window is closed by the user. This can be achieved by executing the following statements immediately after the call to PROC_showpropsheet:

ON CLOSE PROC_closepropsheet(psh%):QUIT
ON ERROR PROC_closepropsheet(psh%):PRINT'REPORT$:END
Because the property sheet uses space on the heap, it is essential that you remove it before executing a CLEAR, CHAIN or RUN statement. Failure to do so is very likely to crash BBC BASIC for Windows.

Boxes and buttons

Windows™ programs containing push buttons, edit boxes and so on generally incorporate them within dialogue boxes. BBC BASIC for Windows supports this use by means of the WINLIB2 library. However it is perfectly possible to incorporate such items directly within your program's output window, or indeed as completely independent windows.

The WINLIB5 and WINLIB5A libraries contains a set of procedures and functions for incorporating push buttons, edit boxes etc. in your program without the need to create a dialogue box to contain them. The library should be loaded from your program using the command:

INSTALL @lib$+"WINLIB5"
or
INSTALL @lib$+"WINLIB5A"

The functions contained are:

The WINLIB5A library contains an identical set of functions but each takes an additional first parameter: the parent or owner window handle. Use this library instead of WINLIB5 when you need to place the buttons, boxes or controls on (or for them to be owned by) a window other than your main output window. For example you might wish to place a combo box on a toolbar or rebar.

The WINLIB5U library contains an identical set of functions to WINLIB5A, except that those receiving a text string assume it to be in Unicode (UTF-8) format, allowing non-ANSI (e.g. foreign language) characters to be incorporated.

FN_button(text$,x%,y%,cx%,cy%,id%,style%)

This function creates a rectangular pushbutton. The string text$ specifies the text to appear inside the button, the values x% and y% specify the position of the button and the values cx% and cy% specify the size of the button (in pixels, where 0,0 is the top-left corner of the window).

The value id% is a unique identifier of the pushbutton, and can be any constant you choose (within reason) or a value returned from FN_setproc. The value style% can be zero, but other values allow you to modify the appearance or behaviour of the button, for example the value &100 (BS_LEFT) causes the text to be left-justified rather than centered, &80 (BS_BITMAP) creates a bitmap button, 3 (BS_AUTOCHECKBOX) creates a checkbox and 9 (BS_AUTORADIOBUTTON) creates a radiobutton.

Clicking on the button causes an ON SYS event in the same way as a menu selection, with @wparam% equal to the value of id%.

The function returns the window handle of the button, which is needed when removing the button with PROC_closewindow.

To load an image into a bitmap button do:

LR_LOADFROMFILE = 16
BM_SETIMAGE = 247
SYS "LoadImage", 0, bmpfile$, 0, cx%, cy%, LR_LOADFROMFILE TO hbitmap%
SYS "SendMessage", hbutton%, BM_SETIMAGE, 0, hbitmap%
The value bmpfile$ is the name of a Windows™ bitmap file containing the image, cx% and cy% are the dimensions of the image in pixels and hbutton% is the value returned from FN_button. Once you have finished with the button (but not before) delete the bitmap handle:
SYS "DeleteObject", hbitmap%

FN_combobox("",x%,y%,cx%,cy%,id%,style%)

This function creates a combo box. The text string is unused and should be set to an empty string, the values x% and y% specify the position of the box and the values cx% and cy% specify the size of the box (in pixels, where 0,0 is the top-left corner of the window).

The value id% is a unique identifier of the combo box, and can be any constant you choose (within reason) or a value returned from FN_setproc. The value style% can be zero, but other values allow you to modify the appearance or behaviour of the box, for example the value 3 (CBS_DROPDOWNLIST) creates a drop down list combo box. The function returns the window handle of the box.

To enter a list of strings into a combo box do:

CB_ADDSTRING = 323
SYS "SendMessage", hbox%, CB_ADDSTRING, 0, "Combobox item 0"
SYS "SendMessage", hbox%, CB_ADDSTRING, 0, "Combobox item 1"
etc.
where hbox% is the value returned from FN_combobox. An initial (default) selection can be made as follows:
CB_SETCURSEL = 334
SYS "SendMessage", hbox%, CB_SETCURSEL, index%, 0
To determine which item is selected in a combo box do:
CB_GETCURSEL = 327
SYS "SendMessage", hbox%, CB_GETCURSEL, 0, 0 TO sel%

FN_editbox(text$,x%,y%,cx%,cy%,id%,style%)

This function creates an edit box. The string text$ specifies the initial contents of the box, the values x% and y% specify the position of the box and the values cx% and cy% specify the size of the box (in pixels, where 0,0 is the top-left corner of the window).

The value id% is a unique identifier of the edit box, and can be any constant you choose (within reason) or a value returned from FN_setproc. The value style% can be zero, but other values allow you to modify the appearance or behaviour of the box, for example the value &80 (ES_AUTOHSCROLL) allows the contents of the box to scroll horizontally. The function returns the window handle of the box.

To read the contents of an edit box do:

DEF FNgettext(hbox%)
LOCAL text%
DIM text% LOCAL 65535
SYS "GetWindowText", hbox%, text%, 65535
= $$text%
where the parameter hbox% is the value returned from FN_editbox.

To create a scrollable, multi-line edit box you can specify the style value as WS_VSCROLL OR ES_AUTOHSCROLL OR ES_MULTILINE. To create an edit control which behaves as an independent window, with its own title bar, system menu and close box you can additionally specify the style value as WS_POPUP OR WS_CAPTION OR WS_SYSMENU. Note that in this latter case you must set the id% value to zero (it has the meaning of menu handle in the case of a popup window).

FN_listbox("",x%,y%,cx%,cy%,id%,style%)

This function creates a list box. The text string is unused and should be set to an empty string, the values x% and y% specify the position of the box and the values cx% and cy% specify the size of the box (in pixels, where 0,0 is the top-left corner of the window).

The value id% is a unique identifier of the list box, and can be any constant you choose (within reason) or a value returned from FN_setproc. The value style% can be zero, but other values allow you to modify the appearance or behaviour of the box, for example the value 2 (LBS_SORT) causes the contents of the box to be sorted. The function returns the window handle of the box.

To enter a list of strings into a list box do:

LB_ADDSTRING = 384
SYS "SendMessage", hbox%, LB_ADDSTRING, 0, "Listbox item 0"
SYS "SendMessage", hbox%, LB_ADDSTRING, 0, "Listbox item 1"
etc.
where hbox% is the value returned by FN_listbox. To determine which item is selected do:
LB_GETCURSEL = 392
SYS "SendMessage", hbox%, LB_GETCURSEL, 0, 0 TO sel%
The value sel% gives the index (starting at 0) of the currently selected item or −1 if no item is selected.

FN_staticbox(text$,x%,y%,cx%,cy%,id%,style%)

This function creates a static box. The string text$ specifies the contents (if a text box), the values x% and y% specify the position of the box and the values cx% and cy% specify the size of the box (in pixels, where 0,0 is the top-left corner of the window).

The value id% is a unique identifier of the static box, and can be any constant you choose (within reason) or a value returned from FN_setproc. The value style% can be zero, but other values allow you to modify the appearance or behaviour of the box, for example the value &E (SS_BITMAP) indicates that the box will contain a bitmap image. The function returns the window handle of the box.

To load a bitmap image into a static box do:

LR_LOADFROMFILE = 16
STM_SETIMAGE = 370
SYS "LoadImage", 0, bmpfile$, 0, cx%, cy%, LR_LOADFROMFILE TO hbitmap%
SYS "SendMessage", hbox%, STM_SETIMAGE, 0, hbitmap%
The value bmpfile$ is the name of a Windows™ bitmap file containing the image, cx% and cy% are the dimensions of the image in pixels and hbox% is the value returned from FN_staticbox. Once you have finished with the box (but not before) delete the bitmap handle:
SYS "DeleteObject", hbitmap%

FN_createwindow(class$,text$,x%,y%,cx%,cy%,id%,style%,exst%)

This function can be used to create child window types other than those described above. The parameters have the same meaning as for the other types except that class$ is the window class name and exst% is an extended window style. The function returns the window handle.

Note that the style% parameter is exclusive-ORed with &50000000, which is the numeric equivalent of WS_CHILD + WS_VISIBLE. If you prefer the window not to be constrained within the bounds of its parent, add &C0000000 (WS_CHILD + WS_POPUP) to the supplied parameter.

PROC_closewindow(hwnd%)

This procedure can be used to close (i.e. remove) any window created with the above functions. The value hwnd% is the window handle returned from the appropriate function.

PROC_setfocus(hwnd%)

This procedure can be used to control which window has the input focus (i.e. receives keyboard input). The value hwnd% is the handle of the window to receive the focus, which can be the value returned from one of the above functions or @hwnd% in order for your BASIC program to receive keyboard input again.

FN_setproc(PROCname)

This function (available in version 1.4 or later of WINLIB5) takes as a parameter the name of a procedure and returns an ID number which you can use when creating a control or a menu item. Using this function automates the process of executing a procedure when (for example) a button or menu item is clicked, without needing to use ON SYS. Here are some examples of its use:

To create a menu, in which selecting Open causes PROCopen to be executed and selecting Exit causes PROCexit to be executed:

SYS "CreatePopupMenu" TO hfile%
SYS "AppendMenu", hfile%, 0, FN_setproc(PROCopen), "&Open"
SYS "AppendMenu", hfile%, 0, FN_setproc(PROCexit), "E&xit"
To create buttons in a dialogue box, where clicking on Button 1 causes PROCbutton1 to be executed and clicking on Button 2 causes PROCbutton2 to be executed:
INSTALL @lib$+"WINLIB2"
dlg% = FN_newdialog("Button test", 200, 100, 100, 100, 8, 1000)
PROC_pushbutton(dlg%,"Button 1",FN_setproc(PROCbutton1),20,10,64,16,0)
PROC_pushbutton(dlg%,"Button 2",FN_setproc(PROCbutton2),20,32,64,16,0)
To create buttons on your main window, where clicking on Button 3 causes PROCbutton3 to be executed and clicking on Button 4 causes PROCbutton4 to be executed:
hbutt3% = FN_button("Button 3",300,20,100,24,FN_setproc(PROCbutton3),0)
hbutt4% = FN_button("Button 4",300,90,100,24,FN_setproc(PROCbutton4),0)
If you need to know the values of @wparam% and/or @lparam% (unlikely in the case of menu selections or button presses, but possible with other controls) you can arrange for them to be passed to your procedure by adding a pair of parentheses when you call FN_setproc, as follows:
hedit% = FN_editbox("",300,20,100,24,FN_setproc(PROCeditbox()),0)
You must then define your procedure to receive two parameters:
DEF PROCeditbox(W%, L%)
where W% and L% receive the values of @wparam% and @lparam% respectively.

Sprites

The SPRITELIB library contains a set of procedures and functions for creating and controlling sprites. A sprite is a graphical object which is displayed in front of, and independent of, whatever else is plotted in the BBC BASIC window. A sprite can have any shape and pattern: where it is opaque it will completely obscure what is behind and where it is transparent the normal window contents will show through. You can move a sprite around without having to worry about redrawing the background which is revealed.

The library should be loaded from your program using the command:

INSTALL @lib$+"SPRITELIB"

The functions contained are:

FN_initsprites(nsprites%)

This function initialises the sprite system. It requires one parameter: the maximum number of sprites which will need to be displayed. This parameter should be set as small as possible, since the larger the number of sprites the more the memory used, and the greater the slowing effect on the display.
IF FN_initsprites(2) = 0 STOP
The returned value is TRUE if the sprite system was initialised successfully and FALSE if not. The function will fail if the supplied parameter is zero, or if there is insufficient memory.

FN_createsprite(sprite%,file$,width%,height%)

This function creates a sprite of a given size and with a specified shape and appearance. It requires four parameters: the number of the sprite (which must be between zero and one less than the parameter supplied to FN_initsprites), the name of the file containing the sprite image (and transparency mask), the width of the sprite and the height of the sprite (both in pixels).

The file must be an Icon-format file (usually having the extension .ICO) generated using a suitable icon editor. BBC BASIC for Windows is supplied with a simple icon editor (ICONEDIT.BBC) which will suffice if no other editor is available. Under normal circumstances you should specify the same dimensions in the FN_createsprite call as were used when the icon was created, however if you do not do so the sprite will be scaled to the specified size (with some attendant loss of quality).

ok% = FN_createsprite(0, "bbcmicro.ico", 64, 64)
The returned value is non-zero if the sprite was created successfully, and zero otherwise. The most likely reason for the function to fail is if the file does not exist or is not a suitable icon-format file.

The sprite is not displayed until PROC_movesprite or PROC_updatesprite is executed.

FN_createspritefrombmp(sprite%,bmpfile$)

This function is similar to FN_createsprite except that instead of an icon (.ICO) file being required a bitmap (.BMP) file must be specified, and there are no size parameters (the sprite may be scaled by passing appropriate width and height values to PROC_updatesprite). The function makes it straightforward to create a sprite at run-time, since a suitable BMP file can be generated using the *GSAVE command. The sprite may be any size (within reason) and is not limited to 256 x 256 pixels as would be the case for an icon file.

The BMP file must contain a single bitmap which is twice the height of the sprite, the upper half of which contains the sprite's transparency mask and the lower half of which contains the sprite's image. Here is an example of a suitable bitmap:

BBC Micro sprite

The mask is white where the sprite is fully transparent and black where the sprite is fully opaque (intermediate transparency values are not supported). In areas where the sprite is transparent its image should be black.

PROC_movesprite(sprite%,xpos%,ypos%,show%)

This function displays (or hides) a sprite, and moves it to a specified position on the screen. It requires four parameters: the number of the sprite, the horizontal and vertical coordinates of the sprite (in BBC BASIC graphics units) and a value which determines whether the sprite should be displayed (1) or not (0). The coordinates refer to the top left-hand corner of the sprite (which is consistent with text characters plotted in the VDU 5 mode).
PROC_movesprite(0, 200, 200, 1)
In the case of an animated sprite (.ANI file) the show% parameter specifies the required frame number (+1), so 1 causes the first frame to be displayed, 2 causes the second frame to be displayed and so on.

Sprites are unaffected by the current graphics window (if any) and always display in front of any other graphics or text. Sprites have a predefined priority order such that, if they overlap, a higher-numbered sprite always appears in front of a lower-numbered sprite.

PROC_updatesprite(sprite%,xpos%,ypos%,show%,wide%,high%)

This procedure is similar to PROC_movesprite except that it takes additional wide% and high% parameters. These parameters may be set to the desired width and height of the sprite in pixels, respectively. So for example a sprite could be made to get smaller or larger as it moves.

In addition, by specifying negative values for wide% and/or high% it is possible to reflect the sprite about its horizontal or vertical axis (or both). Note that this feature works only for sprites having a 1-bit mask (not a linear alpha mask) and relies on an undocumented feature of Windows.

PROC_exitsprites

When you have finished with the sprites, you should disable them:
PROC_exitsprites
Because the sprite routines use space on the heap, it is essential that you call PROC_exitsprites before executing a CLEAR, CHAIN or RUN statement. Failure to do so is very likely to crash BBC BASIC for Windows.

Formatting and conversion

BBC BASIC does not include the PRINT USING statement which, in many dialects of BASIC, provides a convenient means of formatting numeric output. Although equivalent effects may be obtained by using the format-control variable @%, if necessary in conjunction with string-manipulation functions (e.g. MID$), they are less straightforward to achieve.

BBC BASIC also lacks the UPPER$ (or UCASE$) and LOWER$ (or LCASE$) functions provided in some dialects of BASIC to convert strings to uppercase (capitals) or lowercase characters respectively.

The FNUSING library provides replacements for these operations. It should be loaded from your program using the command:

INSTALL @lib$+"FNUSING"
Alternatively, since the functions are quite short, you might prefer to incorporate them in your own program (use the Insert command from the File menu).

The functions contained are:

FNusing(format$,value)

The FNusing function takes two parameters, a format string and a numeric value. It will normally be used within a PRINT statement in the following context:
PRINT FNusing(fmt1$,val1) FNusing(fmt2$,val2) .....
A significant difference from the conventional PRINT USING statement is that each format string can only refer to one numeric value, so you must call FNusing for each value you want to output.

The format string is a string literal or variable containing special formatting characters, as follows:

# The hash character is used to represent a digit position. Digit positions are always filled: if the number has fewer digits than positions specified it is right-justified (preceded by spaces) in the field. A decimal point may be inserted at any position in the field and numbers are rounded as necessary. For example:
PRINT FNusing("##.##",.78)
 0.78
PRINT FNusing("###.##",987.654)
987.65
+ A plus sign at the beginning or end of the format field causes the sign of the number (plus or minus) to be printed before or after the number. For example:
PRINT FNusing("+###.##",2.4)
  +2.40
PRINT FNusing("##.##+",55.678)
55.68+
PRINT FNusing("##.##+",-3)
 3.00-
A minus sign at the end of the format field causes negative numbers to be printed with a trailing minus sign. For example:
PRINT FNusing("##.##-",-68.95)
68.95-
PRINT FNusing("###.##-",-7)
  7.00-
** A double asterisk at the beginning of the format field causes leading spaces in the field to be filled with asterisks. The ** also specifies two more digit positions. For example:
PRINT FNusing("**#.#",12.39)
*12.4
PRINT FNusing("**##.##",-0.9)
**-0.90
$$ A double dollar (or pound) sign at the beginning of the format field causes a dollar (or pound) sign to be printed to the immediate left of the formatted number. The $$ also specifies two more digit positions, one of which is the currency symbol. For example:
PRINT FNusing("$$###.##",45.67)
  $45.67
PRINT FNusing("££###.##",123.45)
 £123.45
**$ A **$ (or **£) at the beginning of the format field combines the effects of the previous two formats. Leading spaces are filled with asterisks, and a dollar (or pound) sign is printed before the number. **$ specifies three more digit positions, one of which is the currency symbol. For example:
PRINT FNusing("**$##.##",2.34)
***$2.34
, A comma to the left of the decimal point in the format string causes a comma to be printed between every third digit before the decimal point. For example:
PRINT FNusing("#,###.##",1234.5)
1,234.50
PRINT FNusing("##,###,###",1E6)
 1,000,000
^^^^ Four carets may be placed after the digit characters to specify exponential format. The four carets allow space for "E-xx" to be printed. For example:
PRINT FNusing("##.##^^^^",234.56)
 2.35E2
PRINT FNusing("##.##^^^^",1E-30)
 1.00E-30
If the format string includes any characters other than those listed above, they are incorporated verbatim in the output string. For example:

PRINT FNusing("Price ££#.## including VAT",29.99)
Price £29.99 including VAT
If the number cannot be represented in the format supplied, question marks are printed:
PRINT FNusing("##.##",123)
?????

FNlower(string$)

The FNlower function takes a string parameter and returns a string in which capital letters (A to Z inclusive), if any, have been converted to lowercase (a to z).
PRINT FNlower("The Quick Brown Fox")
the quick brown fox

FNupper(string$)

The FNupper function takes a string parameter and returns a string in which lowercase letters (a to z inclusive), if any, have been converted to capitals (A to Z).
PRINT FNupper("The Quick Brown Fox")
THE QUICK BROWN FOX

Multiple Document Interface

The MDILIB library contains a set of procedures and functions for using the Windows™ Multiple Document Interface (MDI). This allows a program to open multiple output windows, typically so the user can work with more than one document at a time or with more than one type of view. For example a spreadsheet program might use one window for a tabular view of the data and another window for a graphical view.

The library should be loaded from your program using the command:

INSTALL @lib$+"MDILIB"

The functions contained are:

PROC_initmdi(menu%)

This procedure initialises the Multiple Document Interface. It requires one parameter: the handle of the Window top-level menu. Every MDI program must have a Window menu, because the contents of that menu are automatically modified by the operating system as child windows are added and removed. Therefore as a minimum your program should include code similar to the following:
SYS "CreatePopupMenu" TO hwindow%
SYS "CreateMenu" TO hmenu%
SYS "AppendMenu", hmenu%, 16, hwindow%, "&Window"
SYS "SetMenu", @hwnd%, hmenu%
SYS "DrawMenuBar", @hwnd%
PROC_initmdi(hwindow%)
See the section on adding popup and sub-menus for more details. PROC_initmdi must be called just once at the start of your program; once it has been called no output to the screen is possible until you have created one or more child windows (if necessary you can still display information using a message box).

PROC_exitmdi

This procedure shuts down the Multiple Document Interface. It must be called before your program exits and before executing a CLEAR, CHAIN or RUN statement; failure to do so is very likely to crash BBC BASIC for Windows. You are advised to include ON CLOSE and ON ERROR statements in your program so that you can ensure PROC_exitmdi is called as necessary.

FN_createmdichild(name$)

This function creates a child window and gives it the specified name; the name is displayed in the title bar of the child window and in the Window menu. If the child window is maximised it no longer has a title bar, but the name is then automatically appended to the main program's title.

The child window is created with a default size and position, but your program can change that subsequently if necessary. It can be minimised, maximised, re-sized and moved by the user in the usual way, except that the child window is constrained to remain within the confines of your program's main window. You can create as many child windows as you like (within reason!).

FN_createmdichild returns the window handle of the child window it has created:

hwnd1% = FN_createmdichild("Hello world")
hwnd2% = FN_createmdichild("Rectangles")
hwnd3% = FN_createmdichild("Circles")
Once you have created a child window you can send output to it in the same way as you would to BASIC's normal output window, however you must first select the appropriate window by changing the values of @hwnd% and @memhdc%. The easiest way of doing that (and of ensuring they are restored to their original values afterwards) is to pass them as parameters of a procedure. So for example a procedure for writing text to a child window might be:
DEF PROCwritetext(A$, @hwnd%, @memhdc%)
PRINT A$
ENDPROC
To write a text string to a particular window you would call this procedure as follows:
PROCwritetext("Some text", hwnd1%, FN_hdc(hwnd1%))
(see below for a description of FN_hdc)

As it stands this will work fine when writing text to just one window, but if you write text to two or more windows concurrently you will find that all the windows share the same text output position, so sending a 'newline' to one window will affect the subsequent position of text written to another window. One way to provide each child window with its own 'private' text position is to pass the X and Y coordinates to the procedure:

DEF PROCwritetext(A$, @hwnd%, @memhdc%, RETURN @vdu%!48, RETURN @vdu%!52)
PRINT A$
ENDPROC
which you would call as follows:
PROCwritetext("Text for window 1", hwnd1%, FN_hdc(hwnd1%), xpos1%, ypos1%)
PROCwritetext("Text for window 2", hwnd2%, FN_hdc(hwnd2%), xpos2%, ypos2%)
Since they are passed by reference the variables containing the text coordinates for each window are automatically updated. All the other text and graphics parameters (colour, plotting mode, window positions etc.) are similarly shared between the child windows, so you may need to set the parameters appropriate to that window before performing any output.

Note that (in BBC BASIC for Windows) MDI child windows do not display the text cursor (caret). User input, when required, should normally be done via a dialogue box.

FN_hdc(hwnd%)

This function returns the memory hdc belonging to the specified window handle. This is needed when selecting a particular child window for output (it must be copied into @memhdc%).

PROC_closemdichild(hwnd%)

This procedure closes the specified child window. It has the same effect as the user clicking on the window's close button or using the keyboard shortcut Ctrl+f4.

You cannot prevent the user closing a child window (there is no direct equivalent to ON CLOSE) but you can detect that he has closed it using the IsWindow API call:

SYS "IsWindow", hwnd1% TO res%
IF res% = 0 THEN
  REM child window has been closed
  REM take appropriate action if necessary
ENDIF

PROC_tile

This procedure causes Windows™ to tile all the currently-open child windows within your program's output window. It would typically be called in response to the user selecting the Tile item in the Window menu.

PROC_cascade

This procedure causes Windows™ to cascade all the currently-open child windows within your program's output window. It would typically be called in response to the user selecting the Cascade item in the Window menu.

Calendar functions

The DATELIB library contains a set of procedures and functions for performing operations on dates. The library should be loaded from your program using the command:
INSTALL @lib$+"DATELIB"

The functions contained are:

FN_mjd(day%, month%, year%)

This function takes a date (consisting of a day-of-month, a month and a year) and converts it to the corresponding Modified Julian Day number. The Modified Julian Day is a count of days starting from Wednesday 17th November 1858 (which is MJD 0). Days prior to that date have negative MJD numbers. You can easily calculate the number of days between two different dates by subtracting their Modified Julian Day numbers.

The parameters supplied are the day of the month (1-31), the month number (1-12) and the year number (1-9999). Note that the functions in the DATELIB library will behave consistently for any date in that range (for example, converting from DMY to MJD and back will return the original values) but should not normally be used for dates prior to the introduction of the Gregorian calendar (in the UK on Thursday 14th September 1752, MJD −38779). For earlier dates the day, month and year values may not be correct, and since use of the old Julian calendar persisted in some countries until as late as 1927 care should be taken when using this function.

FN_day(mjd%)

This function takes a Modified Julian Day number and returns the day-of-month (1-31) to which it corresponds.

FN_month(mjd%)

This function takes a Modified Julian Day number and returns the month (1-12) to which it corresponds.

FN_year(mjd%)

This function takes a Modified Julian Day number and returns the year (1-9999) to which it corresponds.

FN_dow(mjd%)

This function takes a Modified Julian Day number and returns the day-of-week (0-6, where 0 is Sunday) to which it corresponds.

FN_dim(month%, year%)

This function takes a month (1-12) and a year (1-9999) and returns the number of days in the month (in the range 28 to 31). By setting month% to 2 (February) you can use this function to determine whether the year is a Leap Year.

FN_today

This function returns the Modified Julian Day number corresponding to today's date (assuming the PC's clock is correctly set).

FN_date$(mjd%, format$)

This function takes a Modified Julian Day number and a format string, and returns a formatted string containing the date. The format string can contain any of the following codes:
dDay of month as digits with no leading zero.
ddDay of month as digits with leading zero for single-digit days.
dddDay of week as a three-letter abbreviation.
ddddDay of week as its full name.
MMonth as digits with no leading zero.
MMMonth as digits with leading zero for single-digit months.
MMMMonth as a three-letter abbreviation.
MMMMMonth as its full name.
yYear as last two digits, but with no leading zero.
yyYear as last two digits, but with leading zero for years less than 10.
yyyyYear represented by full four digits.
For example:
date$ = FN_date$(mjd%, "ddd dd MMM yyyy")
will return a string of the form "Sun 22 Feb 2004".

FN_readdate(date$, code$, minyear%)

This function parses a string containing a date, and returns the corresponding Modified Julian Day number. In addition to the date you must supply a string containing one of the following codes: "dmy", "mdy", "ymd", "ydm", "dym" or "myd"; this informs the function of the order in which the various elements of the date (day, month, year) are present in the string. If the year is specified as a four digit number the third parameter is not used; if only the last two digits of the year are specified the third parameter is the minimum year number to return. For example if the third parameter is 1950, two digit year numbers correspond to the years 1950 to 2049 inclusive.

The FN_readdate function attempts to make sense of the date string however it is formatted, so long as the elements are in the specified order. For example it will accept "22/2/2004", "22 Feb 04", "22-02-04" etc. If it cannot make sense of the string it will return the value &80000000.

Direct3D graphics

The D3DLIB (etc.) libraries contain a set of procedures and functions for displaying and animating three-dimensional graphics. They provide an interface to Microsoft's Direct3D™ and require DirectX version 8.0 or later to be installed (version 9.0 or later for D3D9LIB and D3D9LIBA). The required library should be loaded from your program using a command similar to this:
INSTALL @lib$+"D3DLIB"

The functions contained are:

The libraries with an A suffix (D3DLIBA.BBC, D3D9LIBA.BBC) provide an additional parameter to the PROC_render function containing the camera's roll-angle.

FN_initd3d(hw%,cull%,light%)

This function initialises the Direct3D system and returns a pointer to a Direct3D device. If the returned value is zero it probably indicates that DirectX 8.0 or later is not installed.

The value hw% is the handle of the window which is to contain the 3D graphics. It can be set to @hwnd% if the graphics are to be displayed in BBC BASIC's main output window or to the handle of a child window (for example as returned from FN_staticbox) if you want them to appear in a separate frame. Note that you cannot mix Direct3D graphics and normal BBC BASIC output (text or graphics) in the same window.

The value cull% specifies the culling mode, which determines whether surfaces behave as single sided or double sided. Possible values are 1 (none), 2 (clockwise) or 3 (counterclockwise). If in doubt, set to 1.

The value light% determines whether Direct3D's lighting engine is enabled. Set to 1 to enable lighting or to 0 to disable lighting. When lighting is disabled all objects appear normally as if uniformly illuminated. When lighting is enabled it is necessary for all objects to include surface normals in the vertex description.

FN_load3d(pdev%,file$,num%,fmt%,size%)

This function loads an object or scene (comprising a set of triangles, each consisting of three vertices) from a file. It returns a pointer to a vertex buffer; if zero is returned the file could not be opened.

The value pdev% is the pointer returned from FN_initd3d. The value file$ is the name of a file containing vertex data.

The values num%, fmt% and size% are outputs from FN_load3d and are set to the number of vertices, the vertex format and the size in bytes of each vertex respectively.

The file format is as follows:

Number of vertices (4 bytes, LSB first)
Vertex format (2 bytes, LSB first)
Vertex size in bytes (2 bytes, LSB first)
Data for each vertex (see below)

Vertex description

The vertex data must conform to the requirements of Direct3D's flexible vertex format. Each vertex consists of one or more of the following items, in the specified order:
CodeSizeDataComments
&00212XYZ position Always required
&01012Surface normal When lighting used
&0404Diffuse colour When neither texture nor material specified
&0804Specular colour For shiny objects
&1008UV texture coordinates When texture specified
To obtain the vertex format code add together the codes for the items included. To obtain the vertex size add together the sizes of the items included. The XYZ position and surface normal items each consist of three 4-byte floating point numbers (see FN_f4). The diffuse colour and specular colour items each consist of 4-byte colour values (&FFrrggbb). The texture coordinates consist of a pair of 4-byte floating point numbers. The simplest vertex description consists of an XYZ position and a diffuse colour (format &042; size 16 bytes). See FN_f4 for an example of creating a file in this format. Refer to Microsoft documentation for more details.

FN_loadtexture(pdev%,file$)

This function loads a texture map from an image file (BMP, ICO, GIF, JPEG, EMF or WMF format) and returns a pointer to the texture; if zero is returned the file could not be opened or recognised. The filename must begin with a drive letter to distinguish it from a web URL.

The value pdev% is the pointer returned from FN_initd3d. The value file$ is the name of the image file.

The image will be padded to a size of 2^n pixels in both horizontal and vertical directions.

PROC_release(pobj%)

This function releases an object (Direct3D device, vertex buffer or texture) when it is no longer required. It should be used to free the resources used by the object(s), otherwise you may eventually run out of memory.

The value pobj% is the pointer returned from FN_initd3d, FN_load3d or FN_loadtexture.

FN_f4(num)

This function converts a number to a 4-byte (single precision) floating point value. All floating point values used by Direct3D (e.g. within vertex descriptions) are in this format. As an illustration of its use the following code segment creates a file containing a single triangle consisting of three vertices, suitable for being loaded by FN_load3d:
F% = OPENOUT"TRIANGLE.FVF"
PROC4(3):REM 3 vertices
PROC4(&100042):REM vertex size &10 and format &42
PROC4(FN_f4(-1.0)):PROC4(FN_f4(-1.0)):PROC4(FN_f4(1.0)):PROC4(&FF0000FF)
PROC4(FN_f4(1.0)):PROC4(FN_f4(-1.0)):PROC4(FN_f4(1.0)):PROC4(&FF00FF00)
PROC4(FN_f4(0.0)):PROC4(FN_f4(1.0)):PROC4(FN_f4(0.0)):PROC4(&FFFF0000)
CLOSE #F%
DEF PROC4(A%):BPUT#F%,A%:BPUT#F%,A%>>8:BPUT#F%,A%>>16:BPUT#F%,A%>>24:ENDPROC

PROC_render(pdev%,....)

This procedure draws a 2D view of the 3D world to the screen. It takes 23 parameters as follows (24 parameters in the case of D3DLIBA and D3D9LIBA):
pdev% The value returned from FN_initd3d.
bcol% The background colour (&FFrrggbb).
nlight% The number of lights. Set to zero if lighting is not used.
light%() An array of pointers to D3DLIGHT8 structures (see note 1).
nobj% The number of objects (i.e. vertex buffers).
mat%() An array of pointers to D3DMATERIAL8 structures (see note 2).
tex%() An array of texture pointers (e.g. returned from FN_loadtexture).
vbuf%() An array of vertex buffer pointers (e.g. returned from FN_load3d).
vnum%() An array of vertex counts (e.g. returned from FN_load3d).
vfmt%() An array of vertex format codes (e.g. returned from FN_load3d).
vsize%() An array of vertex sizes (e.g. returned from FN_load3d).
yaw() An array of yaw angles (rotations about the Y-axis).
pitch() An array of pitch angles (rotations about the X-axis).
roll() An array of roll angles (rotations about the Z-axis).
X() An array of translations along the X-axis.
Y() An array of translations along the Y-axis.
Z() An array of translations along the Z-axis.
eye() An array eye(0), eye(1), eye(2) holding the XYZ coordinates of the eye or camera.
look() An array look(0), look(1), look(2) holding the XYZ coordinates of a point on the eyeline.
fov The vertical field-of-view in radians (equivalent to the camera's zoom).
ar The aspect ratio of the 3D graphics window (width/height).
zn The distance from the camera to the near plane (objects nearer than this are invisible).
zf The distance from the camera to the far plane (objects further away than this are invisible).
roll (D3DLIBA and D3D9LIBA only) The camera's roll angle (in radians).
Notes:
  1. A Direct3D directional light can be created as follows. Refer to Microsoft documentation for other light types.
    DIM light%(0) 103
    light%(0)!0 = 3 : REM directional light
    light%(0)!4 = FN_f4(1)  : REM red component
    light%(0)!8 = FN_f4(1)  : REM green component
    light%(0)!12 = FN_f4(0) : REM blue component
    light%(0)!64 = FN_f4(0) : REM. X component of direction
    light%(0)!68 = FN_f4(0) : REM. Y component of direction
    light%(0)!72 = FN_f4(1) : REM. Z component of direction
    
  2. A matt Direct3D material can be created as follows. Refer to Microsoft documentation for other material types.
    DIM mat%(0) 67
    mat%(0)!0 = FN_f4(1) : REM red component of colour
    mat%(0)!4 = FN_f4(1) : REM green component of colour
    mat%(0)!8 = FN_f4(1) : REM blue component of colour
    
  3. The arrays of object parameters should contain at least as many elements as the value of nobj%. Unused arrays, for example mat%() or tex%(), should contain zeros (this is the initial state following DIM).

  4. Rotations take place around world axes in the order roll then pitch then yaw.

Plotting angled ellipses

The BBC BASIC for Windows ELLIPSE statement plots only axis-aligned ellipses. The ELLIPSE library contains the procedures PROCellipse and PROCellipsefill which provide the facility to plot an outline or filled ellipse rotated by a specified angle. The library should be loaded from your program using the command:
INSTALL @lib$+"ELLIPSE"
Alternatively, since the procedures are quite short, you might prefer to incorporate them in your own program (use the Insert command from the File menu).

PROCellipse(x,y,a,b,angle)

This procedure plots an outline ellipse centred at graphics coordinates x,y and with radii of length a and b. The fifth parameter specifies that the ellipse should be rotated anticlockwise by angle radians (if the angle is zero the ellipse is plotted with the a axis horizontal, as with the normal ELLIPSE statement).

The ellipse is drawn in the current graphics foreground colour and mode, as specified by GCOL.

PROCellipsefill(x,y,a,b,angle)

This procedure works in an identical fashion to PROCellipse, except that a filled (solid) ellipse is plotted. Again, the ellipse is drawn in the current graphics foreground colour and mode, as specified by GCOL.

Sorting data arrays

The SORTLIB library provides a fast, highly optimised and flexible means of sorting data contained in arrays. SORTLIB will sort byte arrays, integer arrays, floating point arrays and string arrays. The library should be loaded from your program using the command:
INSTALL @lib$+"SORTLIB"
It contains the single function FN_sortinit.

FN_sortinit(dir%,smode%)

Before any sorting operations can be carried out the library must be initialised as follows:
sort% = FN_sortinit(dir%,smode%)
where dir% determines the sorting direction (0 = ascending, 1 = descending) and smode% determines how strings are sorted:

smode%string sort action
0word sort, case sensitive
1word sort, ignore case
2ASCII (character code) sort
&1000string sort, case sensitive
&1001string sort, ignore case

In a word sort the hyphen and apostrophe are treated differently from the other nonalphanumeric symbols, in order to ensure that words such as "coop" and "co-op" stay together within a sorted list. In a string sort, the hyphen and apostrophe are treated like the other symbols. In an ASCII sort the order corresponds to the regular BASIC less-than (<) and greater-than (>) operators.

If you prefer, you can initialise it multiple times with the different options and then CALL the appropriate variable when needed:

sortascendingnormal% = FN_sortinit(0,0)
sortdescendingnormal% = FN_sortinit(1,0)
sortascendingignorecase% = FN_sortinit(0,1)
sortdescendingignorecase% = FN_sortinit(1,1)
To sort the contents of an entire array do the following:
C% = DIM(array(),DIM(array()))+1 
CALL sort%, array(0)
To sort the contents of a '1-based' array (where the first element has the subscript 1) do the following:
C% = DIM(array(),DIM(array())) 
CALL sort%, array(1)
To sort only part of an array, set C% to the number of elements you want to sort and specify the first element to be sorted:
C% = howmany% 
CALL sort%, array(first%)
To sort multiple arrays according to the contents of a key array do the following:
C% = size% 
CALL sort%, keyarray(0), array2$(0), array3%(0)...
There can be any number of dependent arrays of any type. If the primary key array contains two or more identical elements, the remaining array(s) will be used as secondary keys, in the order specified.

To sort a two-dimensional array, where the contents of the first row are used as a key for the other rows, do the following:

DIM array(2,999)
C% = DIM(array(),DIM(array()))+1
CALL sort%, array(0,0), array(1,0), array(2,0)

Socket (network) connections

The SOCKLIB library contains a set of procedures and functions for making socket connections to a remote computer via a local area network or the internet, and transferring data in both directions.

The library should be loaded from your program using the command:

INSTALL @lib$+"SOCKLIB"

The functions contained are:

PROC_initsockets

PROC_initsockets must be called (once) before any other functions in SOCKLIB are accessed.

PROC_exitsockets

PROC_exitsockets must be called after all socket operations have been completed. You should normally incorporate ON ERROR and ON CLOSE statements in your program to ensure that PROC_exitsockets is called even if the program is terminated unexpectedly. If you don't do so your program might not work correctly if executed a second time.

FN_gethostname

FN_gethostname returns, as a string, the network name of your local computer (the one on which BBC BASIC for Windows is running).

FN_tcplisten(host$,port$)

FN_tcplisten creates a listening socket to listen for incoming TCP/IP connections. If your program needs to respond to connections made from a remote computer (a web server would be an example) it must call FN_tcplisten. The function takes two string parameters: the name of the local computer (typically as returned from FN_gethostname) and the port name or number on which you want to listen. For example if you are listening for incoming HTTP (Hyper Text Transport Protocol) connections you would normally set port$ to "80".

If the listening socket is created successfully the socket number is returned. If the call fails a negative number is returned (see the code of SOCKLIB.BBC for details). You can call FN_socketerror to discover more about the error.

FN_tcpconnect(host$,port$)

FN_tcpconnect makes a TCP/IP connection to a remote machine. You would call this function if your program needs to initiate a connection (a program to send email would be an example). The function takes two string parameters: the name or IP address of the remote computer and the port name or number on which to make the connection. For example if you were connecting to an SMTP mail server would would normally set port$ to "25" or "mail". If an IP address rather than a machine name is specified, it must be supplied as a string of the form "xx.xx.xx.xx".

If the connection is made successfully the socket number is returned. If the call fails a negative number is returned (see the code of SOCKLIB.BBC for details). You can call FN_socketerror to discover more about the error.

FN_check_connection(socket%)

FN_check_connection checks to see if an incoming connection has been received by a listening socket; it takes as a parameter the socket number (as returned from FN_tcplisten). If no connection has been received zero is returned, and the listening socket continues to listen for a connection. If an incoming connection has been received, FN_check_connection returns the socket number on which the connection has been opened. Note that if you want to listen for further connections, another listening socket must be opened.

FN_writesocket(socket%,buffer%,size%)

FN_writesocket writes data to a connected socket. It takes three parameters: the socket number (as returned from FN_tcpconnect or FN_check_connection), the address of a buffer containing the data, and the length of the data in bytes. If the function succeeds it returns the number of bytes sent.

Note that it is possible for FN_writesocket to return a value less than the total length of the data. This indicates that only some of the data has been sent, and you should make further calls (adjusting the values of buffer% and size% accordingly) until all the data has been sent.

FN_writelinesocket(socket%,string$)

FN_writelinesocket writes a string, followed by the characters carriage return (CHR$13) and line feed (CHR$10), to a connected socket. It takes two parameters: the socket number (as returned from FN_tcpconnect or FN_check_connection) and the string to be sent. If the function succeeds it returns zero.

FN_readsocket(socket%,buffer%,size%)

FN_readsocket reads data from a connected socket. It takes three parameters: the socket number (as returned from FN_tcpconnect or FN_check_connection), the address of a buffer to receive the data, and the maximum length of the data in bytes (the size of the buffer). If the function succeeds it returns the number of data bytes received. If the socket has been disconnected (e.g. by the remote computer) −1 is returned.

FN_readsocket does not wait for data to be received. If no data has been received since the last call, it returns zero.

FN_readlinesocket(socket%,maxtime%,string$)

FN_readlinesocket reads a line from a connected socket (a line is defined as a string of characters terminated by CRLF, LFCR or LF, where CR signifies carriage return and LF signifies line feed); the terminator is not returned as part of the string. It takes three parameters: the socket number (as returned from FN_tcpconnect or FN_check_connection), a maximum time to wait in centiseconds (e.g. 100 signifies one second) and the name of the string variable in which the line will be returned. If the function succeeds it returns the length of the string in characters. If the socket has been disconnected (e.g. by the remote computer) −1 is returned.

FN_getpeername(socket%)

FN_getpeername returns the IP address of the remote machine to which a socket is connected, as a string of the form "xx.xx.xx.xx". It takes as a parameter the socket number (as returned from FN_tcpconnect or FN_check_connection). If the function fails an empty string is returned.

PROC_closesocket(socket%)

PROC_closesocket closes a socket connection. It takes as a parameter the socket number (as returned from FN_tcpconnect or FN_check_connection).

FN_socketerror

FN_socketerror returns the number of the most recent socket error to have occurred (if any). If FN_tcpconnect or FN_tcplisten returns a negative number, you can call FN_socketerror to discover more details of the error.

The error code numbers can be found in the Microsoft™ file WINERROR.H.

Antialiased graphics

The built-in BBC BASIC graphics statements, and graphics drawn using the Windows™ Graphics Device Interface (GDI), are not antialiased. As a result things like curves and gently-sloping lines can have aliases (or jaggies) causing the appearance to be impaired.

The GDIPLIB library contains a set of procedures and functions for drawing antialiased graphics. It relies upon the presence of the Microsoft GDI+ graphics subsystem, which is installed as standard only with Windows XP (or later). However it is available for earlier versions of Windows (98 onwards) in the form of a redistributable file which can be downloaded from Microsoft's site (search for GDIPLUS.DLL).

The library should be loaded from your program using the command:

INSTALL @lib$+"GDIPLIB"

The functions contained are:

PROC_gdipinit

PROC_gdipinit must be called before any of the other functions in GDIPLIB are used.

PROC_gdipexit

PROC_gdipexit must be called after the GDIPLIB library has been finished with.

FN_gdipcreatepen(colour%, style%, width)

FN_gdipcreatepen is used to create a pen for drawing lines. It takes three parameters: the colour of the pen, the style of the pen and the width of the pen. It returns a value corresponding to the created pen.

The colour should be supplied as an integer equivalent to the hexadecimal value &AARRGGBB where AA is the alpha channel or opacity (00 = transparent, FF = opaque), RR is the red component (00 = no red, FF = maximum red), GG is the green component and BB is the blue component.

The style can be a combination of one or more of the following values:

LineEndFlat = 0
LineEndSquare = 1
LineEndRound = 2
LineEndTriangle = 3
LineEndSquareAnchor = &11
LineEndRoundAnchor = &12
LineEndDiamond = &13
LineEndArrow = &14
LineStartSquare = &100
LineStartRound = &200
LineStartTriangle = &300
LineStartSquareAnchor = &1100
LineStartRoundAnchor = &1200
LineStartDiamond = &1300
LineStartArrow = &1400
LineDash = &10000
LineDot  = &20000
LineDashDot = &30000
LineDashDotDot = &40000
The width is the width of the pen in pixels. Note that this need not be an integer.

FN_gdipcreatebrush(colour%)

FN_gdipcreatebrush is used to create a brush for filling solid shapes. It takes one parameter, the colour of the brush (in the same format as for FN_gdipcreatepen above), and returns a value corresponding to the created brush.

PROC_gdipdeletepen(pen%)

PROC_gdipdeletepen deletes a previously created pen. It takes one parameter: the value returned by FN_gdipcreatepen. Pens must be deleted after use.

PROC_gdipdeletebrush(brush%)

PROC_gdipdeletebrush deletes a previously created brush. It takes one parameter: the value returned by FN_gdipcreatebrush. Brushes must be deleted after use.

PROC_gdipline(pen%, x1, y1, x2, y2)

PROC_gdipline draws a straight line between two points. It takes five parameters: the value returned by FN_gdipcreatepen, the x and y coordinates of the start of the line and the x and y coordinates of the end of the line (in BBC BASIC graphics units). Note that the coordinates do not need to be integers.

PROC_gdippolyline(pen%, points%, x(), y())

PROC_gdippolyline draws a set of straight line segments between specified points. It takes four parameters: the value returned by FN_gdipcreatepen, the number of points, an array containing the x-coordinates of the points and an array containing the y-coordinates of the points (in BBC BASIC graphics units). Note that the arrays must not be integer arrays, and that the first point corresponds to subscript zero. If you are drawing a closed polygon the number of points should be one more than the number of vertices, with the first and last points coinciding.

PROC_gdipbezier(pen%, x1, y1, x2, y2, x3, y3, x4, y4)

PROC_gdipbezier draws a Bezier curve. It takes nine parameters: the value returned by FN_gdipcreatepen and the x and y coordinates of four control points (in BBC BASIC graphics units). Note that the coordinates do not need to be integers.

PROC_gdippolybezier(pen%, points%, x(), y())

PROC_gdippolybezier draws a set of Bezier curves. It takes four parameters: the value returned by FN_gdipcreatepen, the number of control points (>=4), an array containing the x-coordinates of the points and an array containing the y-coordinates of the points (in BBC BASIC graphics units). Note that the arrays must not be integer arrays, and that the first point corresponds to subscript zero.

You can draw an angled ellipse (or at least a very close approximation to it) by using 13 control points, as below. The coordinates x,y are the centre of the ellipse, a,b are the major and minor radii respectively and t is the angle of rotation (radians):

DEF PROCgdipellipse(pen%, x, y, a, b, t)
LOCAL c, d, o, p, q, r, x(), y()
DIM x(12), y(12)
c=a*SIN(t) : a=a*COS(t)
d=b*SIN(t) : b=b*COS(t)
o=a*0.552285 : q=c*0.552285
p=b*0.552285 : r=d*0.552285
x() = x-a,x-a-r,x-o-d,x-d,x+o-d,x+a-r,x+a,x+a+r,x+o+d,x+d,x-o+d,x-a+r,x-a
y() = y+c,y-p+c,y-b+q,y-b,y-b-q,y-p-c,y-c,y+p-c,y+b-q,y+b,y+b+q,y+p+c,y+c
PROC_gdippolybezier(pen%, 13, x(), y())
ENDPROC

PROC_gdiparc(pen%, xc, yc, xr, yr, as, ad)

PROC_gdiparc draws an outline (axis-aligned) elliptical arc. It takes seven parameters: the value returned by FN_gdipcreatepen, the x and y coordinates of the centre of the ellipse (in BBC BASIC graphics units), the x and y radii, the start angle (degrees, clockwise from the x-axis) and the length of the arc (degrees, clockwise).

PROC_gdipsector(brush%, xc, yc, xr, yr, as, ad)

PROC_gdipsector draws a filled (axis-aligned) elliptical sector. It takes seven parameters: the value returned by FN_gdipcreatebrush, the x and y coordinates of the centre of the ellipse (in BBC BASIC graphics units), the x and y radii, the start angle (degrees, clockwise from the x-axis) and the size of the sector (degrees, clockwise).

PROC_gdippolygon(brush%, vertices%, x(), y(), fmode%)

PROC_gdippolygon draws a filled polygon. It takes five parameters: the value returned by FN_gdipcreatebrush, the number of vertices, an array containing the x-coordinates of the vertices, an array containing the y-coordinates of the vertices (in BBC BASIC graphics units) and the fill mode. Note that the arrays must not be integer arrays, and that the first vertex corresponds to subscript zero.

The fill mode should be 0 for alternate and 1 for winding. The images below illustrate the effect of the different modes on a five-pointed star:

Alternate Winding
AlternateWinding

COM automation

The COMLIB library contains a set of procedures and functions for controlling applications via the COM (Component Object Model) interface, otherwise known as COM Automation or ActiveX. This is a method for co-operation and data sharing between applications, standardised by Microsoft, which allows the manipulation of other programs through a defined interface.

The library should be loaded from your program using the command:

INSTALL @lib$+"COMLIB"
The functions contained are:

PROC_cominit

This procedure initializes the library. It must be called just once, typically in the initialisation phase of your program, before you use any of the other functions in COMLIB.

PROC_cominitlcid(lcid%)

This procedure works the same as PROC_cominit except that it takes as a parameter a Locale Identifier. By specifying an appropriate value you may be able to overcome problems caused by regional variations. For example commands to Microsoft Office programs must normally be in the user's local language, but by specifying the correct Locale Identifier you can force them to accept commands in English.

Some common Locale Identifier values are as follows:

LCIDLanguage
1031German
1032Greek
1033English
1034Spanish
1036French
1037Hebrew
1040Italian
1043Dutch

PROC_comexit

You should call this procedure just once when you have finished with the COM library, typically on exit from your program. It does not free the memory used by objects created with FN_createobject; these must be explicitly released using PROC_releaseobject before calling PROC_comexit.

FN_createobject(ProgID$)

This function is the fundamental starting point of a COM operation and returns a reference to a created object. It takes as a parameter either the name of the object or a class ID; for most programs that support COM this will be the "Application" object. These programs contain standalone COM servers. Here are some common ones:

Program Name ProgID
Microsoft Access Access.Application
Microsoft Excel Excel.Application
Microsoft Outlook Outlook.Application
Microsoft PowerPoint PowerPoint.Application
Microsoft Word Word.Application
Internet Explorer InternetExplorer.Application
Text to Speech Sapi.SpVoice

For example you would create an instance of the Excel application (assuming it is installed on your PC) as follows:

xlapp% = FN_createobject("Excel.Application")
You use the value returned in xlapp% for all subsequent references to that object. When you have finished with the object you should call PROC_releaseobject.

Note that you may not immediately be able to see Excel (etc.) running, but if you look at the "Processes" tab in Windows Task Manager you will see that it is there.

FN_getobject(parentref%,"child.grandchild")

Objects can, and usually do, contain other objects in a hierarchical style; that is the essence of the COM model. If you are familiar with Visual Basic for Applications, the macro language for Microsoft Office, then you will know that Excel contains Workbook objects, Word contains Document objects etc. The hierarchy is denoted by a dot operator. You can obtain an explicit reference to these subordinate objects with FN_getobject:
mychild% = FN_getobject(parent%,"name_of_child(1)")
mysheet% = FN_getobject(xlapp%,"Workbooks(1).Worksheet(1)")
myrange% = FN_getobject(mysheet%,"Range(""D1"")")
The value returned is a pointer or handle to the object and must be stored for future use. It may refer to a single object or a collection of objects. To specify a particular item in a collection you need to call it by name or its item number in the collection.

The first parameter of FN_getobject may be the Application object or some other child object that you already have a handle to; this is the starting point to look up the named object that you give as the second parameter. The problem that you will most likely have is knowing what objects exist! For this you will need the application's documentation of its COM hierarchy, or an Object Viewer. See Object Browser for more details.

PROC_releaseobject(objref%)

This procedure is used to release an object's resources (memory etc.) back to Windows:
PROC_releaseobject(xlapp%)
This must be called after the object is no longer required and before your program exits. The parameter is the object reference or handle returned by FN_createobject. Objects obtained using FN_getobject do not need to be explicitly released as they will be released when their parent object is released. However, you may prefer to release all objects to be on the safe side.

It is important that objects be released whenever your program exits, even unexpectly as the result of an error or the user clicking Close. You should therefore ensure that appropriate calls to PROC_releaseobject are made from your program's ON CLOSE and ON ERROR routines.

PROC_putvalue(objref%,"Property(value)")

This procedure writes a value to one of the object's properties. PROC_putvalue is one of the prime procedures for manipulating the remote object. You need to define exactly which object you want to access and what property you want to change. See Objects, Methods and Properties for more detail. If the property name does not exist or the value is not of the correct data type, or not in the correct range, the call will fail.

PROC_putvalue takes two parameters. Firstly a handle to the object whose property you wish to change, or a handle to a parent object. Secondly a reference to the property and value; this may inclue the path down the object tree:

PROC_putvalue(xlapp%,"Visible(BTRUE)")
PROC_putvalue(xlapp%,"ActiveBook.Activesheet.cell(1,1).value(""Some text"")")
The value being 'put', such as the Boolean BTRUE or the string "Some text" must be enclosed in parentheses. The type or format of the data has to be correct for the call to succeed. Although many applications will accept several data types it is up to the user to ensure that only sensibly typed data reach the application. Packaging the data to a format and syntax that the application will accept can present a real challenge! There are two main aspects:

Setting the correct data type

The 'put' data can be in many forms; some are native to BBC BASIC for Windows and some are not. The data type is forced by putting a prefix character in front of the value:

Data type

Prefix Examples
Long Integer (4 bytes) None cell(1,1).value(3)
value(mydata%)
Short Integer (2 bytes) S value(S 525)
Unsigned Integer (4 bytes) U value(U 123456)
Double Floating Point (8 bytes) F value(F 3.66)
cell(1,1).value(F myfloat#)
Single Floating Point (4 bytes) G value(G myfloat)
value(G 2.67777)
Boolean (2 bytes) B Visible(B TRUE)
Saved(B FALSE)
Object reference (4 bytes) O ChartWizard(O oResizeRange%, Nul, , xlColumns%)
Null (no data content) N BorderAround(Nul,4,7)
String " value("""+a$+""")
worksheet(""Sheet 1"")

If the prefix is omitted COMLIB assumes a floating-point numeric value if a decimal point is present and an integer numeric value if not. This works well with constants but if a variable name is passed omitting the prefix will cause it to be sent as an integer, potentially resulting in truncation (conversely a structure member will be sent as a floating point value, because of the presence of the dots!).

It should be pointed out that all the prefixes present a restriction in that there could be confusion with variable names that start with capitals B, F, G, N, O, S and U. If for instance you had used variables North% and South% as parameters then this would fail:

PROC_callmethod(objref%,"child.some_method(North%,South%)")
The first variable North% would be ignored (a Null was assumed) and the second would look for a non-existent variable named outh%! This requires the user to choose variable names that do not conflict, in much the same way as you have to avoid variable names starting with BASIC keywords.

Note that in any case the passing of named variables is not compatible with compiling the program with the Abbreviate names crunch option, unless you use the REM!Keep compiler directive. It may therefore be better to convert the variables' values to strings using STR$:

PROC_callmethod(objref%,"child.some_method("+STR$North%+","+STR$South%+")")

Formatting string data

The writing of strings can be confusing. Strings must be enclosed within quotation marks, which you can represent in your program either as CHR$34 or using the escape convention "". So to name an Excel worksheet as My Sheet you could do either of the following:
PROC_putvalue(Xlbook%,"WorkSheets(1).Name(""My Sheet"")")
PROC_putvalue(Xlbook%,"WorkSheets(1).Name("+CHR$34+"My Sheet"+CHR$34+")")
If the property value itself includes quotation marks they must each be represented in your program as CHR$34+CHR$34 or """". So for example to set an Excel formula to the value =A2 &" "& B2 you could do:
PROC_putvalue(oRng%,"Formula(""=A2 &"""" """"& B2"")")
or
quote$=CHR$34+CHR$34
PROC_putvalue(oRng%,"Formula(""=A2 &"+quote$+" "+quote$+"& B2"")")

PROC_callmethod(objref%,"methodname(param1,param2,...)")

This procedure actions an object method. Whenever you need to have the remote application do something you call PROC_callmethod with the required parameter list. Methods are things like Add, Open, Quit, Select and Sort; the names of methods are often verbs. Many methods have required parameters and some have optional parameters. To let the application know which parameters you are supplying you must provide the full list in the correct sequence, placing dummy parameters, nulls or commas for the missing optional items up to the point where you have your last parameter (any optional parameters after your last set value can be ignored).

For example Addshape is a method which takes one integer and four floating point values:

PROC_callmethod(oWB%,"ActiveSheet.Shapes.AddShape(17, 466.5, 40.5, 95.5, 120.1)"
PROC_callmethod(oChart%,"ChartWizard(O oResizeRange%, xl3DColumn%, , xlColumns%)")
In this last example we have one parameter that was not defined and was optional. In such cases the application will use its default value. This can also be filled with anything beginning with the capital N such as Null or simply left empty. In each case a null variant is sent to the application.The commas are counted to determine that it was a parameter:
PROC_callmethod(myrange%,"BorderAround(Null,4,7)")
You may have noticed that the earlier example used 'O oResizeRange%' as a parameter. The prefix O defines the value as an Object or Collections reference. In this case you have passed an object reference as a parameter just as you might pass an array in BBC BASIC.

FN_getvalueint(objref%,"property.value")

This function retrieves an integer property value from the object. It will return an integer, if at all possible, no matter what type was being held in the application. Thus if the property is actually a date or a string you will receive no useful information and no warning.
R% = FN_getvalueint(oWB%,"ActiveSheet.cells(8,2).value")
R% = FN_getvalueint(xlapp%,"Visible")
In the first instance the application's internal format may well have been floating point, in which case FN_getvalueint will round it to an integer. In the second instance the data type is Boolean but it is translated into integer with the standard values of 0 for FALSE and -1 for TRUE.

FN_getvaluefloat(objref%,"property.value")

This function retrieves a floating point property value:
n = FN_getvaluefloat(oWB%,"ActiveSheet.cells(8,2).value")
If the application's internal data format is not floating point it will be converted to a floating point value if possible.

FN_getvaluestr(objref%,"property.value")

This function retrieves a property value as a string, and since nearly all data types can be expressed that way it is very reliable:
a$ = FN_getvaluestr(oWB%,"ActiveSheet.cells(8,2).value")
The string a$ will contain the textual version of the data, be it a number, date, string or a formula.

FN_getvariant(objref%,"property.value",_ret{})

This function gets a variant type and returns an integer 0 if successful or an error code if not. The function is intended to retrieve nearly any data type, and thus allow you to get to the raw bytes returned in the structure passed by reference. The structure _ret{} is defined as:
DIM _ret{vartype%,ldata%,hdata%,text$,data$}
The member _ret.vartype% will contain the variant type. The low and high data words are returned in _ret.ldata% and _ret.hdata% respectively. A string describing the variant type is returned in _ret.text$ and the data expressed as a string is returned in _ret.data$. This function is useful if the data type to be returned is unknown. Examination of _ret.vartype% will allow correct interpretation of the resulting data, or allow your program to choose which FN_getvaluexxx to use.

When a string data type is returned you should use the 8-bit ANSI version at _ret.data$. The 16-bit Unicode string, which you might expect to find at the address returned in _ret.ldata% cannot be accessed because COMLIB frees the memory it occupied.

FN_coerce(var{},vartype%)

This function takes variant var{} and converts it to the type specified by vartype%. The structure is defined in variant format. Whilst it is mainly for internal use within COMLIB it is of such versatility for type conversion it is documented here. To use outside the COM automation setting you still need to call PROC_cominit before use and PROC_comexit afterwards to allow access to the required Windows library files.

If the conversion succeeds FN_coerce returns zero, otherwise it returns an error code.

COM hierarchy and the Object Browser

All Microsoft programs that support automation have a VBA Editor window. This is activated by pressing Alt+F11. With this window open press function key F2 to open the Object Browser. From here you can look up the available COM objects exposed, along with methods, properties and constants. The Help files will take you to the VBA help and to the methods and properties in the COM hierarchy for the application. You will need to understand what the icons mean in the browser window.

Objects, Methods and Properties

What is an Object? In software terms an object is a piece of portable reusable code that has a defined interface.

An object has two types of thing you can do to it. First it has Methods which are actions that do things with it, and secondly it has Properties which are the way it looks or what information it contains. Objects can encompass other objects. The overall object, as far as Office automation is concerned, is the Application object. This is a COM server, and your BBC BASIC program acts as the client. This Application object will contain other smaller objects such as workbooks or documents or task lists. These objects have Collections of other objects such as words or paragraphs or cells. Finally you get down to the smallest parts that only have methods and properties. Before you can manipulate these methods or change the properties you have to access them.

To access an individual property or method you have to spell out exactly which one you want. Starting from the application object we refer to the property by listing all the steps to get there, for example:

Application.workbook(1).worksheet(2).cells(1,3).value(23.6)
The Object that you create is called an instance of the class of objects. You can run three or five instances of Word.Application. Each one is a separate copy of the software stored in memory with its own workspace. These instances stay until you destroy them.

What is a property and what is a method? Methods do things, they take actions and the order in which you activate methods is important. Common methods include Open, Add, Sort, Quit, Save. Methods can create objects, as in the case of Add: Add items, Add documents, Add worksheets. Properties change informational states, and it is not important what order you do this in. Properties include things like paragraph settings, font size, font colour, cell values. There are a lot more properties than methods and you will need good documentation or an Object Browser to find them all.

When we have many similar objects we refer to them as Collections. There is often a plural in the name. Shapes is a collection of Shape objects. Cells is a collection of range objects. You can refer to the individual Shape by number Shape(1) or it may have a name as in Shape("Triangle"). So how do you know how many there are and what names they have? Well collections always have a property called Count which is the number in the collection. Then you can use a FOR loop to go through all the Shapes asking for their name:

n% = FN_getvalueint(myobj%,"ActiveDocument.Shapes.Count")
FOR i% = 1 TO n%
  a$ = FN_getvaluestr(myobj%,"ActiveDocument.Shape("+STR$i%+").name")
  PRINT a$
NEXT i%
Collections are actually arrays of objects, and like any array you have to make sure you keep track of what is in what element and make sure that you don't go out of range. If you call a method and specify a collection as the object don't be surprised when every member of the collection gets acted upon.

Variants and variant format

Variants are used in COM processes to allow flexibility of data types. To do this the variant has to contain both the data and some indications of the data type. The structure has a length of 16 bytes. The official definition is 2 bytes to define the data type, 6 bytes undefined and 8 bytes of data, but we can use the following for simplicity:
DIM var{t%,r%,l%,h%}
Here var. t% AND &FFFF is the vartype, var.l% and var.h% are the data bytes. The member var.r% is reserved and not used. Integer type data and object references are placed in var.l%. Floating point (8 byte) format, currency and date use both var.l% and var.h%. When a string is contained then a pointer to the string is held in var.l%. The string is in Unicode (16-bit character) format and cannot be used directly by BBC BASIC.

String manipulation

The STRINGLIB library contains several functions for manipulating or creating character strings. It should be loaded from your program as follows:
INSTALL @lib$+"STRINGLIB"
The functions contained are:

FN_lower(A$)

FN_lower converts a string to lower case. Unlike the similar function in the FNUSING library, account is taken of the current language setting.
PRINT FN_lower("The Quick Brown Fox")
the quick brown fox

FN_upper(A$)

FN_upper converts a string to upper case (capitals). Unlike the similar function in the FNUSING library, account is taken of the current language setting.
PRINT FN_upper("The Quick Brown Fox")
THE QUICK BROWN FOX

FN_title(A$)

FN_title converts a string to title case (the first letter of each word is converted to a capital). Account is taken of the current language setting.
PRINT FN_title("the quick brown fox")
The Quick Brown Fox

FN_binary(N%)

FN_binary converts the supplied integer parameter to a binary string.
PRINT FN_binary(22)
10110

FN_tobase(num%, base%, min%)

FN_tobase converts the integer value num% to a string in number base base% with leading zeroes added (if necessary) so that the total number of characters is at least min%.
PRINT FN_tobase(22, 8, 4)
0026

FN_findreplace(text$, old$, new$, start%)

FN_findreplace searches the string text$ for occurrences of the substring old$, and if found replaces them with the string new$, optionally specifying where in the original string the search should start. The parameter text$ is modified, so it must be a variable. The returned value is the number of replacements made.
text$ = "The quick brown fox"
num% = FN_findreplace(text$, "brown", "silver", 0)
PRINT text$
The quick silver fox

FN_findreplacei(text$, old$, new$, start%)

FN_findreplacei works the same as FN_findreplace except that a case-insensitive comparison is made.
text$ = "The quick brown fox"
num% = FN_findreplacei(text$, "BROWN", "silver", 0)
PRINT text$
The quick silver fox

FN_instrr(A$, B$, S%)

FN_instrr works like the INSTR function except that the position of the last rather than the first match is returned (effectively the search takes place in reverse, starting at the end of the string). The third parameter specifies where to start the search (set it to zero to search the entire string).
PRINT FN_instrr("the quick brown fox", "o", 0)
18

FN_instri(A$, B$, S%)

FN_instri works like the INSTR function except that a case-insensitive comparison is made.
PRINT FN_instri("the quick brown fox", "O", 0)
13

FN_instrri(A$, B$, S%)

FN_instrri works like FN_instrr except that a case-insensitive comparison is made.
PRINT FN_instrri("the quick brown fox", "O", 0)
18

FN_trim(A$)

FN_trim removes leading and trailing spaces from the supplied string.
PRINT """" FN_trim("   The quick brown fox   ") """"
"The quick brown fox"

FN_split(text$, delim$, array$())

FN_split splits a string into parts at the specified delimiter, and stores the parts in the specified array. The value returned is the total number of parts stored in the array (if the string does not contain the specified delimiter, the returned value is 1 and the entire string is copied into element zero of the array). If the array doesn't already exist it is created. If the array exists, but it is not large enough, the original array is discarded (the memory it occupied is leaked) and a new array created.
parts% = FN_split("The quick brown fox", " ", a$())
PRINT a$(1)
quick

FN_join(array$(), delim$, num%)

FN_join reverses the effect of FN_split. It returns a string consisting of the first num% elements of the supplied array (starting at element zero), separated by the supplied delimiter.
parts% = FN_split("The quick brown fox", " ", a$())
PRINT FN_join(a$(), "+", parts%)
The+quick+brown+fox

Multiple output windows

The MULTIWIN library provides the capability of having two or more largely independent output windows, each capable of receiving normal BBC BASIC text and graphics output. The library should be loaded from your program as follows (the library calls FN_createwindow in WINLIB5A so that must be installed as well):
INSTALL @lib$+"WINLIB5A"
INSTALL @lib$+"MULTIWIN"
The functions contained are: There are a few minor restrictions in its use:
  1. The REFRESH command is disabled.
  2. The text cursor (caret) can only be shown in the main window.
  3. The output windows share the same colour palette.
  4. The @ox% and @oy% system variables cannot be used.

PROC_multiwin(extra%)

PROC_multiwin sets up the multiple-window capability and should be called just once during the initialisation phase of your program. It takes one parameter, which is the maximum number of extra output windows required (i.e. in addition to the normal main window).

FN_createwin(n%, text$, x%, y%, w%, h%, i%, s%, e%)

FN_createwin creates a new output window. The parameters are as follows: n% is the window number (between 1 and the value specified in the call to PROC_multiwin), text$ is the window title, x% is the horizontal position of the window, y% is the vertical position of the window, w% is the width of the window, h% is the height of the window, i% is an ID number, s% is the window style and e% is the extended window style.

Normally i% and e% can be set to zero, and s% can be set to &96C00000 which is the numeric equivalent of WS_POPUP + WS_VISIBLE + WS_CLIPSIBLINGS + WS_CLIPCHILDREN + WS_CAPTION. If you prefer the window to be constrained within the bounds of its parent, use WS_CHILD instead of WS_POPUP (&56C00000).

The function returns the handle of the created window.

PROC_selectwin(win%)

PROC_selectwin selects a window to be the active output window; it takes one parameter, the window number (either 0, to select the main window, or between 1 and the value specified in the call to PROC_multiwin).

After calling PROC_selectwin all text and graphics output goes to the selected window.

PROC_closewin(win%)

PROC_closewin closes one of the extra windows. It takes one parameter, the window number (between 1 and the value specified in the call to PROC_multiwin). Each call to FN_createwin should be paired with a call to PROC_closewin and you should ensure that your program contains appropriate ON ERROR and ON CLOSE statements to guarantee that the additional windows are closed before exit.

You cannot close the currently-selected output window; if necessary precede the statement with a PROC_selectwin(0).

No-wait function replacements

The NOWAIT library provides replacements for the GET/GET$ and INKEY/INKEY$ functions, and for the WAIT, SOUND and INPUT statements which, unlike the native versions, don't block or stall the processing of asynchronous events such as ON SYS and ON TIME interrupts. The library should be loaded from your program as follows:
INSTALL @lib$+"NOWAIT"
The functions contained are: These replacements are particularly useful if your program uses the CALLBACK, SUBCLASS or TIMERLIB libraries, or for other reasons needs to guarantee a timely response to interrupts.

FNget and FNget$

FNget and FNget$ directly replace the GET and GET$ functions.

FNinkey(t%) and FNinkey$(t%)

FNinkey and FNinkey$ directly replace the INKEY and INKEY$ functions when used with a positive parameter (the maximum time to wait in centiseconds).

PROCwait(t%)

PROCwait directly replaces the WAIT statement. It takes a single parameter, being the approximate time to wait in centiseconds.

PROCsound(c%, a%, p%, d%)

PROCsound directly replaces the SOUND statement. It takes four parameters: channel/effects, amplitude/envelope, pitch and duration.

FNinput

FNinput provides a partial replacement for the INPUT statement; it returns a string containing the user's response. If you need to display a prompt string, do so using PRINT before calling FNinput. If you need to input a numeric value rather than a string, use VAL to perform the conversion.

Callback routines

Some API functions, in Windows™ or third-party DLLs, require you to specify a callback routine: a subroutine in your program which is called by the external code. The CALLBACK library makes it possible to write such routines in BASIC rather than in assembler code. The library should be loaded from your program as follows:
INSTALL @lib$+"CALLBACK"
The functions contained are: The callback address which you need to pass to the API should be obtained as follows:
address% = FN_callback(FNcbroutine(), npar%)
Here FNcbroutine is the name of the callback function in your program and npar% is the number of parameters it takes (this will be specified in the description of the API function).

The callback routine itself needs to be of the following form:

DEF FNcbroutine(par1%, par2%, ....)
REM Do whatever is necessary here
= return_value%
When using callbacks you must be careful to avoid the use of blocking operations in your program, that is functions which are time-consuming or stall interrupt (event) processing, such as INPUT, GET, SOUND and WAIT (with a non-zero parameter). If required the NOWAIT library provides non-blocking replacements for these.

Not uncommonly the callback will be made from within an API function itself and in this case it is necessary to replace the conventional form of the SYS statement:

SYS "FunctionName", parameters TO result%
with the following non-blocking code:
SYS FN_syscalls("FunctionName"), parameters TO !FN_systo(result%)
Note the exclamation mark! You must call FN_systo even if you don't need the value returned by the API function. In that case simply assign the result to a dummy variable. An alternative syntax, compatible with earlier versions of the CALLBACK library, is as follows:
SYS FN_syscalls("FunctionName"), parameters
result% = FN_sysresult
In the event that you need to call the API function by address rather than by name use the following code:
SYS FN_syscalln(FunctionAddr%), parameters TO !FN_systo(result%)
In the event that one of the parameters needs to be a string, add an explicit NUL-termination:
temp$ = parm$ + CHR$(0)
SYS FN_syscalls("FunctionName"), temp$ TO !FN_systo(result%)
For some examples of the use of the CALLBACK library see this Wiki article.

Window subclassing

Subclassing is an advanced technique and a detailed explanation is outside the scope of this documentation; for more information see this Microsoft article. The SUBCLASS library makes it possible to carry out subclassing in BASIC rather than by writing assembler code. The library should be loaded from your program as follows:
INSTALL @lib$+"SUBCLASS"
The functions contained are: When using the SUBCLASS library your program must avoid statements that are time-consuming or which stall interrupt (event) processing, in particular the GET/GET$ and INKEY/INKEY$ functions, and the WAIT, SOUND and INPUT statements. You should use the replacement functions in the NOWAIT library to achieve this. Some Windows messages, for example WM_NOTIFY, cannot be subclassed using this technique.

PROC_subclass(WM_xxxx, FNhandler())

PROC_subclass initiates subclassing of a Windows message. It takes two parameters: WM_xxxx is the message number and FNhandler() is the name of the function in your program which will handle that message. The handler must be defined as follows:
DEF FNhandler(msg%, wparam%, lparam%)
REM Do the necessary message processing here
= return_value%
You can subclass multiple messages by calling PROC_subclass for each one.

PROC_subclassdlg(dlg%, WM_xxxx, FNhandler())

PROC_subclassdlg is similar to PROC_subclass except that it allows you to subclass messages sent to a dialogue box rather than to the main window. It takes an additional first parameter dlg% which is the value returned from FN_newdialog. You must call PROC_subclassdlg before you call PROC_showdialog.

PROC_subclasswin(hwnd%, WM_xxxx, FNhandler())

PROC_subclasswin is similar to PROC_subclass except that it allows you to subclass messages sent to a window other than the main output window (for example a window created using FN_createwin). It takes an additional first parameter hwnd% which is the handle of the window you want to subclass.

PROC_unsubclass

PROC_unsubclass cancels subclassing of the main window (if multiple messages are being subclassed, they are all cancelled). Optionally you may specify the handle of a window being subclassed, i.e. PROC_unsubclass(hwnd%). There is no equivalent for dialogue boxes.

High speed timers

The standard ON TIME timers have two disadvantages: firstly their minimum period is 10 milliseconds (a maximum rate of 100 Hz) and secondly they must share the same interrupt service routine. These limitations may be overcome by using the TIMERLIB library. The library should be loaded from your program as follows:
INSTALL @lib$+"TIMERLIB"
The functions contained are: Since the timer interrupts available using this library may occur at rates up to 1000 Hz, the problem of stalling them is particularly severe. To ensure a timely response to the interrupts, and reduce the likelihood of flooding the event queue, you should avoid using any time-consuming statements and functions such as INKEY, INPUT, GET, SOUND or WAIT. If necessary you can make use of the replacement routines in the NOWAIT library.

Also, pay particular attention to the precautions described under Notes on the use of interrupts.

FN_ontimer(period%, PROCcallback, flags%)

FN_ontimer sets up a timer. It takes three parameters: period% is the required period (or delay) in milliseconds, PROCcallback is the name of the procedure to be called with the specified periodicity (or after the specified delay) and flags% is 0 for a single-shot (delay) timer and 1 for a periodic timer.

The function returns an identifier for use in a subsequent PROC_killtimer, or zero if the timer could not be created.

PROC_killtimer(timerid%)

PROC_killtimer cancels the timer whose identifier is passed as a parameter. It is important to cancel all periodic timers before quitting your program. You should normally incorporate ON CLOSE and ON ERROR statements to ensure this happens however the program is terminated.

Parsing XML files

XML (Extensible Markup Language) files are increasingly commonly used, for example they are the format in which RSS feeds are delivered. The XMLLIB library provides a number of functions to simplify the parsing of XML files. The library should be loaded from your program as follows:
INSTALL @lib$+"XMLLIB"
The functions contained are:

PROC_initXML(xmlobj{}, xmlfile$)

PROC_initXML initialises the parser and opens the file xmlfile$, which contains the XML data. It returns the structure xmlobj{}, which must be passed to the other library functions as required; xmlobj{} need not be (and should not be) initialised prior to calling PROC_initXML.

You can concurrently parse multiple XML files by using separate object structures for each file.

FN_isTag(xmlobj{})

FN_isTag returns TRUE if the next token to be returned by FN_nextToken is a tag and FALSE if not. It takes one parameter: the object structure returned by PROC_initXML.

FN_nextToken(xmlobj{})

FN_nextToken sequentially finds each token in the XML file, and returns a string containing that token. It takes one parameter: the object structure returned by PROC_initXML.

A token can be either a tag or the text contained between two tags; if it is a tag the first character of the returned string will be "<" and the last character will be ">".

FN_getLevel(xmlobj{})

FN_getLevel returns the current level in the hierarchy (where the base level is 1). It takes one parameter: the object structure returned by PROC_initXML.

At each level of nesting the value returned increases by one. If the returned value is zero it indicates an end-of-file condition (or that PROC_exitXML has been called).

FN_skipTo(xmlobj{}, tagname$, level%)

FN_skipTo scans forwards through the XML file until the specified tag is found, or until the level in the hierarchy is lower than a specified value. It takes three parameters: the object structure returned by PROC_initXML, the name of the tag for which to search and the minimum level. The value returned is the level at which the tag was found, or 0 if the tag was not found within the specified scope.

If the third parameter is set to zero the function will scan the remainder of the file; if the third parameter is set equal to the current level (as returned by FN_getLevel) only the rest of the current 'block' will be searched.

FN_skipToRet(xmlobj{}, tagname$, level%, token$)

FN_skipToRet is similar to FN_skipTo except that it returns, in the string variable specified as the fourth parameter, the token corresponding to the specified tag.

PROC_exitXML(xmlobj{})

PROC_exitXML closes the XML file and cancels the parser. It takes one parameter: the object structure returned by PROC_initXML.

FN_repEnt(text$)

FN_repEnt replaces entity references in the supplied text string; the string is modified (so must be specified as a variable). Entity references are special representations of characters which otherwise have a special meaning in XML (for example <, >, ', " and &).

Note that the returned string may contain Unicode characters encoded as UTF-8; see User-defined modes for how to display such characters.

Extending the assembler

The ASMLIB library provides support for some additional assembler instructions, in particular the CMOV (conditional move) instructions available on the Pentium Pro and later processors, and the SSE (Streaming SIMD Extension) instructions available on the Pentium III and later processors. The library should be loaded from your program as follows:
INSTALL @lib$+"ASMLIB"
The functions contained are: There are a number of limitations in the use of the ASMLIB library:
  1. The additional instructions are accepted only in lower case.
  2. There must be only one instruction per line.
  3. Labels must be placed on separate lines.
  4. Assembler code using the extra instructions cannot be compiled unless all the Crunch options are disabled. As this usually isn't acceptable, you should place the assembler source code in a separate file which you execute using CALL, and give the file an extension other than .BBC to prevent crunching, even if the Crunch embedded program files option is enabled.

FN_asmext

FN_asmext enables the assembler extensions. It should be used in the line immediately preceding the opening bracket ([) of your assembler code, in the following context:
ON ERROR LOCAL [OPT FN_asmext : ]
[OPT pass%
Assembler code starts here...
You should add a RESTORE ERROR statement after the closing bracket (]) of your assembler code, unless that is shortly followed by an ENDPROC or end-of-function (which automatically restore the ERROR status).

FN_cpuid(level%, cpuid{})

FN_cpuid allows you to test whether the processor supports the additional instructions; you should always perform such a test berfore using them. FN_cpuid takes two parameters: a level value specifying what information is required and a structure cpuid{} in which the requested information is returned (the structure should not be initialised prior to calling the function). The function returns FALSE if the CPU does not support the CPUID instruction and TRUE otherwise.

The returned structure contains the members A%, B%, C% and D% corresponding to the values returned in the eax, ebx, ecx and edx registers by the CPUID instruction. To test for the availability of the CMOV (conditional move) instructions use the following code:

IF FN_cpuid(1, cpuid{}) IF cpuid.D% AND &8000 THEN
  REM CMOV instructions available
ELSE
  REM CMOV instructions not available
ENDIF
To test for the availability of the SSE (Streaming SIMD Extension) instructions use the following code:
IF FN_cpuid(1, cpuid{}) IF cpuid.D% AND &2000000 THEN
  REM SSE instructions available
ELSE
  REM SSE instructions not available
ENDIF
To discover the Vendor ID string use code similar to the following:
IF FN_cpuid(0, cpuid{}) THEN
  PRINT "Vendor ID string: " LEFT$($$cpuid{},12)
ELSE
  PRINT "Vendor ID string not available"
ENDIF

More assembler extensions

The ASMLIB2 library further extends the assembler, supporting in particular the SSE2 (Streaming SIMD Extension 2) instructions available on the Pentium 4 and later processors. The library should be loaded from your program as follows:
INSTALL @lib$+"ASMLIB2"
ASMLIB2 is a superset of ASMLIB. It supports all the assembler extensions provided by ASMLIB and contains compatible functions. It is never necessary to INSTALL both ASMLIB and ASMLIB2.

To test for the availability of the SSE2 instructions use the following code:

IF FN_cpuid(1, cpuid{}) IF cpuid.D% AND &4000000 THEN
  REM SSE2 instructions available
ELSE
  REM SSE2 instructions not available
ENDIF

High quality sound patch

The HQSOUND library patches the BBC BASIC for Windows run-time engine, in memory, to cause the SOUND and ENVELOPE statements to generate 'CD quality' stereo sound output (16-bit samples, 44.1 kHz sampling). A processor which supports the SSE2 instruction set is required. The library should be loaded from your program as follows:
INSTALL @lib$+"HQSOUND"
If run from the BBC BASIC for Windows IDE the patch persists for the entire session and can only be removed by quitting and restarting. The functions contained are:

PROC_hqinit(pstereo%, pvoices%, pwaves%)

PROC_hqinit patches the run-time engine and returns three pointers as follows:

pstereo% is a pointer to eight consecutive 16-bit words in memory which control the stereo mix of the four sound channels. By default channels 0 and 1 are sent to the left output and channels 2 and 3 to the right output, but by storing values (in the range 0 to &8000) in these memory locations the stereo mix can be changed. To set all four sound channels to the centre of the stereo stage you would do:

pstereo%!0  = &40004000 : REM Channels 0 and 1 left
pstereo%!4  = &40004000 : REM Channels 2 and 3 left
pstereo%!8  = &40004000 : REM Channels 0 and 1 right
pstereo%!12 = &40004000 : REM Channels 2 and 3 right

pvoices% is a pointer to four consecutive bytes in memory which control which of eight 'voices' (waveforms) each of the four sound channels uses. Voice 0 is an approximation to a square wave, voice 1 is a triangular wave, voice 4 is a sine wave and the other five voices are similar to other organ stops. By default all four channels use voice 0 (which is most similar to the 'unpatched' waveform). To set all four sound channels to use voice 1 you would do:

pvoices%?0 = 1 : REM Channel 0
pvoices%?1 = 1 : REM Channel 1
pvoices%?2 = 1 : REM Channel 2
pvoices%?3 = 1 : REM Channel 3

pwaves% is a pointer to eight consecutive waveforms in memory, each of which consists of 8,192 signed 16-bit samples (so the allocated memory is &20000 bytes in total). Any of the eight voices may be replaced with a custom waveform by modifying the associated block.

If the default waveforms are acceptable, and PROC_stereo and/or PROC_voice are used to make the selection, PROC_hqinit may be called without any parameters. If no control over the waveforms, stereo position or voice is required the library may be executed using the code below, in which case there is no need to call PROC_hqinit at all and the library will not remain resident in memory after it has patched the executable:

CALL @lib$+"HQSOUND"

PROC_stereo(channel%, pan%)

PROC_stereo positions one of the four sound channels (0, 1, 2, 3) on the stereo 'stage'. The pan% parameter takes a value from -127 (fully left) to +127 (fully right); zero indicates the centre of the stage. The change happens immediately, even if a note is currently being played on that channel.

PROC_voice(channel%, voice%)

PROC_voice configures one of the four sound channels (0, 1, 2, 3) to a specific 'voice' or 'waveform' (analogous to an organ stop). Eight voices are available, numbered 0 to 7 (see the description of PROC_hqinit above). The change happens immediately, even if a note is currently being played on that channel.

Left CONTENTS

CONTINUE Right


Best viewed with Any Browser Valid HTML 3.2!
© Richard Russell 2017