How do I? - Part 7

From EDM2
Jump to: navigation, search
How Do I? / Part
1 2 3 4 5 6 7 8 9
10 11 12 13 14 15 16 17 18 19

by Eric Slaats

Hello, hello. My mood has improved this month. I've more or less won [hardware.htm the battle with my hardware]. It's giving some last twitches here and there, but overall I'm pleased. Everything seems to be very stable except for the video driver. (It tends to lockup at unpredictable moments.) However, days go by without this and I'm always prepared for it. I have to use Win95 for the Delphi clinics I do and the problems Win95 (and Win NT) has with my new video card (S3 Trio) are much bigger that OS/2's. Despite the annoyance, this gives me a good feeling about my favorite OS.

Anyway, back to the main stuff of this column.

Last month's column generated an interesting number of reactions. The ideas displayed in some of them are too good to withhold from you all. So I decided to look some more at the "unexploited feature" of using OS/2's capability to save and restore window size/position and frame presentation parameters.

I would like to address three issues that came up in the mail I received. The nice thing is that a number of mails also contained solutions for the presented problems. This is nice because it means the column writes itself. <g>

  1. Using the OS2.INI file
  2. Using FCF_SHELLPOSITION frame creation flag
  3. Saving Minimized window positions

Using the OS2.INI file

Saving the Frame PP and the size and position info in the OS2.INI file with one command is very attractive, but it has some downsides for some OS/2 users. The OS2.INI file is a binary database which can contain all sorts of information used by applications. Last month we saw that the entries in INI files have a two level nature. On the highest level, the application is the entry point. For every application, a number of keys can be defined which can each hold data of any kind. This way of storing info is used by the OS2.INI file as well as the private profiles (which we will attend to in a minute).

The OS2.INI file has a tendency to grow. If applications add information to this file, but don't delete it if they are uninstalled, a pile of junk will form in the OS2.INI file which will slow down the system. This is the main reason people don't want applications to store info in the OS2.INI file.

However, there is also an upside! The OS2.INI file is simpler to use than private INI files. It's always open; this means a specific open command doesn't have to be given. The same goes for a specific close. Besides that, we have the use of functions like WinRestoreWindowPos and WinStoreWindowPos which will work specifically on the OS2.INI file.

And I like using the OS2.INI for yet another reason: it's fast. Chances are that the info you're looking for in the OS2.INI is already in memory. Since you don't have to open it, disk access is minimal. So, if you need an application that must come up immediately and uses windowposition, PP and other information from an INI file, the OS2.INI is a great candidate. Of course, a hybrid solution is always possible. Save the time-critical info in the OS2.INI and the other info like paths, etc. in a private INI file.

Having said this, it's time to take a peek at profiles beyond the level we did last month without going into too much detail.

To use a private INI file, we've got to open/create one. The nice thing about the function that handles this is that the INI file will be created if it doesn't already exist. The API call to use is prfOpenProfile.

HAB   hab;         // Anchor-block handle
PSZ   pszFileName; // User-profile file name
HINI  hini;        // Initialization-file handle

hini = PrfOpenProfile(hab, pszFileName);

The hab isn't that exciting, it is simply the anchor block used when we defined. The pszFilename, however, is important. This must be a filename with full path (otherwise the current path is used). It is a good custom to give the profiles an INI extension, however, it's not necessary, so be careful not to overwrite other files. Of course, the INI file may not be named OS2.INI or OS2SYS.INI.

The Handle (HINI) that is returned is also important. It's this handle we will need if we want to do anything with the newly opened/created profile. It's also used for closing the file. The closing call is very simple: PrfCloseProfile(hini). Storing stuff in a private INI file works just the same way as with the OS2.INI file.

