A “Progress Bar” in AppGraphicsPosted: April 15, 2015
A user on our forums posted a question about creating a progress bar using AppGraphics. Because Fortran is often relied upon for long-running computations, a graphical progress bar could be very useful. While AppGraphics doesn’t provide a progress bar solution out of the box, creating one isn’t particularly difficult.
In it’s simplest form, a progress bar is really just a partially filled rectangle. With a little creativity, we can create a window that provides all the progress bar behaviors we might want. For maximum portability, we’ll assume that we want a standalone window that can display progress. Ideally, it should also provide:
- An optional “Cancel” button
- A way to display text
- A simple 0 to 100 percent update mechanism
For fun, we’ll also design our progress bar window to be Fortran-2003-esque using some type-bound procedures. To start, we need a derived type to describe our progress bar:
type progresswindow integer::win integer::cancel character(128)::text contains procedure :: close => closeprogress procedure :: draw => drawprogress end type progresswindow
The type includes integers that refer to our progress window, a possible cancel button, and the text we’re displaying above the progress bar. We’ve also attached two type-bound procedures: a “close” subroutine and a “draw” subroutine.
To initialize this window, we’ll basically just need to create the window and set up some drawing styles. An abbreviated summary is below:
p%win = initwindow(400, 100, title=title, dbflag=.FALSE., closeflag=.FALSE.) call setbkcolor(systemcolor(COLOR_WINDOW_BKGD)) call setcolor(BLACK) call settextstyle(WINDOWS_FONT, HORIZ_DIR, 10) call setfillstyle(SOLID_FILL, RED)
The above code creates a window, configures some colors and fonts to match Windows defaults, and sets up the fill style for a red progress bar.
The first type-bound procedure, close, is exceptionally simple, needing only to close this window, so we’ll skip that for now. In contrast, drawing the window is a bit more involved. We’ll need to take a number of steps. Our type definition pointed to a routine “drawprogress,” which we’ll define as such:
subroutine drawprogress(p, completed, text) use appgraphics implicit none class(progresswindow), volatile::p integer, intent(in)::completed character(*), intent(in), optional::text ...
The drawing routine requires a completed percent, which is assumed to be 0 to 100, to be passed in. Optionally, the developer can also provide an updated text string to display above our progress bar. If the text is passed in, we’ll overwrite the text that our “progresswindow” variable is currently storing before we proceed with drawing.
First, we need to clear our viewport of existing graphics with an appropriate call:
call setcurrentwindow(p%win) call clearviewport()
Note that prior to clearing the viewport, we’ve set the current AppGraphics window. This operation safeguards against applications where we may have multiple windows present. The developer, however, must be careful to switch windows back to any other windows when necessary.
Next, we can output the text into the window:
if(len_trim(p%text) > 0) then call outtextxy(5, 5, trim(p%text)) end if
If the text length is zero, we actually skip that step. Finally, we need to draw the progress bar. The actual progress bar really has two components: an empty rectangle and a filled rectangle whose size relative to the empty rectangle reflects the percent complete. AppGraphics doesn’t actually provide a quick routine for drawing unfilled rectangles, so we’ll instead use the relative line operations:
progress_total = getmaxx() - 10 ! Below we'll draw a simple box, unfilled call moveto(5, 25) call lineto(progress_total+5, 25) call lineto(progress_total+5, 50) call lineto(5, 50) call lineto(5, 25)
While the vertical coordinates aren’t particularly important, one should note that the empty rectangle should be about as wide as our window less five pixels on each side as a margin. Next, we can compute the width of filled rectangle based on what the developer passes into the drawing function:
! Bounds-check our completed value progress_completed = progress_total*completed/100 if(progress_completed > progress_total) then progress_completed = progress_total elseif(progress_completed < 0) then progress_completed = 0 end if
One additional step we’ve taken above is to ensure that the final width does not exceed the maximum width of our empty progress bar or that a negative with result from the user’s value. Once we have a width based on our completed progress, we can draw a filled bar:
call bar(5, 25, progress_completed+5, 50)
This rather simple progress bar is actually quite effective. To test out how well it works, we can use a simplistic timing demo to watch the bar draw itself:
program main use progress implicit none type(progresswindow), volatile::p integer::counter real::last_time, now_time p = initprogress("Timed Progress") call cpu_time(last_time) now_time = last_time counter = 0 do while(counter < 100) do while(now_time - last_time < 0.2) call cpu_time(now_time) end do last_time = now_time counter = counter + 1 call p%draw(counter) end do call p%close() end program main
Because of our use of type-bound procedures, the actual driver routine is rather clean.
If you want to try out this simple progress bar, you can download the actual progress bar module and a slightly more advanced driver program (it implements a cancel button) below: