Previous: 1 First steps
Next: 3 It’s been a long road, getting from there to here ...
In this section:
The way in which we are going to incorporate your existing Fortran code in your application is initially by fleshing out the File menu. The File menu is a fairly standard part of any Windows program, and it will normally contain at the very least the following settings:
If you look at anyone of your favourite applications other than anything in the Microsoft office suite (because Microsoft tends not to follow its own recommendations) then you will see the above list and other items. You will also probably see separators above and below Print although Open, New, Save, and Save As will all fit in the same block without a separator. Some of the menu items are followed by an ellipsis, or three dots, which means that the menu item opens up to a dialog, and doesn’t just perform an action.
The first 4 options are all about your program’s dataset. New is there to let you create a dataset from scratch, Open to bring a pre-existing dataset into your program. Save is an option to save your dataset – either because you created it from scratch, and don’t want to lose it, or because you imported an existing dataset and modified it, and want to save the changes over the top of the pre-existing dataset – while Save As is where you want to save that modified dataset with a new name, which will preserve your original data.
Print, on the other hand, is about several things: getting a hard copy of your dataset for one thing, or saving your results. The action word Print has its origins in the past, where printing was the main way of saving the results of an analysis. Saving to a file would be done with an option when selecting a printer, such as ‘print to file’. Personally, I think that a more modern action word might be ‘Reports’, but we’ll stick to Print for the time being. The point is that everything down to Save As is about your dataset.
The final one is Exit. Now Exit is a bit superfluous, because there is the ‘Close’ box in the top right of the Window (see Figure 1.1 in the previous Chapter) but Windows applications often have multiple routes to the same destination.
What we are going to do first is to concentrate on only two of the submenu options: Exit and Open. Why? Because one of them is dead easy, and the other is the way to implement much of your original, pre-Windows, programming.
Your first job in enhancing the File menu, with a lot of submenu items rather like we enhanced the About menu, which I suggest that it is best to do by adding callbacks that initially do nothing for New, Open, both Save options and Print. For Exit we are going to use something new, which is a standard callback. A standard callback saves us from having to write a whole callback function. We have the choice of two functions for this option: ‘EXIT’ and 'CONFIRM_EXIT'. Using ‘EXIT’, the menu declaration would be:
IW = WINIO@ (‘%mn[[Exit]]&’, ‘EXIT’)
When you select that item from the File menu, the program closes. You can probably see the downside of that, as to select it by accident, your program will close abruptly, and if you had work in progress, it will be lost. At least ‘CONFIRM_EXIT’ will ask you whether to shut down or not. You should try both. In the long run, neither is really what you want, because you will eventually want to see if your user has work in progress that he should really save first, but it will do for now.
The on-line help file contains a list of standard callbacks for reference and you may like to take a look now to see what else is there. Don’t worry if there seems to be a lot of options that you can’t see a use for at the moment.
Now, we have come to the point where you need to start integrating your existing Fortran code into this Windows/ClearWin+ framework. Obviously, it pays to check that your existing Fortran code will compile and run using FTN95. The chances are that it will, but it won’t if you have used any of the small number of things that have been deleted from the Fortran standard, or have used some third-party facilities. You need to root out and replace those things if you have used them, and one of the easiest ways to find out is simply to try it and see. Compilation will also fail if your code contains compiler directives (for a different compiler) that FTN95 does not recognise.
Some advice from me at this point is not to become too obsessive about bringing your Fortran up to date, and certainly don’t try to add extra facilities at this stage. Stick with whatever data structures your code already has.
If you run into a lot of problems, then you might want to read Chapter 3 to understand some of the problems you encounter, but I can’t solve all of them as I’m not psychic! On the other hand, (hopefully), you will meet few problems and then can move on.
What you may have is that old-fashioned procedure (Method A) where you:
because linear programs like this do fit the File/Open paradigm pretty well. Now, if your program has any sort of conversational input, then you must set that aside for the moment, and if there isn’t a routine for reading in the dataset all in one go, then that is something that you have to write.
There are some Windows programs, with MS Word and CorelDRAW! fitting into this category, where the input file is the result, and the application exists to ‘polish’ that input or add to it, those sorts of programs still have to read in the complete dataset, but what they do (Method B) is to:
Of course, in Method A, there are intermediate stages in which you can alter the input data, and in both methods there will have to be a way to save it, but those are things that I will deal with later. Let’s just imagine that your program is of the method A type. If it is, then you probably have the right sort of structure to just accept MS Windows way of getting a file name to open instead of whatever you did previously.
It is probably a good time to sort out all the subprograms into how they fit in the three stages listed above.
A friend of mine refers to programs that follow Method A as calculating programs, and those that follow Method B as processing programs. Mostly, I write programs of the Method A type, but many of the applications I use that were written by someone else fall into the Method B category.
Several things flow from the number of items in the File menu, and one of them is the large number of callback functions which you are likely to require. Indeed, you may well want to have a large number of top-level menus rather than the 3 in the demonstrator program. Microsoft recommends a maximum of 10, but in my experience 11 or 12 is still not too confusing for the user, whereas any more than that can be. Imagine then that you have 10 top-level main menu items each with 6 submenu items and possibly even some sub-submenus. You are likely then to find that you have 50 or 60 lines of WINIO@ function calls matched with 50 or 60 callback functions. The menu lines as WINIO@ calls are going to fill up the main program segment to an inordinate degree because all of those lines will need perhaps a similar number of toolbar entries and all the instructions about what to do with the graphics area which at the present moment is a fixed area coloured blue so that you can see it.
You may also need to undertake some tasks when the main window opens, and you may have other tasks when you request that main window to close, such as saving the user’s work. All of this means that if you put everything in the same subprogram (or worse, in the PROGRAM routine) it will become very long and therefore difficult to follow and, if necessary, to debug. My recommendation is therefore that instead of putting everything in one routine, you divide the WINIO@ calls into logical groups and put each group in its own subroutine.
Typically, then, you might have one subroutine for start-up and close-down, another subroutine for the menus, another subroutine for a toolbar, and another subroutine for the graphics.
There is only one issue with dividing the WINIO@ calls across several subroutines and that relates to the way FTN95 closes down. As the windows close, WINIO@ returns a value, and it has to find somewhere to put that value. In my program example, that location is the integer variable IW. IW must be available when FTN95 tries to put a value into it. There are multiple ways of ensuring that, and for me the simplest way is to put IW into a COMMON block declared in the main program routine and therefore one of the last things to disappear. Another option is to use the SAVE statement to save IW. A third method is to use the compiler directive SAVE to make sure that all local variables are saved. The last option is not very standard Fortran, but you may find it more useful in the short term and also if you don’t like COMMON.
A benefit of putting each menu item in a separate WINIO@ call is that you make it easier to slip in extra menu items, both as a top-level item, or in a sub-menu, but some programmers put lots of format codes into a single WINIO@ call, which then needs loads of continuation lines. You must do it your way ��� I can only advise, although the advice comes from experience of getting in a muddle, failing to learn the lesson, then getting into a muddle again.
Now, we come to an important issue, which is that at program startup, the New and Open options are enabled, but the Save and Save As options have to be disabled while there is nothing to save. Otherwise, you have to write a routine to tell the user why he or she can’t save, and that isn’t what a Windows user will expect. Enabling and disabling menu items, and for that matter, all manner of other Windows controls, is done with what is colloquially called a ‘grey code’. A grey code is 1 for enabled, and 0 for disabled, which means that 0 is ‘greyed out’.
Incidentally, the Help menu items are never disabled, and so they don’t need grey codes. The grey codes are stored in a set of INTEGER variables, or more conveniently in an INTEGER array. I tend to put them in COMMON so that they can be accessed from anywhere I want them to be set or reset. To cope with future enhancements to the File menu, I suggest making the array 10 items long. I will call it MENU_GREY_FILES. You are going to have to set the grey codes for Open and New to 1 initially, and for Save and Save As to 0. Print, similarly, has to be disabled when there is nothing to print. Exit, presumably, is always enabled. You could disable it when the work is unsaved, but the close box at the top right of the caption bar cannot be disabled, so there is no real point in disabling, or greying out, File/Exit.
I have found it convenient for my applications to cope with a single dataset at any time, and therefore usually have a Close option as well, but for the time being, I will avoid that. However, with an eye to adding Close and a recent files list, I will assign positions in the array as follows, with some gaps for other potential entries to be added to the Files menu:
• Open | 1 | value | = | 1 |
• New | 2 | 1 | ||
• Save | 3 | 0 | ||
• Save As | 4 | 0 | ||
7 | 0 | |||
• Exit | 10 | 1 |
Basically, in my style of programming, the grey codes go into a named COMMON block, which is declared initially with the default values, and is declared in every routine where the grey codes need to be accessed for inspection or to change them. Sometimes there are relatively few things that need grey codes, and the whole lot go into one COMMON block, but programs with lots of grey codes benefit from having separate COMMON blocks.
Next, there is a question of where to do the assignment. I don’t like BLOCK DATA, and anyway, it is frowned upon or ‘deprecated’. Maybe it is obsolete too. You could do the assignment in a DATA statement. However, the best place to do any and all initialisations is in an initialisation subroutine. That has the advantage that if your user wants to run a second problem, all the program needs to do to reinitialise itself is to run the subroutine again. I tend to call my initialisation subroutine BLOCK_DATA, which gives me a hint as to what it actually does!
So, with the grey codes pre-initialised, we can begin to flesh out the menu. I will assume that the callback functions are appropriately named, and that they have been declared to be EXTERNAL. With the grey codes pre-initialised, we can begin to flesh out the menu. I will assume in the following code fragment that the callback functions are appropriately named, and that they have been declared to be EXTERNAL. Then, we might have:
INTEGER, EXTERNAL:: KB_FILE_OPEN INTEGER, EXTERNAL:: KB_FILE_NEW INTEGER, EXTERNAL:: KB_FILE_SAVE INTEGER, EXTERNAL:: KB_FILE_SAVE_AS INTEGER, EXTERNAL:: KB_FILE_PRINT INTEGER MENU_GREY_FILES(10) ! where the grey codes are stored
Then, the WINIO@ subroutine calls are:
IW = WINIO@ ('%mn[Files[Open]]&', MENU_GREY_FILES(1), KB_FILE_OPEN) IW = WINIO@ ('%mn[[New]]&', MENU_GREY_FILES(2), KB_FILE_NEW) IW = WINIO@ ('%mn[[Save]]&', MENU_GREY_FILES(3), KB_FILE_SAVE) IW = WINIO@ ('%mn[[Save As ...]]&', MENU_GREY_FILES(4), KB_FILE_SAVE_AS) IW = WINIO@ ('%mn[[|,Print]]&', MENU_GREY_FILES(7), KB_FILE_PRINT) IW = WINIO@ ('%mn[[|,Exit]]&', MENU_GREY_FILES(10), 'EXIT')
The grey code integers have to be predefined individually to have values of either 0 or 1.
ClearWin+ and Windows don’t actually open files for you – you have to do that with the Fortran OPEN statement. What ClearWin+ does is to launch a standard Windows file selection dialog from which you can pick a file name. This is what Windows users expect, but in principle there is nothing to stop you from designing your own system, for example getting the user to type in a fully-qualified file name (fully-qualified means that you give the path as well). The problem with that approach is that it won’t be familiar to Windows users, and they can more easily mistype the name. Here is a minimum routine that works the Windows way so that the points can be explained.
INTEGER FUNCTION KB_FILE_OPEN() C ============== CHARACTER*(256) DATA_FILE, DATA_PATH CHARACTER*(20) DATA_FILTERS(2), DATA_SPECS(2) INCLUDEDATA_PATH = 'C:\DATA_FOLDER\' ! defines first location to look in DATA_FILE = ' ' ! DATA_FILTERS(1) = '*.DAT' ! look firstly for .DAT files DATA_FILTERS(2) = '*.*' ! then look at all files DATA_SPECS(1) = 'DATA FILES' ! display what files are DATA_SPECS(2) = 'ALL FILES' ! CALL GET_FILTERED_FILE@ ('Open data file', DATA_FILE, DATA_PATH, & DATA_FILTERS, DATA_SPECS, 2, 1) OPEN (50, FILE=DATA_FILE, STATUS='OLD') C … check that a filename was actually selected … IF (DATA_FILE .EQ. ' ') RETURN C … actually reading the file goes in here … KB_FILE_OPEN = 2 END
So what is going on here? Well, finally, we are going to use the Fortran OPEN statement, but before that, we are going to get a filename using the GET_FILTERED_FILE@ subroutine, which is a standard part of ClearWin+.
The parameter 2 given in the GET_FILTERED_FILE@ subroutine tells it that you have defined 2 ‘filters’ and their associated ‘specifications’, namely to look for all files with *.*, and to look for *.dat files that are specific to this program. The initial path is given with the variable that I have named DATA_PATH, and the filename is returned in DATA_FILE, which you have to declare as a long character variable, because it might be returned with a long associated path. For an input file, it must exist already, so the final parameter is set to 1 to make that so. Otherwise, say for an output file (which we will get on to in due course) it could be set to 0.
If you have set a ‘must exist’ flag then the file must be one that already exists, so the next Fortran statement is to open the file. Then there isn’t very much chance that the file will not be found. However, if your program dithers around between getting the filename and opening the file then there is always a chance that if the file is on removable media then the user might actually have removed it!
A further issue is that if the user selected ‘Cancel’, so that no file was actually selected, then you run the risk of trying to open a file named according to the initial contents of the character variable DATA_FILE. It is therefore advisable to enter the file selection procedure with this name set to (say) a blank string, and then you can check that a file with a genuine name has been returned before proceeding. You have to do this check for yourself because the subroutine does not report ‘that Cancel’ was selected.
I will leave it up to you to flesh out what your program does after the file is opened, but my recommendation is that after you finish reading from it then you use the CLOSE statement to release the file. The reasons for that will become clear later on. It is highly likely that if you are adding a ClearWin+ Windows interface to an existing program that reads its data from a file, then you will already have a subroutine for reading in the data and checking that it is valid for your program. Perhaps it is time to integrate that into the File/Open callback routine. You will probably discover that your old procedure for checking the data is simply to reject the file if there is an input error. This leads inevitably to the Fortran STOP case. I suggest that this is not good enough for a Windows application and therefore all the READ statements need to have END= and ERR= options and that you need to have the procedure in place just in case the input data has been corrupted in some way.
Probably the correct approach with END= and ERR= is to pop up a little window using a standard icon and containing an error message and the button which will take the user back into the KB_FILE_OPEN routine just in case they want to try to open a different file. The problem of unreadable files is compounded if you choose a very commonplace file extension such as
.DAT, for the simple reason that there will be many files on a computer system that are not input data for your program. Even if you pick something unusual as an extension, there is always the thought that someone has been there before. I therefore recommend that the first line of an input data file for your Windows version of your program contains information that confirms that it is an input data file for your program, and also gives the version number of your program that it is formatted and laid out for. Your program would then need to check that it is a valid file, and that it can read the data for that particular version number specified in the data file.
I am particularly irritated by my favourite drawing application CorelDRAW! that won’t read my older files. I think it would be useful if it could. I suggest that you always make your program capable of reading obsolete past version data files as the users will appreciate that.
Once you have implemented the callback for the Open submenu item in the File menu and have integrated your existing Fortran code to respond to that open command then essentially you have completed the first major hurdle in producing a Windows version of your program. You still do not have any form of interactive input, you can’t create a new file, you probably haven’t implemented Save yet or Save As, and so far I haven’t discussed the issue of saving or presenting your results.
Without my crystal ball, it is difficult to know what your old Fortran program does and how it does it. If the program was written for an early mainframe, then it would have assumed that the assignment of logical unit numbers to different devices was fixed. For many computers, logical unit number 5 was the card reader and logical unit 6 was the lineprinter. It was not always so, and I certainly have used at least one computer where it was different. However, after a while pretty much all computers standardised on 5 and 6. You may find that other logical unit numbers were preconfigured in certain ways. For example, I have known computers where the operator had a teletype and that was configured with preconfigured (or preconnected) logical unit numbers, as indeed were the card punch and in some cases paper tape readers and punches.
By the time we got to late model mainframe computers that were accessed by means of a terminal, then logical unit number 5 tended to be assigned to the keyboard on the terminal and logical unit number 6 to the screen. These assignments were also preconfigured and you did not need an OPEN statement of any sort to use the pre-assigned unit numbers.
In fact, an early version mainframes running Fortran 66 there wasn’t even an OPEN statement and that one selected things like magnetic tapes or discs and assigned logical unit numbers to them via job control cards slipped into the card deck. Later model mainframes running Fortran 77 introduced OPEN statements.
FTN95 has preassigned logical unit numbers for the console, and it is advisable when using files not to use single digit logical unit numbers for files. Instead, use numbers from 10 upwards.
Where input was on cards and output was on a lineprinter, we users often had little choice but to print everything and accept that the results of a program were encapsulated in the printouts. Once it was possible to save results in a file there was always an issue with what you called it and whether or not you overwrote some results that you had saved earlier by using the same file name. The introduction of personal computers continued this little puzzle. Moreover, after certain time had passed, it was possible that one forgot which input data matched which output file. Of course, good practice would always have been to have printed the input data again in the output file. One way round the problem was to have an output file that had the same basic name as the input file but using a different file extension. An example of this would be to have the horrid file extension .DAT on the input file and say .RES on the output or results file.
If you adopt the procedure of having an output file that has the same name as the input file but simply a different file extension, then that is something that you can do at the time of opening the input file. The way to do it is to trim off the file extension and replace it with the output file extension. I suggest not using .DAT or .RES because they are so commonplace.
Another possibility presents itself and that is to add the results of an analysis with your program back on top of the input data file, in other words to APPEND the results. That solves the problem of linking the input and output, and for a Windows program has an additional benefit. If you have the input data and the output data in the same file, then if you reopen that file in your application you can read the results as well and avoid re-analysing the problem, but simply go on to look at various ways of presenting the results and interpreting them. I’m thinking in terms of an engineering analysis here, but for obvious reasons, when you save a word processor file you save all the work that you did on it. The separation of input and output skews that truth. It’s not my business to tell you how to code or run your program, but a little bit of thought at this stage they make your Windows application rather better than simply bolting on a graphical user interface (GUI). I therefore think that it is better to reserve Save and Save As to another Chapter. As for New, well that leads you into the area of interactive input, which is also deferred for the time being. What I think comes next is to set the Windows program aside for the time being, and work on making sure that your old Fortran program works as it did when it was a standalone ‘console’ application.
If you don’t have that old Fortran program, then you will probably want to skip the next couple of chapters, in which I discuss some of the issues that you may encounter. My assumption is that you want to (a) get your old Fortran program to work, (b) to tidy it up a bit, but not (c) to rewrite it completely in modern syntax – although that is up to you.
At some point in your program development you will probably become aware of just how complicated the Windows interface is. I have two recommendations about how you organise your workflow. Firstly, I think that it is highly beneficial to recognise that you will have a vast number of subprograms. For me, that makes it imperative to have multiple source code files and also to group the subprograms logically within those files. I find it particularly useful to group the subprograms in a way that reflects the menu structure of the application, so that for example, the source code for all the callbacks to the File menu are grouped in one of those source code files, and all the callbacks for Edit are in another.
Taking the File menu item and its sub menus to which they call, there has to be a callback for each, then with New, Open, Save, Save As, Print and Exit there will be at least 5 subprograms (Exit using a standard callback). Any additions will increase that number, for example a way of getting at recently accessed files, more than one print option, and so on.
If we suppose that the application ends up with 10 top-level menus, each of which typically has 6 to 8 submenu items we could end up with 60 to 80 callback subprograms. Now far be it from me to tell you how to manage your own files, but I would certainly struggle with source code files with that number of subprograms. If you use the Plato integrated development environment you will discover that it has the concept of a project consisting of multiple source code files. I think that you will discover for yourself that the management of those files insofar as it relates to compilation and linking is done particularly well. However, Plato does not straitjacket you into the way in which you distribute your subprograms across the different source code files – that is up to you to manage.
Because I store my subprograms on the basis of the top-level menu item that they refer to, then I also tend to name them after the program and the menu item so that for a program called SURVEY, the source code files would be named:
and so on.
The second matter is that a master program window may well run to hundreds of those format codes, and if you follow my advice to then you will use as few format codes in any WINIO@ statement as you possibly can so that the calls to that particular function have format strings that are comprehensible and also more easily modified, then you will discover that you have possibly hundreds of statements. I have said this before, and no doubt I will keep on saying it, but too much content in one WINIO@ simply leads to confusion. Remember, the content consists of the format codes, associated variables and callback functions, and the sequence of the variables and callback functions depends on the sequence of format codes. But, equally, too many WINIO@ calls in your main program makes that difficult to follow. Similarly, too many routines in any one source code file makes it difficult to find things.
My suggestion therefore is that instead of putting everything in one routine (which may of course be the main program segment), you spread out the WINIO@ calls and their related statements across a number of subprograms, grouping them again by function. The very big subprograms will be those where the menu system is defined in the first place and also the definition of the items in a toolbar or toolbars. Some of the functions of a master window can be defined quite simply, and therefore those functions can more logically be put in the same subprogram.
I find it very useful to have a subprogram that deals with each of the following groups:
The division of the master window definition into subroutines brings with it the possibility of a set of problems that I will discuss in more detail later. Therefore, in the early stages of development of your program I suggest that you begin to create your source code file arrangements but for the time being run with only a single master window generation routine which can conveniently in the early stages of program development be the PROGRAM segment.
You will always need a callback function for a menu item, and in Section 2.4 it was explained that the Windows file selection dialog could be invoked using the routine GET_FILTERED_FILE@. There is another way, which is to use a standard callback ‘FILE_OPENR’ or open file for reading. The prerequisites still have to be defined before the routine is entered, but this time with format codes %fs and %ft.
CHARACTER*(220) FILENAME, DATA_PATH IW = WINIO@ ('%fs[DATA_PATH]&') IW = WINIO@ ('%ft[*.TXT]&') IW = WINIO@ ('%mn[File[Open]]&', 'FILE_OPENR[Open]', FILENAME, & KB_OPEN_FILE)
You will see straightaway that this approach has similarities to the GET_FILTERED_FILE@ route, but I think that it is less obvious what is going on. You can repeat the %fs and %ft format codes to define the file filters. The main benefit to describing it is that there is a related FILE_OPENW, which is useful for Save As. In any case, you still have to OPEN the file and conduct all the checks, as well as reading it. The string that I have put in above as Open could easily be something more relevant, for example:
FILE_OPENR[Accounts file for BusyBee program]’
A thing that can easily catch you out is the subroutines in the FTN95 library that are a hangover from FTN77 in pre-Windows days when it ran with a DOS-extender called DBOS. They are still in the library. They are subroutines called OPENR and OPENRW. Don’t be misled – in the case of a Windows application you need to get the filename in the standard Windows way, not through those subroutines, and after that, OPEN (and INQUIRE) is the way to go. The contents of the old FTN77 libraries still exist, and many things in there are useful. Just don’t confuse them with ClearWin+ even though it is easy to do.
Please note that I haven’t said anything about output, which is especially important if your program expects to output on the screen. For the time being, just let it do what it wants. That way, you will still be able to check that it works the way it always did.
I have programs that generate such voluminous outputs that in the old mainframe days there simply wasn’t the memory to store it in. I tended to do one or two things or both with the results as I calculated them. One was just to print them, and that was my permanent record of the calculation. The other thing was to punch the results on paper tape, usually so that I could further process them in a different program – usually in graphical form. Sometimes I did both, and when I moved to a PC, I saved the information not on a paper tape, but on a disk file.
The important thing as far as this Chapter is concerned is to be able to open a file using the standard Windows dialog, and get your results, being confident that your program still works as it always did in respect of producing results.
As well as the errors that crop up if you open a file that you shouldn’t have and then try to read it, the detectable errors are primarily those that will prevent the reading process from going any further. You will need to supplement your READ statements (if you haven’t already done so) with END= and ERR= clauses, so instead of (for example)
READ (50,*) A, B, C
You probably need
READ (50,*, END=800, ERR=900) A, B, C
Where statements 800 and 900 respectively pop up a message telling the user that there is the relevant error in the file. As to what the error is, when you reach the end of file prematurely, it is fairly obvious, but with a reading error, it may not be so obvious. You can always add an IOSTAT= clause, having defined an integer to contain the error number, as in:
READ (50,* , END=800, ERR=900, IOSTAT=IERRORNO) A, B, C
But that can be less helpful than you would think, as there are well over 400 detectable errors – with the messages not being the same from compiler to compiler, and therefore possibly not what you are used to. In general, you might be better off defining a character string ERROR_MSG and using IOMSG= as in:
READ (50,* , END=800, ERR=900, IOMSG=ERROR_MSG) A, B, C
However, a read error is simply where the executable detects something that doesn’t fit with what it has been told, and your dialog needs to be more helpful, for example by including the line number where the fault occurred, or some other information such as the type of data that was being read when the error occurred. It doesn’t matter what it was, it is probably irrecoverable, and your reading routine needs to return control without invoking a FORTRAN STOP.
There is another kind of error that can crash your program, and that is some data that causes the error to occur during execution of whatever algorithm your application uses, with a simple example being something that should have a non-zero value being read in as zero. Those things are difficult to detect a priori, and many old codes simply wait until a crash occurs and point it out then. That’s not how a Windows application is expected to behave!
Anyway, should your datafile be read without errors, then it is a question of returning control, but only after setting the grey controls appropriately. What you might want to do is to make the File menu prevent File/New or File/Open being invoked, but to allow File/Save As to become active. There is no need to un-grey File/Save if the input file is not changed. All that is required is for the grey codes to be in scope and to change the values, ClearWin+ takes care of actioning the change.
It is up to you whether or not your program automatically does the appropriate analysis, or waits for you to perhaps action it via another menu command such as Run.
If you find your way to the definitions for buttons in the FTN95 online help, you run the risk of being overwhelmed with information. The simplest form of button is defined using the %bt format code, but it isn’t always the best button to use because it is rather tall compared to buttons used in many other applications. However, I recommend using it in your early developments. The simplest buttons are set up using the %bt format code. The text on the button is specified in the following square bracketed part of the format string, so a button might be just:
IA = WINIO@ ('%bt[OK]&')
The modifiers include:
^ | the button has a callback function |
` | the button is the default button (i.e. the one that is selected if the user presses the Enter key) |
~ | the button can be greyed out (but then it will also require an integer as a status variable) |
As the button width is sized to contain its text, then you can give a button a larger size in average character widths with a number (like 6 in the following example). In this way you can make all buttons in a row have the same width, but beware, it doesn’t mean “6 or larger” it means “just 6” so you have to be sure that buttons are big enough to contain their text. An example of this is:
IA = WINIO@ (‘%6bt[Accept]&’)
As to which button is declared the default, then you must consider firstly if you want a default button at all, and secondly what is a benign outcome of selecting it accidentally. For example, in response to a dialog “Shall I format your hard disk?”, would you prefer to have “No” or “Yes” as the default, and which would you prefer to have selected in the case of your cat walking across your keyboard?
Greying-out a button is a way of letting your users know that (for example) the data entry in a dialog is incomplete, or that there is some sort of a condition that your program won’t accept. You shouldn’t grey out the Cancel option, as users must always be allowed to change their minds. However, when you have greyed out the Accept, OK, or “Go for it!” button, a user must be able to work out rather easily why he or she can’t progress. This generally means that dialogs should be simpler rather than complicated; you should suggest helpful default values for parameters and settings, and if it is going to be difficult to work out, provide an instant “Help” or “Why can’t I progress?” option.
If a button does not have a callback, then selecting it causes its window to close. If there are several buttons, then the return code from WINIO@ contains the button number that closed the window. Buttons are numbered consecutively from 1 in the order that they are described in the WINIO@ calls. In addition, a return code of 0 is provided if the user closes a window by means of the close box (the square symbol with a X at the right of the caption bar). Your program can inspect these exit return codes to decide what to do next.
The “tt” name comes from their originally intended use, which is “textual toolbar”. These buttons are rather less tall than %bt buttons, and instead of being sized horizontally to fit the text in small increments, the size increment is a bigger step. They don’t repond to a size qualifier, but otherwise, they function rather like %bt buttons.
Starting with Vista, Microsoft used wider but less high buttons in standard dialogs, and %tt matches this much better than %bt. As it happens, textual toolbars fell slightly out of fashion after Windows 95, to be replaced by icons or icons and text. In Windows 95, a row of very square 3D silver-grey textual toolbar buttons looked very smart, but now they look dated. Windows XP introduced a very different style for buttons via something called the XP manifest. For a time, ClearWin+ programs had to declare that they were using the XP manifest or they would get the old style, but from Windows 10 the XP manifest is the default and you don’t need the line in your RESOURCES (see later) if you don’t intend your program to run on old versions of Windows. The subject of manifests generally is way beyond the material covered in this text book. A further disadvantage is that once the XP manifest is in use, the %tt buttons have rounded corners, and they don’t make such a good toolbar anyway. I always presumed that the lower height was so as not to take up too much vertical space in the toolbar, leaving more room for the client area.
For the time being, I recommend that any action buttons that you program are done via the %bt button format code. ClearWin+ offers some alternative ways of specifying action buttons, with format codes %bb, %bn as well as %tt. Those other button formats may prove more to your taste, especially in the way that they allow you to have icons as well as text on the button itself. The problem with so much choice is that you may become overwhelmed with detail too early in the development process and it is therefore much better to proceed with the simplest form of button and to refine it later. Details of the other button formats and their use in toolbars are given later in the book in chapter 10. Of course, the precise programming details are given in the online Help files, it is just that advice on when and where to use them does not fall within the remit of those documents.
In my view, a status bar at the bottom of the master window balances its appearance and is an aesthetic improvement. Different applications use status bars in different ways. For example, Microsoft Word on which I am writing this, uses the status bar to tell me which page of the document I am on, what the current word count is (although that is only a character count when a file is loaded), that I am in focus mode (whatever that is), which page view I am using and also a slider that allows me to zoom in or out.
ClearWin+ has 2 ways of specifying that the master window has a status bar. One of those is to use the format code %sb, and the other way is via an %ob… %cb structure. Personally, I think it is premature to cover the details of a status bar at this point in the development of an application, but it is always worth remembering that a status bar is a useful adjunct to everything else that we do in a Windows program. I will discuss status bars in greater detail later on in the book (in sections 18.3 and 18.4 to be precise). Status bars are nice, but not necessarily something that you need to include at a very early stage in developing your application.
FORTRAN and the ART of Windows Programming, Copyright © Eddie Bromhead, 2023.