But now to the main point of this small venture. Can we convert the sample function so it simply saves the window information from the OS2.INI file into one that uses a private profile? The idea for this came from Mark Kimes (the author of the FM/2 utilities), and is a simple but very effective idea. Since the WinStoreWindowPos and WinStoreWindowPos only work with the OS2.INI file, simply use the OS2.INI as a temporary buffer. The following steps are needed:

  • On WM_SAVEAPPLICATION just use WinStoreWindowPos
    - Copy the info in OS2.INI to a private profile
    - Delete the info in the OS2.INI
  • On application restart
    - Copy the info from the private INI to OS2.INI
    - use WinRestoreWindowPos
    - Delete the info from the OS2.INI (not really necessary because on exiting the applet this is already done)

Simple and effective. In the sample added this month, the following lines are added at the start of the program:

HINI    hini;   // Handle to private INI file
ULONG   ulSize; // Size of the data to be copied
PVOID   pBuffer;// Buffer

hini = PrfOpenProfile(hab, "Sample7.INI"); // Open private profile
if (hini)
        {
        //----------------------------------------------------------------------
        // Query the info from a private INI into a memory buffer
        //----------------------------------------------------------------------
        PrfQueryProfileSize(hini, APPNAME, WINPOS, &ulSize);
        DosAllocMem(&pBuffer, ulSize, PAG_READ| PAG_WRITE |PAG_COMMIT);
        PrfQueryProfileData(hini, APPNAME, WINPOS, pBuffer, &ulSize);
        PrfCloseProfile(hini);  // Close private profile

        //----------------------------------------------------------------------
        // Write the info to the OS2.INI
        //----------------------------------------------------------------------
        PrfWriteProfileData(HINI_USERPROFILE, APPNAME, WINPOS, pBuffer, ulSize);
        DosFreeMem(pBuffer);
     }

Everything is very straightforward. First, the profile is opened. If this can't be done, there's no need to carry on. If it's open, we query the size of the info. We need this to declare a memory buffer and to write the buffer to the OS2.INI. The info is then copied from the profile to the buffer with the PrfQueryProfileData call. After that, the profile is closed and we can copy the data in the OS2.INI. (Remember, this one doesn't have to be closed. I'll go into the specifics of memory handling some other time.)

The code for the WM_SAVEAPPLICATION message is somewhat more complex, but is, in fact, nothing more than the inverse of the above code. The following lines are added:

case WM_SAVEAPPLICATION:
        {
        HINI    hini;   // Handle to private INI file
        ULONG   ulSize; // Size of the data to be copied
        PVOID   pBuffer;// Buffer
        //------------------------------------------------------------------
        // Store window information in OS2.INI
        //------------------------------------------------------------------
        WinStoreWindowPos(APPNAME, WINPOS, WinQueryWindow(hwnd, QW_PARENT));
        //------------------------------------------------------------------
        // Copy the info from the OS2.INI into a memory buffer
        // and delete it from the OS2.INI file
        //------------------------------------------------------------------
        PrfQueryProfileSize(HINI_USERPROFILE, APPNAME, WINPOS, &ulSize);
        DosAllocMem(&pBuffer, ulSize, PAG_READ| PAG_WRITE |PAG_COMMIT);
        PrfQueryProfileData(HINI_USERPROFILE, APPNAME, WINPOS, pBuffer, &ulSize);
        PrfWriteProfileData(HINI_USERPROFILE, APPNAME, WINPOS, NULL, 0);
        //------------------------------------------------------------------
        // Copy the info from the buffer to the private ini file
        // Close the private ini file and free the used memory
        //------------------------------------------------------------------
        hini = PrfOpenProfile(hab, "Sample7.INI"); // Open private profile
        PrfWriteProfileData(hini, APPNAME, WINPOS, pBuffer, ulSize);
        PrfCloseProfile(hini);  // Close private profile
        DosFreeMem(pBuffer);
        }

The only line that may need a little more explanation is the call to PrfWriteProfileData with a NULL. If this is done, the info related to APPNAME and WINPOS (in this case) are deleted from the profile. This way we keep the OS2.INI clean.

Now we come to the second enhancement: using the FCF_SHELLPOSTION parameter to set the window size and position in case there isn't any information stored in the INI file. The solution for this also came from Mark.

