REALbasic University Resources:

RBU: Glossary Defines common REALbasic programming terms
  Archives Previously published columns
Translations: Dutch Courtesy of Floris van Sandwijk
  Japanese Courtesy of Kazuo Ishizuka
  Chinese Courtesy of Dong Li
  RBU Translation Guide Information on Translating RBU into other languages
Books: Matt's Book (2nd Edition!) Ideal for experienced programmers
  Erick's Book Best for beginning programmers
Websites: Mother Ship The publisher of REALbasic
  RB Webring Links to hundreds of REALbasic websites
  RESExcellence Another REALbasic programming column
  REALbasic Developer Magazine The premiere source for REALbasic instruction.

REALbasic University is Sponsored by

Make your Mac do what YOU want it to. Create games, utilities, cool Mac OS X tricks. Download REALbasic now and create your own software.


Print This Article

REALbasic University: Column 061

Monotab: Part II

In our previous lesson, we got Monotab's interface set up, but hadn't started writing the conversion code. We'll finish the program today.

Importing a File

Our first task is to bring in a text file so we can convert it. We already set up listBox1 to accept a dropped file: now we just have to do something with it.

Go to listBox1's DropObject event and add this code:

  
if obj.folderItemAvailable then
addText(obj.folderItem)
end if

This simply checks to see if a folderItem is available, and if so, sends it to a routine called addText. Let's add that method now.

Create a new method (Edit menu, "New Method") and name it addText. First we'll make sure it's a valid file and try to open it as a text file. If either of those operations fail, we'll exit the routine without doing anything. If the file is successfully opened as a text file, we'll extract the text and add it to listBox1.

Here's the code:

  
dim t as textInputStream
dim s as string
dim colNum, i as integer

if f = nil then
return
end if

if f.directory then
return
end if

t = f.openAsTextFile
if t = nil then
return
end if

listBox1.deleteAllRows
s = t.readLine
colNum = countFields(s, chr(9))
listBox1.ColumnCount = colNum
for i = 0 to colNum
listBox1.heading(i) = nthField(s, chr(9), i + 1)
next // i

while not t.EOF
s = t.readLine
listBox1.addRow nthField(s, chr(9), 1)
for i = 2 to colNum
listBox1.cell(listBox1.lastIndex, i - 1) = nthField(s, chr(9), i)
next // i
wend

t.close

Note that we first delete all the rows in listBox1. That's in case the user has already dropped one file on to fill up the listbox: this cleans it up.

Next, we read in just the first line of the text file. From that first line we set up how many fields the file will contain. It's therefore important that the first line of your text file include the same number of fields as all other lines. In fact, we assume that this first line contains the names of each column type (the headers). Obviously, for a commercial or professional program we might not want to assume this, but for a program we're writing for our own use, this is a valid assumption.

To count the fields in the first line, we use the countFields command and tell it the fields are separated by tabs. We then set the headings for listBox1 by extracting them from the first line.

Once the headings have been established, all that's left is to read in the data. We start a while-wend loop which lasts as long as the file has stuff left to read. For each pass through the loop, we read in a single line of text. Then we parse that line with the nthField command (which extracts the text of individual fields by number), and carefully add each field's text to a cell in listBox1.

