January 1998

Persistence pays off

by Mark G. Wiseman

In the December 1997 article "Starting Your Application Minimized," we showed you how to start your application with the main form minimized, maximized, or in its normal state. This month, we'll demonstrate how to make the state, position, and size of your application's main form persistent. To make the form persistent, we'll make a few modifications to the TMinMaxForm class we created last time, and we'll add a new class: WWindowPlacement. 

A helpful helper class

WWindowPlacement, shown in Listings A and B, is a simple wrapper class around the Windows API structure WINDOWPLACEMENT. WWindowPlacement has two methods, Get() and Set(). These methods are based on the Windows API functions GetWindowPlacement() and SetWindowPlacement(). WWindowPlacement also overloads the I/O stream insertion and extraction operators, << and >>. These operators are what make the class useful. Using <<, you can write a form's state, position, and size to a file or to memory (by using strstream). Then, you can use the >> operator to read this information back when you need it.

Listing A: WWindowPlacement header

#ifndef winplace_h
#define winplace_h

#include <vcl\vcl.h>
#pragma hdrstop

class WWindowPlacement : public WINDOWPLACEMENT
   {
   friend  ostream & operator <<
      (ostream & os, const
      WWindowPlacement & placement);
   friend  istream & operator >>
      (istream & is,
      WWindowPlacement & winplace);

   public:
      WWindowPlacement(TForm *form = 0);
      virtual ~WWindowPlacement() {}

      void Get(TForm *form);
      void Set(TForm *form);
   };
Listing B: WWindowPlacement source
#include <vcl\vcl.h>
#pragma hdrstop

#include "winplace.h"

WWindowPlacement::WWindowPlacement
   (TForm *form)
   {
   length = sizeof(WINDOWPLACEMENT);

   if (form == 0)
      {
      if (::GeWWindowPlacement
         (::GetDesktopWindow(), this)
         == FALSE)
         {
         ptMinPosition.x = ptMinPosition.y = -1;
         ptMaxPosition.x = ptMaxPosition.y = -1;
         }

      flags = 0;
      showCmd = SW_SHOWNORMAL;
      rcNormalPosition.left=rcNormalPosition.top = 0;
      rcNormalPosition.right = 639;
      rcNormalPosition.bottom = 479;
      }
   else
      Get(form);
   }

void WWindowPlacement::Get
   (TForm *form)
   {
   ::GeWWindowPlacement(form->Handle, this);

   // If no parent, then this window is on the 
   // desktop and when minimized we should let
   // Windows place the icon
   if (form->ParentWindow == 0)
      flags &= ~WPF_SETMINPOSITION;
   }

void WWindowPlacement::Set
   (TForm *form)
   {
   ::SeWWindowPlacement(form->Handle, this);
   }

ostream & operator <<(ostream &os,
   const WWindowPlacement &placement)
   {
   char sep = ',';

   os << '[' << placement.flags << sep
      << placement.showCmd << sep;
   os << placement.ptMinPosition.x << sep
      << placement.ptMinPosition.y << sep;
   os << placement.ptMaxPosition.x << sep
      << placement.ptMaxPosition.y << sep;
   os << placement.rcNormalPosition.left << sep 
      << placement.rcNormalPosition.top << sep;
   os << placement.rcNormalPosition.right << sep 
      << placement.rcNormalPosition.bottom << ']';

   return(os);
   }

istream & operator >>(istream &is,
   WWindowPlacement &placement)
   {
   char sep;

   is >> sep >> placement.flags >> sep
      >> placement.showCmd >> sep;
   is >> placement.ptMinPosition.x >> sep
      >> placement.ptMinPosition.y >> sep;
   is >> placement.ptMaxPosition.x >> sep
      >> placement.ptMaxPosition.y >> sep;
   is >> placement.rcNormalPosition.left >> sep 
      >> placement.rcNormalPosition.top >> sep;
   is >> placement.rcNormalPosition.right >> sep 
      >> placement.rcNormalPosition.bottom >> sep;

   return(is);
   }

Safe assumption

You may have noticed that when a WWindowPlacement object is constructed without a pointer to a form, the object is initialized to a location of (0, 0) and a size of 640 by 480. You can assume that the users of your application will use a screen resolution of at least 640 by 480--however, you can't safely assume they'll use a higher resolution. When a user first runs your application, the application won't have saved its position and size yet; by defaulting to 640 by 480, it should fit the user's screen.

The main form

To make use of the WWindowPlacement class, you need to make a few changes to the TMinMaxForm class we created last month. Listings C and D contain the modified TMinMaxForm; we've highlighted the changes in color.

Listing C: TMinMaxForm header

//------------------------------------
#ifndef Main2H
#define Main2H
//------------------------------------
#include <vcl\Classes.hpp>
#include <vcl\Controls.hpp>
#include <vcl\StdCtrls.hpp>
#include <vcl\Forms.hpp>
//------------------------------------
class TMinMaxForm : public TForm
   {
   __published:
      void __fastcall
         FormCreate(TObject *Sender);
      void __fastcall
         FormClose(TObject *Sender,
         TCloseAction &Action);

   private:
      bool restoreToMaximized;

   public:
      __fastcall
         TMinMaxForm(TComponent* Owner);
      void __fastcall
         OnRestore(TObject *Sender);
   };

