months ago I had the idea of including a short "We start" chapter into the PB docs.
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!
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.
- 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...
(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)
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:
Debug "Hello World!"
"Hello World" in a MessageRequester:
Code:
MessageRequester("", "Hello World!")
We now continue with a short example using the available variable types, arithmetic operators and displaying the result:
Code:
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 variablesspikey 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:
A = 0
B is a long integer, C is a floating point number, they will be initialised to zero.
Code:
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:
Define.L D = 10, E = 20
F is a string.
Code:
Define.S F
So is G$, however, if you declare strings this way, you must always use the $ notation.
Code:
G$ = "Hello, "
This won't work. (G becomes a new integer variable and a compiler error occurs).
Code:
G = "Goodbye, World!"
H is an array of 20 strings, array indexing begins at zero.
Code:
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:
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:
Global.I J
K will be a new, empty, list of strings at main scope.
Code:
NewList K.S()
M will be a new, empty, map of strings at main scope.
Code:
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:
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:
; 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:
G$ + "World!"
Add an element to the K list.
Code:
AddElement(K())
K() = "List element one"
Add an element to the M map.
Code:
M("one") = "Map element one"
Constantsspikey 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:
#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:
Enumeration
#MyConstantA
#MyConstantB
#MyConstantC
EndEnumeration
Enumeration 10 Step 5
#MyConstantD ; will be 10
#MyConstantE ; will be 15
#MyConstantF ; will be 20
EndEnumeration
Decisions and Conditionsspikey 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:
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:
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
Loopsspikey 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:
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:
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:
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:
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:
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 memoryspikey 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:
; 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:
...
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:
...
; 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 outputAndre 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:
...
; 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:
; If there are some entries in the list, show the results in the debug window.
...
Now replace this with:-
Code:
; 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:
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:
; 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 drawingspikey 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:
; 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 Proceduresspikey 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:
; 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:
Declare.S AttributeString(alngAttributes.L)
Add this new procedure to the implementation section.
Code:
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:
; Convert the attributes to a string, for now.
strAttrib = StrU(alstFiles()\Attributes)
with these,
Code:
; 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:
ACHRS
and on the other two systems:
Code:
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:
CompilerIf #PB_Compiler_OS = #PB_OS_Windows
and
Code:
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:
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:
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:
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 filesspikey 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:
; 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