PureBasic Docs- Ideas/Help needed for a "We start" chapter!?

Everything else that doesn't fall into one of the other PB categories.
User avatar
Andre
PureBasic Team
PureBasic Team
Posts: 2056
Joined: Fri Apr 25, 2003 6:14 pm
Location: Germany (Saxony, Deutscheinsiedel)
Contact:

PureBasic Docs- Ideas/Help needed for a "We start" chapter!?

Post by Andre »

Hi,

months ago I had the idea of including a short "We start" chapter into the PB docs.

While the (freely available, now in english and also german) "PureBasic Book" of Kale is a very good tutorial and has a lot of informations for new (and also advanced) PB user, I thought the PB manual should get a beginners page too! :D
It should be a much shorter (all on one page!) than Kale's book, but use the adavantage of being included in the PB manual itself, so it can link directly to the related pages (command, keyword, ...) in the PB manual and so guide the (beginner) user to further informations. So it's main goal is, to provide the user a better start and guide him through the many manual descriptions...

Unfortunately because of lack of time it didn't progress very much.

So my questions are:
- What do you think about this idea?
- Anyone wants to help creating such a chapter? If yes, just take one topic (e.g. possible "loops" in PureBasic) and post your suggestion in this thread...

Here is what I have so far:
(already included links to other pages in PB manual are not shown here, but can be added by me when including it in PB manual, as well the whole layout according to PB's docmaker syntax)

Last edited: 21th-May-2012

Current status of the project, when included into the reference manual of PureBasic:
=> http://www.purearea.net/pb/english/manu ... rview.html

This is Part 1 - see also Part 2
We start programming...

Overview

The following topics in this chapter should give you some ideas, where to start with PureBasic. It shouldn't replace any larger tutorial or the massive informations, which can be found on the popular PureBasic forums. So the following information texts are short, but they include some "keywords" and links to futher informations, which are included in this reference manual. This chapter covers only a small part of the 1100+ commands available in PureBasic! :)

Topics in this chapter:
- First steps
- Variables and Processing of variables
- Constants
- Decisions & Conditions
- Loops
- String Manipulation [missing]
- Storing data in memory
- Input & Output
- Displaying text output (Console)
- Building a graphical user interface (GUI)
- Displaying graphics output & simple drawing
- Structuring code in Procedures
- Compiler directives (for different behaviour on different OS)
- Reading and writing files
- Memory access
- Other Compiler keywords [missing]
- Other Library functions [missing]
- Advanced functions [missing]
- Some Tipps & Tricks [one example until now]
- ......

First steps with the Debug output (variables & operators)
Normally we would present here the typical "Hello World". You want to see it? Ok here we go with two examples:

"Hello World" in the Debug output window:

Code: Select all

Debug "Hello World!"
"Hello World" in a MessageRequester:

Code: Select all

MessageRequester("", "Hello World!")
We now continue with a short example using the available variable types, arithmetic operators and displaying the result:

Code: Select all

      a = 5
      b = 7
      c = 3
      
      d = a + b   ; we use the values stored in variables 'a' and 'b' and save the sum of them in 'd'
      d / c       ; we directly use the value of 'd' (= 12) divided by the value of 'c' (= 3) and save the result in 'd'
      
      Debug d    ; will give 4
This way you have used variables "on the fly". To force the compiler always declaring variables before their first use, just use the keyword EnableExplicit.


Variables and Processing of variables
spikey wrote:A is an integer - but note that you can't declare variables this way if you use the EnableExplicit directive.
Outside of a procedure A will exist at Main scope, within a procedure it will become local.

Code: Select all

A = 0
B is a long integer, C is a floating point number, they will be initialised to zero.

Code: Select all

Define B.L, C.F
D and E are long integers too. However, they will be initialised to the stated constants. Notice too the alternative syntax of Define used.

Code: Select all

Define.L D = 10, E = 20
F is a string.

Code: Select all

Define.S F
So is G$, however, if you declare strings this way, you must always use the $ notation.

Code: Select all

G$ = "Hello, "
This won't work. (G becomes a new integer variable and a compiler error occurs).

Code: Select all

G = "Goodbye, World!"
H is an array of 20 strings, array indexing begins at zero.

Code: Select all

Dim H.S(19)
Now H is an array of 25 strings. If the array is resized larger, its original contents will be preserved.

Code: Select all

ReDim H.S(24)
J will appear at Global, rather than Main, scope. It will appear in all procedures, but maybe a better way would be to use the Shared keyword inside a procedure on a main scope variable, so that the chances of accidental changes are minimized.

Code: Select all

Global.I J
K will be a new, empty, list of strings at main scope.

Code: Select all

NewList K.S()
M will be a new, empty, map of strings at main scope.

Code: Select all

NewMap M.S()
Note that you can't use the alternative syntax of the Define keyword with NewList or NewMap though. A compiler error will result.

Within a procedure.

Code: Select all

Procedure TestVariables()
  
  ; N and P will be local.
  Define.L N
  Protected.L P
  
  ; Q will be a static local.
  Static.L Q
  
  ; The main scope variable F and the string list K will be available within this procedure.
  Shared F, K()
  
  ; The global scope variable J will be available here implicitly.
  
EndProcedure
Using operators on variables.

Code: Select all

; Add two to A.
A + 2
; Bitwise Or with 21 (A will become 23)
A | 21
; Bitwise And with 1 (A will become 1)
A & 1
; Arithmetic shift left (A will become 2, that is 10 in binary).
A << 1
String concatenation.

Code: Select all

G$ + "World!"
Add an element to the K list.

Code: Select all

AddElement(K())
K() = "List element one"
Add an element to the M map.

Code: Select all

M("one") = "Map element one"

Constants
spikey wrote:In addition to variables PureBasic provides a method to define constants too. In fact it provides several. We’ll have a quick look at them now.

Predefined constants - provided either by PureBasic itself, these all begin #PB_, or from the API for the operating system. The IDE’s "Structure Viewer" tool has a panel which shows all the predefined constants.

User defined constants - by defining a constant name with the prefix # you can provide your own constants to make code more readable.

Code: Select all

#MyConstant1 = 10
#MyConstant2 = “Hello, World!”
Enumerations – PureBasic will automatically number a series of constants sequentially in an Enumeration, by default enumerations will begin from zero – but this can be altered, if desired.

Code: Select all

Enumeration
  #MyConstantA
  #MyConstantB
  #MyConstantC
EndEnumeration

Enumeration 10 Step 5
  #MyConstantD ; will be 10
  #MyConstantE ; will be 15
  #MyConstantF ; will be 20
EndEnumeration

Decisions and Conditions
spikey wrote:There are different ways of processing data obtained from user input or other way (loading from a file, ...). The common arithmetic functions (+, -, *, /, ...) can be combined with conditions. You can use the "If : Else/ElseIf : EndIf" set of keywords or the the "Select : Case/Default : EndSelect" keywords, just use what is the best for your situation!

This example shows the use of "If : ElseIf : Else : EndIf" creates a message, possibly for showing in the status bar of a form or something similar, based upon the number of items and filtered items in an, imaginary, list. Note that unlike some other BASIC languages, PureBasic doesn't use the "Then" keyword and that there is no space in the ElseIf and EndIf keywords.

Code: Select all

Define.L lItems = 10, lFilter = 6
Define.S sMsg

If lItems = 0 
  sMsg = "List is empty."
  
ElseIf lItems = 1 And lFilter = 0
  sMsg = "One item. Not shown by filter."
  
ElseIf lItems > 1 And lFilter = 0
  sMsg = StrU(lItems) + " items. All filtered."
  
ElseIf lItems > 1 And lFilter = 1
  sMsg = StrU(lItems) + " items. One shown by filter."
  
ElseIf lItems = lFilter
  sMsg = StrU(lItems) + " items. None filtered."
  
Else
  ; None of the other conditions were met.
  sMsg = StrU(lItems) + " items. " + StrU(lFilter) +" shown by filter."
  
EndIf

Debug sMsg

This example shows the use of Select : Case : Default : EndSelect to categorize the first 127 ASCII characters into groups. The "For : Next" loop counts to 130 to demonstrate the Default keyword.

Code: Select all

Define.C cChar
Define.S sMsg

For cChar = 0 To 130
  
  Select cChar
      
    Case 0 To 8, 10 To 31, 127
      sMsg = StrU(cChar) + " is a non-printing control code."
      
    Case 9
      sMsg = StrU(cChar) + " is a tab."
      
    Case 32
      sMsg = StrU(cChar) + " is a space."
      
    Case 36, 128   
      sMsg = StrU(cChar) + " is a currency symbol. (" + Chr(cChar) + ")"
      
    Case 33 To 35, 37 To 47, 58 To 64, 91 To 96
      sMsg = StrU(cChar) + " is a punctuation mark or math symbol. (" + Chr(cChar) + ")"
      
    Case 48 To 57
      sMsg = StrU(cChar) + " is a numeral. (" + Chr(cChar) + ")"
      
    Case 65 To 90
      sMsg = StrU(cChar) + " is an upper case letter. (" + Chr(cChar) + ")"
      
    Case 97 To 122
      sMsg = StrU(cChar) + " is a lower case letter. (" + Chr(cChar) + ")"
      
    Default
      ; If none of the preceding Case conditions are met.
      sMsg = "Sorry, I don't know what " + StrU(cChar) + " is!"
      
  EndSelect
  
  Debug sMsg
  
Next cChar

Loops
spikey wrote:Data, Events or many other things can also be processed using loops, which are always checked for a specific condition. Loops can be: "Repeat : Until", "Repeat : Forever", "While : Wend", "For : Next", "ForEach : Next".

In this loop the counter A is increased by one each time, this loop will always perform the same number of iterations.

Code: Select all

Define.I A
For A = 0 To 10 Step 2
  Debug A
Next A
This loop will increment the variable B by a random amount between 0 and 20 each time, until B exceeds 100. The number of iterations actually performed in the loop will vary depending on the random numbers. The check is performed at the start of the loop - so if the condition is already true, zero iterations may be performed. Take the ; away from the second line to see this happen.

Code: Select all

Define.I B
;B = 100
While B < 100
  B + Random(20)
  Debug B
Wend
This loop is very similar to the last except that the check is performed at the end of the loop. So one iteration, at least, will be performed. Again remove the ; from the second line to demonstrate.

Code: Select all

Define.I C
; C = 100
Repeat
  C + Random(20)
  Debug C
Until C > 99
This loop is infinite. It won't stop until you stop it (use the red X button on the IDE toolbar).

Code: Select all

Define.I D
Repeat
  Debug D
Forever
There is a special loop for working with linked lists and maps, it will iterate every member of the list (or map) in turn.

Code: Select all

NewList Fruit.S()

AddElement(Fruit())
Fruit() = "Banana"

AddElement(Fruit())
Fruit() = "Apple"

AddElement(Fruit())
Fruit() = "Pear"

AddElement(Fruit())
Fruit() = "Orange"

ForEach Fruit()
  Debug Fruit()
Next Fruit()

Storing data in memory
spikey wrote:This example gathers information about the files in the logged on user's home directory into a structured linked list. For now the output isn't very exciting but we will come back to this example later on and make it a bit more friendly in several different ways.

Code: Select all

; This section describes the fields of a structure or record, mostly integers in this case,
; but notice the string for the file name and the quad for the file size.
Structure FILEITEM
  Name.S
  Attributes.I
  Size.Q
  DateCreated.I
  DateAccessed.I
  DateModified.I
EndStructure

; Now we define a new list of files using the structure previously specified 
; and some other working variables we'll use later on.
NewList lstFiles.FILEITEM()
Define.S strFolder
Define.L lngResult

; This function gets the home directory for the logged on user.
strFolder = GetHomeDirectory()

; Open the directory to enumerate all its contents.
lngResult = ExamineDirectory(0, strFolder, "*.*")  

; If this is ok, begin enumeration of entries.
If lngResult
  
  ; Loop through until NextDirectoryEntry(0) becomes zero - indicating that there are no more entries.
  While NextDirectoryEntry(0)
    
    ; If the directory entry is a file, not a folder.
    If DirectoryEntryType(0) = #PB_DirectoryEntry_File
      
      ; Add a new element to the linked list.
      AddElement(lstFiles())
      
      ; And populate it with the properties of the file.
      lstFiles()\Name = DirectoryEntryName(0)
      lstFiles()\Size = DirectoryEntrySize(0)
      lstFiles()\Attributes = DirectoryEntryAttributes(0)
      lstFiles()\DateCreated = DirectoryEntryDate(0, #PB_Date_Created)
      lstFiles()\DateAccessed = DirectoryEntryDate(0, #PB_Date_Accessed)
      lstFiles()\DateModified = DirectoryEntryDate(0, #PB_Date_Modified)

    EndIf
    
  Wend
  
  ; Close the directory.
  FinishDirectory(0)
  
EndIf

; If there are some entries in the list, show the results in the debug window.
If ListSize(lstFiles())
  
  ForEach lstFiles()
    
    Debug "Filename = " + lstFiles()\Name
    Debug "Size = " + Str(lstFiles()\Size)
    Debug "Attributes = " + StrU(lstFiles()\Attributes)
    Debug "Created = " + StrU(lstFiles()\DateCreated)
    Debug "Accessed = " + StrU(lstFiles()\DateAccessed)
    Debug "Modified = " + StrU(lstFiles()\DateModified)
    
  Next lstFiles()
  
EndIf
Ok, firstly, the dates in the output are just numbers - this isn't very helpful, so let's make them look a bit more familar. Replace the last three Debug statements with these:

Code: Select all

...
Debug "Created = " + FormatDate("%dd/%mm/%yyyy", lstFiles()\DateCreated)
Debug "Accessed = " + FormatDate("%dd/%mm/%yyyy", lstFiles()\DateAccessed)
Debug "Modified = " + FormatDate("%dd/%mm/%yyyy", lstFiles()\DateModified)
The FormatDate function takes a date in PureBasic's own numeric date format and displays it in a format that we can specify. So now things should begin to look a bit more sensible.

Finally, for now, the list isn't in any particular order, so let's sort the list before we display it. Add this line before the comment about showing the list and the ForEach loop.

Code: Select all

...

; Sort the list into ascending alphabetical order of file name.
SortStructuredList(lstFiles(), #PB_Sort_Ascending, OffsetOf(FILEITEM\Name), #PB_Sort_String)

; If there are some entries in the list, show the results in the debug window.

...
This command takes the structured list, and resorts it into ascending order (#PB_Sort_Ascending), of the Name field of the structure (OffsetOf(FILEITEM\Name)), which is a string value (#PB_Sort_String).

Input & Output

Every PureBasic application can communicate and interact with the user on different ways.

Thereby we distinguish between

a) the pure output of informations
b) the interaction of the PureBasic application with the user, when user-input will be taken and the results will be outputted again.

It's not possible anymore, to use a simple "PRINT" command to output some things on the screen, like it was possible on DOS operating systems (OS) without a graphical user interface (GUI) years ago. Today such a GUI is always present, when you use an actual OS like Windows, Mac OSX oder Linux.

For the output of informations we have different possibilities:

- debug window (only possible during programming with PureBasic)
- MessageRequester (output of shorter text messages in a requester window)
- files (for saving the results in a text-file, etc.)
- console (for simple and almost non-graphic text output, most similar to earlier DOS times)
- windows and gadgets (standard windows with GUI elements on the desktop of the OS, e.g. for applications)
- Screen (Output of text and graphics directly on the screen, e.g. for games)

To be able to record and process input by the user of the PureBasic application, the three last-mentioned output options have also the possibility to get user-input:

- in the console using Input()
- in the window using WaitWindowEvent() / WindowEvent(), which can get the events occured in the window, like clicking on a button or the entered text in a StringGadget
- in the graphics screen using the keyboard
- the is as well the possibility to get user-input using the InputRequester


Displaying text output
Andre wrote: This part of text can be deleted later, I think.... as it will be replaced by spikey's contribution! :-)

In previous chapter "Input & Output" you already got an overview about the different possibilities to output text to the user.

First we will store some data in memory, which will be used later in the output examples:

....

And now we will see several examples of "How to output" text (using the previous stored data):

.....
spikey wrote:In the previous item "Input & Output" you already saw an overview about the different possibilities to output text to the user, and in the item "Storing Data in Memory", we started to build a small application to display the properties of files in a particular folder to the debug window.

Now we're going to revisit this example to improve the data output section to resolve some issues with using the debug window. Firstly, this window is only available in the PureBasic IDE which means its only useful to programmers, secondly it doesn't really give us much control over how our output looks.

PureBasic provides a text mode window, or console window, which can be used in compiled programs. So let's update our example to use it instead.

First, we will need some extra working variables to make this work properly. Amend the variable definitions like this:-

Code: Select all

...

; Now we define a list of files using the structure previously specified.
NewList lstFiles.FILEITEM()
Define.S strAccess, strAttrib, strCreate, strFolder, strModify, strMsg, strNum, strSize
Define.L lngResult

...
Next, remove the output section of code completely, from the comment line:-

Code: Select all

; If there are some entries in the list, show the results in the debug window.
...
Now replace this with:-

Code: Select all


; Open a text mode screen to show the results.
OpenConsole()

; Display a title.
; PrintN displays the string given in the console window and moves the print position to the start of the next line afterwards.
; Space(n) returns n spaces in a string.
PrintN("File list of " + strFolder + ".")
PrintN("-------------------------------------------------------------------------------")
strMsg = "Num Name"
PrintN(strMsg)
strMsg = Space(4) + "Create" + Space(5) + "Access" + Space(5) + "Modify" + Space(5) + "Attrib Size"
PrintN(strMsg)

; If there are some entries in the list, show the results in the console window.
If ListSize(lstFiles())
  
  ; Loop through the linked list to display the results.
  ForEach lstFiles()
    
    ; Tabulate the number of the list index.
    ; ListIndex() returns the current position in the list, counting from zero.
    ; StrU converts an unsigned number into a string.
    ; RSet pads a string to a set length with the necessary number of a specified character at the front.
    ; Here we use it to make sure all the index numbers are padded to 3 characters in length.
    strNum = RSet(StrU(ListIndex(lstFiles()) + 1), 3, " ")
    
    ; Display the item number and file name.
    strMsg = strNum + " " + lstFiles()\Name
    PrintN(strMsg)
    
    ; These lines convert the three date values to something more familiar.
    strCreate = FormatDate("%dd/%mm/%yyyy", lstFiles()\DateCreated)
    strAccess = FormatDate("%dd/%mm/%yyyy", lstFiles()\DateAccessed)
    strModify = FormatDate("%dd/%mm/%yyyy", lstFiles()\DateModified)
    
    ; Convert the file size to a padded string the same as with the index value above,
    ; but allow space for the maximum size of a quad.
    strSize = RSet(StrU(lstFiles()\Size), 19)
    
    ; Convert the attributes to a string, for now.
    strAttrib = RSet(StrU(lstFiles()\Attributes), 6, " ")
    
    ; Display the file's properties.
    strMsg = Space(4) + strCreate + " " + strAccess + " " + strModify + " " + strAttrib + " " + strSize
    PrintN(strMsg)
    
    ; Display a blank line.
    PrintN("")
    
  Next lstFiles()
  
EndIf

; Wait for the return key to be displayed, so the results can be viewed before the screen closes.
PrintN("")
PrintN("Press return to exit")
Input()
All being well the output should appear in a console window looking something like this:-

Code: Select all

File list of C:\Documents and Settings\user\.
-------------------------------------------------------------------------------
Num Name
    Create     Access     Modify     Attrib Size
  1 NTUSER.DAT
    03/07/2008 04/04/2011 02/04/2011     34            18874368

  2 kunzip.dll
    14/07/2008 04/04/2011 14/07/2008     32               18432

  3 ntuser.dat.LOG
    03/07/2008 04/04/2011 04/04/2011     34                1024

  4 ntuser.ini
    03/07/2008 02/04/2011 02/04/2011      6                 278

Press return to exit
This output is from a Windows XP system, later versions of Windows and Linux or Mac OSX will show different files of course.

Building a graphical user interface (GUI)
spikey wrote:In addition to the console window, PureBasic supports the creation of graphical user interfaces (GUI) too. So let's revisit the file properties example from previous items again and turn it into a GUI application.
Note that PureBasic provides a far easier way of getting this particular job done already - the ExplorerListGadget; but, as the example is intended to introduce managing GUI elements, using that gadget would defeat this object a bit.

Code: Select all

; The structure for file information as before.
Structure FILEITEM
  Name.S
  Attributes.I
  Size.Q
  DateCreated.I
  DateAccessed.I
  DateModified.I
EndStructure

; This is a constant to identify the window.
Enumeration
  #wdwFiles
EndEnumeration

; This is an enumeration to identify controls which will appear on the window.
Enumeration
  #txtFolder
  #lsiFiles
EndEnumeration

; Now we define a list of files using the structure previously specified.
NewList lstFiles.FILEITEM()

; And some working variables to make things happen.
Define.S strAccess, strAttrib, strCreate, strFolder, strModify, strMsg, strNum, strSize
Define.L lngResult, lngFlags

; These variables will receive details of GUI events as they occur in the program.
Define.L Event, EventWindow, EventGadget, EventType, EventMenu

; This function gets the home directory for the logged on user.
strFolder = GetHomeDirectory()

; Open the directory to enumerate its contents.
lngResult = ExamineDirectory(0, strFolder, "*.*")  

; If this is ok, begin enumeration of entries.
If lngResult
  
  ; Loop through until NextDirectoryEntry(0) becomes zero - indicating that there are no more entries.
  While NextDirectoryEntry(0)
    
    ; If the directory entry is a file, not a folder.
    If DirectoryEntryType(0) = #PB_DirectoryEntry_File
      
      ; Add a new element to the linked list.
      AddElement(lstFiles())
      
      ; And populate it with the properties of the file.
      lstFiles()\Name = DirectoryEntryName(0)
      lstFiles()\Size = DirectoryEntrySize(0)
      lstFiles()\Attributes = DirectoryEntryAttributes(0)
      lstFiles()\DateCreated = DirectoryEntryDate(0, #PB_Date_Created)
      lstFiles()\DateAccessed = DirectoryEntryDate(0, #PB_Date_Accessed)
      lstFiles()\DateModified = DirectoryEntryDate(0, #PB_Date_Modified)

    EndIf
    
  Wend
  
  ; Close the directory.
  FinishDirectory(0)
  
EndIf

; Sort the list into ascending alphabetical order of file name.
SortStructuredList(lstFiles(), #PB_Sort_Ascending, OffsetOf(FILEITEM\Name), #PB_Sort_String)

; The interesting stuff starts to happen here...

; This line defines a flag for the window attributes by OR-ing together the desired attribute constants.
lngFlags = #PB_Window_SystemMenu |#PB_Window_SizeGadget | #PB_Window_MinimizeGadget | #PB_Window_MaximizeGadget | #PB_Window_TitleBar

; Open a GUI window.
Openwindow(#wdwFiles, 50, 50, 400, 400, "File Properties", lngFlags)
; A text gadget to show the name of the folder.
TextGadget(#txtFolder, 5, 40, 390, 25, strFolder)
; A list icon gadget to hold the file list and properties.
ListIconGadget(#lsiFiles, 5, 70, 390, 326, "#", 30)
; Add columns to the ListIconGadget to hold each property.
AddGadgetColumn(#lsiFiles, 1, "Name", 200)
AddGadgetColumn(#lsiFiles, 2, "Created", 90)
AddGadgetColumn(#lsiFiles, 3, "Accessed", 90)
AddGadgetColumn(#lsiFiles, 4, "Modified", 90)
AddGadgetColumn(#lsiFiles, 5, "Attributes", 150)
AddGadgetColumn(#lsiFiles, 6, "Size", 150)

; Load the files into the list view.
ForEach lstFiles()

  ; Display the item number and file name.
  strNum = StrU(ListIndex(lstFiles()) + 1)
  
  ; These lines convert the three date values to something more familiar.
  strCreate = FormatDate("%dd/%mm/%yyyy", lstFiles()\DateCreated)
  strAccess = FormatDate("%dd/%mm/%yyyy", lstFiles()\DateAccessed)
  strModify = FormatDate("%dd/%mm/%yyyy", lstFiles()\DateModified)
  
  ; Convert the file size to a padded string the same as with the index value above,
  ; but allow space for the maximum size of a quad.
  strSize = StrU(lstFiles()\Size)
  
  ; Convert the attributes to a string, for now.
  strAttrib = StrU(lstFiles()\Attributes)
  
  ; Build a row string.  
  ; The Line Feed character 'Chr(10)' tells the gadget to move to the next column.
  strMsg = strNum + Chr(10) + lstFiles()\Name + Chr(10) + strCreate + Chr(10) + strAccess + Chr(10) + strModify + Chr(10) + strAttrib + Chr(10) + strSize
  
  ; Add the row to the list view gadget.
  AddGadgetItem(#lsiFiles, -1, strMsg)
  
Next lstFiles()

; This is the event loop for the window.  
; It will deal with all the user interaction events that we wish to use. 

Repeat
  ; Wait until a new window or gadget event occurs.
  Event = WaitwindowEvent()
  ; In programs with more than one form, which window did the event occur on.
  EventWindow = EventWindow()
  ; Which gadget did the event occur on.
  EventGadget = EventGadget()
  ; What sort of event occurred.
  EventType = EventType()
  
  ; Take some action.
  Select Event
      
    Case #PB_Event_Gadget
      ; A gadget event occurred.
      If EventGadget = #txtFolder
      ElseIf EventGadget = #lsiFiles
      EndIf
      
    Case #PB_Event_Closewindow
      ; The window was closed.
      If EventWindow = #wdwFiles
        Closewindow(#wdwFiles)
        Break
      EndIf
      
  EndSelect
  
  ; Go round and do it again.
  ; In practice the loop isn't infinite because it can be stopped by clicking the window's Close button.
ForEver
At this point the application already has some useful features. However, it has some problems too:-
1) You can't choose a folder to show.
2) You can't update the list contents without closing and restarting the program.
3) If you resize the window, the gadgets don't resize with it.
4) The attributes column is still not very useful.
We will revisit this program again later on to fix all these issues.

Displaying graphics output & simple drawing
spikey wrote:This example show how to create a simple drawing. It uses the 2D drawing commands to draw two sine waves at different frequencies and shows the harmonic produced by combining the two waves. It uses procedures, which we will discuss in more detail later on, to break the drawing tasks into three self-contained tasks.
Drawing the axes - demonstrates the Line command.
Drawing the legend - demonstrates the Box and DrawText commands.
Drawing the wave forms - demonstrates the LineXY command and shows how to use color.

Code: Select all

; Form.
Enumeration
  #wdwHarmonic
EndEnumeration

; Gadgets.
Enumeration
  #txtPlot1
  #cboPlot1
  #txtPlot2
  #cboPlot2
  #imgPlot
EndEnumeration

; Image.
Enumeration
  #drgPlot
EndEnumeration

; Event variables.
Define.L Event, EventWindow, EventGadget, EventType, EventMenu

; Implementation.
Procedure CreateWindow()
  ; Creates the window and gadgets.  

  If OpenWindow(#wdwHarmonic, 450, 200, 650, 680, "Harmonics", #PB_Window_SystemMenu|#PB_Window_SizeGadget|#PB_Window_MinimizeGadget|#PB_Window_TitleBar)
    
    ; This is a non-visual gadget used to draw the image, later its contents will be displayed in #imgPlot.
    CreateImage(#drgPlot, 640, 640, 24)
    
    ; Label for the Plot 1 combo.
    TextGadget(#txtPlot1, 2, 5, 40, 20, "Plot 1:")
    
    ; The Plot 1 combo.
    ComboBoxGadget(#cboPlot1, 45, 5, 150, 20)
    AddGadgetItem(#cboPlot1, 0, "Sin(X)")
    AddGadgetItem(#cboPlot1, 1, "Sin(X * 2)")
    AddGadgetItem(#cboPlot1, 2, "Sin(X * 3)")
    AddGadgetItem(#cboPlot1, 3, "Sin(X * 4)")
    AddGadgetItem(#cboPlot1, 4, "Sin(X * 5)")
    AddGadgetItem(#cboPlot1, 5, "Sin(X * 6)")
    
    ; Select Sin(X)
    SetGadgetState(#cboPlot1, 0)
    
    ; Label for the Plot 1 combo.
    TextGadget(#txtPlot2, 210, 5, 40, 20, "Plot 2:")
    
    ; The Plot 1 combo.
    ComboBoxGadget(#cboPlot2, 255, 5, 150, 20)
    AddGadgetItem(#cboPlot2, 0, "Sin(X)")
    AddGadgetItem(#cboPlot2, 1, "Sin(X * 2)")
    AddGadgetItem(#cboPlot2, 2, "Sin(X * 3)")
    AddGadgetItem(#cboPlot2, 3, "Sin(X * 4)")
    AddGadgetItem(#cboPlot2, 4, "Sin(X * 5)")
    AddGadgetItem(#cboPlot2, 5, "Sin(X * 6)")
    
    ; Select Sin(X * 2), otherwise the initial display is a bit uninteresting.
    SetGadgetState(#cboPlot2, 1)
    
    ; The visual image gadget on the window.
    ImageGadget(#imgPlot, 2, 30, 646, 616, 0, #PB_Image_Border)
    
  EndIf
  
EndProcedure

Procedure PlotAxes()
  ; Draws the axes on the image #drgPlot.

  ; Send drawing commands to #drgPlot.
  StartDrawing(ImageOutput(#drgPlot))
  
  ; Draw a white background.
  Box(0, 0, ImageWidth(#drgPlot), ImageHeight(#drgPlot), #White)
  
  ; Draw the axes in black.
  Line(1, 1, 1, ImageHeight(#drgPlot) - 2, #Black)
  Line(1, (ImageHeight(#drgPlot) - 2) /2, ImageWidth(#drgPlot) -2, 1, #Black)
  
  ; Finished drawing.
  StopDrawing()
  
EndProcedure

Procedure PlotLegend(alngPlot1, alngPlot2)
  ; Draws the legend on the image #drgPlot.
  
  Protected.S strFunc1, strFunc2, strLabel1, strLabel2, strLabel3
  
  ; Set label text 1.
  If alngPlot1 = 0 
    strFunc1 = "Sin(X)"
    
  Else
    strFunc1 = "Sin(X * " + StrU(alngPlot1 + 1) + ")"
    
  EndIf
  
  ; Set label text 2.
  If alngPlot2 = 0 
    strFunc2 = "Sin(X)"
    
  Else
    strFunc2 = "Sin(X * " + StrU(alngPlot2 + 1) + ")"
    
  EndIf
  
  ; Set label text.
  strLabel1 = "Y = " + strFunc1
  strLabel2 = "Y = " + strFunc2
  strLabel3 = "Y = " + strFunc1 + " + " + strFunc2 
  
  ; Draw legend.
  StartDrawing(ImageOutput(#drgPlot))
  
  ; Box.
  DrawingMode(#PB_2DDrawing_Outlined)
  Box(20, 10, TextWidth(strLabel3) + 85, 80, #Black)
  
  ; Label 1.
  Line(30, 30, 50, 1, #Blue)
  DrawText(95, 22, strLabel1, #Black, #White)
  
  ; Label 2.
  Line(30, 50, 50, 1, #Green)
  DrawText(95, 42, strLabel2, #Black, #White)
  
  ; Label 3.
  Line(30, 70, 50, 1, #Red)
  DrawText(95, 62, strLabel3, #Black, #White)
  
  StopDrawing()
  
EndProcedure

Procedure PlotFunction(alngPlot1, alngPlot2)
  ; Draws the waveforms on the image #drgPlot.

  Protected.L lngSX, lngEX
  Protected.F fltRad1, fltRad2, fltSY1, fltEY1, fltSY2, fltEY2, fltSY3, fltEY3
  
  StartDrawing(ImageOutput(#drgPlot))
  
  ; Set initial start points for each wave.
  lngSX = 1
  fltSY1 = ImageHeight(#drgPlot) / 2
  fltSY2 = fltSY1
  fltSY3 = fltSY1
  
  ; Plot wave forms.
  For lngEX = 1 To 720
    
    ; Sine function works in radians, so convert from degrees and calculate sine.
    
    ; Function 1
    If alngPlot1 = 0 
      fltRad1 = Sin(Radian(lngEX))
      
    Else
      ; If the function should have a multiplier, account for this.
      fltRad1 = Sin(Radian(lngEX) * (alngPlot1 + 1))
      
    EndIf
    
    ; Function 2
    If alngPlot2 = 0 
      fltRad2 = Sin(Radian(lngEX))
      
    Else
      fltRad2 = Sin(Radian(lngEX) * (alngPlot2 + 1))
      
    EndIf
    
    ; Plot function 1 in blue.
    ; Calculate end Y point.
    fltEY1 = (ImageHeight(#drgPlot) / 2) + (fltRad1 * 100)
    ; Draw a line from the start point to the end point.
    LineXY(lngSX, fltSY1, lngEX, fltEY1, #Blue)
    ; Update the next start Y point to be the current end Y point.
    fltSY1 = fltEY1
    
    ; Plot function 2 in green.
    fltEY2 = (ImageHeight(#drgPlot) / 2) + (fltRad2 * 100)
    LineXY(lngSX, fltSY2, lngEX, fltEY2, #Green)
    fltSY2 = fltEY2
    
    ; Plot harmonic in red.
    fltEY3 = (ImageHeight(#drgPlot) / 2) + ((fltRad1 + fltRad2) * 100)
    LineXY(lngSX, fltSY3, lngEX, fltEY3, #Red)
    fltSY3 = fltEY3
    
    ; Update the start X point to be the current end X point.
    lngSX = lngEX
    
  Next lngEX
  
  StopDrawing()
  
EndProcedure

;- Main
CreateWindow()
PlotAxes()
PlotLegend(GetGadgetState(#cboPlot1), GetGadgetState(#cboPlot2))
PlotFunction(GetGadgetState(#cboPlot1), GetGadgetState(#cboPlot2))

; Reload the image gadget now drawing is complete.
ImageGadget(#imgPlot, 2, 30, 646, 616, ImageID(#drgPlot), #PB_Image_Border)

;- Event loop
Repeat
  
  Event = WaitWindowEvent()
  EventWindow = EventWindow()
  EventGadget = EventGadget()
  EventType = EventType()
  
  Select Event
      
    Case #PB_Event_Gadget
      If EventGadget = #txtPlot1 Or EventGadget = #txtPlot2
        ; Do nothing.
        
      ElseIf EventGadget = #imgPlot
        ; Do nothing.
        
      ElseIf EventGadget = #cboPlot1 Or EventGadget = #cboPlot2
        ; If one of the combo boxes changed, redraw the image.
        PlotAxes()
        PlotLegend(GetGadgetState(#cboPlot1), GetGadgetState(#cboPlot2))
        PlotFunction(GetGadgetState(#cboPlot1), GetGadgetState(#cboPlot2))
        ImageGadget(#imgPlot, 2, 30, 646, 616, ImageID(#drgPlot), #PB_Image_Border)
        
      EndIf
      
    Case #PB_Event_CloseWindow
      If EventWindow = #wdwHarmonic
        CloseWindow(#wdwHarmonic)
        Break
      EndIf
      
  EndSelect
  
ForEver

Structuring code in Procedures
spikey wrote:We're going to revisit the file properties example again. This time to introduce procedures and to address some of the limitations identified in the program in previous items.

Code: Select all

; The structure for file information as before.
Structure FILEITEM
  Name.S
  Attributes.I
  Size.Q
  DateCreated.I
  DateAccessed.I
  DateModified.I
EndStructure

; This is a constant to identify the window.
Enumeration
  #wdwFiles
EndEnumeration

; This is an enumeration to identify controls that will appear on the window.
Enumeration
  #btnFolder
  #btnUpdate
  #txtFolder
  #lsiFiles
EndEnumeration

Procedure FilesExamine(astrFolder.S, List alstFiles.FILEITEM())
  ; Obtains file properties from strFolder into lstFiles.
  
  Protected.L lngResult
  
  ; Clear current contents.
  ClearList(alstFiles())
  
  ; Open the directory to enumerate its contents.
  lngResult = ExamineDirectory(0, astrFolder, "*.*")  
  
  ; If this is ok, begin enumeration of entries.
  If lngResult
    
    ; Loop through until NextDirectoryEntry(0) becomes zero - indicating that there are no more entries.
    While NextDirectoryEntry(0)
      
      ; If the directory entry is a file, not a folder.
      If DirectoryEntryType(0) = #PB_DirectoryEntry_File
        
        ; Add a new element to the linked list.
        AddElement(alstFiles())
        
        ; And populate it with the properties of the file.
        alstFiles()\Name = DirectoryEntryName(0)
        alstFiles()\Size = DirectoryEntrySize(0)
        alstFiles()\Attributes = DirectoryEntryAttributes(0)
        alstFiles()\DateCreated = DirectoryEntryDate(0, #PB_Date_Created)
        alstFiles()\DateAccessed = DirectoryEntryDate(0, #PB_Date_Accessed)
        alstFiles()\DateModified = DirectoryEntryDate(0, #PB_Date_Modified)
        
      EndIf
      
    Wend
    
    ; Close the directory.
    FinishDirectory(0)
    
  EndIf
  
  ; Sort the list into ascending alphabetical order of file name.
  SortStructuredList(alstFiles(), #PB_Sort_Ascending, OffsetOf(FILEITEM\Name), #PB_Sort_String)
  
EndProcedure

Procedure.S FolderSelect(astrFolder.S)
  ; Displays a path requester and returns the new path, or the old one if the requester is cancelled.
  
  Protected.S strSelect
  
  strSelect = PathRequester("Choose a folder.", astrFolder)
  
  If strSelect = ""
    strSelect = astrFolder
  EndIf
  
  ProcedureReturn strSelect
  
EndProcedure

Procedure LabelUpdate(astrFolder.S)
  ; Updates the folder label.
  
  SetGadgetText(#txtFolder, astrFolder)
  
EndProcedure

Procedure ListLoad(alngListView.L, List alstFiles.FILEITEM())
  ; Load the files properties from lstFiles into the list view lngListView.
  
  Protected.S strAccess, strAttrib, strCreate, strFolder, strModify, strMsg, strNum, strSize
  
  ; Remove previous contents.
  ClearGadgetItems(alngListView)
  
  ForEach alstFiles()
    
    ; Display the item number and file name.
    strNum = StrU(ListIndex(alstFiles()) + 1)
    
    ; These lines convert the three date values to something more familiar.
    strCreate = FormatDate("%dd/%mm/%yyyy", alstFiles()\DateCreated)
    strAccess = FormatDate("%dd/%mm/%yyyy", alstFiles()\DateAccessed)
    strModify = FormatDate("%dd/%mm/%yyyy", alstFiles()\DateModified)
    
    ; Convert the file size to a padded string the same as with the index value above,
    ; but allow space for the maximum size of a quad.
    strSize = StrU(alstFiles()\Size)
    
    ; Convert the attributes to a string, for now.
    strAttrib = StrU(alstFiles()\Attributes)
    
    ; Build a row string.  
    ; The Line Feed character 'Chr(10)' tells the gadget to move to the next column.
    strMsg = strNum + Chr(10) + alstFiles()\Name + Chr(10) + strCreate + Chr(10) + strAccess + Chr(10) + strModify + Chr(10) + strAttrib + Chr(10) + strSize
    
    ; Add the row to the list view gadget.
    AddGadgetItem(#lsiFiles, -1, strMsg)
    
  Next alstFiles()
  
EndProcedure

Procedure WindowCreate()
  ; Creates the wdwFiles window.
  
  Protected.L lngFlags
  
  ; This line defines a flag for the window attributes by OR-ing together the desired attribute constants.
  lngFlags = #PB_Window_SystemMenu |#PB_Window_SizeGadget | #PB_Window_MinimizeGadget | #PB_Window_MaximizeGadget| #PB_Window_TitleBar
  
  ; Open a window.
  OpenWindow(#wdwFiles, 50, 50, 400, 400, "File Properties", lngFlags)
  ; A button to choose a folder.
  ButtonGadget(#btnFolder, 5, 5, 100, 30, "Select Folder")
  ; A button to update the list.
  ButtonGadget(#btnUpdate, 105, 5, 100, 30, "Update List")
  ; A text gadget to show the name of the folder.
  TextGadget(#txtFolder, 5, 40, 390, 25, "")
  ; A list icon gadget to hold the file list and properties.
  ListIconGadget(#lsiFiles, 5, 70, 390, 326, "#", 30)
  ; Add columns to the ListIconGadget to hold each property.
  AddGadgetColumn(#lsiFiles, 1, "Name", 200)
  AddGadgetColumn(#lsiFiles, 2, "Created", 90)
  AddGadgetColumn(#lsiFiles, 3, "Accessed", 90)
  AddGadgetColumn(#lsiFiles, 4, "Modified", 90)
  AddGadgetColumn(#lsiFiles, 5, "Attributes", 150)
  AddGadgetColumn(#lsiFiles, 6, "Size", 150)
  
EndProcedure

Procedure WindowDestroy()
  ; Closes the window.
  ; If necessary, you could do other tidying up jobs here too.
  
  CloseWindow(#wdwFiles)
  
EndProcedure

Procedure WindowResize()
  ; Resizes window gadgets to match the window size.
  
  ResizeGadget(#txtFolder, #PB_Ignore, #PB_Ignore, WindowWidth(#wdwFiles) - 10, #PB_Ignore)
  ResizeGadget(#lsiFiles, #PB_Ignore, #PB_Ignore, WindowWidth(#wdwFiles) - 10, WindowHeight(#wdwFiles) - 74)
  
EndProcedure

;- Main
; Now we define a list of files using the structure previously specified.
NewList lstFiles.FILEITEM()

; And some working variables to make things happen.
Define.S strFolder
Define.L Event, EventWindow, EventGadget, EventType, EventMenu

; This function gets the home directory for the logged on user.
strFolder = GetHomeDirectory()

; Create the window and set the initial contents.
WindowCreate()
WindowResize()
LabelUpdate(strFolder)
FilesExamine(strFolder, lstFiles())
ListLoad(#lsiFiles, lstFiles())

;- Event Loop
Repeat
  
  ; Wait until a new window or gadget event occurs.
  Event = WaitWindowEvent()
  EventWindow = EventWindow()
  EventGadget = EventGadget()
  EventType = EventType()
  
  ; Take some action.
  Select Event
      
    Case #PB_Event_Gadget
      ; A gadget event occurred.
      If EventGadget = #btnFolder
        ; The folder button was clicked.
        strFolder = FolderSelect(strFolder)
        LabelUpdate(strFolder)
        FilesExamine(strFolder, lstFiles())
        ListLoad(#lsiFiles, lstFiles())
        
      ElseIf EventGadget = #btnUpdate
        ; The update button was clicked.
        FilesExamine(strFolder, lstFiles())
        ListLoad(#lsiFiles, lstFiles())
        
      ElseIf EventGadget = #txtFolder
        ; Do nothing here.
        
      ElseIf EventGadget = #lsiFiles
        ; Do nothing here.
        
      EndIf
      
    Case #PB_Event_SizeWindow
      ; The window was moved or resized.
      If EventWindow = #wdwFiles
        WindowResize()  
      EndIf
      
    Case #PB_Event_CloseWindow
      ; The window was closed.
      If EventWindow = #wdwFiles
        WindowDestroy()
        Break
        
      EndIf
      
  EndSelect
  
ForEver
Previously, we mentioned four limitations to this program. This new version uses procedures to address three of them.

1) You couldn't choose a folder to show.
The "FolderSelect" procedure shows a path requester to allow the user to select a folder. The variable "strFolder" is updated with the result of this procedure. The button also calls "LabelUpdate", "FilesExamine" and "ListLoad" to display the contents of the new folder in the window.

2) You can't update the list contents without closing and restarting the program.
Now, when the "Update List" button is clicked, "FilesExamine" and "ListLoad" are called again to update the display.

3) If you resize the window, the gadgets don't resize with it.
The "WindowResize" procedure is called in the event loop to resize the gadgets when the form is resized. Also, although this program didn't really need to, but this procedure is called after calling "WindowCreate" to make sure the gadgets are the right size initially.

Notice how several of the procedures are called more than once to perform similar but not identical functions. This improves the efficiency of the program.

We have one final limitation to overcome in a later item.

Compiler directives (for different behaviour on different OS)
spikey wrote:This will be our last visit to the File Properties program. There is one limitation discussed previously to overcome and we've left it until now because it is a special case.

So far the Attributes column on the display has simply been an integer. This is because the return values of the GetFileAttributes and DirectoryEntryAttributes instructions have a different meaning on Windows systems compared with Mac and Linux systems.

We can't allow for this difference at run-time, however we can use Compiler Directives to have the program behave differently on the three different operating systems.

Add this new procedure declaration to that section.

Code: Select all

Declare.S AttributeString(alngAttributes.L)
Add this new procedure to the implementation section.

Code: Select all

Procedure.S AttributeString(alngAttributes.L)
  ; Converts an integer attributes value into a string description.
  ; Supports Linux, Mac and Windows system's attributes.
  
  Protected.S strResult
  
  strResult = ""
  
  CompilerIf #PB_Compiler_OS = #PB_OS_Windows
    
    ; These are the attributes for Windows systems.
    ; A logical-and of the attribute with each constant filters out that bit and can then be used for comparison.

    If alngAttributes & #PB_FileSystem_Archive
      strResult + "A"
    Else
      strResult + " "
    EndIf
    
    If alngAttributes & #PB_FileSystem_Compressed
      strResult + "C"
    Else
      strResult + " "
    EndIf
    
    If alngAttributes & #PB_FileSystem_Hidden
      strResult + "H"
    Else
      strResult + " "
    EndIf
    
    If alngAttributes & #PB_FileSystem_ReadOnly
      strResult + "R"
    Else
      strResult + " "
    EndIf
    
    If alngAttributes & #PB_FileSystem_System
      strResult + "S"
    Else
      strResult + " "
    EndIf
    
  CompilerElse
    
    ; These are the attributes for Mac and Linux systems.
    
    If alngAttributes & #PB_FileSystem_Link
      strResult + "L "
    Else
      strResult + "  "
    EndIf
    
    ; User attributes.
    If alngAttributes & #PB_FileSystem_ReadUser
      strResult + "R"
    Else
      strResult + " "
    EndIf
    
    If alngAttributes & #PB_FileSystem_WriteUser
      strResult + "W"
    Else
      strResult + " "
    EndIf
    
    If alngAttributes & #PB_FileSystem_ExecUser
      strResult + "X "
    Else
      strResult + "  "
    EndIf
    
    ; Group attributes.
    If alngAttributes & #PB_FileSystem_ReadGroup
      strResult + "R"
    Else
      strResult + " "
    EndIf
    
    If alngAttributes & #PB_FileSystem_WriteGroup
      strResult + "W"
    Else
      strResult + " "
    EndIf
    
    If alngAttributes & #PB_FileSystem_ExecGroup
      strResult + "X "
    Else
      strResult + "  "
    EndIf
    
    ; All attributes.
    If alngAttributes & #PB_FileSystem_ReadAll
      strResult + "R"
    Else
      strResult + " "
    EndIf
    
    If alngAttributes & #PB_FileSystem_WriteAll
      strResult + "W"
    Else
      strResult + " "
    EndIf
    
    If alngAttributes & #PB_FileSystem_ExecAll
      strResult + "X"
    Else
      strResult + " "
    EndIf
    
  CompilerEndIf
  
  ; Return the result.
  ProcedureReturn strResult
  
EndProcedure
Finally, replace these two lines in the ListLoad procedure

Code: Select all

    ; Convert the attributes to a string, for now.
    strAttrib = StrU(alstFiles()\Attributes)
with these,

Code: Select all

    ; Call AttributeString to convert the attributes to a string representation.
    strAttrib = AttributeString(alstFiles()\Attributes)
Now when the program is executed a string display will be shown instead of the integer values.
On a Windows system it would look something like this (assuming all attributes are set):

Code: Select all

ACHRS
and on the other two systems:

Code: Select all

L RWX RWX RWX
The "CompilerIf" instruction works much like an "If" instruction - however it is the compiler that makes the decision at compile-time, rather than the executable at run-time. This means that we can include different code to handle the file attributes on the different operating systems.

The lines between:

Code: Select all

CompilerIf #PB_Compiler_OS = #PB_OS_Windows
and

Code: Select all

CompilerElse
will be compiled on Windows systems. The constant #PB_Compiler_OS is automatically defined by PureBasic to allow this kind of program logic.

The other section will be used on Mac and Linux systems - which work the same way, conveniently. If these operating systems had different attribute values too, then we could use "CompilerSelect" and "CompilerCase" to make a three-way determination.

Code: Select all

CompilerSelect #PB_Compiler_OS
    
  CompilerCase #PB_OS_Linux
    ; Code for Linux systems.
    
  CompilerCase #PB_OS_MacOS
    ; Code for Mac systems.
    
  CompilerCase #PB_OS_Windows
    ; Code for Windows systems.
    
CompilerEndSelect
The last compiler directive that we're going to discuss here is:

Code: Select all

EnableExplicit.
There is a good reason for using this directive. It requires that all variables must be defined explicitly before usage, in some way, (using Define, Dim, Global, Protected, Static etc.) Doing so eliminates the possibility of logic errors due to mistyped variable names being defined "on-the-fly". This type of subtle error will not affect a program's compilation but could well present as an inconvenient bug at run-time. Using this directive eliminates this kind of problem as a compiler error would occur.

For example:

Code: Select all

EnableExplicit

Define.L lngField, lngFieldMax

; ...

If lngField < lngFeildMax
  ; If EnableExplicit is omitted this section of code may not execute when intended because lngFeildMax will be zero.
EndIf

Reading and writing files
spikey wrote: This example will write 100 random records each containing a byte, a floating point number, a long integer and a string. It then reads all the records back and displays them in the debug window.

It demonstrates the GetTemporaryDirectory, CreateFile, OpenFile, EOF and a number of Read and Write data instructions too.

It works fine as far as it goes, but has a drawback. As the string value has a variable length - you can't randomly access the records because you can't predict where each new record will start in the file. They must be all be read back in the same sequence as they were written. This isn't a problem with the small number of records created here but this could be an inconvenience with a larger file. PureBasic offers a way to handle this situation too - but an example would be too complex for this topic. See the "Database" sections of the help file or reference manual to see how it could be done.

Code: Select all

; Define some variables.
Define.F fltFloat
Define.L lngCount, lngFile
Define.S strFolder, strFile, strString

; Create a temporary file name.
strFolder = GetTemporaryDirectory()
strFile = strFolder + "test.data"

; Create the temporary file.
; If #PB_Any is used, CreateFile returns the file's number.  
; Useful if you may have more than one file open simultaneously.
lngFile = CreateFile(#PB_Any, strFile)

If lngFile
  ; If this was successful - write 100 random records.
  For lngCount = 1 To 100
    
    ; Write a random byte (0 - 255).
    WriteByte(lngFile, Random(#MAXBYTE))
    
    ; Create and write a random float.
    ; This calculation is there to make the number have a floating point component (probably).
    fltFloat = Random(#MAXLONG) / ((Random(7) + 2) * 5000)
    WriteFloat(lngFile, fltFloat)
    
    ; Write a random long.
    WriteLong(lngFile, Random(#MAXLONG))
    
    ; Create and write a random string in Unicode format.
    ; Note the use of WriteStringN to delimit the string with an end of line marker.
    strString = "String " + StrU(Random(#MAXLONG))
    WriteStringN(lngFile, strString, #PB_Unicode)
    
  Next lngCount
  
  ; Close the file.
  CloseFile(lngFile)
  
Else
  ; If this was unsuccessful.
  Debug "Could not create the file: " + strFile 
  
EndIf

; Open the file for reading this time.
lngFile = ReadFile(#PB_Any, strFile)

If lngFile
  ; If this was successful - read and display records from the file.

  ; Reset the counter.
  lngCount = 1
  
  ; Loop until the 'end of file' is reached.
  ; This will read all of the records regardless of how many there are.
  While Eof(lngFile) = 0
    
    ; Print a header line.
    Debug "------------------------------------------------------------------------------------------------"
    
    Debug "[" + StrU(lngCount) + "]"
    lngCount + 1
    ; Read a byte value and print it.
    Debug StrU(ReadByte(lngFile), #PB_Byte)
    
    ; Read a float value..
    Debug StrF(ReadFloat(lngFile))
    
    ; Read a long value.
    Debug StrU(ReadLong(lngFile), #PB_Long)
    
    ; Read a string value.
    Debug ReadString(lngFile, #PB_Unicode)
    
  Wend
  
  ; Print the trailing line.
  Debug "------------------------------------------------------------------------------------------------"
    
  ; Close the file.
  CloseFile(lngFile)
  
  ; Tidy up.
  DeleteFile(strFile)
  
Else
  ; If this was unsuccessful.
  Debug "Could not open the file: " + strFile 
  
EndIf

Memory access
...
and further topics see Part 2
Bye,
...André
(PureBasicTeam::Docs & Support - PureArea.net | Order:: PureBasic | PureVisionXP)
IdeasVacuum
Always Here
Always Here
Posts: 6425
Joined: Fri Oct 23, 2009 2:33 am
Location: Wales, UK
Contact:

Re: PureBasic Docs- Ideas/Help needed for a "We start" chapt

Post by IdeasVacuum »

Certainly is a very good idea Andre, many newbies are 'lost' to begin with (I was).

For obvious reasons, the majority of examples on the forum tend to be biased towards Windows OS.

I'd say keep the bulk of the chapter OS neutral, maybe introduce the supported platforms and the ramifications of cross-platform programming last.
IdeasVacuum
If it sounds simple, you have not grasped the complexity.
Hysteria
User
User
Posts: 50
Joined: Sat Oct 23, 2010 8:51 am
Location: UK

Re: PureBasic Docs- Ideas/Help needed for a "We start" chapt

Post by Hysteria »

Good idea!

It would be worth highlighting differences between PB and more traditional BASICs, especially related to syntax.

Things like MID$, LEFT$, RIGHT$ becoming MID, LEFT, RIGHT

(though, granted it's not too difficult to find it out)

However, I would have appreciated something more obvious when I first started using PB's DATA statements.

I remember getting a strange error which I couldn't find in the documentation (and can't remember now) and wishing I could find a complete and comprehensive list of error messages. But more-so, would have liked something saying:

When you use strings in your DATA statements, you must append '.s' to both the READ and DATA commands

I know it's in the documentation, and when I read it now, I feel ashamed I didn't see it sooner :oops: but there we go, I've used DATA countless times in other BASICs but got caught out here. I think the biggest problem was the strange error that didn't really help. If it had said 'Missing variable type in DATA statement', I'd have been fine :D

In fact, it would be good to have one page per command in the help. Going back to DATA it is on a page with a lot of other (albeit relevant) information and you don't see the full syntax until you scroll down and you've got to go down the bottom to see the stuff about appending the variable etc. Maybe I'm just being picky :D
User avatar
Andre
PureBasic Team
PureBasic Team
Posts: 2056
Joined: Fri Apr 25, 2003 6:14 pm
Location: Germany (Saxony, Deutscheinsiedel)
Contact:

Re: PureBasic Docs- Ideas/Help needed for a "We start" chapt

Post by Andre »

Thanks to you too, for your suggestions! :D

Of course this chapter should be OS-neutral, so no OS-specific examples etc...
The (main) differences between the OSes supported by PureBasic are maybe content of another chapter in the manual. But that's not the goal here.

Maybe the "We start" chapter should include some basic comparisons to other BASICs. But generally I think that's not the focus of a reference manual. For such things the (now freely available) book of Kale (see link in my first post) is the right thing.

Main goal of this (not too long) chapter should be, to give the newbie a start and guide him/her to other pages of the manual. Like I did with the already written "Input & Output" section - there the user gets an overview about what's possible and will be linked to the other pages of the manual.

More suggestions, or if anyone wants also contributions, are very welcome! :)
Thanks!
Bye,
...André
(PureBasicTeam::Docs & Support - PureArea.net | Order:: PureBasic | PureVisionXP)
User avatar
spikey
Enthusiast
Enthusiast
Posts: 581
Joined: Wed Sep 22, 2010 1:17 pm
Location: United Kingdom

Loops Item

Post by spikey »

Data, Events or many other things can also be processed using loops, which are always checked for a specific condition. Loops can be: "Repeat : Until", "Repeat : Forever", "While : Wend", "For : Next", "ForEach : Next".

In this loop the counter A is increased by one each time, this loop will always perform the same number of iterations.

Code: Select all

Define.I A
For A = 0 To 10 Step 2
  Debug A
Next A
This loop will increment the variable B by a random amount between 0 and 20 each time, until B exceeds 100. The number of iterations actually performed in the loop will vary depending on the random numbers. The check is performed at the start of the loop - so if the condition is already true, zero iterations may be performed. Take the ; away from the second line to see this happen.

Code: Select all

Define.I B
;B = 100
While B < 100
  B + Random(20)
  Debug B
Wend
This loop is very similar to the last except that the check is performed at the end of the loop. So one iteration, at least, will be performed. Again remove the ; from the second line to demonstrate.

Code: Select all

Define.I C
; C = 100
Repeat
  C + Random(20)
  Debug C
Until C > 99
This loop is infinite. It won't stop until you stop it (use the red X button on the IDE toolbar).

Code: Select all

Define.I D
Repeat
  Debug D
Forever
There is a special loop for working with linked lists and maps, it will iterate every member of the list (or map) in turn.

Code: Select all

NewList Fruit.S()

AddElement(Fruit())
Fruit() = "Banana"

AddElement(Fruit())
Fruit() = "Apple"

AddElement(Fruit())
Fruit() = "Pear"

AddElement(Fruit())
Fruit() = "Orange"

ForEach Fruit()
  Debug Fruit()
Next Fruit()
User avatar
Andre
PureBasic Team
PureBasic Team
Posts: 2056
Joined: Fri Apr 25, 2003 6:14 pm
Location: Germany (Saxony, Deutscheinsiedel)
Contact:

Re: PureBasic Docs- Ideas/Help needed for a "We start" chapt

Post by Andre »

@spikey: well done, a good introduction for the "Loops" topic! I've included your contribution in my first post. Thank you :D

Btw.: All links to other chapters of the PB manual (the used keywords + commands) I can add, when including it in the manual.

Any further suggestions + contributions are very welcome! :mrgreen:
Bye,
...André
(PureBasicTeam::Docs & Support - PureArea.net | Order:: PureBasic | PureVisionXP)
User avatar
Blood
Enthusiast
Enthusiast
Posts: 161
Joined: Tue Dec 08, 2009 8:34 pm
Location: United Kingdom

Re: PureBasic Docs- Ideas/Help needed for a "We start" chapt

Post by Blood »

Why not just update Kale's book and include that into the main PB package? It seems your re-inventing the wheel here.
C provides the infinitely-abusable goto statement, and labels to branch to. Formally, the goto is never necessary, and in practice it is almost always easy to write code without it. We have not used goto in this book. -- K&R (2nd Ed.) : Page 65
User avatar
Andre
PureBasic Team
PureBasic Team
Posts: 2056
Joined: Fri Apr 25, 2003 6:14 pm
Location: Germany (Saxony, Deutscheinsiedel)
Contact:

Re: PureBasic Docs- Ideas/Help needed for a "We start" chapt

Post by Andre »

Blood wrote:Why not just update Kale's book and include that into the main PB package? It seems your re-inventing the wheel here.
That's no real option.

First point: Kale's book is under the Creative Commons licence, so it can't be included into the PB manual, which is part of a commercial/shareware package.

Second point: this "We start" chapter should be a short overview to give the PB user a (better) start into the language and include links to further information already included in the PB manual. So this chapter should never become a competitor to Kale's 400 site book!
Bye,
...André
(PureBasicTeam::Docs & Support - PureArea.net | Order:: PureBasic | PureVisionXP)
WilliamL
Addict
Addict
Posts: 1214
Joined: Mon Aug 04, 2008 10:56 pm
Location: Seattle, USA

Re: PureBasic Docs- Ideas/Help needed for a "We start" chapt

Post by WilliamL »

Looks like a big job!
Last edited by WilliamL on Fri May 06, 2011 9:13 pm, edited 1 time in total.
MacBook Pro-M1 (2021), Sonoma 14.3.1 (CLT 15.3), PB 6.10b7 M1
User avatar
blueznl
PureBasic Expert
PureBasic Expert
Posts: 6161
Joined: Sat May 17, 2003 11:31 am
Contact:

Re: PureBasic Docs- Ideas/Help needed for a "We start" chapt

Post by blueznl »

I'm against it.

:mrgreen:
( PB6.00 LTS Win11 x64 Asrock AB350 Pro4 Ryzen 5 3600 32GB GTX1060 6GB)
( The path to enlightenment and the PureBasic Survival Guide right here... )
User avatar
spikey
Enthusiast
Enthusiast
Posts: 581
Joined: Wed Sep 22, 2010 1:17 pm
Location: United Kingdom

Variables and Processing of Variables Item

Post by spikey »

A is an integer - but note that you can't declare variables this way if you use the EnableExplicit directive.
Outside of a procedure A will exist at Main scope, within a procedure it will become local.

Code: Select all

A = 0
B is a long integer, C is a floating point number, they will be initialised to zero.

Code: Select all

Define B.L, C.F
D and E are long integers too. However, they will be initialised to the stated constants. Notice too the alternative syntax of Define used.

Code: Select all

Define.L D = 10, E = 20
F is a string.

Code: Select all

Define.S F
So is G$, however, if you declare strings this way, you must always use the $ notation.

Code: Select all

G$ = "Hello, "
This won't work. (G becomes a new integer variable and a compiler error occurs).

Code: Select all

G = "Goodbye, World!"
H is an array of 20 strings, array indexing begins at zero.

Code: Select all

Dim H.S(19)
Now H is an array of 25 strings. If the array is resized larger, its original contents will be preserved.

Code: Select all

ReDim H.S(24)
J will appear at Global, rather than Main, scope. It will appear in all procedures, but maybe a better way would be to use the Shared keyword inside a procedure on a main scope variable, so that the chances of accidental changes are minimized.

Code: Select all

Global.I J
K will be a new, empty, list of strings at main scope.

Code: Select all

NewList K.S()
M will be a new, empty, map of strings at main scope.

Code: Select all

NewMap M.S()
Note that you can't use the alternative syntax of the Define keyword with NewList or NewMap though. A compiler error will result.

Within a procedure.

Code: Select all

Procedure TestVariables()
  
  ; N and P will be local.
  Define.L N
  Protected.L P
  
  ; Q will be a static local.
  Static.L Q
  
  ; The main scope variable F and the string list K will be available within this procedure.
  Shared F, K()
  
  ; The global scope variable J will be available here implicitly.
  
EndProcedure
Using operators on variables.

Code: Select all

; Add two to A.
A + 2
; Bitwise Or with 21 (A will become 23)
A | 21
; Bitwise And with 1 (A will become 1)
A & 1
; Arithmetic shift left (A will become 2, that is 10 in binary).
A << 1
String concatenation.

Code: Select all

G$ + "World!"
Add an element to the K list.

Code: Select all

AddElement(K())
K() = "List element one"
Add an element to the M map.

Code: Select all

M("one") = "Map element one"
User avatar
Andre
PureBasic Team
PureBasic Team
Posts: 2056
Joined: Fri Apr 25, 2003 6:14 pm
Location: Germany (Saxony, Deutscheinsiedel)
Contact:

Re: PureBasic Docs- Ideas/Help needed for a "We start" chapt

Post by Andre »

@spikey: I have added your latest contribution to the "Variables and processing of variables" chapter. Thanks a lot! :D
Bye,
...André
(PureBasicTeam::Docs & Support - PureArea.net | Order:: PureBasic | PureVisionXP)
User avatar
spikey
Enthusiast
Enthusiast
Posts: 581
Joined: Wed Sep 22, 2010 1:17 pm
Location: United Kingdom

Decisions and Conditions Item - updated

Post by spikey »

There are different ways of processing data obtained from user input or other way (loading from a file, ...). The common arithmetic functions (+, -, *, /, ...) can be combined with conditions. You can use the "If : Else/ElseIf : EndIf" set of keywords or the the "Select : Case/Default : EndSelect" keywords, just use what is the best for your situation!

This example shows the use of "If : ElseIf : Else : EndIf" creates a message, possibly for showing in the status bar of a form or something similar, based upon the number of items and filtered items in an, imaginary, list. Note that unlike some other BASIC languages, PureBasic doesn't use the "Then" keyword and that there is no space in the ElseIf and EndIf keywords.

Code: Select all

Define.L Items = 10, Filter = 6
Define Message$

If Items = 0 
  Message$ = "List is empty."
  
ElseIf Items = 1 And Filter = 0
  Message$ = "One item. Not shown by filter."
  
ElseIf Items > 1 And Filter = 0
  Message$ = StrU(Items) + " items. All filtered."
  
ElseIf Items > 1 And Filter = 1
  Message$ = StrU(Items) + " items. One shown by filter."
  
ElseIf Items = Filter
  Message$ = StrU(Items) + " items. None filtered."
  
Else
  ; None of the other conditions were met.
  Message$ = StrU(Items) + " items. " + StrU(Filter) +" shown by filter."
  
EndIf

Debug Message$

This example shows the use of Select : Case : Default : EndSelect to categorize the first 127 ASCII characters into groups. The "For : Next" loop counts to 130 to demonstrate the Default keyword.

Code: Select all

Define.C Char
Define Message$

For Char = 0 To 130
 
  Select Char
     
    Case 0 To 8, 10 To 31, 127
      Message$ = StrU(Char) + " is a non-printing control code."
     
    Case 9
      Message$ = StrU(Char) + " is a tab."
     
    Case 32
      Message$ = StrU(Char) + " is a space."
     
    Case 36, 128   
      Message$ = StrU(Char) + " is a currency symbol. (" + Chr(Char) + ")"
     
    Case 33 To 35, 37 To 47, 58 To 64, 91 To 96
      Message$ = StrU(Char) + " is a punctuation mark or math symbol. (" + Chr(Char) + ")"
     
    Case 48 To 57
      Message$ = StrU(Char) + " is a numeral. (" + Chr(Char) + ")"
     
    Case 65 To 90
      Message$ = StrU(Char) + " is an upper case letter. (" + Chr(Char) + ")"
     
    Case 97 To 122
      Message$ = StrU(Char) + " is a lower case letter. (" + Chr(Char) + ")"
     
    Default
      ; If none of the preceding Case conditions are met.
      Message$ = "Sorry, I don't know what " + StrU(Char) + " is!"
     
  EndSelect
 
  Debug Message$
 
Next Char
Last edited by spikey on Wed Aug 03, 2011 5:56 pm, edited 3 times in total.
User avatar
Andre
PureBasic Team
PureBasic Team
Posts: 2056
Joined: Fri Apr 25, 2003 6:14 pm
Location: Germany (Saxony, Deutscheinsiedel)
Contact:

Re: PureBasic Docs- Ideas/Help needed for a "We start" chapt

Post by Andre »

Thanks spikey, I've added this to the "Decisions and Conditions" chapter. :D
Bye,
...André
(PureBasicTeam::Docs & Support - PureArea.net | Order:: PureBasic | PureVisionXP)
User avatar
blueznl
PureBasic Expert
PureBasic Expert
Posts: 6161
Joined: Sat May 17, 2003 11:31 am
Contact:

Re: PureBasic Docs- Ideas/Help needed for a "We start" chapt

Post by blueznl »

Feel free to rip parts of the guide, I've said this ages ago, so I'll just repeat it once more... A little note would be nice but not even required.
( PB6.00 LTS Win11 x64 Asrock AB350 Pro4 Ryzen 5 3600 32GB GTX1060 6GB)
( The path to enlightenment and the PureBasic Survival Guide right here... )
Post Reply