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 105

OOP University: Part Twenty-Seven

Last time I demonstrated how to create a reusable progress bar dialog. It makes it much easier to add a simple progress bar to any program, however, there are situations where the method used doesn't work. Today we'll explore some alternative approaches.

Timers and Threads

For our reusable progress dialog, I've chosen to use a global property, gPleaseStopProgress, to alert us to when the user has cancelled. I had to do this because the dialog itself can't know anything about the task being worked on and the task needs some way to know that it should be cancelled.

This method, however, has some disadvantages and won't work in all situations. For one, it requires you add the global property to every project that uses the dialog. For another, your task must constantly check the global property's status to see whether or not it has been cancelled. For a simple loop task, that's fine, but for a complicated task that doesn't repeat, you'd have to interrupt your task's code with dozens of cancel-test checks.

There are also certain events that don't seem to respond well to normal processing within REALbasic. For example, I receive this letter from Matt Eastburn who experienced a problem with his progress bars not updating during serial communications:

Hi Marc,

It is me again (the RealBASIC windows user) - my current question relates to uing the progress bar (and Serial Communications).

My problem lies in the area that the progress bar does not wish to update when being used within the Serial Comms Control. Like so:

  
sub DataAvailable()
// Read all of the contents in the Serial Buffer
Buffer = Buffer + SerialDriver.readAll

//Serial_In_TF.text = Buffer

// Update ProgressBar1 - Have assumed that there is 10 hrs in Oximeter
ProgressBar1.Value = len(Buffer)
ProgressBar1.refresh

// Jump the progress abr ahead if x is received,
// because is the last character to be sent
if Right(Buffer, 1) = "x" then
ProgressBar1.Value = 20000
ConvertBuffer
end if

if len (Buffer) > 0 then
end if

end sub

So in the Open Event I set the --> ProgressBar1.Maximum = 20000

The only way I get it to update is to wave the mouse around on Window 1 , plus it doesn't seem that it will download the entire contents of the SerialDriver Unless I also wave the mouse around - do you have any idea of what my problem maybe.

Matt Eastburn

I did some experimenting with Matt's code and he's correct, at least to an extent. I didn't experience the same required mouse movement issue, but my reusable progress bar did not update during serial communication. It seems that for some reason the serial port monitoring blocks the cancel monitoring normally done during normal tasks.

I ended up creating a workaround that involves testing for Command-period manually during the loop. Not the greatest technique, but it worked. (Of course for Matt's Windows machine he'd check for the Escape key, not Command-period.)

If you're curious, here's how I changed Matt's code to work on my machine:

  
dim s as string
dim amount as integer

if cancelCheck.value and keyboard.asyncCommandKey and keyboard.asyncKeyDown(&h2F) then
progressDialog.updateProgress("User aborted...", 20000)
progressDialog.stopProgress
serialDriver.close
else

// Read all of the contents in the Serial Buffer
s = SerialDriver.readAll
amount = len(s)
Buffer = Buffer + s

// Update ProgressBar1 - Have assumed that there is 10 hrs in Oximeter
s = "Item " + str(len(Buffer)) + " out of 20,000"
progressDialog.updateProgress(s, amount)

// Jump the progress abr ahead if x is received,
// because is the last character to be sent
if Right(Buffer, 1) = "x" then
progressDialog.stopProgress
end if
end if

This is within the dataAvailable event of serialDriver (a serial control).

There are other techniques that can also work in unusual situations.

One approach is to use a timer control. Set it to fire every so often (perhaps every second) and when it runs, have it check to see if userCancelled is true.

Unforunately, if your task is CPU intensive (a long calculation for instance), this won't work because timers have a low priority and just won't fire if the computer's too busy.

A better approach is to spin your task off in a thread. Now threads tend to scare off people, but they're really easy to work with, though there are some gotchas and they can get complicated in certain circumstances. We've never covered threads on RBU before, so this is a good introduction.

First, understand what a "thread" is: it's an independent task that runs on its own. Threads are what allows a single program, such as the Mac Finder, to do more than one thing at a time (for example, copy one set of files while still being responsive to other commands).

Within REALbasic, normally when you start a long task, the whole program stops until that task is finished. If you put that task into a thread, the program doesn't stop. The user can still interact with the program. Of course any data or result generated by the task won't be available until the task is finished, but at least the user can continue to work with your program.

