Washington Apple Pi

A Community of Apple iPad, iPhone and Mac Users

Building a Check Register in REALbasic, Part 5

By Brent Malcolm

Washington Apple Pi Journal, reprint information

This article is a continuation of the series I began in the July-August issue of the Washington Apple Pi Journal about simple programming in REALbasic (RB). I began by showing the would-be programmer how to use RB to construct a simple check register. In later installments I have transformed that simple check register into a full-featured application by adding more features as the series has developed. The application through Part 4 can be downloaded here:

http://www.wap.org/journal/realbasic/

This article will demonstrate the addition of the following functions:

Preferences

All Mac users are familiar with using preferences to establish the desired behavior of applications. For the check register I'll use one simple preference to demonstrate how preferences can be saved. As you may know, application preferences are saved in a Preferences folder located in the Users Library, commonly denoted thus: ~Library/Preferences.

To demonstrate a preference I'll offer a choice of how CheckWindow operates. If the user wants to enter more than one check during a single session, it is more convenient if the window remains open rather than closing after every check entry. To allow that window response, I'll make that choice a preference.

To begin, I'll add two boolean properties, manyCheck and prefsHaveChanged, to globalFinancial. Next I'll add a new preferences window where the user can make a selection. I've designated the preferences window as a Sheet window, a form now familiar to Mac users that seems to slide down out of the title bar of the parent window (see Figure 1). Within the preferences window I have added a few new controls. The simplest control is the Separator (the line between the title and the text). The others are the familiar Group Box and the Radio Buttons. The radio buttons are named rbMultipleChecks and rbSingleCheck. RB takes care of managing the radio button logic so that only one can be selected. I have added the following to the preferences window Open event handler to set the buttons to whatever value is held by the variable manyCheck:

  if manyCheck = True Then
    rbMultipleChecks.value = True
  else
    rbSingleCheck.Value = True
  end if

The window's OK button gets the following code:

  if rbMultipleChecks.value = true then
    manyCheck = true
  elseif rbSingleCheck.value = true then
    manyCheck = false
  end if

  prefsHaveChanged = True
  self.close

Note that the if-then test looks at which radio button is selected, sets manyCheck accordingly and sets the change flag.

Figure 1

Figure 1

The boolean variable prefsHaveChanged has been added to the list of properties in globalFinancial. The variable prefsHaveChanged will be tested when the application is closed to execute writePrefs if this variable is true. I do this by adding the following to the transWindow method saveAll:

if prefsHaveChanged then writePrefs

The Cancel button only closes the window, so it receives the simple code:

self.Close

Saving and Reading Preferences

The writePrefs method will write a preference file to the location described above and is placed in globalFinancial as follows:

   dim f As folderItem
  dim o As TextOutputStream

  f = preferencesFolder.child("Bank Checking prefs")
  o = f.createTextFile

  // manyCheck status
  if manyCheck = true then
    o.writeline "manyCheck" + chr(9) + "true"
  else
    o.writeline "manyCheck" + chr(9) + "false"
  end if
    o.Close

RB makes a simple task of locating the preference file. It is simply a "child" of "preferencesFolder." The code writes the legend "manyCheck," then a tab and then the word "true" or "false." I'll also need a companion method (readPrefs) to read the preference file when the application opens:

    dim f As folderItem
  dim instream As TextInputStream
  dim s As string

  manyCheck = false

  // get manyCheck flag
  f = preferencesFolder.child("Bank Checking prefs")
  if f.exists then
    inStream = f.openastextFile
    s = inStream.readLine
    if NthField(s, chr(9), 2) = "true" then manyCheck = true
    instream.Close
  Else
    prefsHaveChanged = True // force writePrefs to make a file
  End


The call to readPrefs is placed in the transWindow initialize method so the manyCheck flag is set upon application start. Note that if a preference file is not found, the prefsHaveChanged flag will force writePrefs to write a file when the application closes.

The only remaining task is to get the preferences window to open. As you know the Preferences menu item always appears in the Apple menu but RB doesn't have a way to place any menu item in the Apple menu.  Instead, RB places the Preferences menu item in the Edit menu, and in the Property/Value table its Super is set to PrefsMenuItem (See Figure 2). This tells RB that when the application is compiled, the Preferences menu item must appear in the Apple menu.

Figure 2

Figure 2

The Preference Menu Handler is added to transWindow:

  Preference.Show

Now that I've established a preference for how CheckWindow should operate, I have to make some changes to that window to reflect that preference. If the preference manyCheck is set false, CheckWindow will not change at all, but if manyCheck is true the window must remain open until I'm done entering checks. To accomplish this, the Cancel button will be labeled "Done" and the OK button will function as before, but won't cause the window to close. Therefore, the Cancel button Open event handler will be coded as follows:

  if manyCheck then
    me.caption = "Done"
  Else
    me.caption = "Cancel"
  End