In the example presented last month, I proposed building in a default window size and position to handle the case that there isn't any INI information present. The PM is capable of placing and sizing a window. Just use the FCF_SHELLPOSITION parameter in the creation of your main window. The advantage of this method is that the PM will size the window according to the screen resolution you're using, thus creating a better alternative. However, we dismissed this option last month to prevent 'flashing' at startup. The window would appear in the size and place the PM decides and then disappear and pop-up in the place and with the size that is saved in the OS2.INI file.

The solution, using the FCF_SHELLPOSITION flag, is very simple. Create a variable which will hold the FCF flags you like to use except the FCF_SHELLPOSITION flag. Check the OS2.INI for the existence of window information. If it isn't there, add the FCF_SHELLPOSITION flag. The simplest way to check if the information we want is available is to check the size of the info we're looking for. If it is zero, it definitely isn't there. The OS/2 Profile API has a function just for that: the PrfQueryProfileSize. This function will return the size in bytes of a specific key value. The function will also fail if the needed information isn't there at all. In both cases we can use FCF_SHELLPOSITION. This means the following will do the trick:

ULONG flFrameFlags = FCF_TITLEBAR   |FCF_SYSMENU |FCF_MENU    |
                     FCF_SIZEBORDER |FCF_MINMAX  |FCF_TASKLIST;
ULONG ulIniSize = 0;

if(!PrfQueryProfileSize(HINI_USERPROFILE, APPNAME, WINPOS, &ulIniSize) || !ulIniSize)
             flFrameFlags |= FCF_SHELLPOSITION;

This works nice, however, we also need to check if FCF_SHELLPOSITION is used when the WinRestoreWindowPos is issued. For this we can use the ulIniSize variable. If it is zero, the FCF_SHELLPOSITION flag is used. So the call to WinRestoreWindowPos should look like this:

if (ulIniSize)
        WinRestoreWindowPos(APPNAME, WINPOS, hwndFrame);

Promises, promises. Well, here's the last issue: what to do with minimized windows? It's very annoying if window information is saved when a window is minimized. This can happen when the user closes the window from the window list when it's minimized. The annoying thing about this is that the window won't be visible the next time the application is started. It can be made visible through the window-list, using 'cascade' or 'tile'. However, less experienced users will probably think the application is not working. Reason enough to avoid this.

How should we handle this? It's easy enough, we just have to check if the window is minimized on WM_SAVEAPPLICATION time. If so, we can do two things, restore the window position and save the window info, or simply don't save any info at all. I guess everyone can change the example so that no save takes place, so I'll handle the restore proc.

But first things first, how do we know a window is minimized? For that we have to go back to something that's been said in earlier articles: window-words. Window-words are space that is allocated for each window to hold information about that window. One of the items we can find there is the window's styles. These may be already known as the WS_* items you use when creating a window. Examples are WS_ANIMATE, WS_CLIPSIBLINGS and, indeed, WS_MINIMIZED and WS_MAXIMIZED. This info can be queried by using WinQueryWindowULong with QWL_STYLE as an index parameter. This will return the window-words that contain the style information. Comparing the return of this function with WS_MINIMIZED will give us the sought after 'if construct'.

if (WinQueryWindowULong(hwndFrame, QWL_STYLE) & WS_MINIMIZED)

One last thing is needed: restoring the window position. For this we can use another function that's already been looked at, the WinSetWindowPos API. By calling this one with only the SWP_RESTORE flag, the function simply resets the WS_MINIMIZED flag in the window-words. We could do this ourselves with some boolean magic, but why do something that can be done for us? Remember the first rule, Don't do what can be done for you!

This leaves us with:

if (WinQueryWindowULong(hwndFrame, QWL_STYLE) & WS_MINIMIZED)
                                WinSetWindowPos(hwndFrame, HWND_TOP, 0,0,0,0, SWP_RESTORE);

Well, that's it for this month. I took the liberty of taking some of the reactions and reworking them into this article. For an example of some of the above techniques, see the sample file (ZIP, 23k) this month. Thanks for all the responses. Next month we delve into something new; we will be looking at controls other than the framewindow.