Now here's where threads get complicated. Generally when you're writing your program, you expect things to happen in a linear fashion (i.e., step-by-step). The user clicks the "sort" button, you call your sort routine; the user clicks print, you call your print routine; etc. But threads change all that. If your sort routine is in a thread, your user could start the sort then print the data before it's sorted -- perhaps not what they wanted.

There also can be interdependencies within your code. One routine might be in a thread, working on some task, when a second routine wants to work on the next part of that task but can't because the first isn't finished. For this kind of situation you must be sure every process in your program knows the previous process is finished before continuing (each process could set a "finished" property, for instance).

Another problem is when two different routines need to work on the same data structure. If you have a database that's being sorted by one thread, what happens if another thread attempts to delete or add a record to that same data structure? The solution to this problem is anticipate it and create a system to "lock" the data (by setting a flag) so that other routines won't mess with the data until it's unlocked. REALbasic gives you an easy way to do this via the semaphore class.

These kinds of issues with threads can be complicated, but we'll leave them for advanced users and won't get into them any further in today's tutorial. I just want you to understand some of the more complicated problems that threads can present. However, in most cases, thread usage is straightforward.

For our purpose today, let's assume we've got a simple but time consuming task we need to run in the background but have it cancellable if the user desires. For an example, let's just use the single process bar code we used before. Except this time we'll run it from within a thread.

First, we need to create a thread object. We do this by adding a new class to our project (File menu, "New Class"). Name the class longProcessThreadClass. Double-click on it to open it, and go to its run event. Put in this code:

  
dim i as integer
dim s as string

const bigNum = 1000000

// Init progressDialog
progressDialog.init("Single bar count", true, window1.cancelCheck.value)
progressDialog.startProgress("Counting...", bigNum \ 1000)

for i = 1 to bigNum
// if evenly dividable by 1000
if i / 1000 = i \ 1000 then
s = "Item " + str(i) + " out of " + str(bigNum)
progressDialog.updateProgress(s, 1)
end if
if window1.cancelCheck.value and gPleaseStopProgress then
progressDialog.updateProgress("User aborted...", bigNum \ 1000)
exit
end if
next // i

// All done, so get rid of dialog
progressDialog.stopProgress

This code is exactly like the code we used before: the only difference is that we now specify that the cancelCheck control is available on window1.

Now drag longProcessThreadClass onto window1 to create an instance of it, and rename the instance longProcessThreadInstance for clarity's sake.

Create a new pushButton on window1 with a caption of "Test single bar with thread" and inside its action event put this:

  
longProcessThreadInstance.run

That's it! You can now run the test app and when you click the new button, it will run the thread. The program is still responsive to your actions, except we're displaying a modal dialog (the progress bar dialog) which prevents you from doing much. But the program will respond to a cancel command (albeit slowly).

Note that within your thread code you still need to periodically check to see if the user has cancelled. But the thread system has the advantage that it will run without bringing all other aspects of your program to a complete halt. A thread would be ideal for use when you want a non-dialog progress bar (i.e., like the progress bar displayed at the bottom of the window in Internet Explorer or Safari or other web browsers to show page-loading progress).

Above I mentioned that this method cancels "slowly" -- what I mean is that it doesn't respond the instant you press Command-Period, but if you hold down the keys, it will stop after a second or two. I believe this is because of a flaw in how userCancelled works (it must not poll the keyboard often enough or something). When I changed the code to add in a manual check using asyncCommandKey, the system worked instantly, but of course we run into the limitations I mentioned earlier regarding key codes.

Here's how I modified the run code:

  
if window1.cancelCheck.value then
if gPleaseStopProgress then
progressDialog.updateProgress("User aborted...", bigNum \ 1000)
exit
// Uncomment these lines for instant cancelability
elseif keyboard.asyncCommandKey and keyboard.asyncKeyDown(&h2F) then
progressDialog.updateProgress("User aborted...", bigNum \ 1000)
exit
end if
end if// window1.cancelCheck.value

This is included in the today's project file, but it's commented out, so you can test the project both ways. Remember, you'll need to modify the check if you're on Windows or using a non-English keyboard. (For those of you confused by key codes, I've got an explanation of those coming in next week's Letters segment.)

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

Next Week

Lessons in debugging.

Letters

No letter today since the column focused on responding to one.


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