The OK button Action event handler, which ended before with a call to close the window, now ends with the following:

  // Reset Check Window if necessary
  if Not manyCheck then
    self.Close
    Return
  Else
    BalText.text = "Balance:  $"+ TransWindow.transList.Cell(ntransactions - 1, 6)
    CkNrText.text="Check Number : " + str(nextcknr)
    ckdate.setfocus
    CkDate.SelStart = 0
    CkDate.SelLength = Len(CkDate.text)
    ckamount.text = ""
    pay.text = ""
    PayeeList.scrollposition = 0
    PayeeList.listindex = - 1
  End

This added code obviously closes the window if manyCheck is false. If it is true, this code reconfigures CheckWindow to its original condition so another check can be entered.

Examining the Date of the Transaction

Occasionally I may enter a new transaction out of sequence. Obviously, I'd like the Register to remain in date and check-number order. I accomplish this by adding a new testEntryDate method to globalFinancial that will compare every transaction with the last entry and call for a sort if the transactions are out of order.

I add a call to the testEntryDate method to OK button action events of the three types of transactions I have programmed: CheckWindow, DepositWindow and atmWindow. This will insure that any out-of-sequence transactions are handled properly. A pointer to the last transaction (lineNr) is sent to the testEntryDate method and the comparison is made to the transaction before lineNr.

   // Tests last entry to see if it's earlier than previous entry

  if TransWindow.translist.cell(lineNr - 1, 0) < TransWindow.translist.cell(lineNr - 2, 0) then
    Transwindow.DateSort()
  else
    FindBalance()
  end if

This method simply looks at the date of the last line in the check register and compares it with the date of the previous line. If the date on the last line is earlier, then the method calls for the register to be sorted by the DateSort method, also newly added to transWindow:

    transList.SortedColumn = 2  //sort by check number
  transList.Sort
  transList.SortedColumn = 0  //then by date
  transList.Sort

  FindBalance()
  TransWindow.translist.scrollposition = 1000 //set slider to bottom


Those of you who have persevered with these articles from the beginning may recall that transWindow's transList has a hidden column 0 in which I stored the transaction date in totalSeconds, RB's master property for storing date/time variables. This allows me to do a date sort using column 0.

Voiding a Check

If you need to void a check that has already been written and recorded, you can do it by:

I add the new menu item in the same manner as other menu items and name it EditVoidCheck. The menu handler is only one line that opens the new window:

  voidCheck.show

VoidCheck is shown in Figure 3. It is merely an edit box (ckNr) and two familiar buttons. Its Open event handler simply places the cursor in the edit box:

  ckNr.setfocus

Figure 3

Figure 3

The Cancel button does the familiar task of closing the window with no further action. The OK button action handler follows:

    dim i, j as integer
  dim s, vDate As string

  for i = 1 to ntransactions - 1
    if TransWindow.transList.Cell(i, 2) = ckNr.text then
      j = i
      exit
    end
  next

  // Is entry outside the check number range?
  if j = 0 and val(ckNr.text) <> nextcknr then
    MsgBox "That is not in the range of Check Numbers for this year"
    self.close
    return
  end

  if ckNr.text <> str(nextcknr) then
    // Build a replacement line; first get original date
    vDate = TransWindow.transList.cell(j, 1)
    s = buildVoidLine(vDate, ckNr.text)
    isEdit = true
    TransWindow.addTransRow(s, j)
  else
    // Entry is the next check number
    s = buildVoidLine(today.shortDate, ckNr.text)
    TransWindow.addTransRow(s, ntransactions)
  end

  FindBalance  //recompute Balance
  ListHasChanged = True
  self.close


First every record in the register is searched to find the check number that matches the entry in the edit box. If no match is found the message box so advises. If a match is found a replacement record must be built with the word "Void" in the Payee/Payer column, a check in the "cleared" box, and "0.00" in the debit column. First the date of the original transaction is determined. Then, that date and the check number are sent to the new method, buildVoidLine:

  dim s As string

  s = vDate + chr(9)
  s = s + ckNr + chr(9) // check nr
  s = s + "x" + chr(9)  //  check considered cleared
  s = s + "Void" + chr(9)
  s = s +  "0"  // debit
  return s

This method constructs a record in the same format as a record that is read from the data file so the same transWindow method, addTransRow can be used to insert the revised record into the Check Register. However, there is one nuance in this transaction that must be managed. The method addTransRow begins by adding a new line to the list box, transList, but I don't want to add a new line, I want to modify an existing line. To do this I've added a new globalFinancial property, isEdit, as Boolean, and I've changed the beginning line in addTransRow to read:

  if not isEdit then transList.addRow""

This insures that the new record is simply inserted in place of the old record.

Editing the Depositor and Payee name files

