Previous: 5 Going back to the File menu
Next: 7 Graphical interaction – early steps
In this section:
If your original old program expected to read its input dataset from a file, you probably rationalised that behaviour on several grounds instead of having some sort of interactive input. Perhaps you thought the effort of writing the interactive input routine was too hard or would take too much of your valuable time. Perhaps you had already used up the available RAM in your computer. Or maybe you had given it some thought and had decided that the very many man-days of work that some programming team had already put into a text editor would require you to duplicate that effort when you set about putting some sort of an interactive front-end on your program. It is definitely true that it is difficult to devote the time and resources to generating very effective user interfaces in respect of input data.
To be frank, it may not even be worth all the effort if you are programming for yourself and are quite happy generating input files in a text editor. It is sometimes even easier to generate the input files in Excel, and then save them either as comma-separated files or by cutting and pasting the tables into your text editor. If your program is no more sophisticated than using the File menu and the standard Windows file selection dialog to find and open your data file instead of going through the File Explorer and using a command window (or DOS box) from which to run your program, then it may well even save you little time.
I have several programs like that. Their original versions used the whole capacity of a respective mainframe, and they lacked graphics to present their results because simply there was no graphics output device. I used to generate punched tapes of results and take them to a different computer in another building about a mile away from the first where there was a primitive pen plotter, and there I could run a different program to generate graphical output. I did that for decades. Sometimes, I still run the logical successors to those programs by still using a text editor to create an input file, but instead of punching a paper tape, I originally saved the intermediate data on removable media. When I got bigger, better and faster computers with more capacity, I saved the intermediate results on a hard disk. Finally, I integrated the relevant parts of the graphics plotting program with the analysis program using ClearWin+. For something I might not run for years on end, that is a quite satisfactory solution, and one that I suggest may even meet some of your needs.
However, you have not come to this book to stop at such a primitive stage: you very obviously want the complete graphical user interface for your program that includes interactive input. Some of the ideas about interactive input are presented in the rest of this chapter, where they take the form of dialog boxes (or windows) that contain fields in which you can enter INTEGER, REAL or CHARACTER data, you can set options, and choose things from drop-down lists, to introduce only some of the ways that your program can request and accept its inputs.
One of the reasons that I suggested that you develop your program with File/Open as your first major step to implementing the menu system was that once you have a full dataset, then you can set about altering it with different values very easily. Let’s imagine that you have several types of data in your datafile. Imagine that your data input represented a network object that was made up of points or nodes that have an identifying number and x,y coordinates. Suppose that there are NP of these, and that the list has no missing entries, so that it runs with numbers from 1 to NP. Similarly, there are NL links, each of which also has a number running from 1 to span class="mono">NL, and a link is defined by the node numbers at each end. You could imagine that the dataset could be stored in a file that comprises one line that gives NP and NL, then NP lines for the nodes, and NL lines defining the links.
Once you had read the data in from your datafile, you could actually draw this network, finding the range of each coordinate, scaling and then fitting to the screen. For the time being, forget about refinements like the links having different properties, so needing to be drawn in different colours or line thicknesses, or even numbering the nodes and links.
Now suppose that you wanted to change one of the coordinates. What you would have to do is to open a dialog box that allowed you to pick the number of that particular node and edit its values. If you accepted the change, then you could redraw the network showing the change. For simplicity, let us suppose that we keep the coordinates and linkages as a set of arrays in something like the following:
COMMON /NETWORK/ NP, NL, X(50), Y(50), LINKS(50,2)
As defined, there would be a maximum of 50 nodes and 50 linkages, taking up 1200 bytes, or basically, next to nothing. You could have a dialog box that allowed you to change either of NP or NL or both, but that would lead you into the additional problem of specifying the extra node coordinates or linkages. As a first step, add two menu items into your Edit menu, perhaps labelled Nodes and Links and prepare two dummy callback routines. Starting with the one for the links, the information that a user would have to put into the dialog consists of the link number, and the node numbers at each end. All of these values are integers, and ClearWin has a special data entry box for integers, generated with the format code %rd. For simplicity, imagine first a dialog that asks the user for a link number. Our first efforts at a dialog box might look something like this:
INTEGER FUNCTION KB_EDIT_LINKS() C ================================ COMMON /NETWORK/ NP, NL, X(50), Y(50), LINKS(50,2) INCLUDE <WINDOWS.INS> N = 1 IL = WINIO@ ('%ca[Edit Links]&') IL = WINIO@ ('Link No.%ta&') IL = WINIO@ ('%rd', N) END
That would certainly permit a user to change the number 1 they were presented with to anything else, but it might allow them to change it to zero, a number greater than 50, or even to a negative number. It would be important to limit the numbers that they could choose to between 1 and NP. You can do this with the %il or integer limits format code, as follows:
IL = WINIO@ ('%il&', 1, NL) IL = WINIO@ ('%rd&', N) IL = WINIO@ ('%`il&')
By turning off the limits using the grave modifier to the integer limits format code you prevent those limits from affecting any further parts of the dialog box, if any. The user would still have to type in the number. You can add a spin wheel control using the format code %dd, with the increment specified as a parameter:
IL = WINIO@ ('%il&', 1, NL) IL = WINIO@ ('%dd&', 1) IL = WINIO@ ('%rd&', N) IL = WINIO@ ('%`il&') ! leave out the & if this is the last WINIO@
The dialog box would then look something like this:
The user can either type in the number they want, or use the up and down arrows on the spin wheel by clicking on them and the number will advance or retreat by 1 at a time, stopping at the limits specified. Now, we have to add the opportunity to edit the node numbers at each end of the link, and we will also have to add a callback function to the link number box, because if the user changes the link number, then the node numbers at each end will also change. The node numbers will need to be limited to a range between 1 and NP with the appropriate %il formats. On closing the dialog, the variable N will contain the last value to appear in the %rd box.
We will certainly want an ordinary button to Accept the changes, and probably a Cancel button to reject them, and obviously, we will want to be able to change the node numbers at each end of the link. However, they must not be the same.
Each end will need its own %rd input box, each with its own spin wheel and each limited to the range 1 to NP inclusive.
IL = WINIO@ (%2nlEnd 1 &') IL = WINIO@ ('%il&', 1, NP) IL = WINIO@ ('%dd&', 1) IL = WINIO@ ('%rd&', L1) IL = WINIO@ ('%`il&')
And broadly the same for End 2. However, the values for L1 (the node at End 1) and L2 (the node at End 2) will change if a different link is selected. That will require a callback function to be applied to the %rd format code for the link number, requiring also a ^ qualifier as well as the callback. The callback will need access to the LINKS array and also to L1 and L2. It will need to reset L1 and L2 and then RETURN, but before it does so, it will need to refresh the %rd boxes for L1 and L2, which is done with a CALL WINDOW_UPDATE@ (L1).
Subsequently, the Accept button will need to invoke a callback to enter the values for L1 and L2 into the appropriate positions in the LINKS array, or this might be done in a section of code after the dialog window closes. In the former case, the Edit Links dialog stays open, in the second case it will have closed.
Note that we have started to proliferate callback functions, and this reinforces the need for source code organisation, as we have yet to be able to add a link, or to remove one, and there is the potential for a similar labyrinthine structure associated with Edit Nodes, and the properties of those links.
Your dialog will end up looking something like this:
The dialog in Figure 6.2 may well be satisfactory in the first instance, but it would be better if things lined up, which you can do with the alignment format codes and space characters in the relevant format string. The design of dialogs should lead the user to the right boxes almost intuitively – if they have used Windows before, then there are conventions that they will already know. Don’t try to reinvent the wheel, just go with what users may well be accustomed to.
Therefore, rather than let a user accept the case where the numbers at both ends of the link are the same, it is better to have a callback on the %rd format codes that check that they are not. A simple, audible, warning using CALL BEEP@ may do the job, with greying-out of the Accept button until the situation is rectified.
Assuming that you go on to create a second complete dialog for the node coordinates using %rf instead of %rd, and %fl instead of %il (although you probably don’t need to), then you may well double the total number of dialog windows and callback functions.
In the Edit Links dialog, you might add buttons for Add and Remove, so that you can add links or remove them. In either case, you are inhibited by the values in the %il format for the link number. There are two alternative approaches to this problem. The simplest, in the case of Add, is to close the dialog and then reopen it with NL increased by 1, and the Link number presented to the user as the new value of NL. Clearly, as specified, the number cannot be increased to more than 50, and if 50 has reached, the New button should be greyed out. You will also want the End
1 and End 2 boxes to be empty, which you do by invoking %rd with the option [initially_blank]. Even so, numeric values have to be given to the ends, and for example if you specified 0, then the logic in your dialog box could be if the number is zero, then specify initially_blank. It would be a good idea to keep the Accept button greyed out until acceptable end numbers had been input.
The second alternative is rather more complicated, but it does mean that the dialog stays open and does not close and reopen giving a perceptible ‘blink’. The option is to use the functions SET_INTEGER_LIMITS@ and SET_FLOAT_LIMITS@ to reset the %il and %fl limits. To use them in a callback, you would need to know the windows ‘handle’ for the appropriate numeric input box, which you get by asking for it via the %lc format code. %lc follows the %rd (say) and needs an integer variable to store the handle which is returned by Windows.
SET_INTEGER_LIMITS@ has 3 parameters: the Windows handle, and the minimum and maximum changed values. Setting the floating point limits is similar.
To delete a particular link N it is just a matter of moving the data for links N+1 … NL down to N … NL-1 and resetting NL. I would advise zeroing the end numbers for the NL+1 link to simplify matters if the user decides to add a different link.
It is a more complicated matter to remove a node, because to do so makes any links connecting to that node need to be reconnected somewhere else. Moreover, if you do change the numbering by just removing a node you probably have to re-number all of the links. One option might be to forbid the removal of a node (by greying-out the removal option) that still had a link connecting to it, but even so, if the number sequence for the nodes must be complete, all the links would require renumbering anyway. What you do is up to you, and is Fortran not ClearWin+. The correct place for these types of dialogs is in response to menu commands in an Edit menu option, as that includes both editing existing data and creating new.
For something short such as a label, use the %rs format. As in the case of %rd and %rf, the input box length can be controlled with a multiplier, and if a grey-state variable is added, the format is modified with ~. A ^ tells the format to accept a callback function.
The %rs format also has options, such as changing the case of what is input, echoing each character with an asterisk if a password is entered. There is a response to %dd which is rather different to that for %rd and %rf, and isn’t appropriate to discuss just yet. There are formats for more extensive text input: %rs is a ‘one-liner’.
Suppose now that the drawing surface on the client area of your main window was used to display the network of nodes and links. In the case of a change to a link, or movement of a node, then the callback for the Accept button could simply redraw the network after blanking the previous drawing with a DRAW_FILLED_RECTANGLE@ of the appropriate size. You might find that drawing such a network is more testing than you imagine: not just drawing the links, which would be done using DRAW_LINE_BETWEEN@, or even marking the nodes, which could be done with DRAW_ELLIPSE@ (which could be filled) or DRAW_RECTANGLE@ (also may be filled). The difficulty would be where to display the numbers marking each node and link. If the whole thing is drawn in black including the numbers, you won’t see them clearly because the lines and text all overlay each other. The answer in this case is colour for the links and nodes, and positioning the legends appropriately.
After redrawing, use CALL GRAPHICS_UPDATE@.
When a program is given the File/Open command, and it opens a pre-existing and presumably fully-formed datafile then it can do some drawing about the topic of the program and its data, and the user can be presented with options to change the data and whether or not to save the changes.
In contrast, the File/New command starts with no data at all. The user sometimes has to input something to get the process started. Take for example a program that mimicked a diary. The very minimal information to set up a diary, even a blank one, would be the year for the diary because without that information one would not know how many days to assign to February. Based on the diary function, it might be useful to know the user’s name, and for example, whether it was a conventional diary running from January to December, or an academic year diary, running from September to August. Other optional input might be a colour, useful for the front cover and to differentiate between diaries for multiple users and then used on every page as an aide memoir. A diary is still a diary if it does not have any entries, and after inputting the essential and optional start-up information, it is reasonable to assume that saving it as a file would lead to a file that could be opened and re-read at a later time using File/Open.
Sometimes, in the design of the first dialog box when a program is started and File/New is selected, it does not deserve a caption bar to stop it being moved out of the way, and is presented right in the middle of the screen so that nothing can be done other than to enter the requisite data. It would then definitely require a Cancel button as well as an Accept button.
Other applications may have different start-up requirements to make a meaningful dataset. In the case of the network, it would not be to enter the number of nodes NP and the number of links NL. It would be to start with NP=0 and NL=0, and to enable only Edit Nodes until at least 2 nodes had their coordinates specified. After that, Edit Links can be enabled. NP and NL would then look after themselves.
It could be argued that a network with no nodes and no links isn’t much of a network, but it would work as a dataset that could be saved or re-opened. It might be rational to follow File/New by opening Edit Nodes automatically – such decisions are left to you to program.
An example from my own programming is the one that plots student topographic surveys made using a modern, sophisticated, electronic theodolite that is grandly called a total station. The total station is set up on a tripod over a point with known coordinates, and the instrument is sighted at a second point with known coordinates, and the angle measurement system zeroed. The survey then proceeds effectively by sighting through the total station telescope at a reflector target carried on a pole by one student and positioned at various places with the instrument indicating the horizontal angle and distance, both of which are measured electronically. Modern instruments are quite capable of recording this information onboard and with the appropriate software producing a map (back at the office, for obvious reasons). For students learning the techniques of surveying, learning the on-board software constitutes training not education, as the software is very different from manufacturer to manufacturer. For the purposes of education, students record the readings in a survey book and then they have to plot out their survey. Decades ago, they would have done it with a big protractor and a scale rule (and they wouldn’t have had a total station, but would have used a more primitive optical system). The problem basically is that you have learnt how to plot things after the first reading and after that it just becomes a time-wasting and boring exercise. A program that can plot out your data is then something that not only solves the boredom and time-wasting problem but may also shorten a field course by days, saving expense, staff time and keeping interest going.
The program to solve the plotting problem therefore is quite simple: let the student groups input their readings and produce a plot. The initial dialog simply has to record the coordinates of those two points – where the instrument was set up, and where it was sighted to. Then we get to an interesting problem and that is whether or not to make the program useful for anything else, in my case, to plot surveys that I might have done myself! If I do them, I will also include information on levels, but for the student exercise those levels aren’t recorded. A further problem is to define how the printer will scale the survey when it is plotted.
The figure below shows that first setting-up dialog box.
The figure actually contains two versions, both my initial efforts as the first program I ever wrote6 using ClearWin+, and secondly on the right, and evolved version of the program and of the corresponding dialog. What I had learnt in the interim was that instead of the program being run on desktop computers it was going to be run on laptop computers where the screens were not only smaller but were widescreen. As part of my learning process, I discovered rather better how to link the data boxes in terms of meaning. Even so, I hadn’t learned every trick in the book. Also, over several versions of Windows (and these dialogs are taken as screenshots under Windows 10) it has become more normal to have wider buttons and also to right justify them rather than centre them. You will probably see that surveying does not use x and y coordinates but refers to them as Eastings and Northings, and the pole on which the target is carried around may well be colloquially called a ‘wander pole’ but it is properly called a ‘detailing rod’.
If I were writing the application today, then I would make sure that the Next button was greyed-out until satisfactory information had been entered. I might also produce a captionless version if the File/New menu command was selected, but a version with the caption if the initial setup information was revisited. However, I did discover that there had to be limits on the plot scale or students might select a scale of 1:1 requiring thousands of sheets of paper to be printed! (and so there are limits set on the acceptability of the plot scale value).
The dialog is made up of text entry boxes and entry boxes for real numbers, together with a single %`rb tick box (see the following section 6.10) and some %rs boxes for text entry. There aren’t any limits, except on the scale box, and no spin wheels. The dialogs do, however, have coloured backgrounds, using the %bg format code. Garish colours do not work well, and although RGB colours can be used, there are some colours already specified in Windows that can be used. I often choose the button face colour, as in:
IA = WINIO@('%bg[btnface]&')
For purposes of program development, it is extremely useful if you do not create long format strings in any one WINIO@ function call, but instead confine yourself to one data input box per WINIO@ statement. Of course, this is a recommendation, and you must program in the way with which you feel most comfortable.
I recommend that at this stage you stop reading this book and go and develop some dialog boxes to suit your own particular program. Mastery of the aesthetics is something that you will probably only acquire by trial and error, and I suggest that you do not aim for anything terribly complicated, at least initially.
Now that many computers, especially laptops, feature wide screens with a 16:9 or 16:10 aspect ratio instead of the once ubiquitous 4:3, it makes sense to have dialog boxes that are of a similar aspect ratio, i.e. even if not exactly matching the screen aspect ratio, then certainly wider than tall. In principle, Windows programs should be able to operate on a screen with 800:600 pixels (the old 4:3 aspect ratio) and the design of dialogs is central to fitting everything in.
As an example, I remember in my early efforts programming a dialog box to accept an angle and a distance, along with certain other things for that student topographic survey data plotting program. The dialog box I came up with looked like the one on the left in Figure 6.2.
The early version of the dialog box was perfectly adequate and simple to use, until one day someone tried to use it on a very old and minimal specification laptop with an 800 x 600 resolution screen. This is the minimum resolution that windows should run on, but as it happens the least specification that the program had been tried on had a vertical resolution of 768 pixels, where the dialog box fitted perfectly adequately. The opportunity was taken to not only shrink the vertical height of the dialog in the course of an update to the program but also to rationalise one or two things, not least the input of the degrees, minutes and seconds into the layout so that the user was helped slightly as to what was required by way of input.
At the same time the legends particularly on the radio buttons were improved and so the distances and heights were given units.
Note that integer limits were specified for degrees (0..359), minutes (0..59) and seconds (0..59) and the boxes were shortened to make this slightly more evident. The limits naturally were fixed. As an aside, some nations prefer to use an angular measurement system in which a hundred units make up a right angle, and these units are called grads in some countries and gon in others. The angles are recorded to 3 decimal places which is probably how the instruments record them, thus giving units of the third decimal place that one might call milligrads or milligon. Entering angle data in grads is therefore simpler than in degrees, minutes and seconds. A similar point is that metres are easier to enter than feet and inches, and kilometres easier than miles and yards, or miles and feet, or even miles and furlongs, or the dreaded unit of rods, poles or perches! The differences need to be taken into account if your application copes with different systems of measurement.
The action buttons could in principle be made even more explanatory with the use of an icon. The fragment of code that follows illustrates the WINIO@ function calls to generate the input boxes for the angle and distance:
IA = WINIO@ ('%2nlAngle%ta&') IA = WINIO@ ('%il&', 0, 359) IA = WINIO@ ('%`bg[window]%4rd[INITIALLY_BLANK]°&', IDEG) IA = WINIO@ ('%il&', 0, 59) IA = WINIO@ ("%`bg[window]%3rd[INITIALLY_BLANK]'&", IMIN) IA = WINIO@ ('%il&', 0, 59) IA = WINIO@ ('%`bg[window]%3rd[INITIALLY_BLANK]"&', ISEC) IA = WINIO@ ('%`il&') IA = WINIO@ ('%taDistance &') IA = WINIO@ ('%`bg[window]%8rf[INITIALLY_BLANK]m&', DIST)
The numeric values are input into three %rd boxes whose sizes are specified, and a much larger
%rf box for the distance. Spacing is managed by means of the space characters in the format string and ‘tabs’ input using the %ta format code. Limits are defined for the integer input %rd boxes using the %il format code and its parameters, and finally turned off with %`il. It doesn’t seem to be necessary to turn off one set of limits before specifying another. If the user is specifying a new observation complete with its angle and distance, then the input boxes are specified as initially blank. There is a second pass possible for when the boxes contain numbers and are being presented for editing in which case the boxes are not specified as initially_blank, the logic for which is which is pure Fortran! Also, the opportunity has been taken to colour the background of the boxes for various uses, but in this pass through the formation of the dialog, the background for the boxes is a standard colour specified by the keyword window, the numeric value for which is set in WINDOWS.INS.
In summary, beware of making dialogs too tall. Consider the layout of input boxes to assist the user in understanding how the values are intended to relate to each other (for example, degrees, minutes and seconds).
The dialog in figure 6.2 is really a simple data entry form, tailored to the specific needs of those student groups and in principle, little different to the dialogs for the network. Essentially, they put numbers in the appropriate boxes and then press the Next button. So far, so good. But the target user is a student or a student group and that means that they make mistakes. Making mistakes requires the ability to move backwards and forwards through all the readings, correcting errors. What is more, because the way the program works (extremely simply) it is possible to take the readings along a particular feature, for example a kerb line, and then when all the points plotted to join them with the line. In a commercial application you could imagine many different types of line pattern and colours, but for a simple student exercise, solid, dashed or no line at all very sensible minimal options, intended to keep the learning process as short as possible.
Because of the way the program works with the opportunity to join the points with a line, then the opportunity to insert a point out of order or actually to put a point in which was mistakenly left out as they entered the readings, is in that set of action buttons. When reviewing data as well it is very useful not to have to scroll through the entire list (using Next and Back) but having the ability if necessary to jump to a particular point in the list using Go To.
Not all action buttons need to have a callback. For example, in the data entry dialog here, pressing the Next button has no dialog, but instead, it is as though the statements that follow the last WINIO@ statement start to be executed. The program code can look at the return code from WINIO@, which in this case is the variable IA, and it determine which button was pressed. The close box returns 0, and the individual action buttons in the order in which they were specified in the sequence of WINIO@ function calls, are numbered 1 to 6 inclusive.
The way the program is designed, the data input form closes when any button is pressed, and it stays closed if the return code is 0 or 6, although the point data values are saved if they are not all zero. When the Next button is pressed, the return value is 1, and that causes the displayed data to be saved, and the point number to be incremented. If the resulting number is in the existing list of points, the data values are extracted from the arrays of values, and then are available to be displayed, and possibly edited, whereas if the point number is incremented to extend the list, the boxes are all displayed with the initially_blank attribute. Back is similar, but the point is decremented, and the user can’t go back beyond point 1, at which state the Back button is greyed out. Go To launches a separate dialog to get a relevant point number to display, and Insert and Delete act on the current point, and may force a reshuffle in the data set.
The momentary blink when a dialog box is closed and reopened is sometimes distracting and rather than exit to do the logic after the specification of the contents of the dialog box, sometimes it is better to make use of callback functions and keep the dialog box open. In this particular application, student users have reported that they are not distracted by the blink, but instead regarded it as confirmation that their data entry has been accepted. In simple programming terms, exiting the dialog and re-entering it allows the initially_blank attribute to be applied in a very simple way: you either have it or you don’t. You also have the choice as to whether to write the two variants of the WINIO@ format code (i.e. with or without [initially_blank] explicitly as two separate statements, or to decide whether to splice in the option using concatenation operators as in this example where the alternative would be to declare INIT = ‘’:
INIT = '[initially_blank]' IA = WINIO@ ('%rd'//INIT//'&' etc
It is possible to set the action button return codes in a different order under program control, but I think that is not wise and using the default numbering has less potential for errors.
Updating the limits was made easier when the previously undocumented subroutines for updating limits were improved and documented.
Windows provides two ways for a user to make selections (or choose options) when using a dialog box, and there are radio buttons and tick boxes. In current and past incarnations of Windows, the former was represented by a round icon which was ‘hollow’ if not selected, and ‘filled’ if it was. The latter was a square icon, ‘hollow’ or empty if not selected, but filled with an x if selected. Both types of option control are specified in ClearWin+ with the %rb format code, with tick boxes requiring a particular qualifier (the grave symbol). I note that currently, tick boxes have a tick!
As far as ClearWin+ is concerned, both radio buttons and tick boxes can be ‘ganged’, that is that only one of a set can be selected, and selecting that one deselects the others in a set. However, the normal behaviour of a conforming Windows application is for radio buttons to be ganged and tick boxes not. Radio buttons and tick boxes can also have associated callback functions, and while that does mean that they can function as action buttons, they should not be used that way but instead should only be used locally, for example to grey-out data input boxes that aren’t relevant to the option selected.
Each option control needs its own INTEGER code or state value which is set to 1 when the option is selected, and to 0 when that option is deselected. Ganging the option controls means that this setting is done automatically. When a dialog is set up it is conventional to initialize a set of ganged radio buttons with the most common option being the one that is preselected.
As with so many controls in dialog box windows, the controls are easiest to line up if the explanatory text lies to the right of the control. The judicious use of %ob … %cb boxes (which may be invisible) is helpful in grouping the controls.
The outline boxes have numerous attributes, including invisible, named_l (where the name is given and is left-justified), and even to create a status bar. There are numerous other box options, including being able to set up a grid of boxes. For details, please see the documentation in the help file. On the grounds that it is not conventional to gang check boxes, we might have:
IC = WINIO@ ('%ob[named_l][Dorothy]&') IC = WINIO@ ('%`rb[Ruby slippers]&', IOZ) IC = WINIO@ ('%`rb[Tin man]&', JOZ) IC = WINIO@ ('%`rb[Lion]&', KOZ) IC = WINIO@ ('%`rb[Scarecrow]&', MOZ) IC = WINIO@ ('%cb')
in which case a box titled Dorothy would appear with a check box for Ruby slippers and for all of her 3 companions on the yellow brick road. It would duplicate the information to have a ganged check box Without ruby slippers, as the two cases are with tick or no tick (formerly, the box was filled with a cross), and that duplication is something to avoid. All the companions are either present or not present as well.
A similar selection process could include radio buttons, which in the case that they are not mutually exclusive might be ganged. The term radio button harks back to those early transistor radios (and indeed, some still in cars) where pressing one button to select it releases all the others.
An example where radio buttons are ganged is:
IC = WINIO@ ('%ob[named_l][Hero]&') IC = WINIO@ ('%3ga&', LEAD_ACTOR(1), LEAD_ACTOR(2), LEAD_ACTOR(3)) IC = WINIO@ ('%rb[Ajax]&', LEAD_ACTOR(1)) IC = WINIO@ ('%rb[Achilles]&', LEAD_ACTOR(2)) IC = WINIO@ ('%rb[Odysseus]&', LEAD_ACTOR(3)) IC = WINIO@ ('%cb')
Values of 1 need to be given to the initial choice and 0 to the others before the set is displayed. The state variables can also be used with other controls, for example, controlling grey states etc. The Fortran 90 ability to initialise a whole array in one statement is useful, especially with long gang lists, so we might have:
LEAD_ACTOR = 0 LEAD_ACTOR(1) = 1
instead of needing to define all three. Not much is saved with only three variables, but a lot of statements are saved for longer lists. You can also roll the statements onto one line:
LEAD_ACTOR = 0; LEAD_ACTOR(1) = 1 ! initialise ganging
which is sometimes helpful and adequately clear whereas combining quite different statements may not be.
You have several choices : %rs with %dd, %ls, and a multiple selection version of %ls.
When %dd is used with %rs, it should be placed the before %rs format code. In this case it should have a non-zero step value (which is ignored). The subsequent %rs box should have a call-back function which uses CLEARWIN_STRING@('CALLBACK_REASON') to identify the reasons 'SPIN_UP' and 'SPIN_DOWN'. The call-back function (in this case KB_STRING) must provide the response to the spin, because the control itself does not.
WEEKDAY = 'Monday' IK = WINIO@ (%dd%^`rs&', 1, WEEKDAY, KB_STRING)
This variant of the control should be used when the next or previous display value is obvious, for example, by cycling through the days of the week or months of the year, and moreover, it should be configured to that the list is endless or ‘rolls over’. An example of roll-over would be to follow Sunday by Monday again, or December by January. Other lists where the sequence is obvious includes paper, scissors, stone or tinker, tailor, soldier, sailor etc. The reason that your callback should roll over is that the spin wheel arrows do not grey out when the end of a list is reached, because there is no pre-defined list, and the sequence should be obvious because the user does not get to see all the options without scrolling through the whole list.
The advantage of %dd plus %rs is that the list does not need to be assembled, and its disadvantage is the need for roll-over. In comparison, %ls needs a list to be assembled, and because the user should be able to see all the options, then the list needs to be comparatively short.
CHARACTER*(15) LIST(6) LIST(1) = 'Miss Scarlet' LIST(2) = 'Colonel Mustard' LIST(3) = 'Professor Plum' LIST(4) = 'Mrs Peacock' LIST(5) = 'Mr Green' LIST(6) = 'Mrs White' NUM = 1 IW = WINIO@ ('%15.6ls&', LIST, 6, NUM)
In this case, the sequence is not ‘obvious’, and it is unlikely that the user will want to select the next or previous value, but will want to see all options. NUM is the number of the initial selection, but this will be altered and returned as the number of the selection made by the user, so it has to be a variable. The control can be greyed-out.
The length of the list is fixed, but if there are blank entries (e.g. LIST has 8 values, and the last two are blank) then two further entries could be added, or items removed from the display by blanking them.
In Figure 6.2, the text entry box with a %rs was replaced with a drop-down list using %ls, and that reduced the generality of the application while increasing its utility to the identified users, whose surveying is limited to the items in the list during their field course. An alternative might be to have a list item ‘Other’, which if selected would un-grey a text entry box. The items in a list box control are ‘ganged’, meaning that only one can be selected at a time (See section 6.13 for ganging). If the option must exist for none of the items to be selected, an option ‘None’ or similar needs to be provided. This has been done for the radio buttons in Figure 6.2. It is not necessary to do it for tick boxes.
The list box is suitable for longer lists than can easily be catered for with radio buttons. Typically, radio buttons are useful when the user needs to make a choice between up to half a dozen items or less, as for more, the repetition of the format takes up too much space in the dialog. List boxes come into their own as the radio button sequence begins to take up too much space, and list boxes look somewhat ridiculous with only 2 or 3 choices. They also have a limit on length, especially towards the bottom of a screen, and the number of items displayed can be set along with the width of the display, respectively the 6 and the 15 in the above example, which displays the whole list. If the depth of the drop-down part of the box does not show the whole list, then a scroll bar is provided automatically.
There is also a multiple selection box, %ms, and an editable combo box, %el, for which you are referred to the help file for details.
At various times in the execution of a Windows application it is necessary to pop up dialog boxes to inform the user of various things, including if they’ve made a mistake and why, when certain actions have been completed such as saving a file or issuing a print job to Windows print spooler etc. It is possible to proliferate such boxes by having one for every event, or even a selection with different buttons, different standard icons and so on. However, in the interests of brevity (and that’s a joke because there is no such thing as ultimately a brief Windows program source code) you might find it useful to create and use a single dialog box that is configured on the fly.
SUBROUTINE POP (CAPTION, LINE1, LINE2, ICON, NBUTTONS, BUTTONLABELS, NRET) C ============== CHARACTER*(*) CAPTION, LINE1, LINE2, ICON, BUTTONLABELS(NBUTTONS) IW = WINIO@('%ca['//CAPTION//']%si'//ICON//LINE1//'&') IF (LINE2 .NE. '') THEN IW = WINIO@ ('%nl'//LINE2//'&') ENDIF IW = WINIO@ ('%2nl%rj&') DO 10 I=1,NBUTTONS IW = WINIO@ ('%bt['//BUTTONLABELS(I)//' &') 10 CONTINUE IW = WINIO@ ('%sf') NRET = IW END
An alternative to coding a general-purpose pop-up message dialogs is to proliferate dialog subroutines with one for just about every purpose. The example I have given above would need all its parameters setting before it was called, and the total length of code might not be much shorter overall than generating custom dialogs for every use. There is, however, a third option, which is to include all possible caption, text, icon type and button labels and numbers in a database of arrays and then simply call up the pop-up dialog using an index number from the database. Your personal preference will depend on your programming style. Mine is to have subroutines, in which case the button selection is returned as a parameter. Your programming standard might well be to declare the arguments with their INTENT attribute, in which case, IRET would be:
INTENT (OUT) IRET
Or alternatively, to program the pop-up information dialog as a function and have a selected button as its return code. A further refinement might be to indicate to the routine which of the buttons was to be the default one, although that may be in some cases simply assumed to be the first one.
Some sub-menu items as well as radio buttons can be effectively ganged, meaning that only one of the group can be selected at any one time. You have to take care not to make work for yourself, and so, for example, in a case where a coordinate grid is drawn on a drawing surface, and the logical choice is a grid or no grid, then only one tick box is required, and it would not be right to have 2 radio buttons labelled ‘Grid’ and ‘No grid’, as a ‘Grid’ tick box that isn’t ticked means ‘No grid’.
If the choice were between ‘No grid’, ‘Blue grid’ or ‘Green grid’ (‘No grid’ because the other two options mean that there is a grid), then the three need to be ganged. It would make the programming more complicated, and the result actually less intuitive, to have to make the choice between ‘Grid’ and ‘No grid’, keeping the green and blue options greyed out while ‘No grid’ is selected.
In a dialog, an %ob - %cb box frame labelled ‘Grid’ would mean that you only needed radio buttons ‘Green’ and ‘Blue’ – but they could not be ganged through the %ga mechanism, because that would mean that the two could not be deselected at the same time. You would have to do the logic yourself in the callback. Instead of a logic involving IFs, a radio button state can be changed with:
ISTATE_1 = IABS (ISTATE_1 – 1)
ABS works with both INTEGER and REAL these days, but I am a traditionalist! However, I do forget cleverness like this, so I usually put in an inline comment when I use it.
And in the example, ISTATE_2 has to be 0 if ISTATE_1 is 1, but both can be zero. For clarity, an
IF is best, and not some fancy operator that might save a processor cycle or two.
As mentioned above, some Menu options may have a tick as well as a grey option, accelerator key(s) and the obligatory callback. The tick is specified with a qualifier hash character (#) and the menu item then also needs its own state variable. Top level menu items cannot be ticked, and the order of the parameters is:
State variable | grey code | callback |
Qualifier # | Qualifier ~ | Qualifier not required |
Any associated accelerator key sequence is given after a TAB character as above.
You may have noticed that a sequence of radio buttons or tick boxes positioned one above the other using %nl lines them up beautifully, which is one advantage of having the legend to the right. Input boxes for real, integer and text variables tend not to line up. It might be possible to get them to line up by positioning their legends to the right of the boxes, and that is one option. The other option is to use outline boxes to force alignment.
An outline box consists of a pair of format codes, beginning %ob and finishing %cb. Currently there are 17 options that may be used to show how a box should be drawn, with some of them duplicating functionality. For example, %ob[no_border] is the same as %ob[invisible]. I tend to use the invisible boxes rather a lot to make things line up. Three of the options will splice a name into the top line of the box either left justified, right justified or centred. Eight of the options relate to the appearance of the box, a few are duplicates and one provides one of the alternatives for setting up a status line. See the online help for more details.
I tend to use named_l or invisible options most of all. You can also set up a grid of boxes, for example with %3.2ob, which was set up a grid of six boxes: three across and two down.
When contemplating using outline boxes you should consult the online help file for some or all of the options. The following example makes three %rd data input boxes line up despite the fact that they have different length captions:
IW = WINIO@ ('%ob[named_l][Coordinates]&') IW = WINIO@ ('%ob[invisible]&') IW = WINIO@ ('Easting (m)%nlNorthing (m)%nlOrtho height (m)&') IW = WINIO@ ('%cb%ob[invisible]&') IW = WINIO@ ('%10rf%10rf%6rf&') IW = WINIO@ ('%cb%cb')
You might think that lining things up in a dialog box would be dead easy – but it’s not. Part of the reason is that the fonts used by default are proportionally spaced, but that’s not the whole story, even though it accounts for some horizontal spacing issues. Another part of the problem is that some of the objects (which Windows documentation calls ‘controls’) aren’t exactly one row high or an exact number of grid steps wide. If you use %gd then you can see what space those objects actually use.
Your first attempts at programming dialog boxes will probably not please your sense of aesthetics at all. You will find yourself fighting ClearWin+’s spacing by adding extra blanks into the WINIO@ format strings, or even worse, having discovered some format codes for positioning things (Hint: the format codes are %ap and %rp). Those positioning formats will eventually have you pulling your hair out. Why? Well one of their issues is that a new version of Windows will almost certainly change the default font, or the font metrics, and that will undo all your careful spacing. Indeed, sometimes those changes will occur with a Windows Update. Another possibility that I recommend NOT to use is to change the %ta tab spacing with %tl.
It is far better to use ClearWin+’s automatic spacing, because then those changes to Windows defaults will have minimal effect.
If you used tick boxes (formerly check boxes) or radio buttons on successive lines within an
%ob…%cb structure, you will notice that they DO line up vertically. That is because they all align to the left margin of the structure, with the text, which may be of variable length, to the right. The width of the bounding box is set by the extent of the longest label. So there’s a hint with %rd and %rf boxes: put the text AFTER, not before, the numeric control.
Another hint is to put all the text in one invisible %ob…%cb structure, with the %rd and %rf
boxes in another invisible %ob…%cb structure to the right of it.
If you look again at Figures 6.1 and 6.2 you will see that the dialog boxes have a grey background, set with %bg[btnface] where btnface is the default colour for button faces in the particular version of Windows that the application runs in. That was very much the style of things in Windows 7. However, it isn’t a great idea to give dialogs a background colour, and it is better to stick with defaults. The reasons range from making your app look old fashioned when Microsoft changes the Windows defaults to making your text look unreadable, especially if ever ‘greyed out’. Some programmers like to impose their preferred style and corporate colours on dialogs: it depends on how much work you want to give yourself I suppose!
What I refer to as a ‘sticky’ dialog is a dialog window that can be moved around the screen, but when re-opened after being closed, re-opens in exactly the same place as it was when it was closed. To do this, you need to memorise the coordinates on closure, and use them to position the dialog when it is (re-)opened. In both cases the coordinates are the upper left corner of the window. You will need to position the window on startup using the %sp (set position) format code, and find its location on closedown using the %cc format code. Now, you should remember that every dialog window will need its own position information, and, the first time the window is displayed, that the %sp format code will need positional information – which needs to be defined on program startup.
I have a program that has 80 dialogs, the pixel x and y coordinates of which are predefined in two arrays stored in their own COMMON block with two other variables as:
COMMON /STICKY/ IPOSX(80), IPOSY(80), NPOS, KHAND
IPOSX and IPOSY are fleshed out on program initialisation. Each dialog has its own ID number, which is set on entry to the dialog, and stored in NPOS. The position setting WINIO@ call is:
IA = WINIO@('%sp&', IPOSX(NPOS), IPOSY(NPOS))
But to find the coordinates on closure a somewhat more complex procedure is followed, as the %cc format code needs a callback function, and that callback function also requires to know the handle of the window, hence:
IA = WINIO@('%hw%cc&', KHAND, KB_GET_POSITION_FN)
That callback function employs the routine GET_WINDOW_LOCATION@. The routine also returns the window width and height, which aren’t used.
INTEGER FUNCTION KB_GET_POSITION_FN() C ------------------------------------- C ... called on closure control %cc of a sticky window to get final C position, so window can be restored there when next opened. C ----------------------------------------------------------------- COMMON /STICKY/ IPOSX(80), IPOSY(80), NPOS, KHAND INCLUDE <WINDOWS.INS> C ----------------------------------------------------------------- CALL GET_WINDOW_LOCATION@ (KHAND, IX, IY, IWIDTH, IHEIGHT) IPOSX(NPOS) = IX IPOSY(NPOS) = IY GET_POSITION_FN = 0 ! Guarantees continue to exit RETURN END
(My own routines are rather more complex than this, because they also contain code to check for acceptable positions).
Now, all of this works fine if all dialogs are called from the main window. However, sometimes a dialog launches another dialog, for example, if a dialog has a Help button that launches a subsidiary dialog. That situation means that the variables NPOS and KHAND defined above have to be saved, which could be done by turning them from single values into arrays, or by any other mechanism that you prefer.
You have probably forgotten what Section 1.12 said about callback functions. You could flip back, but here it is again.
Most controls can have an associated callback function – menu items must have a callback, unless selecting them invokes a further submenu. The reason for the menu criterion is that menu selections cause actions to be taken.
In a dialog, the only action buttons are recognisably buttons with clear action labels. Many of them don’t need callback functions, because selecting them simply closes the dialog, and the user can determine which button was pressed and therefore what action to take by inspecting the return code. It is only an action button that may not close the dialog which needs a callback, for example, an action button labelled Clear may require various selections made in the dialog to be changed to default (usually zero or blank) values and the dialog is not closed.
Other controls, such as data input boxes, radio buttons and tick boxes, sliders and so on may have callback functions. However, such callback functions should only be used where one option being selected has an impact on other controls in a dialog, and should not be used as action buttons.
So here’s my example. A surveyor has 4 pegs in the ground, and he knows the coordinates of each of them. For convenience, they are lettered A, B, C and D. Let’s forget the start of the program where he has to input the coordinates of the 4 pegs, and move on to the bit where after he has set up his theodolite on a particular peg, he sights to another peg, and zeroes all the scales on the instrument. That means that by reading angles and distances, he can locate other things like corners of buildings, gateposts and so on, so that he can plot a map (and the program does that for him). At some point, he has to have a box in which he enters the name of the peg over which he has set up, and the name of the peg he has initially sighted to (the target). That takes 2 boxes. Logically, drop-down selection boxes are used, because we don’t want the user to enter E in the set up box when only A..D are available. However, if (say) A is entered in the first box, then A is not a valid selection in the target box . The callback for the setup drop down box must remove A from the list available for the target, and then un-grey the second box. A callback function is needed for the second (target) box because once a value has been selected, it should be possible to un-grey the Accept action button, which should stay greyed out until the two peg names have been selected.
Go on – try to program that for yourself. It’s a great exercise. It also lets you gain experience in laying out dialogs as well as writing callback functions.
Footnotes:
6. Actually, it was the first that was fully-developed and released on users other than myself.
FORTRAN and the ART of Windows Programming, Copyright © Eddie Bromhead, 2023.