The SYS statement allows you to call an API function either by name or by specifying its memory address. Only the most commonly used functions can be called by name, specifically those which are present in the following Dynamic Link Libraries, which are automatically loaded into memory when BBC BASIC for Windows is run:
Once you have finished with them, it is important to 'release' any DLLs which you have loaded:SYS "LoadLibrary", "OLEDLG.DLL" TO oledlg% SYS "GetProcAddress", oledlg%, "OleUIChangeIconA" TO chicon% SYS chicon%, ci% TO uint%
To ensure that the DLLs are released however your program exits, include code similar to the following in a 'cleanup' routine called from your ON CLOSE and ON ERROR handlers:SYS "FreeLibrary", oledlg%
oledlg% += 0 : IF oledlg% THEN SYS "FreeLibrary", oledlg%
title$ = "New title" SYS "SetWindowText", @hwnd%, title$
If the final parameter is 1 the state of the title bar is inverted (if it was highlighted it is un-highlighted; if it was not highlighted it becomes highlighted). If the final parameter is 0 the title bar is returned to its original state.SYS "FlashWindow", @hwnd%, 1 WAIT 20 SYS "FlashWindow", @hwnd%, 0
You will probably want to flash the title bar more than once to attract attention.
The above program segment will load the variable xscreen% with the width of the desktop and the variable yscreen% with the height of the desktop, both in pixels.SYS "GetSystemMetrics", 0 TO xscreen% SYS "GetSystemMetrics", 1 TO yscreen%
The final numeric value determines what kind of symbol is displayed and what options are presented to the user:message$ = "Test message" caption$ = "Test caption" SYS "MessageBox", @hwnd%, message$, caption$, 0
Value Symbol 16 Stop symbol 32 Question mark 48 Exclamation mark 64 Information symbol
The 'symbol' value and the 'options' value should be added together. When more than one option is offered, you can tell which was selected by storing the return value as follows:
Value User options 0 OK 1 OK and Cancel 2 Abort, Retry and Ignore 3 Yes, No and Cancel 4 Yes and No 5 Retry and Cancel 6 Cancel, Try Again and Continue (Windows 2000 or later only)
The value of result% will be one of the following:SYS "MessageBox", @hwnd%, message$, caption$, 0 TO result%
Note: If a dialogue box is open when you display the message, you should normally specify as the first parameter not @hwnd% but the window handle of the dialogue box itself, for example:
Value Selection 1 OK 2 Cancel 3 Abort 4 Retry 5 Ignore 6 Yes 7 No 10 Try Again 11 Continue
where dlg% is the value returned from FN_newdialog (note the exclamation point).SYS "MessageBox", !dlg%, message$, caption$, 0
See also the *REFRESH command.SYS "UpdateWindow", @hwnd%
The sound is determined by the value of beep%, as follows:beep% = 0 SYS "MessageBeep", beep%
The actual sound produced will depend on the user's sound scheme as selected in the Windows™ Control Panel.
Value Sound 0 Default sound 16 Critical stop 32 Question 48 Exclamation 64 Asterisk
You can control the playback volume as follows:SND_ASYNC = 1 SND_FILENAME = &20000 wave$ = "\windows\media\tada.wav" SYS "PlaySound", wave$, 0, SND_FILENAME + SND_ASYNC
where volume% is in the range 0 (minimum) to 65535 (maximum). Note that the volume change will affect all subsequent wave audio output.SYS "waveOutSetVolume", -1, volume% + (volume% << 16)
Alternatively you can load the WAV file into memory and then play the sound from there. You can load the file using the following code:
Then you can play the sound as many times as you like with:wave$ = "\windows\media\tada.wav" file% = OPENIN(wave$) size% = EXT#file% CLOSE #file% DIM tada% size%-1 OSCLI "LOAD """+wave$+""" "+STR$~tada%
You can abort a sound which is already playing as follows:SND_ASYNC = 1 SND_MEMORY = 4 SYS "PlaySound", tada%, 0, SND_MEMORY + SND_ASYNC
The PlaySound function also allows you to play 'system sounds':SYS "PlaySound", 0, 0, 0
Note that the SND_ALIAS signifies that a system sound should be played, whereas SND_FILENAME signifies that a WAV file should be played and SND_MEMORY signifies a sound in memory.SND_ASYNC = 1 SND_ALIAS = &10000 SYS "PlaySound", "SystemAsterisk", 0, SND_ALIAS + SND_ASYNC SYS "PlaySound", "SystemExclamation", 0, SND_ALIAS + SND_ASYNC SYS "PlaySound", "SystemExit", 0, SND_ALIAS + SND_ASYNC SYS "PlaySound", "SystemHand", 0, SND_ALIAS + SND_ASYNC SYS "PlaySound", "SystemQuestion", 0, SND_ALIAS + SND_ASYNC SYS "PlaySound", "SystemStart", 0, SND_ALIAS + SND_ASYNC
The PlaySound function may not work if your program has previously executed a SOUND statement. In that case, use SOUND OFF before calling PlaySound.
If ndevs% is non-zero it should be safe to use the SOUND statement, although it can still fail if (for example) another program is currently using the sound system.SYS "waveOutGetNumDevs" TO ndevs%
The value of tick% will be set to the number of milliseconds since Windows was started (it wraps around to zero if Windows has been running continuously for approximately 49 days and 17 hours!).SYS "GetTickCount" TO tick%
or to use the TIME pseudo-variable:pause = INKEY(delay%)
However both of these methods have their disadvantages. The INKEY delay can be truncated by pressing a key, which may be undesirable, and the TIME method keeps the processor fully occupied, so other applications will run slowly whilst your program is paused. A better method is to use WAIT:TIME = 0 REPEAT UNTIL TIME >= delay%
This is probably the best method for long delays, but an alternative is to use the Sleep function:WAIT delay%
The program will pause for approximately delay% milliseconds. Note that during this time the ESCape key is not tested, nor can the window be closed. Therefore this method should be used only for short delays.SYS "Sleep", delay%
The command line is returned in memory and is terminated with a NUL character (CHR$0). The $$ indirection operator converts it to a normal BASIC string.SYS "GetCommandLine" TO cmdline% cmdline$ = $$cmdline%
If you simply want to know the command line 'tail' (i.e. everything after the filename) you can use the system variable @cmd$.
DEF FNnulterm$(P%) LOCAL A$ WHILE ?P% A$ += CHR$?P% P% += 1 ENDWHILE = A$
To discover the directory from which your program was loaded you can use the @dir$ system variable.DEF FNgetmodulefilename LOCAL filename% DIM filename% LOCAL 260 SYS "GetModuleFileName", 0, filename%, 260 = $$filename%
DEF FNwinerror LOCAL message%, winerr% DIM message% LOCAL 255 SYS "GetLastError" TO winerr% SYS "FormatMessage", &1000, 0, winerr%, 0, message%, 255, 0 = $$message%
The position is specified as the offset, in pixels, from the top-left corner of the desktop to the top-left corner of BASIC's output window. To change the window's size without moving it you can do the following:SWP_NOSIZE = 1 SWP_NOZORDER = 4 SYS "SetWindowPos", @hwnd%, 0, xpos%, ypos%, 0, 0, \ \ SWP_NOSIZE + SWP_NOZORDER
The width and height are specified in pixels, including the border and the title bar. If want to specify the dimensions excluding the border and title bar, i.e. of the region usable by BASIC, then the best way of doing it is to select a user-defined mode with VDU 23,22.... However an alternative method is as follows:SWP_NOMOVE = 2 SWP_NOZORDER = 4 SYS "SetWindowPos", @hwnd%, 0, 0, 0, width%, height%, \ \ SWP_NOMOVE + SWP_NOZORDER VDU 26
If you want to alter both the size and the position you can use the MoveWindow function:DIM rc{l%,t%,r%,b%} rc.l% = 0 rc.t% = 0 rc.r% = width% rc.b% = height% SYS "AdjustWindowRect", rc{}, &CF0000, 0 SYS "SetWindowPos", @hwnd%, 0, 0, 0, rc.r%-rc.l%, rc.b%-rc.t%, 6 VDU 26
Whenever you intentionally change the window size, you should reset BASIC's text and graphics clipping regions to the new size by issuing the VDU 26 command.SYS "MoveWindow", @hwnd%, xpos%, ypos%, width%, height%, 1 VDU 26
To discover the current size of the window you can use the GetClientRect function, which returns the width and height in pixels:
This gives the usable size of the window, i.e. excluding the title bar etc. Should you need to know the full size (and position) you can do the following:DIM rc{l%,t%,r%,b%} SYS "GetClientRect", @hwnd%, rc{} Width% = rc.r% Height% = rc.b%
To find the 'normal' size of the window, even if it is maximised or minimised, you can use the GetWindowPlacement function:DIM rc{l%,t%,r%,b%} SYS "GetWindowRect", @hwnd%, rc{} Xpos% = rc.l% Ypos% = rc.t% Width% = rc.r% - rc.l% Height% = rc.b% - rc.t%
Don't use the window position returned by GetWindowPlacement since it is in workspace (not screen) coordinates and probably won't be what you want if the Taskbar is at the top of the screen.DIM wp{length%,flags%,showcmd%, minpos{x%,y%}, \ \ maxpos{x%,y%}, normal{l%,t%,r%,b%}} wp.length% = DIM(wp{}) SYS "GetWindowPlacement", @hwnd%, wp{} Width% = wp.normal.r% - wp.normal.l% Height% = wp.normal.b% - wp.normal.t%
If necessary change the MODE 8 to whichever MODE is required, or replace it with a suitable VDU 23,22 command. It is important to execute a MODE or VDU 23 statement after changing the window style.GWL_STYLE = -16 WS_THICKFRAME = &40000 WS_MAXIMIZEBOX = &10000 SYS "GetWindowLong", @hwnd%, GWL_STYLE TO ws% SYS "SetWindowLong", @hwnd%, GWL_STYLE, ws% AND NOT WS_THICKFRAME \ \ AND NOT WS_MAXIMIZEBOX MODE 8
This will have exactly the same effect as clicking on the minimise button in the title bar, or right-clicking on the title bar and selecting Minimize from the menu. The ShowWindow function can also be used to restore the window to its original size and position, maximise the window (equivalent to clicking on the maximise button) or hide the window (so it appears neither on the desktop nor in the taskbar):SW_MINIMIZE = 6 SYS "ShowWindow", @hwnd%, SW_MINIMIZE
If you maximise the window you should normally use a VDU 26 to reset the text and graphics viewports so they fill the full area:SW_RESTORE = 9 SYS "ShowWindow", @hwnd%, SW_RESTORE SW_MAXIMIZE = 3 SYS "ShowWindow", @hwnd%, SW_MAXIMIZE SW_HIDE = 0 SYS "ShowWindow", @hwnd%, SW_HIDE
If you hide the window, you are liable to confuse the BBC BASIC for Windows IDE when you exit your program. To prevent this you should execute the following code before quitting:SW_MAXIMIZE = 3 SYS "ShowWindow", @hwnd%, SW_MAXIMIZE VDU 26
SW_NORMAL = 1 SYS "ShowWindow", @hwnd%, SW_NORMAL SYS "SetForegroundWindow", @hwnd%
The above example leaves the window position and size unchanged, but you can if you like change these at the same time:HWND_TOPMOST = -1 SWP_NOSIZE = 1 SWP_NOMOVE = 2 SYS "SetWindowPos", @hwnd%, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE+SWP_NOMOVE
Here x%,y% is the position and cx%,cy% the size (using the same units as described previously for Repositioning the window).SYS "SetWindowPos", @hwnd%, HWND_TOPMOST, x%, y%, cx%, cy%, 0 VDU 26
If you don't need your window to stay on top, but simply want to move it to the top, you can use the BringWindowToTop function:
SYS "BringWindowToTop", @hwnd%
As this removes the close button, make sure you provide an obvious means for quitting the program (Alt-F4 will still work so long as you haven't used ON CLOSE).GWL_STYLE = -16 WS_BORDER = &800000 SWP_NOSIZE = 1 SWP_NOMOVE = 2 SWP_NOZORDER = 4 SWP_FRAMECHANGED = &20 SYS "GetWindowLong", @hwnd%, GWL_STYLE TO ws% SYS "SetWindowLong", @hwnd%, GWL_STYLE, ws% AND NOT WS_BORDER SYS "SetWindowPos", @hwnd%, 0, 0, 0, 0, 0, SWP_NOSIZE + \ \ SWP_NOMOVE + SWP_NOZORDER + SWP_FRAMECHANGED
When you run your program not even the Windows™ taskbar will be visible, nor will the user be able to re-size or move the window, so make sure you provide an obvious means for quitting the program (Alt-F4 will still work so long as you haven't used ON CLOSE).GWL_STYLE = -16 HWND_TOPMOST = -1 WS_VISIBLE = &10000000 WS_CLIPCHILDREN = &2000000 WS_CLIPSIBLINGS = &4000000 SYS "GetSystemMetrics", 0 TO xscreen% SYS "GetSystemMetrics", 1 TO yscreen% SYS "SetWindowLong", @hwnd%, GWL_STYLE, WS_VISIBLE + \ \ WS_CLIPCHILDREN + WS_CLIPSIBLINGS SYS "SetWindowPos", @hwnd%, HWND_TOPMOST, 0, 0, xscreen%, yscreen%, 0 VDU 26
Bear in mind that display resolutions vary, from 640 x 480 pixels to 1920 x 1440 pixels or more, and widescreen (16:9) displays are becoming increasingly common. Ideally you should write your program in such a way that it will behave sensibly with any display resolution and shape. If that isn't practical you can check that the dimensions are suitable (xscreen% and yscreen% in the above example contain the width and height in pixels) and prompt the user if not.
In the unlikely event that the screen dimensions exceed 1920 x 1440 pixels it is important that you do not issue the VDU 26 listed above. Instead you should use the code provided at Using windows larger than 1920 x 1440 pixels.
The solution is to output the graphics directly to the printer. Unfortunately you cannot use BASIC's built-in graphics statements to do this, you must use the Windows™ API instead. However this is quite easy, and there are equivalents for all of the normal graphics operations, for example:
BASIC statement | Printer API equivalent |
---|---|
MOVE x,y | SYS "MoveToEx",@prthdc%,x,y,0 |
DRAW x,y | SYS "LineTo",@prthdc%,x,y |
RECTANGLE x,y,dx,dy | SYS "Rectangle",@prthdc%,x1,y1,x2,y2 |
ELLIPSE x,y,a,b | SYS "Ellipse",@prthdc%,x1,y1,x2,y2 |
The main differences are that the coordinate system is different (vertical coordinates are measured downwards, with the origin at the top of the page) and the resolution in dots-per-inch varies between printers. If you only ever expect to output to a particular printer then you can use absolute coordinates, but it is far better to scale your graphics output so that it will look correct whatever the printer. There are two ways of achieving that, scaling to a particular size (in inches or cm) or scaling to fit the size of the paper.
To scale your printed graphics to a particular size you can find the resolution of the printer in dots-per-inch as follows:
The two values will often be the same. To scale your graphics to fit the paper you can discover the coordinates of the left, right, top and bottom margins as follows:_LOGPIXELSX = 88 _LOGPIXELSY = 90 SYS "GetDeviceCaps", @prthdc%, _LOGPIXELSX TO dpix% SYS "GetDeviceCaps", @prthdc%, _LOGPIXELSY TO dpiy%
As usual you need to output at least one conventional character to the printer at the start; you may want to print a title anyway, but if not you can simply output a space. The following code draws a straight line diagonally across the page, according to the current *MARGINS setting:marginl% = @vdu%!232 marginr% = @vdu%!236 margint% = @vdu%!240 marginb% = @vdu%!244
You may find that the line is too thin, or you want to print it in a colour other than black. You can change the line thickness and colour as follows:VDU 2,1,32,3 SYS "MoveToEx", @prthdc%, @vdu%!232, @vdu%!240, 0 SYS "LineTo", @prthdc%, @vdu%!236, @vdu%!244 VDU 2,1,12,3
The colour must be specified as the amounts of red, green and blue, in the range 0 (none) to 255 (maximum).VDU 2,1,32,3 pcol% = red% + (green% << 8) + (blue% << 16) SYS "CreatePen", 0, thickness%, pcol% TO pen% SYS "SelectObject", @prthdc%, pen% SYS "MoveToEx", @prthdc%, @vdu%!232, @vdu%!240, 0 SYS "LineTo", @prthdc%, @vdu%!236, @vdu%!244 VDU 2,1,12,3
This sets the colour of subsequently printed text according to the amounts of red, green and blue specified, in the range 0 (none) to 255 (maximum). For example:pcol% = red% + (green% << 8) + (blue% << 16) SYS "SetTextColor", @prthdc%, pcol%
would result in blue text.SYS "SetTextColor", @prthdc%, &FF0000
You should ensure that at least one text character is sent to the printer before issuing the SetTextColor call, otherwise it might not be effective. You may want to print some black text anyway but if not you can simply send an initial space character:
VDU 2,1,32,3
The *MARGINS command is necessary for BBC BASIC for Windows to take note of the changed page dimensions. If you want to set the margins to values other than the default (10 mm), specify the appropriate values in the command.DEF PROClandscape LOCAL psd%, dm% DIM psd% LOCAL 83 !psd% = 84 psd%!16 = 1024 SYS "PageSetupDlg", psd% SYS "GlobalLock", psd%!8 TO dm% dm%!40 = 1 dm%?44 = 2 SYS "ResetDC", @prthdc%, dm% SYS "GlobalUnlock", psd%!8 SYS "GlobalFree", psd%!8 SYS "GlobalFree", psd%!12 *MARGINS 10,10,10,10 ENDPROC
To set the format back to portrait, change the 2 to 1 in the ninth line:
As an alternative, and possibly more reliable, method you can present the user with the Print, Print Setup or Page Setup dialogue box which will allow him not only to select the required orientation but also the printer, printer options (e.g. print quality) and paper size.dm%?44 = 1
The program creates a menu bar containing eight items, consisting of the colour names listed. In each case one letter of the name is preceded by an ampersand (&): this determines the keyboard shortcut letter associated with the menu item.SYS "CreateMenu" TO hmenu% SYS "SetMenu", @hwnd%, hmenu% SYS "AppendMenu", hmenu%, 0, 0, "Blac&k" SYS "AppendMenu", hmenu%, 0, 1, "&Red" SYS "AppendMenu", hmenu%, 0, 2, "&Green" SYS "AppendMenu", hmenu%, 0, 3, "&Yellow" SYS "AppendMenu", hmenu%, 0, 4, "&Blue" SYS "AppendMenu", hmenu%, 0, 5, "&Magenta" SYS "AppendMenu", hmenu%, 0, 6, "&Cyan" SYS "AppendMenu", hmenu%, 0, 15, "&White" SYS "DrawMenuBar", @hwnd% : Click% = -1 ON SYS Click% = @wparam% : RETURN : REPEAT WAIT 1 click% = -1 SWAP click%,Click% IF click%<>-1 THEN COLOUR 128+click% : CLS UNTIL FALSE END
The ON SYS statement is activated whenever a menu item is selected (either by clicking with the mouse or using the keyboard shortcut). The system variable @wparam% contains a menu identifier which is equal to the third parameter of the relevant AppendMenu function (these values have been chosen to correspond directly to colour numbers), and this is copied to the global variable Click%.
The value of Click% is monitored by polling it within the program's main loop; when its value has been modified by the menu selection the background colour is changed.
If for any reason you want to remove the menu bar, you can do that as follows:
however in that case you should make sure you destroy the menu before quitting your program, so that the memory it uses is released:SYS "SetMenu", @hwnd%, 0
SYS "DestroyMenu", hmenu%
Note that in the AppendMenu calls which specify popup or sub-menus the second parameter is set to 16 rather than to zero. In this case the third parameter is set to the handle of the popup menu (hpop1% or hpop2%) or sub-menu (hsub%) rather than an arbitrary ID number.SYS "CreatePopupMenu" TO hsub% SYS "AppendMenu", hsub%, 0, 0, "&Black" SYS "AppendMenu", hsub%, 0, 15, "&White" : SYS "CreatePopupMenu" TO hpop1% SYS "AppendMenu", hpop1%, 0, 1, "&Red" SYS "AppendMenu", hpop1%, 0, 2, "&Green" SYS "AppendMenu", hpop1%, 0, 4, "&Blue" : SYS "CreatePopupMenu" TO hpop2% SYS "AppendMenu", hpop2%, 0, 3, "&Yellow" SYS "AppendMenu", hpop2%, 0, 5, "&Magenta" SYS "AppendMenu", hpop2%, 0, 6, "&Cyan" SYS "AppendMenu", hpop2%, 16, hsub%, "&Others" : SYS "CreateMenu" TO hmenu% SYS "AppendMenu", hmenu%, 16, hpop1%, "&Primary" SYS "AppendMenu", hmenu%, 16, hpop2%, "&Secondary" SYS "SetMenu", @hwnd%, hmenu% SYS "DrawMenuBar", @hwnd% : Click% = -1 ON SYS Click% = @wparam% : RETURN : REPEAT WAIT 1 click% = -1 SWAP click%,Click% IF click%<>-1 THEN COLOUR 128+click% : CLS UNTIL FALSE END
To insert one or more separators (horizontal dividing lines) within the popup or sub-menu, add the following statement at the appropriate point(s):
SYS "AppendMenu", hpop2%, &800, 0, 0
The first parameter is the handle of the popup menu (as returned by CreatePopupMenu), the second parameter is the menu item identifier (specified as the third parameter of AppendMenu) and the third parameter is 8 to add a tick mark or 0 to remove the tick mark.SYS "CheckMenuItem", hpopup%, itemid%, 8 SYS "CheckMenuItem", hpopup%, itemid%, 0
The first parameter is the handle of the popup menu (as returned by CreatePopupMenu), the second parameter is the menu item identifier (specified as the third parameter of AppendMenu) and the third parameter is 0 to enable the item and 1 to disable the item (and change it to grey to indicate that it is disabled). You can also use the value 2 which disables the item but does not make it grey.SYS "EnableMenuItem", hpopup%, itemid%, 0 SYS "EnableMenuItem", hpopup%, itemid%, 1
An alternative method of specifying the item you want to disable (or enable) is to give its position in the menu rather than its ID; this is particularly useful when the item is a sub-menu (which doesn't have an ID as such). To do this you add &400 to the third parameter:
If the menu item you are enabling or disabling is visible, you will need to update the display in order for its appearance to reflect its state (e.g. greyed-out when disabled):SYS "EnableMenuItem", hpopup%, itempos%, &400 SYS "EnableMenuItem", hpopup%, itempos%, &401
You can discover the current state of a menu item using the GetMenuState function:SYS "DrawMenuBar", @hwnd%
The returned variable state% will be a combination of one or more values, including:SYS "GetMenuState", hpopup%, itemid%, 0 TO state%
1 Greyed 2 Disabled 8 Ticked
The first parameter is the handle of the popup menu (as returned by CreatePopupMenu) and the second parameter is the menu item identifier (specified as the third parameter of AppendMenu).SYS "DeleteMenu", hpopup%, itemid%, 0
An alternative method of specifying the item you want to delete is to give its position in the menu rather than its ID; this is particularly useful when the item is a sub-menu (which doesn't have an ID as such). To do this you specify &400 as the third parameter:
You can insert a new item into an existing menu using the InsertMenu function:SYS "DeleteMenu", hpopup%, itempos%, &400
The parameters are the same as for AppendMenu apart from position% which specifies where in the menu the new item should be inserted.SYS "InsertMenu", hpopup%, position%, 0, itemid%, "Name" SYS "InsertMenu", hpopup%, position%, 16, hsubmenu%, "Name"
To check whether the clipboard contains any text, you can use the following function:text$ = "The five boxing wizards jump quickly"+CHR$13+CHR$10 SYS "GlobalAlloc", &2000, LEN(text$)+1 TO hdata% SYS "GlobalLock", hdata% TO tmp% $$tmp% = text$ SYS "GlobalUnlock", hdata% SYS "OpenClipboard", @hwnd% SYS "EmptyClipboard" SYS "SetClipboardData", 1, hdata% SYS "CloseClipboard"
which will set res% to 1 if there is text in the clipboard and to 0 otherwise. Once you have established that there is text available, you can read it with the following program segment:SYS "IsClipboardFormatAvailable", 1 TO res%
SYS "OpenClipboard", @hwnd% SYS "GetClipboardData", 1 TO hdata% IF hdata% THEN SYS "GlobalLock", hdata% TO tmp% text$ = $$tmp% SYS "GlobalUnlock", hdata% REM Do something with text$ ENDIF SYS "CloseClipboard"
Alternatively, and more easily, your BASIC program can utilise one or more of the standard dialogue boxes provided by Windows™, for example the File Open (or File Save), Choose Colour, Choose Font, Browse for Folder, Print, Print Setup or Page Setup dialogue boxes. In each case you must create and initialise a data structure in memory before calling the Dialogue Box function. The examples given below illustrate only the simplest use of the dialogue boxes; there are many more options available which are beyond the scope of this manual but may be found in Microsoft documentation.
The string ff$ specifies a file filter which tells the dialogue box function what file type(s) it should display by default. In the example shown the BMP file type is specified. You can specify more than one type by adding an appropriate description and extension for each, for example:DIM fs{lStructSize%, hwndOwner%, hInstance%, lpstrFilter%, \ \ lpstrCustomFilter%, nMaxCustFilter%, nFilterIndex%, \ \ lpstrFile%, nMaxFile%, lpstrFileTitle%, \ \ nMaxFileTitle%, lpstrInitialDir%, lpstrTitle%, \ \ flags%, nFileOffset{l&,h&}, nFileExtension{l&,h&}, \ \ lpstrDefExt%, lCustData%, lpfnHook%, lpTemplateName%} DIM fp{t&(260)} ff$ = "BMP files"+CHR$0+"*.BMP"+CHR$0+CHR$0 fs.lStructSize% = DIM(fs{}) fs.hwndOwner% = @hwnd% fs.lpstrFilter% = PTR(ff$) fs.lpstrFile% = fp{} fs.nMaxFile% = DIM(fp{}) - 1 fs.flags% = 6
The string is terminated by two CHR$0 characters. If you do not supply a filter string, all file types will be displayed.ff$ = "BMP files"+CHR$0+"*.BMP"+CHR$0+"GIF files"+CHR$0+"*.GIF"+CHR$0+CHR$0
To list more than one file type under the same description, separate the extensions with semicolons:
Once you have created and initialised the data structure you can call the File Open dialogue as follows:ff$ = "Image files"+CHR$0+"*.BMP;*.GIF;*.JPG"+CHR$0+CHR$0
If the returned value is non-zero, the selected filename can be found in memory at fp{}. If the returned value is zero, the file selection failed (for example the user selected Cancel).SYS "GetOpenFileName", fs{} TO result% IF result% filename$ = $$fp{}
You can call GetSaveFileName rather than GetOpenFileName. This will display the Save As title and will prompt the user if the selected file already exists.
Once you have created and initialised the data structure you can call the Choose Colour dialogue as follows:DIM cc{lStructSize%, hwndOwner%, hInstance%, \ \ rgb{r&,g&,b&,z&}, lpCustColors%, flags%, \ \ lCustData%, lpfnHook%, lpTemplateName%} DIM cb%(15) cc.lStructSize% = DIM(cc{}) cc.hwndOwner% = @hwnd% cc.lpCustColors% = ^cb%(0)
If the returned value is non-zero, the selected colour can be found in memory (red at cc.rgb.r&, green at cc.rgb.g& and blue at cc.rgb.b&). In the example shown this is used to set the physical colour of logical colour 1 (see COLOUR for details). If the returned value is zero, the colour selection failed (for example the user selected Cancel).SYS "ChooseColor", cc{} TO result% IF result% COLOUR 1, cc.rgb.r&, cc.rgb.g&, cc.rgb.b&
Once you have created and initialised the data structures you can call the Choose Font dialogue as follows:DIM lf{Height%, Width%, Escapement%, Orientation%, \ \ Weight%, Italic&, Underline&, StrikeOut&, \ \ CharSet&, OutPrecision&, ClipPrecision&, \ \ Quality&, PitchAndFamily&, FaceName&(30)} DIM cf{lStructSize%, hwndOwner%, hdc%, lpLogFont%, \ \ iPointSize%, flags%, rgbColors%, lCustData%, \ \ lpfnHook%, lpTemplateName%, hInstance%, lpszStyle%, \ \ nFontType{l&,h&}, pad{l&,h&}, nSizeMin%, nSizeMax%} cf.lStructSize% = DIM(cf{}) cf.hwndOwner% = @hwnd% cf.lpLogFont% = lf{} cf.flags% = 1
If the returned value is non-zero, the selected font name can be found in memory at ^lf.FaceName&(0) and the font size (in decipoints) at cf.iPointSize%. If the returned value is zero, the font selection failed (for example the user selected Cancel). If you also want to know the font style (i.e. bold or italic) you can discover these as follows:SYS "ChooseFont", cf{} TO result%
You can use this information to set the current font, size and style (see *FONT for details):bold% = lf.Weight% >= 600 italic% = lf.Italic& <> 0
SYS "ChooseFont", cf{} TO result% IF result% THEN font$ = lf.FaceName&()+","+STR$(cf.iPointSize%/10) style$ = "" IF lf.Weight% > 600 style$ += "B" IF lf.Italic& <> 0 style$ += "I" IF style$ <> "" font$ += "," + style$ OSCLI "FONT "+font$ ENDIF
Once you have created and initialised the data structure you can call the Browse for Folder dialogue as follows:DIM bi{hOwner%, pidlRoot%, pszDisplayName%, \ \ lpszTitle%, ulFlags%, lpfn%, lParam%, iImage%} DIM folder{t&(260)} titlez$ = title$+CHR$0 bi.hOwner% = @hwnd% bi.pszDisplayName% = folder{} bi.lpszTitle% = PTR(titlez$)
If the value of pidl% is non-zero the path to the selected folder will be found (as a NUL-terminated string) at address folder%. In the above example this is converted to a standard BASIC string folder$ using the $$ indirection operator. If the user selected the Cancel button the value of pidl% will be zero.SYS "SHBrowseForFolder", bi{} TO pidl% IF pidl% SYS "SHGetPathFromIDList", pidl%, folder{} folder$ = $$folder{} SYS "SHGetMalloc", ^malloc% SYS !(!malloc%+20), malloc%, pidl% : REM. IMalloc::Free
Once you have created and initialised the data structure you can call the Print dialogue as follows:DIM pd{lStructSize%, hwndOwner%, hDevMode%, hDevNames%, \ \ hdc%, flags%, nFromPage{l&,h&}, nToPage{l&,h&}, \ \ nMinPage{l&,h&}, nMaxPage{l&,h&}, nCopies{l&,h&}, \ \ hInstance%, lCustData%, lpfnPrintHook%, lpfnSetupHook%, \ \ lpPrintTemplateName%, lpSetupTemplateName%, \ \ hPrintTemplate%, hSetupTemplate%} pd.lStructSize% = DIM(pd{}) pd.hwndOwner% = @hwnd% pd.nMinPage.l& = 1 pd.nMaxPage.l& = 99 pd.flags% = &100
You should add your printing code where shown. You can use the values returned from the Print dialogue to control printing according to the user's choices, for example the chosen number of copies is in pd.nCopies.l& and the selected page range (if any) is in pd.nFromPage.l& and pd.nToPage.l&.SYS "PrintDlg", pd{} TO ok% IF ok% THEN SYS "DeleteDC", @prthdc% @prthdc% = pd.hdc% *MARGINS 10,10,10,10 REM. Add your conventional printing code here. ENDIF
The *MARGINS command is necessary for BBC BASIC for Windows to take note of the (possibly) changed page dimensions. If you want to set the margins to values other than the default (10 mm), specify the appropriate values in the command.
Once you have created and initialised the data structure you can call the Print Setup dialogue as follows:DIM pd{lStructSize%, hwndOwner%, hDevMode%, hDevNames%, \ \ hdc%, flags%, nFromPage{l&,h&}, nToPage{l&,h&}, \ \ nMinPage{l&,h&}, nMaxPage{l&,h&}, nCopies{l&,h&}, \ \ hInstance%, lCustData%, lpfnPrintHook%, lpfnSetupHook%, \ \ lpPrintTemplateName%, lpSetupTemplateName%, \ \ hPrintTemplate%, hSetupTemplate%} pd.lStructSize% = DIM(pd{}) pd.hwndOwner% = @hwnd% pd.flags% = &140
On exit from this routine, assuming the user has selected OK rather than Cancel, the current printer settings will have been changed to those chosen by the user. These may include the printer itself, any printer-specific options (e.g. print quality), the paper size and the page orientation (portrait or landscape). Subsequent output to the printer from your BBC BASIC program will use these settings.SYS "PrintDlg", pd{} TO ok% IF ok% THEN SYS "DeleteDC", @prthdc% @prthdc% = pd.hdc% *MARGINS 10,10,10,10 ENDIF
If you need to know what the new settings are (for example the paper orientation and size) you can do that as follows:
This will set orientation% to 1 for portrait and to 2 for landscape and papersize% to 0 for a custom size or to one of the following constants for a standard size:SYS "GlobalLock", pd.hDevMode% TO dm% orientation% = dm%?44 papersize% = dm%?46 paperlength% = dm%!48 AND &FFFF paperwidth% = dm%!48 >>> 16 SYS "GlobalUnlock", pd.hDevMode%
Other values are used for less common paper sizes. In the case of a custom size (only), paperlength% and paperwidth% are set to the dimensions in tenths of a millimetre.
1 Letter 8½ x 11 in 2 Letter Small 8½ x 11 in 3 Tabloid 11 x 17 in 4 Ledger 17 x 11 in 5 Legal 8½ x 14 in 6 Statement 5½ x 8½ in 7 Executive 7¼ x 10½ in 8 A3 297 x 420 mm 9 A4 210 x 297 mm 10 A4 Small 210 x 297 mm 11 A5 148 x 210 mm
The *MARGINS command is necessary for BBC BASIC for Windows to take note of the (possibly) changed page dimensions. If you want to set the margins to values other than the default (10 mm), specify the appropriate values in the command.
Note that this is a rare occasion when it is correct to write to a system variable (in this case @prthdc%).
(the values of 1000 are the default page margins in hundredths of a millimetre):DIM psd{lStructSize%, hwndOwner%, hDevMode%, hDevNames%, \ \ flags%, ptPaperSize{w%,h%}, rtMinMargin{l%,t%,r%,b%}, \ \ rtMargin{l%,t%,r%,b%}, hInstance%, lCustData%, \ \ lpfnPageSetupHook%, lpfnPagePaintHook%, \ \ lpPageSetupTemplateName%, hPageSetupTemplate%} psd.lStructSize% = DIM(psd{}) psd.hwndOwner% = @hwnd% psd.flags% = 10 psd.rtMargin.l% = 1000 : REM left psd.rtMargin.t% = 1000 : REM top psd.rtMargin.r% = 1000 : REM right psd.rtMargin.b% = 1000 : REM bottom
When you want to display the Page Setup dialogue (typically in response to a menu selection) execute the following code:
The paper size, orientation and margins will be changed according to the user's selections.SYS "PageSetupDlg", psd{} TO ok% IF ok% THEN IF psd.hDevMode% THEN SYS "GlobalLock", psd.hDevMode% TO dm% SYS "ResetDC", @prthdc%, dm% SYS "GlobalUnlock", psd.hDevMode% ENDIF OSCLI "MARGINS "+STR$(psd.rtMargin.l%DIV100)+","+ \ \ STR$(psd.rtMargin.b%DIV100)+","+ \ \ STR$(psd.rtMargin.r%DIV100)+","+ \ \ STR$(psd.rtMargin.t%DIV100) ENDIF
The parameter value 5 selects the window background colour. Other values which you might want to use with GetSysColor are given in the following table:SYS "GetSysColor", 5 TO winbk% COLOUR 15, winbk%, winbk% >> 8, winbk% >> 16 COLOUR 128+15 CLS
Value Colour returned 1 Desktop background 4 Menu background 5 Window background 7 Menu text 8 Window text
The above program segment loads memory addresses store% to store%+size%-1 inclusive with the part of the file starting at offset offset% from its beginning. This will work perfectly well, but is significantly slower than *LOAD. If you need to load a part of a file as quickly as possible, you can use the ReadFile API function:DIM store% size%-1 file% = OPENIN(filename$) PTR#file% = offset% FOR addr% = store% TO store%+size%-1 ?addr% = BGET#file% NEXT addr% CLOSE #file%
Note the use of the system variable @hfile%() to discover the Windows™ file handle, and the need to allocate a 4-byte area of memory at temp% to hold data returned from the API function (the number of bytes loaded).DIM store% size%-1 file% = OPENIN(filename$) PTR#file% = offset% SYS "ReadFile", @hfile%(file%), store%, size%, ^temp%, 0 CLOSE #file%
Similarly to write part of a file you can use the WriteFile function:
These functions are the Windows™ equivalent of the Acorn OSGBPB function.DIM store% size%-1 file% = OPENUP(filename$) PTR#file% = offset% SYS "WriteFile", @hfile%(file%), store%, size%, ^temp%, 0 CLOSE #file%
If you need to call this routine multiple times ensure that you move the DIM statement so that it will be executed only once. If you don't, you may eventually run out of memory. Alternatively, consider using DIM LOCAL.
However, if you have used *FONT to change to a proportional-spaced character font, things are not so simple. Because the characters are different widths, there is no straightforward way of calculating the total display width of a string. In that case you can use the GetTextExtentPoint32 function (see also the built-in WIDTH function for an alternative method when available):Width% = 16 * LEN(string$)
Note that the dimensions returned by GetTextExtentPoint32 are in pixels, so they are doubled to obtain values in BASIC graphics units (one pixel equals two graphics units).DIM size{cx%,cy%} *FONT "Arial",24 SYS "GetTextExtentPoint32", @memhdc%, string$, LEN(string$), size{} Width% = size.cx% * 2 Height% = size.cy% * 2
Once you know the dimensions of the string you can calculate where it needs to be plotted to achieve the required alignment (e.g. centred or right-justified). Use MOVE to set the position then select VDU 5 mode before you PRINT the string to the screen.
If you want to achieve the same effect on the printer rather than the screen, it is slightly more complicated. Firstly, you must specify @prthdc% rather than @memhdc% in the GetTextExtentPoint32 function call; the dimensions returned will then be in printer units (and no doubling is required). Secondly, since there is no equivalent to VDU 5 mode for the printer, you must control the print position using the @vdu% system variable.
The following program segment prints a string centred on a particular column:
Here, @vdu%!−12 determines the horizontal print position, which is moved to the left by half the width of the string. To right-justify the string rather than centre it, delete the /2 at the end of the ninth line.DEF PROCprintcentred(string$, column%) LOCAL size{} DIM size{cx%,cy%} *PRINTERFONT Arial,24 *MARGINS 10,10,10,10 SYS "GetTextExtentPoint32", @prthdc%, string$, LEN(string$), size{} VDU 2 PRINT TAB(column%); @vdu%!-12 -= size.cx%/2 PRINT string$ VDU 3 ENDPROC
BBC BASIC for Windows has no built-in commands for displaying Enhanced Metafiles, but the following procedure illustrates how this may be achieved by means of calls to API functions:
The position and size of the displayed image are determined by the values stored in the structure rect. The values of left%, top%, right% and bottom% must be specified in pixels, and are measured from the top-left corner of BASIC's output window. If necessary, these values must be converted from BASIC graphics units, taking into account that one pixel corresponds to two graphics units, and that BASIC's graphics origin is usually the bottom-left corner of the window.DEF PROCenhmetafile(emffile$,left%,top%,right%,bottom%) LOCAL rect{}, hemf% DIM rect{l%,t%,r%,b%} SYS "GetEnhMetaFile", emffile$ TO hemf% IF hemf% = 0 ERROR 214, "File "+emffile$+" not found" rect.l% = left% rect.t% = top% rect.r% = right% rect.b% = bottom% SYS "PlayEnhMetaFile", @memhdc%, hemf%, rect{} SYS "InvalidateRect", @hwnd%, rect{}, 0 SYS "DeleteEnhMetaFile", hemf% SYS "UpdateWindow", @hwnd% ENDPROC
You may also encounter an older kind of Windows™ Metafile with a .WMF extension. The program WMF2EMF, supplied with BBC BASIC for Windows in the EXAMPLES folder, can be used to convert a .WMF file to a .EMF file.
The value of Interval% determines the period of the timer interrupt, in milliseconds. In this case it is set to a period of 5 ms (a frequency of 200 Hz). The minimum period you can use is system dependent, and is returned from the timeGetDevCaps function as tc.PeriodMin% (in milliseconds). Note that interrupt timers are a scarce resource, so you must always free the timer with timeKillEvent when you have finished with it. You should also be aware that short-period timers can consume a significant portion of CPU time.DIM tc{PeriodMin%, PeriodMax%} DIM P% 10 [.TimerProc inc dword [^Count%] ret 20 ] : Interval% = 5 SYS "timeGetDevCaps", tc{}, 8 TO res% IF res% ERROR 100, "Multimedia timer not available" SYS "timeBeginPeriod", tc.PeriodMin% : SYS "timeSetEvent", Interval%, tc.PeriodMin%, TimerProc, 0, 1 TO TimerID% IF TimerID% = 0 ERROR 100, "Could not start timer" ON CLOSE SYS "timeKillEvent", TimerID% : QUIT ON ERROR SYS "timeKillEvent", TimerID% : PRINT 'REPORT$ : END : REPEAT PRINT Count% UNTIL FALSE
Because it is called as the result of an interrupt, there are restrictions on what you can do within the assembly-language timer routine. The details are beyond the scope of this document, but the safest thing to do is to restrict the code to performing calculations on, and modifying, data values stored in memory.
The following program segment restores the priority to its normal value:SYS "GetCurrentProcess" TO hprocess% SYS "SetPriorityClass", hprocess%, &100
You should use this facility with extreme care, because while the priority of your program is raised other programs may run very slowly or not at all. You may not even be able to stop or interrupt your own program! You should raise the priority only for the shortest possible period, and then return it to normal.SYS "GetCurrentProcess" TO hprocess% SYS "SetPriorityClass", hprocess%, &20
where md% is the screen mode you wish to use. Note the extra VDU 26 which causes the text and graphics clipping regions to be re-sized to take account of the presence of the scroll bar. To create a horizontal scroll bar rather than a vertical scroll bar change the WS_VSCROLL to WS_HSCROLL; to create both a vertical and a horizontal scroll bar change it to (WS_HSCROLL+WS_VSCROLL)GWL_STYLE = -16 WS_HSCROLL = &100000 WS_VSCROLL = &200000 SYS "GetWindowLong", @hwnd%, GWL_STYLE TO ws% SYS "SetWindowLong", @hwnd%, GWL_STYLE, ws% OR WS_VSCROLL MODE md% VDU 26
If you subsequently want to remove the scroll bar(s) you can do that as follows:
To restore the scroll bar(s) thereafter you can do:SB_BOTH = 3 SYS "ShowScrollBar", @hwnd%, SB_BOTH, 0 VDU 26
This command restores both vertical and horizontal scroll bars. To restore just a vertical bar change the SB_BOTH to SB_VERT (1); to restore just a horizontal bar change it to SB_HORZ (0).SB_BOTH = 3 SYS "ShowScrollBar", @hwnd%, SB_BOTH, 1 VDU 26
In most cases you would set minscroll% to zero and maxscroll% to the total number of data lines minus the number of lines displayed at any one time. So if the total number of data lines was 100 and the number displayed was 32 you would set maxscroll% to 68. For a horizontal scroll bar replace the SB_VERT with a SB_HORZ (0).SB_VERT = 1 SYS "SetScrollRange", @hwnd%, SB_VERT, minscroll%, maxscroll%, 0
For a vertical scroll bar @msg% will have the value &115 and the low word of @wparam% will have one of the values 0 (scroll up one line), 1 (scroll down one line), 2 (scroll up one 'page'), 3 (scroll down one 'page') or 5 (scroll to the position indicated by the high word of @wparam%). A suitable PROCscroll would be something like the following:ON MOVE PROCscroll(@msg%,@wparam%) : RETURN
Here vscroll% is the wanted scroll position and vpage% is the number of lines to scroll when the scroll bar is clicked above or below the thumb box (typically the number of displayed lines).DEF PROCscroll(msg%,wp%) SB_VERT = 1 WM_VSCROLL = &115 IF msg% <> WM_VSCROLL ENDPROC CASE wp% AND &FFFF OF WHEN 0: vscroll% -= 1 WHEN 1: vscroll% += 1 WHEN 2: vscroll% -= vpage% WHEN 3: vscroll% += vpage% WHEN 5: vscroll% = wp% >> 16 ENDCASE IF vscroll% < minscroll% vscroll% = minscroll% IF vscroll% > maxscroll% vscroll% = maxscroll% SYS "SetScrollPos", @hwnd%, SB_VERT, vscroll%, 1 ENDPROC
For a horizontal scroll bar change WM_VSCROLL to WM_HSCROLL (&114) and change the SB_VERT to SB_HORZ (0). Also replace vscroll% by hscroll% and vpage% by hpage% throughout.
If you want more control over the scroll bars, you can use the SetScrollInfo API.
In the case of a vertical scroll bar you can take advantage of BASIC's VDU drivers to scroll the visible display (text viewport). To scroll the display down you should position the cursor on the top line of the text viewport and issue a VDU 11 command. Similarly to scroll the display up you should position the cursor in the bottom line and issue a VDU 10. Alternatively you can use VDU 23,7 to scroll the display in any direction.
When the screen is scrolled vertically a blank line is 'scrolled' into the top line or the bottom line respectively. To simulate scrolling through a greater amount of data than the display can show, your program must update the top or bottom line with the appropriate data item as soon as the scroll has taken place. Depending on the nature of your program, this data may either be read from a RAM buffer (e.g. an array), read from a file or generated 'on the fly'.
In the special case of wanting to display Windows Icon (.ICO) or Windows Metafile (.WMF) images you can use the following procedure (remember that you can copy and paste this code from the help window into your program):
Note that the required display position (xpos%, ypos%) must be specified in pixels, and is measured from the top-left corner of your program's output window to the top-left corner of the image. Note also that the file name must include the drive letter (e.g. C:) to distinguish it from a web URL, which can be used as an alternative means of specifying the image.DEF PROCdisplay(picture$,xpos%,ypos%,xsize%,ysize%) LOCAL oleaut32%, olpp%, iid%, gpp%, hmw%, hmh%, picture%, res% SYS "LoadLibrary", "OLEAUT32.DLL" TO oleaut32% SYS "GetProcAddress", oleaut32%, "OleLoadPicturePath" TO olpp% IF olpp%=0 ERROR 100, "Could not get address of OleLoadPicturePath" DIM iid% LOCAL 15, picture% LOCAL 513 SYS "MultiByteToWideChar", 0, 0, picture$, -1, picture%, 256 iid%!0 = &7BF80980 iid%!4 = &101ABF32 iid%!8 = &AA00BB8B iid%!12 = &AB0C3000 SYS olpp%, picture%, 0, 0, 0, iid%, ^gpp% IF gpp% = 0 ERROR 100, "OleLoadPicturePath failed" SYS !(!gpp%+24), gpp%, ^hmw% : REM. IPicture::get_Width SYS !(!gpp%+28), gpp%, ^hmh% : REM. IPicture::get_Height SYS !(!gpp%+32), gpp%, @memhdc%, xpos%, ypos%, xsize%, ysize%, 0, \ \ hmh%, hmw%, -hmh%, 0 TO res% IF res% ERROR 100, "IPicture::Render failed" SYS !(!gpp%+8), gpp% : REM. IPicture::Release SYS "InvalidateRect", @hwnd%, 0, 0 SYS "UpdateWindow", @hwnd% ENDPROC
If you need more control over the directory listing, you can use the FindFirstFile, FindNextFile and FindClose API functions. The following procedure displays the names of all the files in the current directory:
By adapting this routine you can display or otherwise process the disk directory in any way that you want. If you want to filter only certain files, specify an appropriate ambiguous file specification (e.g. "*.BBC") rather than "*" in the FindFirstFile line. As well as the filenames themselves, you can discover other properties of the files from the contents of the dir% structure, as follows:DEF PROClistdirectory LOCAL dir%, sh%, res% DIM dir% LOCAL 317 SYS "FindFirstFile", "*", dir% TO sh% IF sh% <> -1 THEN REPEAT PRINT $$(dir%+44) SYS "FindNextFile", sh%, dir% TO res% UNTIL res% = 0 SYS "FindClose", sh% ENDIF ENDPROC
The common file attribute values are 1 (read only), 2 (hidden), 4 (system), 16 (directory) and 32 (archive); other bits are used for specialised purposes. The values may be combined.
dir%!0 File attributes (see below) dir%!4 Time created (LS 32 bits) dir%!8 Time created (MS 32 bits) dir%!12 Time last accessed (LS 32 bits) dir%!16 Time last accessed (MS 32 bits) dir%!20 Time last modified (LS 32 bits) dir%!24 Time last modified (MS 32 bits) dir%!28 File size in bytes (MS 32 bits) dir%!32 File size in bytes (LS 32 bits)
The time stamps are 64-bit values representing the number of 100-nanosecond intervals since 00:00 on 1st January, 1601. You can convert these values to a more conventional form using the FileTimeToSystemTime function, as follows:
This example converts the time last modified. To convert the time last accessed or time created replace the dir%+20 with dir%+12 or dir%+4 respectively. The times returned will be UTC, not your local clock time.DEF PROCfiletimetosystime(dir%) LOCAL systime% DIM systime% LOCAL 15 SYS "FileTimeToSystemTime", dir%+20, systime% year% = !systime% AND &FFFF month% = systime%?2 weekday% = systime%?4 day% = systime%?6 hour% = systime%?8 minute% = systime%?10 second% = systime%?12 ENDPROC
For this to work the document type must be associated with an application (for example .doc files are usually opened with Microsoft Word™). ShellExecute can also be used to open a web page, by specifying a URL rather than a document name.document$ = "C:\Path\DocFile.doc" SYS "ShellExecute", @hwnd%, "open", document$, 0, 0, 1
Beware that ShellExecute seems to misbehave with Windows™ 95, 98 and Me, especially when opening .doc and .pdf files. Use with care on these systems.
If you want to open a directory (folder) rather than a file you can do that very easily using Windows Explorer:
*EXPLORER C:\Program Files\BBC BASIC for Windows
From the MajorVersion%, MinorVersion% and PlatformID% values you can deduce the version of Windows, as follows:DEF PROCgetversion LOCAL osvi{} DIM osvi{Size%, \ Size of structure \ Major%, \ Major version number \ Minor%, \ Minor Version number \ Build%, \ Build number \ Platform%, \ Platform ID \ SP&(127) \ Service Pack string \ } osvi.Size% = 148 SYS "GetVersionEx", osvi{} MajorVersion% = osvi.Major% MinorVersion% = osvi.Minor% PlatformID% = osvi.Platform% ENDPROC
Windows 95: | MajorVersion% = 4 | MinorVersion% = 0 | PlatformID% = 1 |
Windows 98: | MajorVersion% = 4 | MinorVersion% = 10 | PlatformID% = 1 |
Windows Me: | MajorVersion% = 4 | MinorVersion% = 90 | PlatformID% = 1 |
Windows NT4: | MajorVersion% = 4 | MinorVersion% = x | PlatformID% = 2 |
Windows 2000: | MajorVersion% = 5 | MinorVersion% = 0 | PlatformID% = 2 |
Windows XP: | MajorVersion% = 5 | MinorVersion% = 1 | PlatformID% = 2 |
Windows Vista: | MajorVersion% = 6 | MinorVersion% = 0 | PlatformID% = 2 |
Windows 7: | MajorVersion% = 6 | MinorVersion% = 1 | PlatformID% = 2 |
Windows 8: | MajorVersion% = 6 | MinorVersion% = 2 | PlatformID% = 2 |
Windows 8.1: | MajorVersion% = 6 | MinorVersion% = 3 | PlatformID% = 2 |
Windows 10: | MajorVersion% = 10 | MinorVersion% = 0 | PlatformID% = 2 |
(here 'x' means "don't care")
If you want to find out whether the version of Windows is 32-bits or 64-bits you can use this function, which will return FALSE for 32-bits and TRUE for 64-bits:
DEF FNis64bit LOCAL yes% ON ERROR LOCAL = FALSE SYS "IsWow64Process", -1, ^yes% = yes% <> 0
The special folder returned depends on the parameter id% as follows:DEF FNspecialfolder(id%) LOCAL ppidl%, folder%, malloc% DIM folder% LOCAL 255 SYS "SHGetSpecialFolderLocation", @hwnd%, id%, ^ppidl% SYS "SHGetPathFromIDList", ppidl%, folder% SYS "SHGetMalloc", ^malloc% SYS !(!malloc%+20), malloc%, ppidl% : REM. IMalloc::Free = $$folder% + "\"
To find the locations of the Windows directory and the System directory use the following routines:
0: Desktop 2: Start Menu\Programs 5: Documents 6: Favorites 7: StartUp 8: Recent files 9: Send To 11: Start Menu 20: Fonts 26: Application Data 32: Temporary Internet Files
DEF FNwindowsdirectory LOCAL T% DIM T% LOCAL 260 SYS "GetWindowsDirectory", T%, 260 = $$T% + "\"
The locations of the Temporary directory and the user's Documents folder are available in the system variables @tmp$ and @usr$ respectively.DEF FNsystemdirectory LOCAL T% DIM T% LOCAL 260 SYS "GetSystemDirectory", T%, 260 = $$T% + "\"
You can discover the font currently in use with the GetTextFace function:
The above routine can be used not only to discover the actual font selected as the result of a *FONT command, but also to find the default font used following a MODE change.DEF FNfont LOCAL face% DIM face% LOCAL 31 SYS "GetTextFace", @memhdc%, 32, face% = $$face%
This is fine so long as you are running your program on a machine on which BBC BASIC for Windows is installed, but if you have compiled it to a stand-alone executable, for distribution to others, there is no guarantee that the BBCWIN font will be installed on the target machine.*FONT BBCWIN
If the BBCWIN font is not available Windows™ will select an alternative, and possibly unsuitable, font. In that case you might prefer to supply the BBCWIN.FON file (which can be found in your fonts folder) with your program to ensure the on-screen appearance is what you expect. You will then need to install the font on the target machine using the AddFontResource API function as follows:
This assumes that the file BBCWIN.FON is in the current directory (folder). In practice you will probably want to load it from the directory which contains your executable file. This can be achieved using the @dir$ system variable as follows:SYS "AddFontResource", "BBCWIN.FON" *FONT BBCWIN
On exit from your program, or when you have finished with the font, release it as follows:SYS "AddFontResource", @dir$+"BBCWIN.FON" *FONT BBCWIN
SYS "RemoveFontResource", @dir$+"BBCWIN.FON"
SYS "GetForegroundWindow" TO hw% IF hw% = @hwnd% THEN REM program has input focus ENDIF
Here w% and h% are the width and height of the icon in pixels, and x% and y% are the offsets in pixels from the top left-hand corner of the screen to the top left-hand corner of the icon. The maximum size of an icon is 256 x 256 pixels.IMAGE_ICON = 1 LR_LOADFROMFILE = 16 DI_MASK = 1 DI_IMAGE = 2 SYS "LoadImage", 0, iconfile$, IMAGE_ICON, w%, h%, LR_LOADFROMFILE TO hicon% SYS "DrawIconEx", @memhdc%, x%, y%, hicon%, w%, h%, 0, 0, DI_MASK OR DI_IMAGE SYS "InvalidateRect", @hwnd%, 0, 0
For simplicity the InvalidateRect function here updates the entire screen, not just the region where the icon was displayed. If speed is important specify an update rectangle as in Displaying enhanced metafiles.
Next you must create a data structure in memory as follows:IMAGE_ICON = 1 LR_LOADFROMFILE = 16 iconfile$ = @dir$+"resources\bbcmicro.ico" SYS "LoadImage", 0, iconfile$, IMAGE_ICON, 16, 16, \ \ LR_LOADFROMFILE TO hicon%
Here Tooltip is the string which is displayed if you 'hover' the mouse over the icon.NIF_MESSAGE = 1 NIF_ICON = 2 NIF_TIP = 4 WM_COMMAND = 273 DIM nid{cbSize%, hwnd%, uID%, uFlags%, uCallbackMessage%, \ \ hIcon%, szTip&(63)} nid.cbSize% = DIM(nid{}) nid.hwnd% = @hwnd% nid.uID% = 1234 nid.uFlags% = NIF_ICON OR NIF_TIP nid.uCallbackMessage% = WM_COMMAND nid.hIcon% = hicon% nid.szTip&() = "Tooltip"
Finally to display or remove the icon you use the Shell_NotifyIcon function:
NIM_ADD = 0 SYS "Shell_NotifyIcon", NIM_ADD, nid{}
If you want your program to respond to clicking on the SysTray icon you should add NIF_MESSAGE to the nid.uFlags% value in the code above. This will cause messages to be sent which can be intercepted with ON SYS, in exactly the same way as those from a menu. These messages will have an @wparam% value equal to the specified nid.uID% (1234 in the example above) and an @lparam% value dependent on the mouse operation, as follows:NIM_DELETE = 2 SYS "Shell_NotifyIcon", NIM_DELETE, nid{}
You can expect to receive many 'mouse move' messages (@lparam% = 512) so in most circumstances you will want to ignore these in an efficient manner to avoid being 'flooded'.
@lparam% mouse operation 512 mouse move 513 left button down 514 left button up 515 left button double click 516 right button down 517 right button up 518 right button double click 519 middle button down 520 middle button up 521 middle button double click
The registry has a hierarchical structure, not unlike a filing system. Analogous with directories or folders are keys, which identify locations within the registry. Within each key there can be a number of named values (analogous with files) each of which can contain binary data (typically a number or a string).
So for example to save a string called String$ and an integer value called Integer% in the registry under the key name Settings do the following:
The key name and value names shown above are purely illustrative, but by adopting the style of key in the example (beginning Software\YourName\) you ensure that it is unique to your application.Key$ = "Software\YourName\YourProgram\Settings" SYS "RegCreateKeyEx", &80000001, Key$, 0, "", 0, &F003F, 0, ^K%, ^D% TO R% IF R% = 0 THEN SYS "RegSetValueEx", K%, "String", 0, 1, String$, LEN(String$)+1 SYS "RegSetValueEx", K%, "Integer", 0, 4, ^Integer%, 4 SYS "RegCloseKey", K% ENDIF
To read the values back from the registry do the following:
As always, ensure that you execute the DIM only once to avoid eventually running out of memory. Alternatively, consider using DIM LOCAL.DIM TempBuffer% 255 Key$ = "Software\YourName\YourProgram\Settings" SYS "RegOpenKeyEx", &80000001, Key$, 0, &20001, ^K% TO R% IF R% = 0 THEN L% = 255 : SYS "RegQueryValueEx", K%, "String", 0, ^T%, TempBuffer%, ^L% TO R% IF R% = 0 TempBuffer%?(L%-1) = 13 : String$ = $TempBuffer% L% = 4 : SYS "RegQueryValueEx", K%, "Integer", 0, ^T%, ^V%, ^L% TO R% IF R% = 0 Integer% = V% SYS "RegCloseKey", K% ENDIF
IF INSTR(@cmd$, "/c") THEN SYS "MessageBox", 0, "This screen saver has no options.", "", 48 QUIT ENDIF Preview% = INSTR(@cmd$, "/p") IF Preview% THEN SYS "SetWindowLong", @hwnd%, -16, &50000000 hparent% = VALMID$(@cmd$, Preview%+2) SYS "SetParent", @hwnd%, hparent% SYS "SetWindowLong", @hwnd%, -8, hparent% DIM rc{l%,t%,r%,b%} SYS "GetClientRect", hparent%, rc{} XScreen% = rc.r% YScreen% = rc.b% SYS "MoveWindow", @hwnd%, 0, 0, XScreen%, YScreen%, 0 ELSE SYS "SetWindowLong", @hwnd%, -16, &16000000 SYS "GetSystemMetrics", 0 TO XScreen% SYS "GetSystemMetrics", 1 TO YScreen% SYS "SetWindowPos", @hwnd%, -1, 0, 0, XScreen%, YScreen%, 0 ENDIF VDU 26 : COLOUR 15 : COLOUR 128 : CLS SYS "ShowWindow", @hwnd%, 1 MOUSE MouseX%, MouseY%, MouseB%
CIRCLE FILL XScreen%, YScreen%, YScreen%
IF Preview% = 0 THEN IF INKEY(0)<>-1 QUIT IF INKEY(-1) OR INKEY(-2) OR INKEY(-3) OR INKEY(-99) QUIT MOUSE X%,Y%,B% IF X%<>MouseX% OR Y%<>MouseY% OR B%<>MouseB% QUIT ENDIFOf course if you need to carry out any 'tidy up' processes, do them before each QUIT.
The parameters are the name of the font, the height of the font (in pixels), the weight (400 is normal, 800 is extra bold), the text string you want to draw, the angle to the horizontal (anticlockwise, degrees) and an X,Y starting position in graphics coordinates. The text is drawn in the current text foreground and background colours, as set by COLOUR.DEF PROCangled(font$, height%, weight%, text$, angle, X%, Y%) LOCAL font%, oldfont% X% = (X%+@vdu%!0)>>1 Y% = @vdu%!212-((Y%+@vdu%!4)>>1) SYS "CreateFont", height%, 0, angle*10, angle*10, weight%, \ \ 0, 0, 0, 0, 0, 0, 0, 0, font$ TO font% SYS "SelectObject", @memhdc%, font% TO oldfont% SYS "SetTextColor", @memhdc%, &1000000+@vdu%?70 SYS "SetBkColor", @memhdc%, &1000000+@vdu%?71 SYS "TextOut", @memhdc%, X%, Y%, text$, LEN(text$) SYS "InvalidateRect", @hwnd%, 0, 0 SYS "SelectObject", @memhdc%, oldfont% SYS "DeleteObject", font% ENDPROC
If you want to draw the text with a transparent (VDU 5 style) background rather than an opaque (VDU 4 style) background, replace the SYS "TextOut" line with the following:
SYS "SetBkMode", @memhdc%, 1 SYS "TextOut", @memhdc%, X%, Y%, text$, LEN(text$) SYS "SetBkMode", @memhdc%, 2
If you need to flush the cache to ensure you retrieve the latest version (e.g. it's a webcam picture) then it becomes slightly more complicated:url$ = "https://www.bbcbasic.co.uk/index.html" file$ = "c:\index.html" SYS "LoadLibrary", "URLMON.DLL" TO urlmon% SYS "GetProcAddress", urlmon%, "URLDownloadToFileA" TO UDTF% SYS UDTF%, 0, url$, file$, 0, 0 TO fail% IF fail% ERROR 100, "File download failed"
The URL can specify either an HTTP (Hyper Text Transfer Protocol) document or an anonymous FTP (File Transfer Protocol) file.url$ = "https://www.bbcbasic.co.uk/index.html" file$ = "c:\index.html" SYS "LoadLibrary", "URLMON.DLL" TO urlmon% SYS "GetProcAddress", urlmon%, "URLDownloadToFileA" TO UDTF% SYS "LoadLibrary", "WININET.DLL" TO wininet% SYS "GetProcAddress", wininet%, "DeleteUrlCacheEntryA" TO DUCE% SYS DUCE%, url$ SYS UDTF%, 0, url$, file$, 0, 0 TO fail% IF fail% ERROR 100, "File download failed"
CONTENTS |
CONTINUE |