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!
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:
"Hello World" in a MessageRequester:Code: Select all
Debug "Hello World!"
We now continue with a short example using the available variable types, arithmetic operators and displaying the result:Code: Select all
MessageRequester("", "Hello World!")
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.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
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.B is a long integer, C is a floating point number, they will be initialised to zero.Code: Select all
A = 0
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 B.L, C.F
F is a string.Code: Select all
Define.L D = 10, E = 20
So is G$, however, if you declare strings this way, you must always use the $ notation.Code: Select all
Define.S F
This won't work. (G becomes a new integer variable and a compiler error occurs).Code: Select all
G$ = "Hello, "
H is an array of 20 strings, array indexing begins at zero.Code: Select all
G = "Goodbye, World!"
Now H is an array of 25 strings. If the array is resized larger, its original contents will be preserved.Code: Select all
Dim H.S(19)
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
ReDim H.S(24)
K will be a new, empty, list of strings at main scope.Code: Select all
Global.I J
M will be a new, empty, map of strings at main scope.Code: Select all
NewList K.S()
Note that you can't use the alternative syntax of the Define keyword with NewList or NewMap though. A compiler error will result.Code: Select all
NewMap M.S()
Within a procedure.Using operators on variables.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
String concatenation.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
Add an element to the K list.Code: Select all
G$ + "World!"
Add an element to the M map.Code: Select all
AddElement(K()) K() = "List element one"
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.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
#MyConstant1 = 10 #MyConstant2 = “Hello, World!”
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.
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.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
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.
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 A For A = 0 To 10 Step 2 Debug A Next A
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 B ;B = 100 While B < 100 B + Random(20) Debug B Wend
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 C ; C = 100 Repeat C + Random(20) Debug C Until C > 99
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
Define.I D Repeat Debug D Forever
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.
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
; 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
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.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)
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.
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).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. ...
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:-Next, remove the output section of code completely, from the comment line:-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 ...
Now replace this with:-Code: Select all
; If there are some entries in the list, show the results in the debug window. ...
All being well the output should appear in a console window looking something like this:-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()
This output is from a Windows XP system, later versions of Windows and Linux or Mac OSX will show different files of course.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
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.
At this point the application already has some useful features. However, it has some problems too:-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
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.
Previously, we mentioned four limitations to this program. This new version uses procedures to address three of them.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
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.Add this new procedure to the implementation section.Code: Select all
Declare.S AttributeString(alngAttributes.L)
Finally, replace these two lines in the ListLoad procedureCode: 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
with these,Code: Select all
; Convert the attributes to a string, for now. strAttrib = StrU(alstFiles()\Attributes)
Now when the program is executed a string display will be shown instead of the integer values.Code: Select all
; Call AttributeString to convert the attributes to a string representation. strAttrib = AttributeString(alstFiles()\Attributes)
On a Windows system it would look something like this (assuming all attributes are set):and on the other two systems:Code: Select all
ACHRS
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.Code: Select all
L RWX RWX RWX
The lines between:andCode: Select all
CompilerIf #PB_Compiler_OS = #PB_OS_Windows
will be compiled on Windows systems. The constant #PB_Compiler_OS is automatically defined by PureBasic to allow this kind of program logic.Code: Select all
CompilerElse
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.
The last compiler directive that we're going to discuss here is: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
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.Code: Select all
EnableExplicit.
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