Over a year ago, I wrote a brief article for the May/June 2007 issue of the Washington Apple Pi Journal that was an introduction to programming in REALbasic (RB). I followed that up with another article demonstrating the use of RB to build a simple checkbook register. Since then I have written several more articles, each adding more features to the original program to expand the original simple checkbook register into a full-featured application. This is the final article in my series.
In this installment, I will show you how to add functions that will let you run the application as your own check register. No data files will accompany the application; you will generate them.
The application through Part 7 can be downloaded here:
http://www.wap.org/journal/realbasic/
Up to now, I have stored the application and the data files in the same folder so the application knew where the files were and thus, opened them automatically. This was convenient for a tutorial such as this. However, Macintosh applications are normally stored in the Applications folder and data files are normally stored in the Documents folder. Therefore I must add the capability to open the application and its files while they reside in two separate locations and to generate these files when the application is opened for the first time.
To support the new code additions I will add five new globalFinancial properties: the boolean newAccount, shutdown and fileIsOpen and two folder items, dataFileFolder and dataFile. I will also add a new method, readFilePointer, so the program can locate the checkbook data files.
First, I need to modify the transWindow initialize method to support the case where the application is opened for the first time.
today = new date
readFilePointer
readPrefs
if newAccount Then
NewAcct.ShowModal // Call dialog
if shutdown then Quit // UserCancelled
Else
dataFile = dataFileFolder.child("Checkbook Data")
readDataFile
readPayeeList
readDepositorList
End
setSystemYear
findBankBalance // Compute bank's balance
When the application opens there are two cases to consider so the logic goes like this. First, it looks for a file containing a pointer to the location of the folder that contains the check register files. If no pointer file is found, it follows that the application has never been run and new data files must be generated. However, if a pointer is found but it does not point to valid files, the application has been previously run and a correct pointer must simply be restored.
The following readFilePointer method (added to globalFinancial) manages the first case and is called by the transWindow initialize method above.
dim f As FolderItem
dim s As String
f = preferencesFolder.child("Check Register pointer")
if f <> nil and f.exists then
inStream = f.openAsTextFile
s = instream.ReadAll
dataFilefolder = DocumentsFolder.GetRelative(s)
inStream.Close
Else
// file pointer file does not exist
newAccount = True
End
This method opens a file in the Preferences Folder to get the location of the data file folder. If the file doesn’t exist it must be a first opening and newAccount is set true. The new term “GetRelative” produces a function that points to the data folder location relative to the Documents Folder. The case where the pointer is invalid will be addressed later.
The second file to be opened is the Preference file. Previously, if the Preference file was not present, a message box alerted the user. Instead, I’ll set the change flag:
prefsHaveChanged = True // force a writePrefs
This will force the application to write a new Preference file upon closing.
Next, in the initialize method where newAccount is true, I will call the NewAcct window. The term “ShowModal” halts the program until either the OK or Cancel button in that window is pressed. If the NewAcct Cancel button is clicked, the variable shutdown will be set True. This will cause the application to quit under the assumption that the user doesn’t want to set up the new account at this time.
The two pieces of information needed to open a new account are the account balance and the next check number. The new dialog, NewAcct, shown in Figure 1, briefly describes the files that will be generated and provides edit boxes to collect the required information. In addition the check boxes allow the user to prepare an initial listing of Payees and Depositors.
Figure 1: When you open your checkbook application for the first time, this window appears and prompts you to enter the information you need to get started.
Once the user has provided the requested data in NewAcct, the OK button action handler executes the following:
dim d as new date
dim s, eoyDate As string
if not testEntries then return
d.year = d.year-1
d.month = 12
d.day = 31
eoyDate = d.shortdate
// Build record
s = eoyDate + chr(9)
s = s + chr(9) + chr(9)
s = s + "Brought Forward" + chr(9) + chr(9)
s = s + newAmount.text
TransWindow.addTransRow(s, 0)
nTransactions = nTransactions + 1
nextcknr = val(newCkNr.text)
if addPayees.value = true then
newNames.title = "Payees Names"
newNames.showmodal
End
if addDepositors.value = true then
newNames.title = "Depositors Names"
newNames.showmodal
End
newAccount = True
ListHasChanged = true
self.close
This code begins with the method testEntries that insures all entries are valid. Then, the present year is used to develop the date for 31 December of last year to accompany the Brought Forward amount in the first record of the register that is built and posted in the following lines. Next, if the user has checked either of the “Add” check boxes the newNames dialog box appears with the appropriate title (see Figure 2) to collect the Payee and/or Depositor names and build new files for them (see below).
Figure 2: Add Payee & Depositor names with this window.
Lastly, two flags are set. The newAccount flag blocks the reading of the Data, Payee and Depositor files which have yet to be written. The ListHasChanged flag will prompt the writing of the new data file when the application is closed.
The newNames dialog merely collects names and adds them to the variable arrays depList() or payList() as appropriate. These variables were discussed in Part 3 when I added the Payee and Depositor lists. The Add button code:
If self.title = "Payee Names" then
npayees = npayees +1
PayList.append nameEditField.text
refreshPayees // update the list
nameEditField.text = ""
payeesChanged = true
End
If self.title = "Depositor Names" then
ndepositors = ndepositors + 1
DepList.append nameEditField.text
refreshDepositors // update the list
nameEditField.text = ""
depositorsChanged = true
End
Here the window title identifies which list is involved. Then the list count is incremented, the appropriate list updated, the dialog’s list box and edit field are reset and the change flag is set. The refreshPayees and refreshDepositors are the same but use different variables:
dim i as integer
newNameList.deleteAllRows
for I = 1 to npayees
newNameList.addrow PayList(i)
next
nameEditField.setfocus
This method erases the window’s list box and rebuilds it from PayList. When done the Quit button merely closes the window, since the variable array is already updated.
The final step is to prompt the user to indicate where the application’s files should be saved. The user will specify where the dataFileFolder will be saved within the Documents Folder. The new defineNewFileFolder method will be called from the existing transWindow saveAll method, to which I will add a few lines:
// If it's a new operation, define data file folder
dim ok As boolean
if newAccount = true then
ok = defineNewFileFolder
if not ok then return
End
Notice that by calling the defineNewFileFolder method as a boolean, if the user clicks Cancel, the save operation aborts.
Here is the new transWindow method called defineNewFileFolder. The prompt is made with a special form of the Open dialog called a SelectFolderDialog:
Dim dlg as New SelectFolderDialog
Dim f as FolderItem
dlg.ActionButtonCaption = "Select Folder"
dlg.InitialDirectory = DocumentsFolder
dlg.Title = "Indicate where Checkbook Folder should be located"
f = dlg.ShowModal()
dataFileFolder = f.Child("Checkbook Folder")
dataFileFolder.CreateAsFolder
if dataFileFolder <> Nil then
//File folder defined - define other folder items
dataFile = dataFileFolder.child("Checkbook Data")
payeeFile = dataFileFolder.child("Payee Names")
depositorFile = dataFileFolder.child("Depositor Names")
writeFilePointer // location of dataFileFolder
newAccount = false
return true
Else
return false //user canceled
End
When this method is run, the dialog will prompt the user to specify where the Checkbook Folder is to be placed (see Figure 3). Note the dialog’s title and the button, “Select Folder.” Once this selection is made, the dataFile, the payeeFile and the depositorFile are placed within the Checkbook Folder in that location. Finally, the data pointer file is written to the Preferences Folder.
Figure 3: Where to save the Checkbook files?
The writeFilePointer method:
dim s As String
dim f As FolderItem
s = dataFileFolder.GetSaveInfo(DocumentsFolder) // loc of folder
f = preferencesFolder.child("Bank Checking pointer")
outstream = f.createTextFile
outstream.writeLine s
outstream.Close
Opening files is normally done in applications using the File > Open menu so I must add that menu item here. The menu handler reads as follows:
If promptSave Then return(False)
//create a new openDialog
dim dlg As new OpenDialog
dlg.filter = "text"
dlg.initialDirectory = DocumentsFolder
dlg.promptText = "Select a Checkbook Data file"
dataFile = dlg.showModalwithin(self)
If dataFile.exists and dataFile.name <> "" Then
Else
MsgBox "Invalid Checkbook File!"
return(False)
End
// is a file already open?
if fileIsOpen Then
Transwindow.TransList.deleteAllRows
ntransactions = 0
End
//open file
readDataFile
Return True
This handler must consider the situation where you have a checking data file already open–for instance your current file–and want to open an archive file, so a test for saving the current file is required. Then a new open dialog is constructed so the user can select the desired file. If a file is already open, the check register is cleared and then the data file is read.
If the dataFilePointer is in error and points to a missing data file folder, the condition would be detected by these lines in the readDataFile method:
if not dataFile.Exists Then
resetFilePointer
Else
Then the correct folder must be located by this resetFilePointer method:
Dim dlg as new SelectFolderDialog
Dim f as FolderItem
dlg.InitialDirectory = DocumentsFolder
dlg.Title = "The Checkbook data folder cannot be found."
dlg.PromptText = "Please select the Checkbook data folder."
dlg.ActionButtonCaption = "Select"
f = dlg.ShowModal()
If f <> Nil then
dataFileFolder = f
writeFileFolder // update saved file pointer
Else
Quit //User Cancelled
End
This completes the construction of a working check register. The application, along with the source code, can be downloaded from the WAP Web site:
http://www.wap.org/journal/realbasic/
I hope these articles have convinced you how simple it is to write an application using REALbasic. I encourage you to try it yourself; a trial version of RB can be downloaded here:
Good luck, and happy programming.