PowerBasic

Introduction

PowerBasic is the ultimate Basic compiler for Windows! It was originally Bob Zale's project TurboBasic 1.0 over at Borland in 1985. After Borland discontinued the project following release 1.1 because it feared it could not compete with Microsoft's QuickBasic, Borland transferred the rights to the code to Bob, who left and built a company to market it. PowerBasic 2.0 was published in 1989 by Spectra Publishing. In addition to the regular release, PB 2.1 was released as shareware in 1996 as FirstBasic. Bob started his own company, PowerBasic Inc, to publish PowerBasic 3.0 in 1993.

In addition to command-line-based programs (PB/CC, PB/DOS, etc.), PowerBasic Inc offers PowerBasic for Windows (ex-PB/DLL) which is a compiler to build either DLLs or EXEs for Windows. Scroll down to see what the standard "Hello, world!" program looks like in PB/DLL. Being a small shop, PowerBasic doesn't have the means to promote PB/DLL much, so it has to be one of the most under-marketed gems out there.

Must haves

PowerBasic Pros and Cons

Pros

Cons

Hello, world!

The easiest way

#COMPILE EXE
 
Function PbMain() AS LONG
    MsgBox "Hello, World!"
End Function

The easier way

Here, we'll use the DDT extensions introduced with PB/DLL 6 to create a dialog box:

#Compile Exe
#REGISTER NONE
#DIM ALL
#include "win32api.inc"
 
%IDLABEL = 1000
 
CallBack Function IDD_DIALOG1_CB As Long
  
  Select Case CbMsg
 
                Case %WM_COMMAND
                      Select Case CbCtl
                                Case %IDOK
                                        DIALOG END CBHNDL
                      End Select
      
  End Select
  
End Function
 
Function PbMain() As Long
  Local hDlg As Long
  Local lRetVal As Long
  
  Dialog New %NULL, "Your base are belong to us", 0, 0, 186, 95, _
            %DS_MODALFRAME Or %DS_CENTER Or %WS_POPUP Or %WS_CAPTION Or %WS_SYSMENU, _
            %WS_EX_TOOLWINDOW, TO hDlg
 
  Control Add Label, hDlg, %IDLABEL, "Hello, world!", 71,39,70,8
  Control Add Button, hDlg, %IDOK, "OK", 65,74,50,14
 
  Dialog Show Modal hDlg, Call IDD_DIALOG1_CB TO lRetVal
  
End Function

The hard way

Here, we'll build a full-fledged window ā la Petzold (a trip down memory lane):

$COMPILE EXE
$INCLUDE "WIN32API.INC"
 
FUNCTION WinMain (BYVAL hInstance AS LONG,  BYVAL hPrevInstance AS LONG,  lpCmdLine AS ASCIIZ PTR,  BYVAL iCmdShow AS LONG) AS LONG
 
        LOCAL Msg         AS tagMsg
        LOCAL wndclass    AS WndClassEx
        LOCAL hWnd        AS LONG
        LOCAL szAppName   AS ASCIIZ * 80
        
        szAppName              = "HelloWin"
        
        wndclass.lpfnWndProc   = CODEPTR( WndProc )
        wndclass.cbSize        = SIZEOF(WndClass)
        wndclass.style         = %CS_HREDRAW OR %CS_VREDRAW
        wndclass.cbClsExtra    = 0
        wndclass.cbWndExtra    = 0
        wndclass.hInstance     = hInstance
        wndclass.hIcon         = LoadIcon( hInstance, "HELLOWIN" )
        wndclass.hCursor       = LoadCursor( %NULL, BYVAL %IDC_ARROW )
        wndclass.hbrBackground = GetStockObject( %WHITE_BRUSH )
        wndclass.lpszMenuName  = %NULL
        wndclass.lpszClassName = VARPTR( szAppName )
        wndclass.hIconSm       = LoadIcon( hInstance, BYVAL %IDI_APPLICATION )
        
        RegisterClassEx wndclass
 
        hWnd = CreateWindow(szAppName, _               ' window class name
                          "The Hello Program", _     ' window caption
                          %WS_OVERLAPPEDWINDOW, _    ' window style
                          %CW_USEDEFAULT, _          ' initial x position
                          %CW_USEDEFAULT, _          ' initial y position
                          %CW_USEDEFAULT, _          ' initial x size
                          %CW_USEDEFAULT, _          ' initial y size
                          %NULL, _                   ' parent window handle
                          %NULL, _                   ' window menu handle
                          hInstance, _               ' program instance handle
                          BYVAL %NULL)               ' creation parameters
 
        ShowWindow hWnd, iCmdShow
        UpdateWindow hWnd
 
        WHILE GetMessage(Msg, %NULL, 0, 0)
                TranslateMessage Msg
                DispatchMessage Msg
        WEND
 
        FUNCTION = msg.wParam
 
END FUNCTION  ' WinMain
 
FUNCTION WndProc (BYVAL hWnd AS LONG, BYVAL wMsg AS LONG, BYVAL wParam AS LONG, BYVAL lParam AS LONG) EXPORT AS LONG
 
        LOCAL hDC       AS LONG
        LOCAL LpPaint   AS PaintStruct
        LOCAL tRect     AS Rect
 
        SELECT CASE wMsg
        
                CASE %WM_CREATE
                
                CASE %WM_PAINT
                        hDC = BeginPaint(hWnd, LpPaint)
                        GetClientRect hWnd, tRect
                        DrawText hDC, "Hello, Windows 95!", -1, tRect, %DT_SINGLELINE OR %DT_CENTER OR %DT_VCENTER
                        EndPaint hWnd, LpPaint
                        FUNCTION = 0
                        EXIT FUNCTION
                
                CASE %WM_DESTROY
                        PostQuitMessage 0
                        FUNCTION = 0
                        EXIT FUNCTION
        
        END SELECT
 
END FUNCTION

GUI builders

Why use dialogs instead of windows?

A dialog doesn't require registering a window class, and it can be easily designed using WYSIWYG tools such as those presented below.

From Chris Boss: Dialogs benefit from the extra processing of messages by the DefDialogProc function, (rather than DefWindowProc). A number of common messages are processed differently by this function. This extra processing basically benefits a Dialog with standard controls in areas such as the keyboard (ie. Tabbing).

The disadvantage to Dialogs is that you don't have access to the Dialogs Window procedure, but only a Dialog procedure (they are different). For example, a Dialog procedure will not get a WM_CREATE message, but will get the WM_INITDIALOG message. A Window (non dialog) will get the WM_CREATE message, but not the WM_INITDIALOG. Such things as MDI are not well suited to use with Dialogs. [...] On the other hand Dialogs are easier and will handle more than 90% of your needs.

From Chris Boss, again : DDT is dependant upon the Windows Dialog engine which doesn't give you access to the Dialog Class Window procedure, but just a Dialog procedure called by the Class Window procedure. While you could subclass a DDT Dialog, it is just easier to use SDK created Dialogs if you want more control.

DDT (and any Windows Dialog engine created windows) use the DefDialogProc API function internally, where as SDK windows normally use the DefWindowProc API function. There is a significant difference between the two functions ! Since, DDT uses the Windows Dialog engine, one advantage it has is the a number of common tasks are already handled by the Windows dialog engine (ie. WM_NEXTDLGCTL message). When using SDK style windows you need to write your own code to process some messages, that the Windows Dialog engine handles already.

If your Dialogs are more oriented to standard type Dialogs using  standard controls, then DDT may be better suited. If your Dialogs need to do some custom stuff (ie. MDI, non-rectangular dialog using regions) then SDK windows are better suited.

Most Dialogs can be created with DDT and development is faster and easier. There are instances where SDK style Dialogs are a must though and a programmer should learn how to do both.

Also the Windows Dialog engine (which includes DDT) uses a portion of the available 40 bytes (30 bytes are used) for the extra window bytes , which means you have less of these bytes available for custom storage for the windows extra bytes. Windows 95/98 limits you to 40 bytes (I tested this and it is so and it applies to 32 bit windows and not just 16 bit). If you need more bytes for the extra window bytes, then you will have to do extra work with DDT, since you will have to create your own Global UDTs to store data. There are situations where the extra data space available for SDK windows is very useful.

