A “Progress Bar” in AppGraphics
Posted: April 15, 2015 Filed under: AppGraphics, Tips and Tricks Leave a commentA 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:
Creating Resizeable Windows with AppGraphics
Posted: April 3, 2015 Filed under: AppGraphics | Tags: AppGraphics, Fortran, GUI Leave a commentWith the release of Simply Fortran version 2.23, AppGraphics now supports resizeable windows. This feature greatly improves the functionality of the library, but there is a bit of a learning curve in creating and managing such windows. We’ll walk through a simple example below that should illustrate how to go about creating an application that uses resizeable windows.
The first step is proceed as normal in creating our window. Of course, we still need to specify an initial size:
myscreen = initwindow(320, 240, closeflag=.TRUE.)
In the above declaration, one may notice that there is no mention of a resizing capability. Wee need to use another call to enable resizing:
call enableresize(handle_resize)
The subroutine above will enable resizing for the current window. The argument we’ve included, handle_resize, is actually the name of a subroutine to be called whenever the window changes size. Such a subroutine would be useful if, for example, we need to reposition window controls or redraw graphic contents of a window. We’ll try to do both in this example.
In our main program, we’ll first add a button to our window in the lower right corner:
integer::mybutton
logical::quit
....
mybutton = createbutton(270, 210, 40, 20, "Close", handle_button)
quit = .FALSE.
In the code above, we’ve added a “Close” button that will, when clicked, call a subroutine handle_button that will flip the value of quit to .TRUE. and exit idle mode. We’ll also need an event loop in our code:
do while(.NOT. quit) call draw_window() call loop() end do call closewindow(myscreen)
The event loop above basically draws our window and enters idle mode. If we ever idle mode, it first checks if the quit flag was triggered and, if not, redraws the window. The above structure means that we need only a simple subroutine for our resize callback handle_resize:
subroutine handle_resize() use appgraphics implicit none call stopidle() end subroutine handle_resize
All the logic for laying out our window needs to reside in the draw_window subroutine. We’ll try to do two things in our drawing subroutine:
- Position mybutton in the lower right corner
- Output the window size, centered, in our window
For both of these tasks, we’ll need to know the current window size, which can easily be done with calls to getmaxx and getmaxy:
integer::w,h ... w = getmaxx() h = getmaxy()
Based on the window size, we can actually reposition our button quite easily with a call to setbuttonposition:
call setbuttonposition(mybutton, w-50, h-30, 40, 20)
The next task is to draw something in our window. We’ll simply write the window size near the top of the window, erasing any earlier text first:
integer::tw character(100)::info ... write(info, '(A5,1X,I5,A1,I5)') "Size:", w, "x", h call setviewport(0, 0, w, 40, .FALSE.) call clearviewport() tw = textwidth(trim(info)) call outtextxy( (w/2-tw/2), 5, trim(info))
The code above first writes the current window dimensions into the string info. Next, it defines and immediately clears a viewport where previous text would have existed. Finally, it outputs the text, centered in the window.
When you first run the program, you should see something like:
Unlike other AppGraphics windows, however, this one can be resize by dragging the borders. Thanks to our design, things will continue to look nice at other sizes:
Working with a resizeable window is rather easy as long as you properly handle drawing the window when resize occurs.
If you’d like to try the code out for yourself, feel free to do so by clicking the link below:
It should work fine in Simply Fortran version 2.23 or higher.
Recent Comments