Previous: 16 When I was younger, so much younger than today ... Help
Next: 18 Some more about graphics
In this section:
ClearWin+ provides two format codes: %sc and %cc specifically to invoke through their callbacks’ activities that need to take place as a window opens (%sc) and when a window closes (%cc). The format codes can be thought of as mnemonics of start-up control or start-up callback, and closure control or closure callback. Both of these format codes require a callback function. The closure control format code may be invoked in one of two ways or even both: before the window closes (%cc) and after it has closed (%`cc). The start-up control is executed at completion of the appropriate window’s setup.
Ordinarily, the start-up control is required for a main program window, but it was shown earlier how useful the start-up control could be to launch an invisible window that executed something and then closed itself down by having a callback that returns the value 0.
One particular use for the closure control is to give the user a chance to save their work if they have not already done so. Matters are rather more complicated than they might seem at first, but please be reminded that when the standard callback ‘EXIT’ is invoked the program stops dead in its tracks. This is simply annoying if it happens to you during program development but especially with a commercial application it may result in users raving and issuing death threats! The converse problem is that the user is asked repeatedly whether they want to save their work and that can also be fairly irritating.
Sometimes users are called away from work in progress and typically at the end of the day may shut their computer down without saving their work. ClearWin+ has an answer to this as well, and that is via the exit Windows (%ew) format code. Like the start-up control and the closure control, the exit Windows control must have a callback.
If you have divided your main window definition WINIO@ controls into groups, each of which has a subroutine dedicated to it, then it is logical to make one of those groups cover start-up, closure, and exit Windows. Such a subroutine will be comparatively short especially in comparison to subroutines for menu bars or toolbars, and it will be seen to be quite convenient to park one or two other controls in there. Those controls could include handling the caption, setting up the window properties and even some default colours and parameters.
Something that is often forgotten is that there is a difference between the Fortran start-up and the ClearWin+ main window start-up, and indeed, there is also a difference between main window closedown and Fortran closedown, although novice programmers anxious to get into windows may make the two start-ups nearly coincident and the closedown especially so.
There are two things that are very effectively done at start-up, and they are to initialise all the variables used in the program and to check to see if the program has been launched by the user clicking on an associated data file.
Initialisation, as pointed out earlier in Chapter 2 is better done with a subroutine than with BLOCK DATA, not least because BLOCK DATA is rather frowned upon in modern standards, but also because having a subroutine that initialises data means that it can be called at any time to reinitialise data for a subsequent analysis without having to close the program and restart it. If the initialisation takes a long time, then it should be moved from the Fortran start-up to the main window start-up because then the visual clues can be given to the user that something is still actually going on with your program and it hasn’t just hung up.
If your program has been launched by the user clicking on an associated data file then a command line is created that can be interrogated by your program, firstly to see how many items it contains, and if it contains two, then the first is the name of your program and the second is the data file. If the command line only contains one item, then the program has not been started from a data file but from an icon or menu in Windows. It is quite possible for a user to start your program by name from a command line utility such as Windows Power Shell, in which case it can have more than two items, but that would be relatively unusual and I suggest that it is not a good idea to rely on that mode of starting a program and therefore you should not be looking for additional items such as options or other filenames - unless of course, your program is started from another program that you have written, in which case the command line can contain whatever you want.
FTN77 and FTN95 provided facilities for interrogating the command line long before they were incorporated in the relevant Fortran standard, and as some of those standard conforming functions are also present in FTN95’s library, you have a choice of which to use.
The routines are listed under the tab ‘Command line parsing routines’ as part of the ‘FTN95 library’ and outside of the ClearWin+ entries. I suggest using either CMNARGS@ or COMMAND_ARGUMENT_COUNT and then proceeded as explained above.
In respect of the main window in your application, an important task at start-up is to initialise your variables and set important defaults such as the initial grey codes for your menu and toolbars. If you set those codes on entry to a subroutine that sets up your menu and toolbars, you make those routines rather more complicated than they absolutely need to be. However, as always, the choice is yours to fit in with your programming style.
It is possible to associate certain filename extensions with your program in such a way that if a user clicks on one of those files it will start your program. The mechanism for handling this case is that when your program starts it appears to start as if it were invoked by name with the file name following on a sort of virtual command line. Programs can of course be started in this way from a command window with the user typing in the program and a filename, so although it would appear logical to assume that the filename was related to a file that definitely exists (because the user clicked on it), that may not be the case. Moreover, there is no guarantee that the file is actually a datafile for your program. The checks that you need to undertake are described below in section 17.4 along with the mechanism for reading that command line regardless of whether it was typed in explicitly what is implicit and associated with the user clicking on the file name in a directory window.
If your application is password protected, or protected by some other sort of a code, you can either do the check before the main window is drawn or after the main program window has been drawn fully. In the former case you have to go through the password check before beginning to specify the master window WINIO@ calls, but in the latter case you can do it as part of the start-up control.
A note in the online help file points out that the start-up control may allow the program to display initial %gr data, but also points out that if the drawing surface receives a RESIZE message, then that might be received before the %sc callback is called.
Due to a foible in the way that FTN95 and ClearWin+ progressively closedown various subroutines, along with the asynchronous nature of Windows itself, it is quite possible that
WINIO@ attempts to return a numeric code and assign it to a variable that has already been discarded, i.e. in:
IW = WINIO@ ( ...
it is the variable IW. If IW is a local variable in a subroutine the memory location associated with it on the stack may not be accessible. When this situation arises, a Fortran error occurs, and the program terminates abruptly with a Fortran error that you may not wish a user to see and before certain desirable closedown routines have been executed for example saving the user’s work. There are various ways around the issue which you can adopt according to your preferred programming style. They include:
While the problem is usually quite rare, it will occur more often if your programming style divides the WINIO@ function calls across a number of subprograms. Ordinarily, dividing the WINIO@ calls in this way may not be helpful anywhere other than in the master window definition, but it can still occur. If during program testing the issue occurs then the fastest temporary expedient is simply to make all the local variables have static addresses by means of a compiler option (including a source code OPTION directive).
if you remember that Fortran closedown does not immediately have to follow closure of the ClearWin+ main program window, then you have the opportunity to check if files have been saved and suchlike. Of course, there is nothing to stop you opening a new master window and effectively running a second application from the same Fortran executable. I have a program that offers the user the choice of different toolbar styles. The nature of ClearWin+ is that the toolbar format is decided when the master window is formed. Choosing a different toolbar style means closing one master window and reopening a master window with the new toolbar style. In my experience, there is a substantial difference in the time it takes to reopen a window changing from %tb to %ib or vice versa, or using different icons in a %tb or image bar style. In that particular program I get over this by re-showing the initial splash screen. The process is a little clumsy, but at least the user is given some choice.
There is a format code %ew with a callback that is executed should Windows be in the process of shutting down. This callback might be the same one as is associated with the closure control %cc for the main window, thus giving the user the opportunity to save their work before window shuts down taking the application with it and losing any edits to date.
If possible it is advisable to minimise the work that has to be done in the callback to either %ew or %cc, particularly in respect of saving files, which is why I always recommend that after files have been opened and written to the Fortran CLOSE statement should be executed. I found that the discipline of using the close statement is also useful for files that are opened for reading. If you follow that advice and close the files as soon as possible after they have been written to, then the only files that have to be saved on exiting windows are those where the dataset is currently in use and has not already been saved.
If the same callback is used for both events, then it may not be necessary to remember to update the MRU list (see the following Section 17.8).
You may well remember the facility to add filenames to a ‘command line’ when invoking a program in the pre-windows days, so that the line became:
Program_name input_filename output_filename
An FTN95 Windows program starts with a ‘virtual’ command line. If it only has one item in it, that will be the program name. If, on the other hand, there are two items, that means that the user clicked on an associated filename, and the program was started that way – the second item being the filename.
It is therefore desirable at program startup to inspect the virtual command line. In principle, the second item should be the filename, but if the program has been spawned by another program (as with START_PROCESS@ or START_PPROCESS@) it can also have optional parameters, additional filenames and so on, and this latter case does need consideration, as the second item may then not be a filename.
FTN95 has several (11) library routines for inspecting the command line, both of its own, routines that were introduced before Fortran caught up, and some routines that are now part of the Fortran standard.
If the program was launched by a user clicking on an associated filename, it is usually safe to assume that the file exists, but if the command line was typed, explicitly, at a command prompt or from Windows PowerShell, or indeed if the program was spawned, that may not be the case, and prior to attempting to OPEN it, it is wise to check if it exists and indeed, is openable. Once opened, the contents may be read, but they still need read or other errors to be trapped.
Your application may set file associations for which you need the program name, and then call DEFINE_FILE_EXTENSION@:
CALL GET_PROGRAM_NAME@ (PNAME) K = DEFINE_FILE_EXTENSION@ ('.BUZ', PNAME, 'Busy Bee', 0, 1)
Alternatively, file extensions that are associated may be set up by direct inclusion in the Windows Registry or a .INI file, with the former potentially an option at the time of program installation.
ClearWin+ has a format code %dr that provides a callback function to be called if the user does a drag and drop operation on a file, and drops it on the relevant window. Multiple files can be dropped, in which case the callback is repeatedly invoked.
At program startup, it may be sensible to allow drag and drop into the main program window, but unless there is code to handle the situation at what might be inappropriate points, it is probably best to put %dr into a dialog connected to a menu or toolbar command which is greyed-out at entirely inappropriate moments, and where space is provided to allow the ‘drop’ (together with a caption on the lines of ‘Drop file(s) here’).
It is safe to assume that dropped files actually exist, and once opened, their contents may be read, but they still need read or other errors to be trapped, preceded by a check that the file is actually relevant.
Early versions of Windows expected programs to have their own configuration files, normally files with the extension .INI, and typically with the same name as the application itself. Even Windows had its own WIN.INI file. Later versions of Windows expect the system and all the applications to store their configuration information in a file called the registry. There are advantages to both systems.
An application compiled in FTN95 and using ClearWin+ as its interface to Windows can usefully use the registry to store information that Windows needs to know about it, but for the Fortran user, a .INI file is still useful. One particular use for such a file is to store the most recently used (MRU) file list, and because reading and writing ordinary files is a standard task within Fortran, and entering and deleting items in the registry requires the use of routines unfamiliar to the Fortran user, then the .INI file approach is often simpler.
Reading the MRU filenames is something that can easily be done between Fortran start-up and ClearWin+ main window start-up, and especially since that information may be required very early on in program execution. In early versions of Windows where filenames are still restricted to the 8.3 format (a maximum of eight characters in the main name and three characters in the filename extension), then a shortlist of MRU files was typically added to the Files menu. However, as filenames became longer and the use of nested folders or subdirectories became more common, it has become much more usual to have an ‘Open recent’ or ‘Recent files’ menu item for the user to pick from the MRU list in a dialog. A dialog in any case allows for more MRU files to be listed, to include more of the directory path, and possibly to even present the MRU list with associated dates. In view of the advantages of a dialog over a dynamic menu then should you wish to use the latter the appropriate instructions are given in the FTN95 online manual.
If a dynamic menu is used then the MRU list needs to be read before main window start-up, but if a dialog box is used, the MRU list can be read during the callback from a %sc format code action. When reading an MRU list from a file and presenting it to the user it is worth checking that the file exists using the FTN95 routine FEXISTS@, to avoid the case where a file has been deleted in the interim, or saved on removable media that is no longer connected. The choice is then whether to present the missing file’s name, or to inform the user that the file cannot be opened (or found).
MRU lists need to be updated at closedown, and in an SDI application or closedown of one window in an MDI application at the time the file ceases to be used.
My advice is to keep things as simple as possible. If you have corporate colours, logos etc, they need to be discreet. Fancy coloured backgrounds not only look out of keeping with most styles in different versions of Windows, but such backgrounds also often make an application look cheap and amateurish.
For a main window, especially one where most or all of the client area is a drawing surface, the standard border is too wide, and no_border looks better, but the window does need to retain its frame. At the risk of repetition of points made earlier, a status bar helps balance the appearance of a borderless drawing surface client area, and is useful in its own right.
Some pop-up dialogs may be drawn without a frame or caption and then they need to be volatile as well so that if they lose focus they disappear.
Now that the majority of PC and laptop screens are widescreen format, toolbars on the left can use some of the lateral extent of the screen more effectively than in a squarer format. I have one application that tests the aspect ratio before deciding on a horizontal or a vertical toolbar. Deciding and implementing the either-or approach can be highly effective with toolbars that contain few options. At the present, ClearWin+ does not support a repositionable toolbar, so the decision has to be made at startup.
Bearing in mind the note that a %gr drawing surface might receive a RESIZE message before the
%sc callback has been executed, it might be useful to distract a user’s attention so that they do not resize the master window is one of the first actions. Such a distraction can be provided by means of an initial splash screen presented in what appears to be in advance of the main window being drawn. In any case, it is not unusual for an application to use that initial splash screen to show the user that something is going on if the main window takes a long time to load. Most users will be very familiar with splash screens as they definitely appear with many large applications such as those within Microsoft Office. Very quick loading and simple applications are better off without that initial splash screen with typical examples being Windows accessories like Calculator.
Some commercial applications like CorelDRAW! make use of an initial splash screen to allow the user to select configuration options and also to present the MRU files list. As always, the option is yours when deciding the look and feel of your program.
If an application is intended to run on a variety of computers with very different screen resolutions, then it may be desirable to have more than one format for an initial splash screen decided on the basis of what the particular computer’s screen resolution is, so that any bitmap image is sized appropriately.
A very effective technique is to make the splash screen progressively fade away which is done by using a timer and a routine to make the splash screen progressively more transparent. A splash screen is in any case only a special kind of dialog, typically not having a caption bar so that it cannot be moved. It is a mistake to keep it this splash screen in view for too long or to make it disappear too quickly. In the example below the total duration with which the splash screen is displayed is taken as 2 seconds, and the opacity is decreased in 255 steps thus making the progress appear very smooth. (The SetWindowOpacity function was provided by a user on the Silverfrost forum, and only works in 32-bit Windows).
logical function SetWindowOpacity(hWnd, alpha) C ---------------------------------------------- ! Set opacity level for a window (call after window creation) - ! automatically sets appropriate extended style and sets opacity ! from 0 (transparent) to 255 (no transparency). use mswin integer, parameter:: WS_EX_LAYERED = Z'00080000' integer, parameter:: LWA_COLORKEY = Z'00000001' integer, parameter:: LWA_ALPHA = Z'00000002' STDCALL SetLayeredWindowAttributes 'SetLayeredWindowAttributes' & (VAL, VAL, VAL, VAL) : LOGICAL*4 integer, intent(in):: hWnd integer, intent(in):: alpha integer:: attrib, i ! Get current window attributes to ensure WS_EX_LAYERED extended style is set attrib = GetWindowLong (hWnd, GWL_EXSTYLE) if (IAND(attrib,WS_EX_LAYERED) /= WS_EX_LAYERED) then i = SetWindowLong(hWnd, GWL_EXSTYLE, IOR(attrib, WS_EX_LAYERED)) end if ! Set layered window alpha value SetWindowOpacity = SetLayeredWindowAttributes & (hWnd, 0, CORE1(LOC(alpha)), LWA_ALPHA) end function SetWindowOpacity SUBROUTINE SPLASHER C ------------------- IMPLICIT DOUBLE PRECISION (A-H, O-Z) INCLUDE <WINDOWS.INS> INTEGER, EXTERNAL :: KOUNTER CHARACTER*(60) COMPUTERNAME COMMON /MACHINE/ COMPUTERNAME COMMON /SPLASH/ ICTRL COMMON /Spl_Hnd/ iSplash_Wnd, jSplash_Wnd, NoInc COMMON /DPI/ IXDPI, IYDPI, ID_Platform, & Major, Minor STDCALL SetProcessDPIAware 'SetProcessDPIAware' : LOGICAL*4 LOGICAL IOK SAVE C ------------------------------------------------------------------- CALL GET_OS_VER@ (ID_Platform, Major, Minor) iHDC = getdc (0) ixdpi = GetDeviceCaps(iHDC, LOGPIXELSX) iydpi = GetDeviceCaps(iHDC, LOGPIXELSY) IF (IXDPI .EQ. 96 .AND. IYDPI .EQ. 96) THEN COMPUTERNAME = 'STANDARD' ELSE COMPUTERNAME = 'LARGEFONTS' ENDIF ICTRL = 1 NoInc = 255 Delta_Time = 2.0D0/255.0D0 IB=WINIO@('%ww[no_border,no_caption,no_maxminbox,topmost,'// & 'toolwindow,no_frame]&') IB=WINIO@('%^bm[SPLASHBMP]&','EXIT') IB=WINIO@('%lc%hw&', iSplash_Wnd, jSplash_Wnd) IB=WINIO@('%dl%lw', Delta_Time, KOUNTER, ICTRL) RETURN END INTEGER FUNCTION KOUNTER() C -------------------------- COMMON /SPLASH/ ICTRL COMMON /Spl_Hnd/ iSplash_Wnd, jSplash_Wnd, NoInc LOGICAL IA, SetWindowOpacity IF (NoInc .GE. 1) THEN IA = SetWindowOpacity(jSplash_Wnd, NoInc) NoInc = NoInc - 1 KOUNTER = 2 ELSE ICTRL = 0 KOUNTER = 0 ENDIF RETURN END
Essentially the initial splash window is a captionless window which also does not have many of the fairly standard attributes of a window, set by the more or less self-explanatory options no_border, no_caption, no_maxminbox, topmost, toolwindow, and no_frame. The option topmost makes sure that it shows while the master window is being created beneath it, and the option toolwindow makes it certain not to show in the Windows taskbar. Basically, all you see is the bitmap. There are certain nuances in the ClearWin+ code given in the subroutine SPLASHER, and one is to give the bitmap its own callback, which means that the user can in fact dismiss the initial splash window by clicking on it: in the particular program that this routine comes from the master window in any case opens very quickly. The bitmap has been given the local name SPLASHBMP in a RESOURCES statement.
Handles for the bitmap and the window as a whole are then requested by the use of %lc and %hw, and a timer set with %dl to have an interval given as a parameter. Finally, the window is given a variable by the use of %lw so that the fadeaway process can be terminated by the program if the master window is completed early enough, as it might be if the application was started via a user click on an associated data file. (The ICTRL variable being set to 0 to do that). The timer delay %dl invokes its callback routine KOUNTER at the end of each interval, with a progressive decrease in the opacity and at the end of the sequence ICTRL is set to 0 in order to close the initial splash window.
I have used the routine to set the opacity on many occasions and it is something that originated in a post on the FTN95 user forum. I have left it more or less in its original style, not least because it shows how complicated Windows programming can be if you do not use ClearWin+. This particular routine only works in 32-bit FTN95/ClearWin+.
I have left in the code to determine the screen size so that if you wish you can define a differently sized bitmap for high resolution screens. There is also a fragment of code left in to examine the logical DPI setting, and therefore whether large fonts are in use.
Figure 17.1 shows a typical splash screen graphic. The corporate colours would then ideally always be a green background, with white and yellow text, and a recognizable logo that could be positioned semi-unobtrusively on dialogs and perhaps also on hard copy. If the toolbars do not fill a typical screen, then a version of this could be used as a space filler in a way that keeps the user’s attention focussed on the application. You might consider having variants to suit different monitor resolutions and aspect ratios.
I would draw a large graphic like this in CorelDRAW!, then import it into Corel Photo Paint and export from there into the relevant bitmap format, and importing into Photo Paint does some antialiasing that improves the visual quality of the image onscreen.
A Windows handle is an unsigned integer that serves to identify every single window or control in an application. For many purposes, the handle can be ignored as either it is not required by the application programmer, or it is managed through ClearWin+. The integer is returned to the pool of available handles when the window or control that it refers to is closed. Hence, the handle for a particular control will vary from invocation to invocation. Windows handles are provided by the Windows operating system and are returned to the application program through mechanisms such as the format code %lc, which returns the Windows handle for the control specified in a WINIO@ format string immediately prior to the use of %lc.
Occasionally, a different type of handle is required. This is a ClearWin+ handle. Like a Windows handle, it identifies a particular control, but one that is not simply a standard Windows control, but one that is managed by ClearWin+. In recent releases of the Silverfrost documentation it is described as a User ID (UID) in an attempt to resolve the former ambiguity.
FORTRAN and the ART of Windows Programming, Copyright © Eddie Bromhead, 2023.