Lastly, the Windows Dialog engine adds an extra amount of overhead when processing messages, since it has both a Window Procedure (the Dialog Class handles this) and a Dialog Procedure. Unless you subclass the Dialog, DDT is doing extra processing (albeit very little, but something) than an SDK Window class. If your Dialog requires the optimum speed (we are talking about extremely fast), then SDK style windows will save a few CPU cycles because it has less overhead.

To be practical though, the extra overhead when using the Windows Dialog engine (or DDT) is negligable. It is there, but it is so small that in most instances it is insignificant !

The main point here is :

Chris Boss :-) : Here are a few more minor differences between DDT and SDK : DDT uses Dialog Units, while SDK uses Pixels. Dialog Units has the advantage of autoscaling your Dialog based on the end users Font setting (Small Fonts/Large Fonts), while SDK does not (You have to write your own autoscaling code).

On the other hand, Dialog Units (DDT) prevent you from having exact positioning of controls to an exact pixel. For example, try a column of ten buttons evenly spaced. The odds are you will have a tough time getting them to have the exact same number of pixels between them.

The reason for this, is that DDT defines the default font to MS Sans Serif 8 while the Windows Dialog engine uses the System font instead (if no font is defined). The system font divides more evenly into exact pixels (a Dialog Unit is 2 x 2 pixels when using small fonts - character size is 8 x 16 pixels), while DDT's MS Sans Serif font doesn't divide well (character size is 6 x 13 pixels).

In most cases this doesn't cause a problem, but when the dialog requires some very exact and simetrical positioning of controls, DDT will be a hair off. This is nothing new though, since C programmers who create Dialogs as resources and who use CreateDialog (API) have suffered with this problem long before PB added DDT.

This doesn't mean DDT is bad or at fault, it is just a reality of dealing with the Windows Dialog engine and using Dialog Units. As I said, DDT works for 90% (or more) of the needs for a Windows programmer.

You might find it interesting, that when I had to make a choice of whether to use the Windows Dialog engine or CreateWindowEX in my third party tools to create the Dialogs, I chose the Windows Dialog engine ! The advantages of DefDialogProc (which is called by the Windows Dialog engine) far outwayed the benefits of SDK created windows (CreateWindow). This advantage also exists in DDT !

My recommendation is that a programmer learn both ways of writing Windows apps. For beginners, DDT is much easier so they should start there. At some point it is good to learn how to write apps using the SDK style and to experiment and see the differences. If at some point a programmer finds that SDK style code doesn't offer them any significant advantages, then just stick with DDT. If you find that SDK style code offers some advantages crucial to your needs, then by all means use it !

From Eric Pearson : To clarify... > DDT uses Dialog Units, while SDK uses Pixels.

That's not true if you use SDK techniques to create dialogs. In fact that's the way most of my own programs are written. I create a dialog in a resource file and link it to my program. The program then uses SDK-style techniques to display and control the dialogs. And as a result, almost everything is done in dialog units, not pixels.

I do find myself writing more and more new programs using DDT code, however. For most of my applications (95%?) it is more than sufficient.

My personal summary of "DDT vs. SDK"...

SDK means "using the native API", and that implies that everything is possible if you are willing to write enough code, and to wrestle with the complexities of the API.

DDT is much easier to use. It requires much less coding and knowledge of the Windows APIs, and it can do nearly everything.

FireFly SDK Design (ex-VisualPB, ex-PowerGUI)

PwrDev

EZGUI

PowerBasic Forms

Son of PowerGen, commercial product from PB. Dialog interface builder similar to VB, but adds DDT instructions to BAS file. As long as you don't mess up the instructions added by PBForm, you can edit the code back and forth between the PB IDE and PBForms.

Personally, the sharing of a BAS file between PB and PBForms and the fact that PBForms is a very recent tool... make me prefer other tools like EZGUI or even VD. I'm just don't trust such a recent tool to parse code correctly without messing up the code I added manually in the PB IDE.

PowerGen