(Remember, since listBox1 has several columns, we can't just use the addRow command: that only adds to the first column in the row. The data for the other columns must be added cell by cell.)

When we've finished all that, we close the file and the method is done. The file has been imported and is now in our program's memory.

Converting the Text

In order to convert the text, we'll need a couple special routines. The first is simple: a function that will return a string of n spaces (where n is an integer).

Create a new method (Edit menu, "New Method") and call it padSpaces. Give it n as integer as its parameter, and a return type of string like this:

Here's the code for the method:

  
dim i as integer
dim s as string

s = ""
for i = 1 to n
s = s + " "
next // i

return s

As you can see, this is a pretty simple function. It just adds n spaces to s, effectively returning a string of n spaces.

The second routine we'll need to handle the conversion is a little more complicated. Last week we explored the conversion task and discovered that for us to know how wide (in characters) a column must be, we must know the width of the largest item within that column of data. The example I used was the list of MLS teams, where the top team was the "San Jose Earthquakes," which, while long, isn't as long as the "New York/New Jersey Metrostars."

To figure out this calculation, we need to examine the length of each item within a field. Since we need to save this information, we need a place to store it. Since we'll need a value saved for each column, it makes sense to store this value inside an array with an element for each column.

Let's add a property to window1. Go to the Edit menu and choose "New Property." Add an array like this: colMax(0) as integer.

Now create a new method (Edit menu, "New Method") and call it CalcMax. This routine will go through and figure out the largest (widest) string for each column and store that within the colMax array.

The first thing the routine does is initialize colMax to the number of fields within the current data file. Then it starts up two nested loops: the outer one (col) steps through each column, while the inner one (row) steps through each line (record) of the text and sets n to the width of that field. Then it checks n to see if it's bigger than whatever value has been stored in colMax(col). If it is, then we store n inside colMax(col), replacing what used to be there.

Here's the code:

  
dim row, col, n as integer

redim colMax(listBox1.columnCount - 1)

for col = 0 to (listBox1.columnCount - 1)
colMax(col) = 0
for row = 0 to (listBox1.listCount - 1)

n = len(listBox1.cell(row, col))
if n > colMax(col) then
colMax(col) = n
end if

next // row
next // col

Note that after this routine runs, colMax contains the maximum lengths of each field. That all the routine does: build the colMax array.

Now let's create a new method called exportText. This is where the actual conversion takes place. We basically need to do two things: create a new file where we saved the converted text, and convert the tabs to spaces.

The first part isn't difficult. We ask the user to name and save the new file, and then we try to create it (with the .createTextFile function). If that all works, we're set to convert the text.

Here's the code:

  
dim row, col as integer
dim s, tcell as string
dim f as folderItem
dim t as textOutputStream

f = getSaveFolderItem("", "Converted")
if f = nil then
beep
return
end if

t = f.CreateTextFile
if t = nil then
beep
return
end if

calcMax

s = ""
for col = 0 to (listBox1.columnCount - 1)
s = s + listBox1.heading(col)
s = s + padSpaces(colMax(col) - len(listBox1.heading(col)) + padSlider.value)
next // col

s = s + chr(13)
for row = 0 to (listBox1.listCount - 1)

for col = 0 to (listBox1.columnCount - 1)
tcell = listBox1.cell(row, col)
s = s + tcell

if col < (listBox1.columnCount - 1) then
s = s + padSpaces(colMax(col) - len(tcell) + padSlider.value)
end if
next // col
s = s + chr(13)

next // row

t.write s
t.close

msgBox "All done!"

Before we begin the conversion, we first call calcMax to set the colMax array. Next, we examine the headers of listBox1 and build the first row of s, which will become our exported file. Note that the amount we send padSpaces (the number of spaces between fields) uses the following calculation formula:

C minus L minus P

where C is the size of the largest item within that field, L is the length of the current field value, and P is the value of padSlider.

After adding our headers to s we add a carriage return (chr(13)), then we start some for-next loops. The outer loop is row, in which row counts through each row of listBox1. The inner loop is col, which steps through each column of data. As we encounter each field, we add the appropriate number of spaces to pad out the column. After each row, we add a return character to s to finish off the line.

When we've finished the loops, we write s to the file to save it, and close it. We display a "Done" message so the user knows the file has been saved, and we're all finished.

Before our program will work, however, there's one more thing to do: we must call exportText from our exportButton. Add this line to exportButton's Action event.

  
exportText

There! We're all finished. How does it work? Let's test it using the sample file I gave out last week (Option-click to save it). Here's the result:

  
Date Match Time Channel
May 31 France vs. Senegal 7:25 a.m. ET ESPN2
June 1 Uruguay vs. Denmark 4:55 a.m. ET ESPN2
June 1 Germany vs. Saudi Arabia 7:25 a.m. ET ESPN
June 1 Rep. of Ireland vs. Cameroon (tape) 3:30 p.m. ET ABC
June 2 Argentina vs. Nigeria 1:25 a.m. ET ESPN2
June 2 Paraguay vs. South Africa 3:25 a.m. ET ESPN
June 2 Spain vs. Slovenia 7:25 a.m. ET ESPN
June 2 England vs. Sweden (tape) 3:30 p.m. ET ABC
June 3 Croatia vs. Mexico 2:25 a.m. ET ESPN2
June 3 Brazil vs. Turkey 4:55 a.m. ET ESPN2
June 3 Italy vs. Ecuador 7:25 a.m. ET ESPN2
June 4 China vs. Costa Rica 2:25 a.m. ET ESPN2
June 4 Japan vs. Belgium 4:55 a.m. ET ESPN2
June 4 Korea Republic vs. Poland 7:25 a.m. ET ESPN2
June 4 Brazil vs. Turkey (replay) 1:00 p.m. ET Classic
June 4 Italy vs. Ecuador (replay) 3:00 p.m. ET Classic
June 5 Russia vs. Tunisia 2:25 a.m. ET ESPN2
June 5 United States vs. Portugal 4:55 a.m. ET ESPN2
June 5 Germany vs. Rep. of Ireland 7:25 a.m. ET ESPN2
June 5 United States vs. Portugal (replay) 3:00 p.m. ET ESPN2
June 5 Korea Republic vs. Poland (replay) 3:00 p.m. ET Classic
June 6 Denmark vs. Senegal 2:25 a.m. ET ESPN2
June 6 Cameroon vs. Saudi Arabia 4:55 a.m. ET ESPN2
June 6 France vs. Uruguay 7:25 a.m. ET ESPN2
June 6 United States vs. Portugal (replay) 1:00 p.m. ET Classic
June 6 Germany vs. Rep. of Ireland (replay) 3:00 p.m. ET Classic
June 7 Sweden vs. Nigeria 2:25 a.m. ET ESPN2
June 7 Spain vs. Paraguay 4:55 a.m. ET ESPN2
June 7 Argentina vs. England 7:25 a.m. ET ESPN2
June 7 France vs. Uruguay (replay) 12:00 p.m. ET ESPN2
June 8 South Africa vs. Slovenia 2:25 a.m. ET ESPN2
June 8 Italy vs. Croatia 4:55 a.m. ET ESPN2
June 8 Brazil vs. China 7:25 a.m. ET ESPN
June 8 Argentina vs. England (replay) 1:00 p.m. ET ABC
June 9 Mexico vs. Ecuador 2:25 a.m. ET ESPN2
June 9 Costa Rica vs. Turkey 4:55 a.m. ET ESPN2
June 9 Japan vs. Russia 7:25 a.m. ET ESPN
June 10 Korea Republic vs. United States 2:25 a.m. ET ESPN2
June 10 Tunisia vs. Belgium 4:55 a.m. ET ESPN2
June 10 Portugal vs. Poland 7:25 a.m. ET ESPN2
June 10 Korea Republic vs. United States (replay) 2:00 p.m. ET ESPN2
June 11 Denmark vs. France 2:25 a.m. ET ESPN
June 11 Senegal vs. Uruguay 2:25 a.m. ET ESPN2
June 11 Cameroon vs. Germany 7:25 a.m. ET ESPN
June 11 Saudi Arabia vs. Rep. of Ireland 7:25 a.m. ET ESPN2
June 11 Korea Republic. vs. United States (replay) 1:00 p.m. ET Classic
June 11 Portugal vs. Poland (replay) 3:00 p.m. ET Classic
June 12 Sweden vs. Argentina 2:25 a.m. ET ESPN
June 12 Nigeria vs. England 2:25 a.m. ET ESPN2
June 12 South Africa vs. Spain 7:25 a.m. ET ESPN
June 12 Slovenia vs. Paraguay 7:25 a.m. ET ESPN2
June 12 Denmark vs. France (replay) 1:00 p.m. ET Classic
June 13 Costa Rica vs. Brazil 2:25 a.m. ET ESPN
June 13 Turkey vs. China 2:25 a.m. ET ESPN2
June 13 Mexico vs. Italy 7:25 a.m. ET ESPN
June 13 Ecuador vs. Croatia 7:25 a.m. ET ESPN2
June 13 Sweden vs. Argentina (replay) 1:00 p.m. ET Classic
June 13 Mexico vs. Italy (replay) 3:00 p.m. ET ESPN
June 13 Costa Rica vs. Brazil (replay) 7:00 p.m. ET ESPN2
June 14 Tunisia vs. Japan 2:25 a.m. ET ESPN
June 14 Belgium vs. Russia 2:25 a.m. ET ESPN2
June 14 Portugal vs. Korea Repbulic 7:25 a.m. ET ESPN2
June 14 Poland vs. United States 7:25 a.m. ET ESPN
June 14 Mexico vs. Italy (replay) 1:00 p.m. ET Classic
June 15 1st E vs. 2nd B (1) 2:25 a.m. ET ESPN2
June 15 1st A vs. 2nd F (5) 7:25 a.m. ET ESPN
June 15 Poland vs. United States (replay) 1:00 p.m. ET ABC
June 15 Round of 16 match (tape) 3:30 p.m. ET ABC
June 16 1st F vs. 2nd A (6) 2:25 a.m. ET ESPN2
June 16 1st B vs. 2nd E (2) 7:25 a.m. ET ESPN
June 16 Round of 16 match (tape) 1:30 p.m. ET ABC
June 17 1st G vs. 2nd D (3) 2:25 a.m. ET ESPN2
June 17 1st C vs. 2nd H (7) 7:25 a.m. ET ESPN2
June 18 1st H vs. 2nd C (8) 2:25 a.m. ET ESPN2
June 18 1st D vs. 2nd G (4) 7:25 a.m. ET ESPN2
June 18 Round of 16 (replay) 12:00 p.m. ET Classic
June 18 1st D vs. 2nd G (replay) 2:00 p.m. ET ESPN2
June 19 Round of 16 (replay) 1:00 p.m. ET Classic
June 19 Round of 16 (replay) 3:00 p.m. ET Classic
June 21 Winner (5) vs. Winner (7) (C) 2:25 a.m. ET ESPN2
June 21 Winner (1) vs. Winner (3) (A) 7:25 a.m. ET ESPN2
June 22 Winner (2) vs. Winner (4) (B) 2:25 a.m. ET ESPN2
June 22 Winner (6) vs. Winner (8) (D) 7:25 a.m. ET ESPN
June 22 Quarterfinal match (tape) 1:30 p.m. ET ABC
June 22 Quarterfinal match (replay) 9:30 p.m. ET ESPN2
June 25 Winner (A) vs. Winner (B) 7:25 a.m. ET ESPN2
June 25 Semifinal #1 (replay) 3:00 p.m. ET ESPN2
June 26 Winner (C) vs. Winner (D) 7:25 a.m. ET ESPN2
June 26 Semifinal #1 (replay) 1:00 p.m. ET Classic
June 26 Semifinal #2 (replay) 3:00 p.m. ET ESPN2
June 27 Semifinal #2 (replay) 1:00 p.m. ET Classic
June 29 Semifinal #1 (replay) 2:30 a.m. ET ESPN2
June 29 Semifinal #2 (replay) 4:30 a.m. ET ESPN2
June 29 Third place match (tape) 1:30 p.m. ET ABC
June 30 World Cup final 6:30 a.m. ET ABC
June 30 World Cup final (replay) 12:30 p.m. ET ABC
July 3 World Cup final (replay) 2:30 p.m. ET ESPN2

Looks pretty good, eh? Note that we don't take into account the overall length of each line, so this could create lines too long for email (most email programs wrap text longer than 65 or 70 characters). An improved Monotab would wrap text cells to keep the overall line length within a user-specified maximum. Perhaps we'll do that in a future RBU -- at present I explored creating that version, but it's surprisingly complicated and I never finished it.

If you would like the complete REALbasic project file for this week's tutorial (including resources), you may download it here.

Bonus Program: Convert Times

Just to show you how I'm a pathalogical user of REALbasic, I'm going to let you look a the source for another program I wrote along the lines of Monotab, called Convert Times.

Convert Times was written specifically to look at the World Cup TV schedule above and convert the times from Eastern to Pacific (I live in California). Obviously that's useless for other purposes, but it is interesting to look at the code, and the program presented some interesting challenges. For instance, converting from Eastern to Pacific is the "simple" matter of subtracting three hours: but what happens when a show starts at 2:30 a.m. ET?

If you'd like the project file for Convert Times, you can download it here.

Next Week

We'll have a mini-review of the latest version of REALbasic, 4.5, released last week at the Macworld Expo in New York.

News

REALbasic Developer Magazine Launched!

Speaking of Macworld Expo New York, yours truly was there in person to hand out flyers and printed copies of the premiere issue of REALbasic Developer. The results were outstanding: everyone was excited to see the first printed copies, and the balance of articles seemed to appeal to potential readers.

For people who've already subscribed to REALbasic Developer, your copies are being mailed right now. (International orders will take a little bit longer, due to red tape with the U.S. postal service.) Those who've ordered digital (PDF) subscriptions will receive an email shortly with instructions on how to download your copy.

If you'd still like to subscribe, it's not too late: we'll be doing a second mailing of the premiere issue at the end of August (keep in mind this is the August/September issue) to catch any late subscribers.

Letters

This week we hear from Joe, who writes:

Hi Marc!

Great column, I've found it really helpful while I've been learning REALbasic, although there is something really simple I would like to know. How do I put pictures in? I've looked in the reference part and it says use the Picture tool (I think), but I can't see that anywhere. I know it sounds silly, but I've been trying everything like ImageWell and Canvas but they don't work!

Thanks a lot.

Joe

Pictures are one of those "obvious" things that are only obvious to the engineers who created REALbasic. In truth they're not difficult to work with, especially if you've used graphics in other languages, but the documentation certainly leaves out a few steps for the non-programmer.

There are basically three types of pictures you can work with from within REALbasic:

  • Graphics you import into your RB project file via drag-and-drop
  • Graphic files you read into your program while it's running
  • Graphics you draw programmatically within RB

Let's look at these in reverse order, which is least common usage to most common usage.

Graphics you draw programmatically within RB. You can use REALbasic's drawing commands, which are available within a graphics object, to draw lines, points, circles, polygons, etc. If you save the results of those drawing commands to a picture object, you've got a picture.

Graphic files you read into your program while it's running. Another way of getting a picture object is to load one from a file. If your computer has QuickTime installed, you can open any graphics format QuickTime supports (which is a fair variety).

Here is a program that loads a picture from a file and displays it as the background for window1:

  
dim f as folderItem
dim p as picture

f = getOpenFolderItem("picture")
if f = nil then
return
end if

p = f.openAsPicture

if p <> nil then
window1.backdrop = p
end if

Place this code inside a pushButton's action event to try it.

Note that we have a special file type called "picture" defined (Edit menu, "File Types"): it uses ???? and ???? for the Type and Creator, meaning it will let you open any file. After making sure the picture opening worked (p is not nil), we set the backdrop of window1 to p.

Graphics you import into your RB project file. The most common way of working with pictures is to import a picture into your project: you do this by dragging a picture into REALbasic's project window:

In the above, I've dragged a picture called "pfs.jpg" to my RB project window where it is displayed as pfs. Note that the name appears in italics, meaning that it's a linked item. That means the actual picture is not embedded in your project file (so don't throw away the original item or your project won't compile).

With a picture imported this way, its name appears on menus within REALbasic where you can assign a picture to an item. For instance, windows and canvases have a backdrop property, which displays whatever picture you assign to it. You can pick an imported picture from a popup list to assign to an item's backdrop, like this:

You also could assign the backdrop programatically, like this:

  
window1.backdrop = pfs

You'd generally put that into the item's Open event so the code gets executed as the item opens. If you do it programatically, you could assign it a picture that you obtained via one of the first two methods of obtaining a picture.

With any of the three methods of obtaining a picture, you end up with a picture object. A picture object is simply a REALbasic object that is a picture. Once you've got a picture object, you can tell RB to display it, animate it, even programmatically manipulate it pixel by pixel if you want.

I hope that answers your question, Joe. Have fun using pictures!


About the Column
REALbasic University is a weekly instructional column on programming with REALbasic and is brought to you by REALbasic Developer, the magazine for REALbasic programmers.

Each week we answer select reader questions, and we're always open to ideas for future columns. Send your questions to . (Keep your questions simple and specific. General queries like "How do I write my own web browser?" will be neglected.) Your question won't be answered immediately, but will be answered in a future column. (If you don't want your correspondence published, just be sure to indicate that when you write. Otherwise it's fair game.)

About the Author
is an author, philosopher, graphic designer, photographer, film director, soccer fanatic, and programmer (among other things). He writes for MacOpinion, runs his own software company, Stone Table Software, which sells the revolutionary Z-Write word processor, and is Publisher and Editor of REALbasic Developer. He lives in Northern California with his cats, Mischief and Mayhem, and is rapidly running out of free time.

See the REALbasic University Archives


REALbasic University contents ©2001-2004 by Marc Zeedar and REALbasic Developer. All Rights Reserved.

Email This Article - Comment On This Article

.

Reader Specials

Server Racks Online:
Apple Xserve CompatibleServer Racks and Universal Network Racks
42U KVM Switch Solutions:
High-End Mac and Multi-Platform KVM Matrix switching solutions!
Digital Camera Online:
Great prices on Digital Cameras and accessories!
KVM Switches Online:
Great prices on Mac KVM Switches from the leading manufacturers!
LCD Monitors Online:
Great prices on LCD Monitors from the leading manufacturers!
LCD Projectors Online:
Shop online for LCD Projectors from the leading manufacturers!
USB 2.0 Online:
Great prices on USB 2.0 products from the leading manufacturers

Serious Business Software:
Accounting, Sales, Inventory, CRM, Shipping, Payroll & more!

KVM Switch solutions for MACs:
DAXTEN is a KVM switch, KVM extender and monitor splitter specialist for PC, SUN and MAC applications from name brand manufacturers - offices worldwide.

The "Think Different Store: The iPod Accessories Store - iPod cases, iPod mini, iPod photo, speakers, itrip, inMotion, Soundstage and all other iPod accessories

Earn Cash with the ThinkDifferent Store Affiliates Program

Need A Web Site?
Applelinks Web Hosting Starting at 19.95 a Month

iTunes_RGB_9mm

.

iTunes_RGB_9mm

Cool Mac Gear


iPod 1G-2G
iPod 3G
iPod 4G
iPod Mini
PowerBook-iBook
Keyboard Skins
Garageband