From time to time, the Depositor and Payee name files will may to be edited. A company name could change, or there might be a new name or one you no longer use. To accomplish this edit, I'll build one new window that will be used to edit both the Depositor and Payee name files. This window, EdList, which is shown in Figure 4, is equipped with a listBox, an editField, three radio buttons and two push buttons. It will contain the following new properties: EditIndex as integer, incomplete & replace as boolean and listMod as string. It will be called by two new menu items in the Edit Menu, Edit Depositor List... & Edit Payee List..., that I will construct just as I did the Void Check... menu items above.

First a Little Housekeeping

When this application was built, the Depositors List and the Payees List were only opened by the DepositWindow and the CheckWindow respectively. Now these lists must also be opened by the new EdList, so I need to add four new properties to globalFinancial to accommodate the two lists: depList(0) and payList(0) as string arrays, and nDepositors & nPayees as integers. In addition I'll add three new properties to globalFinancial to accommodate the editing: depositorsChanged and payeesChanged as boolean, and newName as string.

The application will now read the two list files upon opening by adding code to the initialize method. From here on I will describe only the code associated with the Payee functions. The Deposit functions are the same, but of course use a different file.

redim payList(0)
  npayees = 0

  payeeFile = dataFileFolder.child("Payee Names") // location of Payee List

  if payeeFile.exists then
    inStream = payeeFile.openastextFile
    while not instream.eof
      currentLine = instream.readLine  //read Payee list
      payList.append currentLine
      npayees = npayees + 1
    wend
  else
    msgBox "The Payee Names file is missing."
    return
  end if
  instream.close

This is essentially identical to the method in the CheckWindow that read the file into the window's listBox but now reads into the array payList. When the CheckWindow opens it merely reads this array into the listBox.

    dim i as integer

  for i = 1 to npayees
    me.addrow payList(i)
  next
  me.headingindex = 0
  me.sort

Now to Continue Editing

The editing menu handlers will call the EdList init method using the input parameter list, which will define what type of edit (deposit or payee) is called. The Payee Edit menu handler is:

    EdList.init ("payee")
  Return True

The Depositor Edit handler will contain the list parameter, "deposit" instead of "payee."

The EdList init method follows:

   listMod = list

  select case list
  case "deposit"
    self.Title = "Edit Depositors List"
    loadList

  case "payee"
    self.Title = "Edit Payees List"
    loadList

  end Select

  self.Show

The new function Select Case will execute its commands depending on the content of the parameter list, and once that is complete, the window opens. Note that the variable listMod is set to the input parameter list. In the case of "payee," the window's title is defined and load list is called to fill the ListBox. In this method listMod controls the case selection:

  dim i as integer

  select Case listMod
  case "deposit"
    for i = 1 to ndepositors
      editBox.addrow depList(i)
    next
    editBox.headingIndex = 0
    editBox.sort

  case "payee"
    for i= 1 to npayees
      editBox.addrow payList(i)
    next
    editBox.headingIndex = 0
    editBox.sort

  end Select

Again, Select Case defines which set of commands are executed. In the payee case, the names from the array payList are loaded and sorted.

Now, if the user clicks on any name in the listBox named EditBox the CellClick handler does this:

  ModField.text = me.cell(row, 0)
  EditIndex = row
  ModField.setfocus
  Replace = true

First the name in the clicked cell is placed in the editBox, ModField. Then EditIndex is set to the row number clicked.  The focus is placed in ModField and Replace is set True. When the OK button is pressed, here's the result:

  If ModField.text = "" then
    msgBox "You must have an entry in the edit box"
    return
  end if

  NewName = ModField.text
  incomplete = False

  Select case listMod
  case "deposit"
    modDep

  case "payee"
    modPay

  end select

  if incomplete then Return  //one of the "mod" handlers had an error

  self.Close

After an error check the Select Case again determines what method is called. Here's the modPay method:

  dim i As Integer

  If ReplaceButton.value then
    if Replace = false then
      msgBox "You must select a name to replace."
      incomplete = True
      return
    end if
    editBox.cell(editIndex, 0) = newName
  ElseIf NewButton.value = true then
    editBox.addrow newName
    npayees = npayees + 1
    payList.append " "
  Else  //delete Button
    editBox.removeRow editIndex
    npayees = npayees - 1
  end if
  editBox.sort
  for i = 1 to npayees
    payList(i) = editBox.cell(i - 1, 0)
  next
  payeesChanged = True

Depending on which radio button is selected, the name is replaced, added or deleted and then payList is rebuilt. The boolean property, payeesChanged has been established in globalFinancial to trigger the new method writePayeeList if it is True. I've also added a test for payeesChanged and depositorsChanged in the promptSave and EnableMenuItems methods so this writePayeeList is called:

  dim o As textoutputStream
  dim i As integer

  //write Payee Name List
  o = payeeFile.CreateTextFile
  for i = 1 to nPayees
    o.Writeline payList(i)
  next
  o.close
  payeesChanged = false

This concludes this article on REALbasic. Future articles will cover the following:

The compiled application to-date along with the data files and the source code can be downloaded here:

http://www.wap.org/journal/realbasic/