//------------------------------------
extern TMinMaxForm *MinMaxForm;
//------------------------------------

#endif

Listing D: TMinMaxForm source
//------------------------------------
#include <vcl\vcl.h>
#pragma hdrstop

#include "Main2.h"
#include "winplace.h"
#include <fstream.h>
//------------------------------------
#pragma resource "*.dfm"

TMinMaxForm *MinMaxForm;
//------------------------------------
__fastcall
TMinMaxForm::TMinMaxForm(TComponent*
   Owner) : TForm(Owner)
   {
   restoreToMaximized = false;

   Application->OnRestore = OnRestore;
   }
//------------------------------------
void __fastcall
TMinMaxForm::FormCreate(TObject *Sender)
   {
   WWindowPlacement place;
   ifstream is("minmax.set");
   is >> place;

   int cmdShow = System::CmdShow;

   if (cmdShow == SW_SHOWDEFAULT || 
       cmdShow == SW_HIDE)
      {
      STARTUPINFO startupInfo;
      GetStartupInfo(&startupInfo);

      if ((startupInfo.dwFlags &
         STARTF_USESHOWWINDOW) &&
         startupInfo.wShowWindow != SW_NORMAL &&
         startupInfo.wShowWindow != SW_RESTORE)
         cmdShow = startupInfo.wShowWindow;
      else
         cmdShow = place.showCmd;
      }

   if (cmdShow == SW_MINIMIZE ||
      cmdShow == SW_SHOWMINIMIZED ||
      cmdShow == SW_SHOWMINNOACTIVE)
      {
      ::ShowWindow(Application->Handle, SW_HIDE);
      ::ShowWindow(Application->Handle, SW_MINIMIZE);
      Application->ShowMainForm = false;
      }
   else if (cmdShow == SW_MAXIMIZE ||
      cmdShow == SW_SHOWMAXIMIZED)
      WindowState = wsMaximized;

   if (cmdShow == SW_MINIMIZE ||
      cmdShow == SW_SHOWMINIMIZED ||
      cmdShow == SW_SHOWMINNOACTIVE)
      {
      ::ShowWindow(Application->Handle, SW_HIDE);
      ::ShowWindow(Application->Handle, SW_MINIMIZE);
      Application->ShowMainForm = false;
      if (place.showCmd == SW_MAXIMIZE)
         place.flags |= WPF_RESTORETOMAXIMIZED;
      cmdShow = SW_HIDE;
      }

   place.showCmd = cmdShow;

   place.Set(this);

   if (place.flags & WPF_RESTORETOMAXIMIZED)
         restoreToMaximized = true;
   }
//------------------------------------
void __fastcall
TMinMaxForm::OnRestore(TObject *Sender)
   {
   if (restoreToMaximized)
      {
      restoreToMaximized = false;
      WindowState = wsMaximized;
      }

   Visible = true;
   }
//------------------------------------
void __fastcall
TMinMaxForm::FormClose(
   TObject *Sender,
   TCloseAction &Action)
   {
   WWindowPlacement place(this);
   if (::IsIconic(Application->Handle))
      {
      if (::IsZoomed(Handle))
         place.flags |= WPF_RESTORETOMAXIMIZED;
      place.showCmd = SW_MINIMIZE;
      }

   ofstream os("minmax.set");
   if (os) os << place;
   }
//------------------------------------

You'll first change the FormCreate() function. The added code creates an instance of WWindowPlacement called place, then attempts to fill place with the state, position, and size data stored in a file named minmax.set.

If the user has specified a special startup state for the form, through a Windows shortcut or some other means, the code sets place.showCmd equal to that state. Otherwise, you use the state already stored in place.

Finish FormCreate() by checking to see whether place.flags has the WPF_RESTORETOMAXIMIZED flag set. If this flag is set, then set restoreToMaximized to true. You've declared restoreToMaximized as a bool in the private section of TMinMaxForm. You'll need restoreToMaximized in the TMinMaxForm::OnRestore() function.

If a form is maximized, then minimized, and then closed, WwindowPlacement will store this information. The form will appear minimized the next time the application runs. When the user restores the form, it should appear maximized. You accomplish this by checking restoreToMaximized in OnRestore(). If restoreToMaximized is true, you set the form's WindowState property to wsMaximized.

The new function FormClose() handles the OnClose event. In FormClose() you create an instance of WWindowPlacement that contains the data for the TMinMaxForm and saves it to your settings file.

You'll recall from last month's article that the main form is never really minimized. Rather, the VCL hides the main form and minimizes the application's hidden main window. Before writing place to your settings file, you need to check whether the application's main window, Application->Handle, is minimized. If it is, you set place.showCmd equal to SW_MINIMIZE. If the main window is minimized and your main form is maximized, you need to add the WPF_RESTORETOMAXIMIZED flag to place.flags.

Before closing

WWindowPlacement is a very useful and versatile class. In this article, we've chosen to write the state information to a file; however, you can easily modify the code to save this information in the Registry. You can also use WWindowPlacement, without any changes, to save the information for MDI child windows in your application. The underlying Windows API is smart enough to realize that the placement data is relative to a parent window instead of the desktop.

Does this register?

For more information about using the Registry to store application-specific information, see "Putting the Registry to Work" in the November 1997 issue of C++Builder Developer's Journal.