Skip over navigation

How to remember a window's size, state and position

Why do it?

It's often useful to remember the size and state of your program's windows between executions. This article discusses how.

How it's done

The method we're going to use is to save the position in the registry. I won't discuss details of how to access the registry from code in depth here. First of all, decide where in the registry you're going to keep the information. It's customary for applications to place information that varies between users in:

HKEY_CURRENT_USER\Software\MyCompany\MyProgram\X.X

(where X.X is the version number of the program). We'll use such a key in this article.

You can save the window's current position and size when the program is closing down – the OnDestroy form event handler is a good place for this. The program then restores it's position from the registry (if it's been written yet) when opening – we use the form's OnCreate handler for that code.

There are complications when saving and restoring the window state. When the window is minimised, Delphi doesn't minimise the form – it hides it and displays the Application window in the taskbar. The method I've used causes a previously minimised window to flash on-screen briefly. I'd welcome ideas on any alternative approaches.†

Another complication is that when a window is maximised Delphi updates the Width, Height, Left and Top properties of the form to the window's maximised size and position. This means that closing a maximised window stores the maximised size in the registry. When the program is run again it appears maximised, but when the user restores it they expect it to go to the previous normal size and position, but if we reloaded the Left, Top, Height and Width properties, the form won't shrink when restored. We get round this by using the Windows API to get the non-maximised size.

Here's the code – the comments should explain what's happening.

  1const
  2  CRegKey = 'Software\Demos\WdwStateDemo\1.0';
  3 
  4// Helper function to read registry values, and
  5// deal with cases where no values exist
  6function ReadIntFromReg(Reg: TRegistry;
  7  Name: string; Def: Integer): Integer;
  8  {Reads integer with given name from registry
  9  and returns it. If no such value exists, returns
 10  Def default value}
 11begin
 12 if Reg.ValueExists(Name) then
 13    Result := Reg.ReadInteger(Name)
 14  else
 15    Result := Def;
 16end;
 17 
 18procedure TForm1.FormDestroy(Sender: TObject);
 19var
 20  Reg: TRegistry;         // the registry
 21  State: Integer;         // state of window
 22  Pl : TWindowPlacement;  // used for API call
 23  R: TRect;               // used for window pos
 24begin
 25  {Calculate window's normal size and position
 26  using Windows API call - the form's Width, Height,
 27  Top and Left properties will give maximised window
 28  size if form is maximised, which is not what we
 29  want here}
 30  Pl.Length := SizeOf(TWindowPlacement);
 31  GetWindowPlacement(Self.Handle, @Pl);
 32  R := Pl.rcNormalPosition;
 33  Reg := TRegistry.Create;
 34  try
 35    // Open required key - create if doesn't exist
 36    Reg.RootKey := HKEY_CURRENT_USER;
 37    Reg.OpenKey(CRegKey, True);
 38    // Write window size and position
 39    Reg.WriteInteger('Width', R.Right-R.Left);
 40    Reg.WriteInteger('Height', R.Bottom-R.Top);
 41    Reg.WriteInteger('Left', R.Left);
 42    Reg.WriteInteger('Top', R.Top);
 43    // Write out state of window
 44    {Record window state (maximised, minimised or
 45    normal) - special case when minimised since form
 46    window is simply hidden when minimised, and
 47    application window is actually the one
 48    minimised - so we check to see if application
 49    window *is* minimised and act accordingly}
 50    if IsIconic(Application.Handle) then
 51      {minimised - write that state}
 52      State := Ord(wsMinimized)
 53    else
 54      {not minimised - we can rely on window state
 55      of form}
 56      State := Ord(Self.WindowState);
 57    Reg.WriteInteger('State', State);
 58  finally
 59    Reg.Free;
 60  end;
 61end;
 62 
 63procedure TForm1.FormCreate(Sender: TObject);
 64var
 65  Reg: TRegistry;   // the registry
 66  State: Integer;   // state of window
 67begin
 68  Reg := TRegistry.Create;
 69  try
 70    // Open required key - exit if it doesn't exist
 71    Reg.RootKey := HKEY_CURRENT_USER;
 72    if not Reg.OpenKey(CRegKey, False) then Exit;
 73    // Read the window size and position
 74    // - designed form sizes are defaults
 75    Self.Width := ReadIntFromReg(Reg, 'Width', Self.Width);
 76    Self.Height := ReadIntFromReg(Reg, 'Height', Self.Height);
 77    Self.Left := ReadIntFromReg(Reg, 'Left', Self.Left);
 78    Self.Top := ReadIntFromReg(Reg, 'Top', Self.Top);
 79    // Now get window state and restore
 80    State := ReadIntFromReg(Reg, 'State', Ord(wsNormal));
 81    {check if window was minimised - we have special
 82    processing for minimised state since Delphi
 83    doesn't minimise windows - it uses application
 84    window instead}
 85    if State = Ord(wsMinimized) then
 86    begin
 87      {we need to set visible true else form won't
 88      restore properly - but this causes a brief
 89      display of form.
 90      Any ideas on how to stop this?}
 91      Self.Visible := True;
 92      Application.Minimize;
 93    end
 94    else
 95      Self.WindowState := TWindowState(State);
 96  finally
 97    Reg.Free;
 98  end;
 99end;
Listing 1

Worked Example

A demo project that demonstrates what we've described in this article can be found on the delphidabbler/article-demos Git repository on GitHub.

You can view the code in the article-04 sub-directory. Alternatively download a zip file containing all the demos by going to the repository's landing page and clicking the Clone or download button and selecting Download ZIP.

This source code is merely a proof of concept and is intended only to illustrate this article. It is not designed for use in its current form in finished applications. The code is provided on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.

The demo is open source. See the demo's LICENSE.md file for licensing details.

Component

My TPJRegWdwState component encapsulates all this functionality on behalf of the form that owns it. A sister component, TPJWdwState, is included that works with ini files rather than the registry.

Feedback

I hope you found this article useful.

If you have any observations, comments, or have found any errors there are two places you can report them.

  1. For anything to do with the article content, but not the downloadable demo code, please use this website's Issues page on GitHub. Make sure you mention that the issue relates to "article #4".
  2. For bugs in the demo code see the article-demo project's README.md file for details of how to report them.