Creating “Dialogs” from AppGraphics

A simple program in AppGraphics

A simple program in AppGraphics

AppGraphics is a simple graphics library designed for creating graphical user interfaces from Fortran. Although based on an older drawing library, AppGraphics has ben enhanced with a number of Windows controls, or the standard buttons, text boxes, check boxes common in modern Windows applications. While AppGraphics can be used for drawing shape primitives, it can also be used to easily create a “dialog” experience.

In this quick rundown, we will create a simple application for converting common temperature formats in a simple graphical program written entirely in Fortran.  Since our task is so simple, we’ll only need a single Fortran source file to build this.

In our main program, the first step is to initialize our window.  For this example, we’ll create a long, skinny window as shown in the picture:

integer::myscreen
...
myscreen = initwindow(150, 255, title="TempConvert", closeflag=.TRUE.)

We now have a 150 by 255 pixel window with an appropriate title. The closeflag argument instructs AppGraphics to end our program whenever the window closes. To make our window match the operating system’s themes, we’ll need to also change the background. The proper action would be to set the background color to the system’s dialog background color:

call setbkcolor(systemcolor(COLOR_WINDOW_BKGD))

We also need to make sure our fonts match Windows as one would expect:

call settextstyle(WINDOWS_FONT, HORIZ_DIR, 10)

Finally, we need to redraw our background with the newly assigned background color by clearing the window and forcing a redraw:

call clearviewport()

After combining all of the above code, we should now have a standard blank window with the proper fonts configured. If you want to see everything together, you’ll be able to download the complete program at the end of this article.

Next, we need to add our controls to the window. First, we’ll add the text box where a user can enter a temperature:

original = createtextbox(5, 5, 140, 20)
call settextboxcontents(original, "32.0")

Above we’ve created a text box at (5,5) (as measured from the top left) that measures 140 pixels wide and 20 pixels high. These dimensions should leave a consistent 5 pixel distance from the top, left, and right edges of our window. Next, we drop in the temperature of 32 degrees.

We now need to provide a method to select the original temperature’s units of measure. Since only one unit can be chosen, using radio buttons is an obvious choice of control. Using a 4-element array to store each radio button’s identifier, we can create our controls:

integer, dimension(4)::src, dest
character(16), dimension(4), parameter:: units =(/ 'Fahrenheit',  &
                                                   'Celsius   ',  &
                                                   'Kelvin    ',  &
                                                   'Rankine   ' /)
...
    call outtextxy(5, 30, "Original:")
    call beginradiogroup()
    src(1) = createradiobutton(50, 30, 95, 20, units(1))
    src(2) = createradiobutton(50, 50, 95, 20, units(2))
    src(3) = createradiobutton(50, 70, 95, 20, units(3))
    src(4) = createradiobutton(50, 90, 95, 20, units(4))
    call radiobuttonsetchecked(src(1), logical(.TRUE., 1))

The call to beginradiogroup indicates to AppGraphics that all radio buttons created until another call to beginradiogroup represent exclusive choices. If we click on the second button, for example, the first button will now unselect. Like the text box, the calls to createradiobutton specify the position and dimensions of our new buttons. Finally, we’ll select Fahrenheit for our initial temperature.

We’ll also need the possible selections for our final temperature, but the code to do so is extremely similar. For now, we’ll skip ahead to the buttons that actually get work done.

We need two buttons for this program, Convert and Close:

    ignore = createbutton(40, 230, 50, 20, "Convert", convert_temp)
    ignore = createbutton(95, 230, 50, 20, "Close", quit)

The createbutton call returns an integer that represents an identifier for the button. Since we don’t particularly care about the identifier in this program, we can assign the number to a variable named “ignore.” The first four arguments are, again, the position and dimensions of our buttons. The text arguments represent the text on the button when displayed. Finally, the last arguments are subroutines that will be called when a button is clicked. The latter subroutine, quit, is simple:

    subroutine quit()
    implicit none
        
        call stopidle()
        
    end subroutine quit

This subroutine makes sense if we examine the remaining segments of our main program.

After we create all our controls, we basically want the window to sit idle until the user clicks either “Convert” or “Close.” After we create our buttons, we can add two simple calls:

    call loop()
    call closewindow(myscreen)

In the code above, we’ve instructed our program to enter an idle “loop” until instructed to leave idle mode. As soon as stopidle is called, like in our quit subroutine, the program resumes execution at the next line. In this case, we’ll close our window and stop running.

The other button will call convert_temp, a substantially more complicated subroutine. The goal of convert_temp is to read the temperature in our original text box, determine its units of measure, convert to another unit system, and output the result in another text box.

First, we need to retrieve the original temperature:

character(40)::source_text, dest_text
real::source_number, dest_number

    i = gettextboxcontents(original, source_text)
    Read(source_text, *) source_number

in the code above, we read in the text box contents into a string and then read in a number from said string.

Next, we would need to determine the source temperature’s units. This step gets somewhat complicated because we have four possibilities. The full program is a better place to see all the calls. However, one simplifying step might be to convert an original temperature in Kelvin to Celsius and, similarly if appropriate, in Rankine to Fahrenheit.

    ! Check if we have kelvin or rankine and correct to
    ! celsius or fahrenheit
    if(radiobuttonischecked(src(3))) then
        source_number = source_number - 273.15
    elseif(radiobuttonischecked(src(4))) then
        source_number = source_number - 460.67
    end if

The code above checks if the Kelvin radio button is selected using the call radiobuttonischecked, and, if so, converts our source temperature to Celsius immediately. If Rankine is selected, a similar computation occurs to convert the temperature to Fahrenheit.

After we check all the combinations and perform the proper math, we can store our result in the appropriate text box:

    Write(dest_text, '(F10.2)') dest_number    
    call settextboxcontents(converted, trim(adjustl(dest_text)))

Once we set the results textbox with the converted temperature, we can exit our convert_temp subroutine. Notice that we never called stopidle; that call would have ended our program. Instead, we can continue to allow our program to idle in case the user wants to click “Convert” again.

And that’s about it! The hardest part of the code above is properly laying out our Windows controls. The best way to do so is trial and error, but it becomes substantially easier with experience.

You can download the full code for this example here:



Leave a comment