PowerGen Visual Designer 16-bit commercial product from PowerBasic. Use a dialog editor such as Microsoft's Dialog Editor or Borland's Resource Workshop to generate a resource file, and PowerGen will turn it into a .BAS template, not unlike working with VC++ and MFCs. Create the dialog with any editor (Microsoft Developer Studio, etc.), save the file as an ASCII-based .RC file, run Microsoft's RC.EXE applet to turn into a binary .RES file, use PowerBasic's PBRES.EXE applet to convert the .RES file into a .PBR file, and add this reference into your source code (#RESOURCE "MYRES.PBR"). As an alternative, use James Fuller's RSRC.EXE to embed an .RES file into the .EXE .

  1. Launch the Microsoft Dialog Editor
  2. Create a dialog, and save it: This creates two files, MYDLG.DLG (ASCII) and MYDLG.RES (Binary). DE actually works off the .RES file, so any change that you made manually in the .DLG are lost the next time you launch DE and make changes there
  3. Make a copy of those two files, rename the .DLG file with a .RC extension, and edit it as follows:
    1. Remove the following lines from the resource script:
        DLGINCLUDE RCDATA FIXED IMPURE
        BEGIN
            "MYDLG.H\0"
        END
    2. Add #include "MYDLG.H"
    3. Add #include "RESOURCE.H"
    4. Create a  file to include the resource equates, eg. MYDLG.H (equates are constants that you use to refer to widgets through names instead of ID numbers; equates are case sensitive)
  4. Run this .RC file through the RC.EXE resource compiler. This generates an .RES file... which is incompatible with the original .RES that you created above (hence the backup)!
  5. Convert this .RES file into a .PBR with the utility PBRES
  6. Edit your .BAS source file, add a $RESOURCE meta-statement so the PB/DLL compiler includes this resource into the .EXE

Dynamic Dialog Tool (DDT)

Those new commands introduced in PB/DLL 6 let you build a dialog box in your source code instead of using a third-party application. Since it's a pain to build dialogs in code, you'll probably want to draw them in eg. MS Dialog Editor, and fetch coordinates of each widget. James C. Fuller's RC2DDT generates a .BAS template with DDT extensions, ready to copy/paste into your source file

Visual Forms Editor

Cool VB IDE rip-off designer. Proof of concept by Jules Marchildon. Only available as a demo, which won't let you save the dialogs you created. Jules Marchildon. Available on PowerBasic's web site.

Dialog Editor Controls

Shareware. The demo version won't save files. Current release is 2.10. Written by Simon Whiteside. More recent editor than MS Dialog Editor, and supports common controls.

PWinADP

JC Fuller's PBWinADP interface builder Another free (16-bit) interface builder. Create the UI in a RC file with a third-party editor. A PBWin ADP project is a collection of dialogs created in an external Dialog Editor; One dialog is set as the starting dialog; All dialogs should be named DLG_xxx, where xxx is a number (a description for each dialog in the project list.)

Visual MetaFourGL

MetaFourGL, a nice (and free!) interface builder for PowerBasic. No grid to adjust widgets; no support for common controls or cool-bar; can't get properties of a widget by double-clicking on it; when generating .BAS file, says that it generated .BAS and .PBR, but only see .BAS and .RC; Neither Compile nor Run works (if try to run generated . BAS with PBDLL.EXE, err with "Required INITCOMMONCONTROLS"; Generated source code a bit complicated.

A project (eg. MYPROJ.GGP) includes all the forms (dialogs, eg. MYFRM1.GGF) that your application needs. VMGL uses. Add your code in each form. VMGL generates a .BAS file by combining the templates UNTITLED.GGC (code pattern used for each program), UNTITLE2.GGC (code pattern used for all forms other than the main form), and your project-specific files (MYPROJ.GGP and MYFRM1.GGF, here). You should not modify the templates UNTITLED.GGC and UNTITLE2.GGC, as upgrading to the next version of VMGL will replace those files with the latest version.

CodeMax OCX

(RIP?) Free. Available from WinMain.

Web applications

Here's how to build an EXE that you can call with eg. http://localhost/list.exe?dir=temp, and displays a list of OCXs in the directory you specify, each with its filename + date (dd/mm/yyyy) + time (hh:mm:ss) + version number (1.0.0.0):

DDT

Should write an introduction to what is DDT (as opposed to alternatives), how to build dialogs (manually; with PBForms, etc.), and how to interact with controls

Getting the text selected in a listbox

Like most cases, you need to send a message to the listbox to get the string and item # that is currently selected:

Dim lIndex as Long
Control Send CbHndl, %IDC_LISTBOX1, %LB_GETCURSEL, 0, 0 to lIndex
LISTBOX GET TEXT CBHNDL, %IDC_LISTBOX1 TO sListItem
MsgBox sListItem, %MB_TASKMODAL,"You selected item#" & str$(lIndex)

Setting the text of a label

In PBMain()

CONTROL SET TEXT hDlg, %IDC_LABEL1, "Original text"

In the dialog's event handler:

CONTROL SET TEXT CBHNDL, %IDC_LABEL1, "New text

List of DDT instructions

Excerpt from the HLP file

You can convert between Dialog Units and Pixels with the built in DIALOG PIXELS and DIALOG UNITS statements.

All of the control and dialog message equates are located in the DDT.INC file.  This file is simply a subset of the much larger WIN32API.INC file and is provided only for convenience.  Therefore, the use of these two files is mutually exclusive.

If your code processes one of these event messages, it should return non-zero to Windows, by setting FUNCTION = 1 within the Control Callback.  This tells Windows that it should not need to process that message any further.  If you return zero, Windows will pass this message on to your Dialog Callback.  If the message is still unhandled by your Dialog Callback, the DDT dialog engine itself will handle the message on your behalf.  This can add unnecessary overhead to the performance of your application.

CONTROL ADD BUTTON, hDlg, %IDOK, "OK", 34, 32, 40, 14, %BS_DEFAULT OR %WS_TABSTOP CALL OkButton

In the generally case, a dialog Callback Function should return TRUE (non-zero) for all %WM_COMMAND messages it processes.  However, this rule cannot be equally applied to other types of messages, since the return value will be message-specific.
In some cases, especially when dealing with Common Controls and custom controls, it can become necessary to return an additional result value to the dialog engine through a special data area referred to by the name DWL_MSGRESULT.

The default style comprises the combination of %WS_POPUP, %WS_CAPTION, %DS_SETFONT, %DS_NOFAILCREATE, %DS_MODALFRAME, and %DS_3DLOOK.  These equates are equivalent to a style of &H080C00D4.  The extended style default is zero.
If you explicitly specify %WS_CAPTION in your DIALOG NEW statement, then DDT will interpret the width and height values as client dimensions, rather then as overall dialog dimensions.  This can be very useful for the times when you need to build a dialog with particular client dimensions.

Purpose Add a custom control to a DDT dialog.

Syntax  CONTROL ADD classname$, hDlg&, id&, txt$, x, y, xx, yy [, [style&] [, [exstyle&]]] [[,] CALL callback]  

When the Callback Function receives a %WM_COMMAND message, the identity of the control sending the message can be found with the CBCTL function.  Use the CBCTLMSG function to retrieve the notification message value in your callback.  However, many Windows common controls send %WM_NOTIFY messages rather than the more conventional %WM_COMMAND messages.  In such cases, the meaning of the message parameters CBWPARAM and CBLPARAM will vary according to the type of notification message being processed.

Pointers and calling DLLs

More infos here on the solutions available to make calls to DLLs written in C.

Managing data

More infos here.

MySQL

SQLite

Code by Nathan Evans. When performing heavy changes such as creating thousands of records, remember to send the "BEGIN;" and "COMMIT;" SQL instructions before and after making changes, respectively; Otherwise, you will get pathetic performances

SQLTools

From PerfectSync. Eases use of ODBC from PowerBasic to connect to SQL servers.

PowerTree

DvBTree

Cheetah

VB/ISAM

Tsunami Record Manager

This is a free one-DLL database application for Windows by Timm Motl (Advantage Systems, Timm Motl). To use Tsunami, simply copy its header file trm.inc and its engine trm.dll in the same directory as your PB/DLL application, and add #include "trm.inc" in the source file. That .DLL is the only file that is required to work with a Tsunami data file. Tsunami is a page-structured file. Depending on the average size of the records that Tsunami will have to handle, you'll have to decide on the size of the page size in your Tsunami file, ranging from 1K to 8K.

LOCAL PageSize    AS LONG
LOCAL Compression AS LONG
LOCAL KeySegments AS LONG
LOCAL FileDef     AS STRING
LOCAL KeyNo       AS LONG
LOCAL KeyFlags    AS LONG
LOCAL OverWrite   AS LONG
LOCAL lResult as LONG
LOCAL InputString AS STRING
LOCAL Count       AS QUAD
LOCAL FileSize    AS QUAD
LOCAL Record      AS STRING
LOCAL sResult as String
 
PageSize = 1
Compression = %TRUE
KeySegments = 1
KeyNo = 1
KeyFlags = %NO_COMPRESSION
 
FileDef = MKBYT$(PageSize) + MKBYT$(Compression) + MKBYT$(KeySegments) + "Account Number           " + MKBYT$(KeyNo) + MKWRD$(1) + MKBYT$(6) + MKBYT$(KeyFlags)
OverWrite = %TRUE
lResult = trm_Create("MYFILE.DAT", FileDef, OverWrite)
 
hFile = trm_Open(Path + "TRMDEMO.DAT", %FALSE)
 
InputString = "  3934DOWNIN                  DANA               4850 FREDRICK ST              CORAL SPRINGS   FL33071"
lResult = trm_Insert(hFile, InputString)
 
Count = trm_Count(hFile)
FileSize = trm_FileSize(hFile)
 
Record = trm_GetFirst(hFile, CurrKeyPath)
lResult = trm_Result(hFile)
sResult = sResult + TRIM$(MID$(Record,  1,  6)) ' Acct No
sResult = sResult + TRIM$(MID$(Record,  7, 24)) ' Last Name
sResult = sResult + TRIM$(MID$(Record, 31, 18)) ' First Name
sResult = sResult + TRIM$(MID$(Record, 49,  1)) ' Middle Initial
sResult = sResult + TRIM$(MID$(Record, 50, 30)) ' Street
sResult = sResult + TRIM$(MID$(Record, 80, 16)) ' City
sResult = sResult + TRIM$(MID$(Record, 96,  2)) ' State
sResult = sResult + TRIM$(MID$(Record, 98, 5)) ' Zip Code
 
Record = trm_GetNext(hFile)
 
Record = trm_GetEqual(hFile, CurrKeyPath,"  3934")
 
lResult = trm_CloseAll  

Common Controls

Those are additional widgets introduced since Windows 95. When using DDT, you will have to rely on the CONTROL ADD classname instruction.

RichText

LOCAL hRichEd AS LONG
LOCAL CC1 AS INIT_COMMON_CONTROLSEX

hRichEd = LoadLibrary("RICHED32.DLL")
CC1.dwSize=SIZEOF(CC1)
CC1.dwICC=%ICC_WIN95_CLASSES
INITCOMMONCONTROLSEX CC1

Tips and Tricks

Zipping

A PowerBasic interface to zLib.dll is here.

Checking if a file exists

FUNCTION Exist(File$) AS LONG
  LOCAL Dummy&
  Dummy& = GETATTR(File$)
  FUNCTION = (ERRCLEAR = 0)
END Function

Note: Some routines given in the PowerBasic forum such as FileExists() don't work when used in a DIR$ loop.

Writing CGI programs

Creating Dynamic Web Pages with PB/CC

Regex

To extract the section of a string

Dim sFull as String, sMask as String, lPosVar as Long, lLenVar as Long
sFull = "I can be reached at john@acme.com"
sMask    = "[a-z_.-]+@[a-z_.-]+"
REGEXPR sMask IN sFull TO lPosVar, lLenVar
Msgbox MID$(sFull, lPosVar, lLenVar)

To extract a tag

A tag is defined as a sub-pattern of a mask. In this example, we'd like to extract just the actual content of the Description metatag of a web page, ie. ignore the rest of the mask (let's pretend the web page was read into sFull). As of June 2004, it appears that the REGEXPR or REGREPL functions can't do this, so we'll use other functions:

sInput = "blablabla<meta name=""description"" content="some cool doco">blablabla"
'REMAIN() = return stuff after this pattern
'EXTRACT() = return stuff until this pattern
MsgBox EXTRACT$(REMAIN$(sInput,"description"" content="""),""">")

Replacing a section in a string

sFull = ""
sMask = ""
sReplaceMask = ""
REGREPL sMask IN sFull WITH sReplaceMask TO iPosvar, sNewFull
MsgBox sNewFull

Compiling from UltraEdit

Advanced | Tool Configurations:

Click on Insert, and OK

PowerBasic add-on for UltraEdit

To tell UltraEdit how to handle PowerBasic files, just edit WORDFILE.TXT located in UltraEdit's directory, and add this section (Note: Make sure no L11 section already exists):

/L11"PowerBasic" Line Comment Num = 2' Line Comment = ' Block Comment On Alt = #PBFORMS File Extensions = BAS INC
/Delimiters = ~!@^*()+=|\{}[]:;"'       ,.<>
/Function String = "%*^{Sub^}^{Function^}*("
/C1="STATEMENTS"
ABS ACCEL ACCEPT ACCESS ACODE$ ADD ADDR ALIAS ALL AND ANY APPEND ARRAY ARRAYATTR AS ASC ASCEND ASCIIZ ASCIZ ASM AT ATN ATTACH ATTRIB
BAR BASE BAUD BDECL BEEP BIN$ BINARY BIT BITS% BITS& BITS? BITS?? BITS??? BREAK BUTTON BYCMD BYCOPY BYREF BYTE BYVAL
CALC CALL CALLBACK CALLSTK CALLSTK$ CALLSTKCOUNT CASE CATCH CBCTL CBCTLMSG CBHNDL CBLPARAM CBMSG CBWPARAM CBYT CCUR CCUX CD CDBL
CDECL CDWD CEIL CEXT CHDIR CHDRIVE CHECK CHECK3STATE CHECKBOX CHOOSE CHOOSE$ CHOOSE% CHOOSE& CHR$ CINT CLIENT CLNG CLOSE CLSID$
CODEPTR COLLATE COLOR COMBOBOX COMM COMMAND$ CON CONNECT CONST CONST$ CONTROL COS CQUD CREATE CSET CSET$ CSNG CTSFLOW CUR CURDIR$
CURRENCY CURRENCYX CUX CVBYT CVCUR CVCUX CVD CVDWD CVE CVI CVL CVQ CVS CVWRD CWRD
DATA DATACOUNT DATE$ DECLARE DECR DEFAULT DEFBYT DEFCUR DEFCUX DEFDBL DEFDWD DEFEXT DEFINT DEFLNG DEFQUD DEFSNG DEFSTR DEFWRD DELETE
DESCEND DIALOG DIM DIR$ DISABLE DISKFREE DISKSIZE DISPATCH DLL DLLMAIN DLLMAIN& DO DOEVENTS DOUBLE DRAW DSRFLOW DSRSENS DTRFLOW
DTRLINE DWORD
ELSE ELSEIF EMPTY ENABLE END ENVIRON$ EOF EQV ERASE ERR ERRAPI ERRCLEAR ERROR ERROR$ EXE EXIT EXP EXP10 EXP2 EXPLICIT EXPORT EXT
EXTENDED EXTRACT$
FILEATTR FILECOPY FILENAME$ FILESCAN FILL FINALLY FIX FLOW FLUSH FOCUS FONT FOR FORMAT$ FRAC FRAME FREEFILE FROM FUNCNAME$ FUNCTION
GET GET$ GETATTR GLOBAL GOSUB GOTO GUID GUID$ GUIDTXT$
HANDLE HEX$ HIBYT HIINT HIWRD HOST
ICASE ICON IDN IF IFACE IIF IIF$ IIF% IIF& IMAGE IMAGEX IMGBUTTON IMGBUTTONX IMP IN INCR INOUT INP INPUT INPUT# INPUT$ INPUTBOX$
INSERT INSTR INT INTEGER INTERFACE INV ISFALSE ISNOTHING ISOBJECT ISTRUE ITERATE
JOIN$
KILL
LABEL LBOUND LCASE$ LEFT LEFT$ LEN LET LIB LIBMAIN LIBMAIN& LINE LISTBOX LOBYT LOC LOCAL LOCK LOF LOG LOG10 LOG2 LOINT LONG LOOP
LOWRD LSET LSET$ LTRIM$
MACRO MACROTEMP MAIN MAIN& MAKDWD MAKINT MAKLNG MAKPTR MAKWRD MAT MAX MAX$ MAX% MAX& MCASE$ MEMBER MENU MID$ MIN MIN$ MIN% MIN&
MKBYT$ MKCUR$ MKCUX$ MKD$ MKDIR MKDWD$ MKE$ MKI$ MKL$ MKQ$ MKS$ MKWRD$ MOD MODAL MODELESS MOUSEPTR MSGBOX
NAME NEW NEXT NONE NOT NOTHING NOTIFY NULL
OBJACTIVE OBJECT OBJPTR OBJRESULT OCT$ OF OFF ON OPEN OPT OPTION OPTIONAL OR OUT OUTPUT
PARITY PARITYCHAR PARITYREPL PARITYTYPE PARSE PARSE$ PARSECOUNT PBLIBMAIN PBLIBMAIN& PBMAIN PBMAIN& PEEK PEEK$ PIXELS POINTER POKE
POKE$ POPUP PORT POST PRESERVE PRINT PRINT# PRIVATE PROFILE PROGID$ PTR PUT PUT$
QUAD QWORD
RANDOM RANDOMIZE READ READ$ RECORDS RECV REDIM REDRAW REGEXPR REGISTER REGREPL REM REMAIN$ REMOVE$ REPEAT$ REPLACE RESET RESUME
RETAIN$ RETURN RGB RIGHT RIGHT$ RING RLSD RMDIR RND ROTATE ROUND RSET RSET$ RTRIM$ RTSFLOW RXBUFFER RXQUE
SCAN SCROLLBAR SDECL SEEK SELECT SEND SERVER SET SETATTR SETEOF SGN SHARED SHELL SHIFT SHOW SIGNED SIN SINGLE SIZE SIZEOF SLEEP SORT
SPACE$ SPC SQR STATE STATIC STATUS STDCALL STEP STOP STR$ STRDELETE$ STRING STRING$ STRINSERT$ STRPTR STRREVERSE$ SUB SUSPEND SWAP
SWITCH SWITCH$ SWITCH% SWITCH&
TAB TAB$ TAGARRAY TALLY TAN TCP TEXT TEXTBOX THEN THREAD THREADCOUNT THREADID TIME$ TIMEOUT TIMER TO TOGGLE TRACE TRIM$ TRN TRY
TXBUFFER TXQUE TYPE
UBOUND UCASE UCASE$ UCODE$ UDP UNION UNITS UNLOCK UNTIL USER USING USING$
VAL VARIANT VARIANT# VARIANT$ VARIANTVT VARPTR VERIFY VERSION3 VERSION4 VERSION5
WEND WHILE WIDTH WIDTH# WINMAIN WINMAIN& WITH WORD WRITE WRITE#
XINPFLOW XOR XOUTFLOW
ZER
/C2"PRECOMPILER"
!
#BLOAT #COMPILE #DEBUG #DIM #ELSE #ELSEIF #ENDIF #IF #INCLUDE #OPTION #PBFORMS #Register #Resource #Stack #Tools $Bel
$BLOAT $COMPILE $DEBUG $DIM $ELSE $ELSEIF $ENDIF $IF $INCLUDE $OPTION $REGISTER $RESOURCE $STACK $TOOLS
/C2"DEFINITIONS"
$CR $CRLF $BS $DQ $EOF $ESC $FF $LF $NUL $SPC $TAB $VT
%BLACK %BLUE %CYAN %GRAY %GREEN %LTGRAY %MAGENTA %RED
%DEF
%ERR_BADFILEMODE %ERR_BADFILENAME %ERR_BADFILENAMEORNUMBER %ERR_BADRECORDNUMBER %ERR_COMMERROR %ERR_DEVICEIOERROR %ERR_DEVICETIMEOUT
%ERR_DEVICEUNAVAILABLE %ERR_DISKFULL %ERR_DISKMEDIAERROR %ERR_DISKNOTREADY %ERR_FARHEAPCORRUPT %ERR_FILEALREADYEXISTS
%ERR_FILEISOPEN %ERR_FILENOTFOUND %ERR_ILLEGALFUNCTIONCALL %ERR_INPUTPASTEND %ERR_INTERNALERROR %ERR_NOERROR %ERR_OUTOFMEMORY
%ERR_PATHFILEACCESSERROR %ERR_PATHNOTFOUND %ERR_PERMISSIONDENIED %ERR_RENAMEACROSSDISKS %ERR_STRINGSPACECORRUPT
%ERR_SUBSCRIPTPOINTEROUTOFRANGE %ERR_TOOMANYFILES
%PB_CC32 %PB_DLL16 %PB_DLL32 %PB_EXE %PB_REVISION %PB_REVLETTER %PB_WIN32
%VARCLASS_ASC %VARCLASS_BYT %VARCLASS_CUR %VARCLASS_CUX %VARCLASS_DBL %VARCLASS_DWD %VARCLASS_EXT %VARCLASS_FIX %VARCLASS_GUID
%VARCLASS_IFAC %VARCLASS_INT %VARCLASS_LNG %VARCLASS_QUD %VARCLASS_SNG %VARCLASS_STR %VARCLASS_TYPE %VARCLASS_VRNT %VARCLASS_WRD
%VT_ARRAY %VT_BLOB %VT_BLOB_OBJECT %VT_BOOL %VT_BSTR %VT_BYREF %VT_CARRAY %VT_CF %VT_CLSID %VT_CY %VT_DATE %VT_DISPATCH %VT_EMPTY
%VT_ERROR %VT_FILETIME %VT_HRESULT %VT_I1 %VT_I2 %VT_I4 %VT_I8 %VT_INT %VT_LPSTR %VT_LPWSTR %VT_NULL %VT_PTR %VT_R4 %VT_R8
%VT_SAFEARRAY %VT_STORAGE %VT_STORED_OBJECT %VT_STREAM %VT_STREAMED_OBJECT %VT_UI1 %VT_UI2 %VT_UI4 %VT_UI8 %VT_UINT %VT_UNKNOWN
%VT_USERDEFINED %VT_VARIANT %VT_VECTOR %VT_VOID
%WHITE
%YELLOW

Timing a task

LOCAL Start       AS DWORD
LOCAL Finish      AS DWORD
LOCAL TotalTime   AS DWORD
Start = GetTickCount
Finish = GetTickCount
TotalTime = TotalTime + (Finish - Start)

Coordinates

Check where a user double-clicked in a dialog box

SELECT CASE CBMSG
    Case %WM_LBUTTONDBLCLK
        LOCAL sEvent AS ASCIIZ * 32, iX AS INTEGER, iY AS INTEGER
        
        iX = LOWRD(CBLPARAM)
        iY = HIWRD(CBLPARAM)
 
        sEvent = "x=" + Str$(iX) + ", y=" + str$(iY)
 
        Call SetWindowText (CBHNDL,sEvent)

Icons

Setting the title bar icon

DIALOG SEND hDlg, %WM_SETICON, %ICON_SMALL, LoadIcon(BYVAL %NULL, BYVAL %IDI_WINLOGO)

Adding an icon in the resource file, and loading it

    1. In the .RC file, add references to icon files: MYICON               ICON    DISCARDABLE     "cool.ico"
    2. Use RC.EXE to convert this .RC file into a .RES file
    3. Use PBRES.EXE to turn the .RES file into a .PBR
    4. In your program, add a reference: #RESOURCE "MYRES.PBR"
    5. Load the icon: Call SendMessage (hDlg, %WM_SETICON, %ICON_SMALL, LoadIcon(GetModuleHandle("MYPRG.EXE"), "MYICON"))

Displaying an icon

hDC = GetDC (CBHNDL)
Call DrawIcon (hDC, 33, 180, hIcon)
Call ReleaseDC (CBHNDL, hDC)

Static

Selecting a different font

LOCAL hOldFont AS LONG
LOCAL hNewFont AS LONG
 
hNewFont = CreateFont(0,0,0,0, %FW_HEAVY        ,%TRUE,%FALSE,%ANSI_CHARSET,%OUT_TT_PRECIS,0, _
    %PROOF_QUALITY,%DEFAULT_PITCH,%FF_DONTCARE,"Arial")
 
CONTROL Send CBHNDL, %IDC_LABEL,%WM_GETFONT,0,0 to hOldFont
CONTROL Send CBHNDL, %IDC_LABEL, %WM_SETFONT, hNewFont,0
CONTROL Set TEXT hDlg, %IDC_LABEL, "PowerBasic Rules!"
CONTROL Send CBHNDL, %IDC_LABEL,hOldFont,0,0

Changing the font

LOCAL hOldFont AS LONG
 
CONTROL Send CBHNDL, %IDC_LABEL,%WM_GETFONT,0,0 to hOldFont
CONTROL Send CBHNDL, %IDC_LABEL, %WM_SETFONT, GetStockObject( %SYSTEM_FIXED_FONT ),0
CONTROL Set TEXT hDlg, %IDC_LABEL, "PowerBasic Rules!"
CONTROL Send CBHNDL, %IDC_LABEL,hOldFont,0,0

RichEdit widget

Change color and font

Type charformat2a
    cbSize As Long
    dwMask As Dword
    dwEffects As Dword
    yHeight As Long
    yOffset As Long
    crTextColor As Dword
    bCharSet As Byte
    bPitchAndFamily As Byte
    szFaceName As Asciiz * %LF_FACESIZE
    wpad2 As Integer
    wWeight As Word
    sSpacing As Integer
    crBackColor As Dword
    lcid As Long
    dwReserved As Dword
    sStyle As Integer
    wKerning As Word
    bUnderlineType As Byte
    bAnimation As Byte
    bRevAuthor As Byte
    bReserved1 As Byte
End Type
 
LOCAL MyFormat AS charformat2a
LOCAL sFaceName AS ASCIIZ * 64
sFaceName = "Verdana"
                                        
MyFormat.cbSize = SizeOf(MyFormat)
MyFormat.dwMask = %CFM_FACE OR %CFM_COLOR
MyFormat.szFaceName = sFaceName
MyFormat.crTextColor = RGB (0,0,255)
 
CONTROL Send CBHNDL, %IDC_RICHEDIT1, %EM_SETCHARFORMAT, 0,VARPTR(MyFormat)

Threads

#COMPILE EXE
#DIM ALL
#REGISTER NONE
#INCLUDE "WIN32API.INC"
 
GLOBAL hDlg                              AS LONG
 
FUNCTION ThreadInst(BYVAL lVal AS LONG) AS LONG
    LOCAL lRet AS LONG
 
    'Need WHILE, or will execute once and go
    While %TRUE
        Call SetWindowText (hDlg, TIME$)
        SLEEP 1000
    Wend
End FUNCTION
 
CALLBACK FUNCTION IDD_DIALOG1_CB AS LONG
    'Nichts
END FUNCTION
 
FUNCTION PBMain () AS LONG
    LOCAL hThread AS LONG, lRetVal AS LONG
 
    THREAD CREATE ThreadInst(0) TO hThread
    
    DIALOG NEW 0, "About to be erased...", 0, 0, 279, 169, _
            %DS_MODALFRAME OR %DS_CENTER OR %WS_POPUP OR %WS_CAPTION OR %WS_SYSMENU, _
            %WS_EX_TOOLWINDOW, TO hDlg
 
    DIALOG SHOW MODAL hDlg,CALL IDD_DIALOG1_CB TO lRetVal
    
    THREAD CLOSE hThread TO retVal
        
END FUNCTION

Timer

#COMPILE EXE
#DIM ALL
#REGISTER NONE
#INCLUDE "WIN32API.INC"
 
GLOBAL hDlg                              AS LONG
 
CALLBACK FUNCTION IDD_DIALOG1_CB AS LONG
  
    SELECT CASE CBMSG
        Case %WM_TIMER
            Call SetWindowText (hDlg, TIME$)
    END SELECT
END FUNCTION
 
FUNCTION PBMain () AS LONG
    LOCAL hThread AS LONG, lRetVal AS LONG
    DIALOG NEW 0, "About to be erased...", 0, 0, 279, 169, _
            %DS_MODALFRAME OR %DS_CENTER OR %WS_POPUP OR %WS_CAPTION OR %WS_SYSMENU, _
            %WS_EX_TOOLWINDOW, TO hDlg
 
    Call SetTimer(hDlg, BYVAL &H0000FEED, 1000, BYVAL %NULL)
 
    DIALOG SHOW MODAL hDlg,CALL IDD_DIALOG1_CB TO lRetVal
        
    KillTimer hDlg, &H0000FEED
END FUNCTION

TreeView

  1. Add a control in your dialog box

    CONTROL ADD "SYSTREEVIEW32", hDlg, %IDC_TREE1, "Tree1", 7, 7, 202, 72, _
                  %WS_CHILD or %WS_VISIBLE or %TVS_HASBUTTONS or %TVS_HASLINES or _
                  %TVS_LINESATROOT or %TVS_SHOWSELALWAYS, %WS_EX_CLIENTEDGE, _ , 'CALL IDC_TREE1_CB
     
  2. In the dialog callback function, add items to the tree:

    GLOBAL CurrentItem AS STRING
    GLOBAL hDlgTree As Long

    FUNCTION TVInsertItem(byval hTree as long, byval hParent as long, sTxt as string) as long
        local tv_insert as TV_INSERTSTRUCT
        local tv_item as TV_ITEM

        if hParent then
            tv_item.mask      = %TVIF_CHILDREN OR %TVIF_HANDLE
            tv_item.hItem     = hParent
            tv_item.cchildren = 1
            TreeView_SetItem hTree, tv_item
        end if

        tv_insert.hParent              = hParent
        tv_insert.Item.Item.mask       = %TVIF_TEXT
        tv_insert.Item.Item.pszText    = strptr(sTxt)
        tv_insert.Item.Item.cchTextMax = len(sTxt)

        function = TreeView_InsertItem(hTree, tv_insert)

    end function

    CALLBACK FUNCTION IDD_DIALOG1_CB AS LONG

    LOCAL hTree AS LONG, hRoot AS LONG, hParent AS LONG
    LOCAL lpNmh As NMHDR Ptr
    LOCAL lpTV As NM_TREEVIEW PTR
    LOCAL szTxt As AsciiZ * 61
    STATIC
    tItem As TV_ITEM

    hDlgTree = GetDlgItem(CbHndl,%IDC_TREE1)

    SELECT CASE CBMSG
        CASE %WM_INITDIALOG
            CONTROL HANDLE CBHNDL, %IDC_TREE1 to hTree
            hRoot = TVInsertItem(hTree, 0, "Inbox")
            hParent = TVInsertItem(hTree, hRoot, "From GROUCHO: ")
            hParent = TVInsertItem(hTree, hRoot, "From ZEPPO: ")
            hRoot = TVInsertItem(hTree, 0, "Outbox")
            hParent = TVInsertItem(hTree, hRoot, "From HARPO: ")
            hParent = TVInsertItem(hTree, hRoot, "From CHICO: ")

    case %WM_NOTIFY
        lpNmh = CblParam 

        Select Case @lpNmh.Code 

  3.       Case %TVN_SELCHANGED
            lpTV = CblParam
            tItem.hitem = @lpTV.ItemNew.hItem
            tItem.mask = %TVIF_TEXT
            tItem.pszText = VarPtr(szTxt)
            tItem.cchTextMax = 61&
            Call TreeView_GetItem(hDlgTree, tItem)
            CurrentItem = szTxt
     
          Case %NM_DBLCLK
            CONTROL SET TEXT CBHNDL, %IDC_RICHEDIT1, CurrentItem
          
          Case %NM_RCLICK
            Call TreeView_DeleteItem (hDlgTree, tItem.hitem)

        End Select 

Listbox

'Get list of items in listbox
CONTROL SEND CBHNDL, %IDC_LIST1, %LB_GETCOUNT, 0, 0 TO lResult
 
'Get currently select item (doesn't work for multiple-selection listboxes)
CONTROL SEND CBHNDL , %IDC_LIST1, %LB_GETCURSEL, 0,0 TO lResult
'Other way
LISTBOX GET TEXT CBHNDL, %IDC_LIST1 TO sTemp          
 
'Get select items in multiple-selection listbox
lResult=SendMessage(GetDlgItem(CBHNDL,%IDC_LIST1), %LB_GETCOUNT,0,0)  ' get count of items
IF lResult>0 THEN
    lResult=lResult-1
    FOR iIndex=0 to lResult   ' zero indexed
        IF (SendMessage(GetDlgItem(CBHNDL,%IDC_LIST1), %LB_GETSEL, iIndex, 0)) THEN
        sTemp = SPACE$(SendMessage(GetDlgItem(CBHNDL,%IDC_LIST1), %LB_GETTEXTLEN, iIndex, 0) + 1)
        iTemp=SendMessage(GetDlgItem(CBHNDL,%IDC_LIST1), %LB_GETTEXT, iIndex, STRPTR(sTemp))
        MsgBox sTemp
    END IF
    NEXT iIndex
END If
 
'Another way, by iterating through array of pointers to selected items
lResult = SendMessage(hList, %LB_GETSELCOUNT, 0, 0)
IF  lResult <> %LB_ERR THEN
    REDIM lArray(lResult - 1) AS LONG
    ReDim sTemp(lResult - 1) AS String
    lResult = SendMessage(hList, %LB_GETSELITEMS, lResult, VARPTR(lArray(0)))
END IF
 
FOR iIndex= 0 TO UBOUND(lArray)
    lResult = SendMessage(hList, %LB_GETTEXT, lArray(iIndex), STRPTR(sTemp(iIndex)))
    MsgBox sTemp(iIndex)
Next iIndex
 
'Empty listbox
Call SendMessage(hList, %LB_RESETCONTENT, 0, 0)  
 
'Check if string exists
Control Send hDlg,%HDList,%LB_FINDSTRINGEXACT, -1,ByVal StrPtr(Msg$) To Li&
'Add string
Call SendMessage(hList, %LB_ADDSTRING, 0, BYVAL STRPTR(txt))
'Alternative
ListBox Add hDlg, %ID_LIST1, Txt

Checking the number of items

iIndex = SendMessage (hControl, %LB_GETCOUNT, 0, 0)

Avoid STRING variables

From Chris Boss

If you attempt to use string variables in PB DLL, your program will be using the Windows OLE string engine. An awful lot of processing goes on in the background, when you use string variables. The C code is using the CHAR type, which if my memory serves me, would be comparable to a Byte array in PB and not a string variable. Because the OLE string engine (which even VB uses) is significantly slower than working with a byte array, the PB DLL code should be significantly slower than the C code.

Displaying a Unicode string

MsgBox only accepts ANSI/ASCII strings. To display a Unicode string, either convert it with the Win32 API WideCharToMultiByte(), or use this:

DECLARE FUNCTION SetWindowTextW LIB "USER32.DLL" ALIAS "SetWindowTextW" (BYVAL hwnd AS LONG, BYVAL lString AS LONG) AS LONG
 
LOCAL hDlg AS LONG
LOCAL sMyString AS ASCIIZ * 64
LOCAL lResult AS LONG
 
lResult = SetWindowTextW (hDlg, BYVAL VARPTR(sMyString))

Note that the SetWindowText defined in PB/DLL's WIN32API.INC is the ANSI version (SetWindowTextA).

An alternative: Declare FUNCTION MessageBoxW LIB "USER32.DLL" ALIAS "MessageBoxW" (BYVAL hwnd AS DWORD, lpText AS ASCIIZ, lpCaption AS ASCIIZ, BYVAL wType AS LONG) AS LONG

Computing the length of an ASCIIZ

Len(sMyString) doesn't work until you used sMyString = SPACE$(128) to fill it up with spaces. The reason is that, after an ASCIIZ variable is created, it is initialized by PowerBasic with a 0... which leads Len() to consider that the size of the string is equal to 0. Use SizeOf(sMyString) instead.

TPC/IP - Connecting to a web server to download headers

ERRCLEAR
 
LOCAL sBuffer as String
LOCAL nTCP as INTEGER
LOCAL n AS INTEGER
LOCAL sBigBuffer as String
LOCAL sServer as String
        
nTCP = FREEFILE
 
sServer = INPUTBOX$("WWW server?")
 
TCP Open PORT 80 AT sServer AS nTCP
If ERR THEN MSGBOX "Server could not be reached!"
        
TCP PRINT nTCP, "HEAD / HTTP/1.0"
TCP PRINT nTCP, ""
TCP PRINT nTCP, ""              
        
Do
    TCP LINE nTCP, sBuffer
    sBigBuffer = sBigBuffer + chr$(13) + sBuffer
Loop While (Len(sBuffer) <> 0)
        
TCP CLOSE nTCP
 
MsgBox sBigBuffer

Note: Under 98/98SE, at least with PB/Win 7.04, you may trigger an error when setting the port using aliases such as "http" instead of setting its numeric value, eg. "PORT 80". The following always triggered error 57 under 98SE:

ERRCLEAR
 
'Replace with PORT 80
TCP OPEN "http" AT "www.acme.com" AS #1 TIMEOUT 5000
If ISFALSE ERR Then
    MsgBox "Online"
    TCP Close #1  
Else
    MsgBox "Offline",,"Err = " & str$(ERR)
END IF

Drawing text in the main window

CASE %WM_PAINT
    hDC = BeginPaint(hWnd, LpPaint)
    GetClientRect hWnd, tRect
                        
    For iIndex = 1 TO 10
        sBufferA = "Test" + str$(iIndex)
        lResult = TextOut(hDc, 100, iIndex * 50, sBufferA, Len(sBufferA))
    Next iIndex
 
    For iIndex = 1 TO 10
        tRect.nLeft = 100
        tRect.nTop = 100 (iIndex/2)
        tRect.nRight = 200
        tRect.nBottom = 600
        DrawText hDC, "Line " + str$(iIndex), -1, tRect, %DT_SINGLELINE OR %DT_CENTER 'OR %DT_VCENTER
    Next iIndex
 
    EndPaint hWnd, LpPaint
 

Creating child windows, and sending messages to them

GLOBAL hButton AS LONG
GLOBAL hList AS LONG
 
%Button = 100
%List  = 101
 
hButton = CreateWindow("BUTTON", "Click me", %WS_VISIBLE OR  %WS_CHILD OR %BS_DEFPUSHBUTTON, _
                      10, 10, 100, 100,hWnd, %Button, hInstance, %NULL)
 
hList = CreateWindow("LISTBOX", "", %WS_VISIBLE OR  %WS_CHILD OR %LBS_EXTENDEDSEL OR %LBS_HASSTRINGS OR %LBS_STANDARD, _
                      110, 10, 150, 150, hWnd, %List, hInstance, %NULL)
 

Converting from ANSI to Unicode, and sending a pop-up message

LOCAL sToHostA as ASCIIZ * %STR_MAX_SIZE
LOCAL sToHostW as ASCIIZ * (%STR_MAX_SIZE * 2)
 
LOCAL sBufferA AS ASCIIZ * %STR_MAX_SIZE
LOCAL sBufferW AS ASCIIZ * (%STR_MAX_SIZE * 2)
 
sBufferA = "All your base are belong to us"
        
SELECT CASE wMsg
    Case %WM_CREATE   
        GetUserName sToHostA, %STR_MAX_SIZE
 
        lResult = MultiByteToWideChar (%CP_ACP, 0&, sToHostA, -1, sToHostW, Len(sToHostA) * 2)
        lResult = MultiByteToWideChar (%CP_ACP, 0&, sBufferA, -1, sBufferW, Len(sBufferA) * 2)
 
        lResult = NetMessageBufferSend ("", sToHostW, "", sBufferW, %STR_MAX_SIZE * 2)

Reading from a sequential file

LOCAL strHosts() AS STRING
 
ERRCLEAR
hFile = FREEFILE
OPEN "HOSTS.DAT" FOR INPUT AS hFile
IF ERR = 53 THEN
    MSGBOX "Error: No HOSTS.DAT in local directory. Exiting."
    EXIT FUNCTION
END IF
 
iIndex = 0
WHILE NOT EOF(hFile)
    REDIM PRESERVE strHosts(iIndex + 1) AS STRING
    LINE INPUT #hFile, strHosts(iIndex)
    iIndex = iIndex + 1
WEND
 
CLOSE hFile

Writing to a sequential file

OPEN "OPEN.DTA" FOR OUTPUT AS #1
PRINT# 1, sBufferA
CLOSE 1

DDT - Obtaining selected items in listbox

CALLBACK FUNCTION DlgProc()
 
        LOCAL lReturn AS LONG
            
        SELECT CASE CBMSG
        
                CASE %WM_COMMAND
                
                        SELECT CASE CBCTL
                        
                                CASE %IDLIST
                                        IF (CBCTLMSG = %CBN_SELCHANGE) THEN
                                                CONTROL SEND CBHNDL , CBCTL, %LB_GETCURSEL, 0,0 TO lReturn
                                                CONTROL SEND CBHNDL, CBCTL, %LB_GETTEXT, lReturn, VARPTR(sToHostA)
                                        END IF
                                        
                                        FUNCTION=0
                                        EXIT FUNCTION
                        
                        END SELECT
                
        END SELECT
 
END FUNCTION

Displaying the host's workgroup/domain

FUNCTION StrToAnsi(BYVAL u AS LONG) AS STRING
 
    DIM Buffer AS STRING
    DIM x      AS STRING
    DIM l      AS LONG
 
    l = lstrlenW(BYVAL u)
    x = PEEK$(u, l * 2)
 
    Buffer = SPACE$(LEN(x) \ 2)
 
    WideCharToMultiByte %CP_ACP, _               ' code page
                      %NULL, _                 ' performance and mapping flags
                      BYVAL STRPTR(x), _       ' Unicode string to convert
                      LEN(x), _                ' len of Unicode string
                      BYVAL STRPTR(Buffer), _  ' buffer for ANSI string
                      LEN(Buffer), _           ' len of ANSI buffer
                      BYVAL %NULL, _           ' default for unmappable chars
                      BYVAL %NULL              ' default flag
 
    IF LEN(Buffer) = 0 THEN
        Buffer = "[none]"
    END IF
 
    FUNCTION = RTRIM$(Buffer)
 
END FUNCTION
 
LOCAL MyWKSTA_INFO_100 AS WKSTA_INFO_100 PTR
 
lResult = NetWkstaGetInfo(BYVAL %NULL, 100, MyWKSTA_INFO_100)
MsgBox "System name: \\" & StrToAnsi(@MyWKSTA_INFO_100.wki100_langroup)

Networking

Enumerating the list of shared drives on the LAN

To achieve this, the EnumAll() function below is called recursively so as to list all the shared drives for each host, and exit until all the entries have been parsed:

Function EnumAll (nr AS NETRESOURCE) as String
    LOCAL hEnum   AS LONG
    LOCAL Entries AS LONG
    LOCAL nSize   AS LONG
    LOCAL ec      AS LONG
    LOCAL x       AS LONG
 
    'Static because this string but live through each recurrent call
    Static sStuff as String
 
    DIM n(1 to 256) AS NETRESOURCE
 
    Entries = 256
    nSize   = SIZEOF(nr) * Entries
 
    ec = WNetOpenEnum(%RESOURCE_GLOBALNET, %RESOURCETYPE_DISK, %NULL, nr, hEnum)
 
    ec = WNetEnumResource(hEnum, Entries, n(1), nSize)
 
    FOR x = 1 TO Entries
        IF (n(x).dwUsage AND %RESOURCEUSAGE_CONTAINER) THEN
            EnumAll n(x)
        Else
            sStuff = sStuff & LTRIM$(n(x).@lpRemoteName) & $CRLF
        END IF
    NEXT
 
    'We're done parsing through, so return the list
    Function = sStuff
END Function
 
Function PBMain
        MsgBox EnumAll BYVAL %NULL
End Function

Working with Win32 APIs

C

PB/DLL

hWnd

BYVAL hWnd AS WORD

lpStr

lpStr AS ASCIIZ PTR

UINT Style

BYVAL Style AS WORD

CHAR

BYTE

 

 

 

 

 

 

 

 

 

 

VARPTR = address where a variable is stored in memory

STRPTR = pointer to a string

CODEPTR = address of a routine

AS STRING

AS STRING * 10
AS ASCIIZ * 10 'Same as above, but last character is ASCII 0, ie. up to 9 characters available

AS ... PTR 'Pointer, eg. AS STRING PTR = char *
DIM MYSTR AS STRING
DIM MYSTRPTR AS STRING PTR
MYSTRPTR = STRPTR(MYSTR)
@MYSTRPTR = "Test" 'Same as MYSTR = "Test"
INCR MYSTRPTR 'Opposit is DECR MYSTRPTR

DIM MYSTRUCTPTR(1 to 10) AS MYSTRUCT PTR
FOR I = 1 TO 10
    @MYSTRUCTPTR(I).FIELD1 = "Item " + STR$(I)
NEXT I

@MYARRAY[I].FIELD1 'For arrays created by other languages

Equates different from numeric constants? Equates have global scope

This is not allowed: VARPTR(lMyLong) =

Instead:

DIM lMyLong AS LONG

DIM pMyLongPtr AS LONG PTR

pMyLongPtr = VARPTR(lMyLong)

@pMyLongPtr = 1 'same as lMyLong = 1

ASCIIZ = address of a string

An ASCIIZ string is very different from a STRING. It's actually the same thing as a string in C: The variable contains an address (pointer) to the memory cell where the first character is located.

LOCAL sMyString AS ASCIIZ * 128
LOCAL pMyString as ASCIIZ PTR
pMyString = VARPTR(sMyString)
 
MoveMemory @pMyString, BYVAL se100.sv100_name, 1
'This also works
MoveMemory BYVAL pMyString, BYVAL se100.sv100_name, 1
'This also works
MoveMemory BYVAL VARPTR(sMyString), BYVAL se100.sv100_name, 1

Round trip

LOCAL sStart AS ASCIIZ * 64
LOCAL sFinish AS ASCIIZ * 64
LOCAL MyWKSTA_INFO_100 AS WKSTA_INFO_100
 
sStart = "Hi there"
'wki100_langroup is a LONG, ie. contains the address of a variable
MyWKSTA_INFO_100.wki100_langroup = VARPTR(sStart)
MoveMemory sFinish, BYVAL MyWKSTA_INFO_100.wki100_langroup, SIZEOF(sFinish)
MsgBox sFinish

Questions

How to set a pushbutton clickable through ENTER in DDT and PB 7.03?

This code generated by PBForms won't let me just hit ENTER as an alternative to a mouse click:

'Originally tried without the long list of parameters as last parameter, to no avail
CONTROL ADD BUTTON, hDlg, %IDC_CANCEL, "Cancel", 140, 35, 55, 20, _
        %WS_CHILD Or %WS_VISIBLE Or %WS_TABSTOP Or %BS_TEXT Or _
        %BS_DEFPUSHBUTTON Or %BS_PUSHBUTTON Or %BS_CENTER Or %BS_VCENTER, _
        %WS_EX_LEFT Or %WS_EX_LTRREADING    
 
Dialog  Send        hDlg, %DM_SETDEFID, %IDC_CANCEL, 0

What's the difference between DIM and LOCAL?

Saw both in samples. DIM is required when declaring arrays (LOCAL triggers an error), eg. Dim rEntry(1) As RASENTRYNAME .

While using DDT, widgets coordinates are wrong

The problem is that Win32 APIs such as GetClientRect() or MoveWindow() use pixels (which are device-dependent) while DDT instructions "dialog units" (which are pretty much device-independent.) Either use only DDT instructions such as CONTROL SET SIZE and the like, or use DIALOG PIXELS ... UNITS and DIALOG UNITS ... TO PIXELS to convert coordinates between Win32 and DDT.

Here's a sample:

'Instead of using GetClientRect and converting from pixels to DUs
DIALOG GET CLIENT CBHNDL TO R.nRight, R.nBottom
CONTROL ADD LISTBOX, CBHNDL, 500,, 0, 0, 85, R.nBottom, etc.

FYI, PBForms and Edwin Knoppert's (PBSoft) VisualDesigner show widget coordinates in dialog units.

When I start PBForms with a maximized dialog

... the PBForms application is covered with my dialog and can't be accessed.

Right-click on the PBForms icon in the taskbar, Minimize, then Restore.

Where can I find more infos on using the Win32 API?

A version is here, but there may be more recent versions on MS' site.

How to emulate OOP in PB?

How does PB compare to other Basic languages like IBasic, PureBasic and RealBasic?

IBasic

PureBasic

RealBasic

Rapid-Q

I need a good graphics library

FastGraph

I need to write a COM server

(From Lance Edmonds, sept 2002) PowerBASIC does not natively support the creation of COM components (yet?!), but you might be able to do it using some of the tools available from http://www.jazzagesoft.com

Are there any wrappers to make it easier to work with widgets?

Especially when using common controls which can be troublesome.

Sometimes, the PB/DLL compiler gets stuck

Remember, PB/DLL is a 16-bit compiler, and Windows' 16-bit engine can go haywire. In NT, open Task Manager, and kill WOWEXEC.EXE.

When declaring APIs, why is the ALIAS part required?

Is PbMain() the equivalent to C's main()...

... and, if yes, do I use it when my Windows application only uses a dialog box instead of a full-fledged window with? IOW, can I get rid of WinMain() and WinProc() ?

What is DDT? 

DDT: Dynamic Dialog Tools(tm)...  With DDT, you'll build complete GUI applications easier than ever.  Don't struggle with form designers.  Don't fight with resource scripts. Don't get lost in a sea of API calls!  With PB/DLL's DDT, a few simple Basic Statements will build a complete Graphical User Interface!

* DIALOG NEW creates an empty dialog box.  CONTROL ADD places buttons, icons, frames, bitmaps, combo boxes, labels, list boxes, text/edit boxes, even custom controls and more.  Add menus.  Then display it with DIALOG SHOW.  It really is just that easy!  Perhaps best of all, DDT is dynamic.  Change dialogs and controls "on-the-fly"!  Alter the size, position, and style at will.  With DDT, we put the Power in your hands. That's precisely where it belongs!

I get Error 480: Parameter mismatch, may need ByCopy when calling a SUB

You defined a SUB, but called it with parentheses:

Sub MyFunc (sMyString AS STRING)
    'nichts
End Sub
 
FUNCTION PBMAIN () AS LONG
    DIM sMyString AS STRING
 
    MyFunc (sMyString)
 
END FUNCTION

Use "MyFunc sMyString", or "Call MyFunc(sMyString)" instead.

Error 420 when calling an API function

Functions return a value. Add an lResult = .

How to maximize a dialog box at creation time?

Tried using 0,0 as width,height and ORing %WS_MAXIMIZE, to no avail.

How to add an icon in the title bar of a dialog bar?

Tried a bunch of switches in PBForm, but none worked.

Temp

Watch out : contrary to what it says in the online help of 7.02, search _is_ case sensitive by default (didn't find </title> because it was </TITLE> !:!!!!!!

1. REGEXPR statement

"While it is possible for more than one match to be found in a particular target string, REGEXPR first selects one or more matches which start at the leftmost possible position, then returns the longest of those.  Use the \s special escape operator to force a match on the shortest match."
=>
'Doesn't work
sMask = "\s[0-9]+"

2. "Tags/sub-patterns:
(parentheses) Parentheses are used to match a Tag, or sub-pattern, within the full search pattern, and remember the match.  The
matched sub-pattern _can be retrieved later in the mask_ (or in a replace operation with REGREPL), with \01 through \99, based upon the left-to-right position of the opening parentheses."
=> "Retrieved later", ie. possible to extract subtag with REGEXPR instead of REGREPL?
        
3. "Parentheses may also be used to force precedence of evaluation with the alternation operator.  For example, "(Begin)|(End)File" would match either "BeginFile" or "EndFile" [...]".

=> Shouldn't the pattern be "(Begin|End)" instead?

4. "Note: Parentheses may not be used with ? + * as any match repetition could cause the tag value to be ambiguous.  To match repeated expressions, use parentheses followed by \01*."
=> Er... that is, sMask = "<content=""()\01*"">"?

5. REGREPL only works if the mask matches the entire original string:

'Doesn't work...
'sFull = "blablabla<content=" + $DQ + "mycontent" + $DQ + ">blablabla"
sFull = "<content=" + $DQ + "mycontent" + $DQ + ">"

sMask = "<content=\q(.+)\q>"

sReplaceMask = "\01"
REGREPL sMask IN sFull WITH sReplaceMask TO lPosvar, sNewFull

Resources