How to record window position in Windows Forms application settings

20,179

Solution 1

My other option is to write more custom code around the application settings and execute it on formLoad and formClosed. This doesn't use data binding.

Drawbacks:

  • More code to write.
  • Very fiddly. The order you set the properties on formLoad is confusing. For example, you have to make sure you've set the window size before you set the splitter distance.

Right now, this is my preferred solution, but it seems like too much work. To reduce the work, I created a WindowSettings class that serializes the window location, size, state, and any splitter positions to a single application setting. Then I can just create a setting of that type for each form in my application, save on close, and restore on load.

I posted the source code, including the WindowSettings class and some forms that use it. Instructions on adding it to a project are included in the WindowSettings.cs file. The trickiest part was figuring out how to add an application setting with a custom type. You choose Browse... from the type dropdown, and then manually enter the namespace and class name. Types from your project don't show up in the list.

Update: I added some static methods to simplify the boilerplate code that you add to each form. Once you've followed the instructions for adding the WindowSettings class to your project and creating an application setting, here's an example of the code that has to be added to each form whose position you want to record and restore.

    private void MyForm_FormClosing(object sender, FormClosingEventArgs e)
    {
        Settings.Default.CustomWindowSettings = WindowSettings.Record(
            Settings.Default.CustomWindowSettings,
            this, 
            splitContainer1);
    }

    private void MyForm_Load(object sender, EventArgs e)
    {
        WindowSettings.Restore(
            Settings.Default.CustomWindowSettings, 
            this, 
            splitContainer1);
    }

Solution 2

The sample below shows how I do it

  • SavePreferences is called when closing the form and saves the form's size, and a flag indicating if it's maximized (in this version I don't save if it's minimized - it will come back up restored or maximized next time).

  • LoadPreferences is called from OnLoad.

  • First save the design-time WindowState and set it to Normal. You can only successfully set the form size if it's WindowState is Normal.

  • Next restore the Size from your persisted settings.

  • Now make sure the form fits on your screen (call to FitToScreen). The screen resolution may have changed since you last ran the application.

  • Finally set the WindowState back to Maximized (if persisted as such), or to the design-time value saved earlier.

This could obviously be adapted to persist the start position and whether the form was minimized when closed - I didn't need to do that. Other settings for controls on your form such as splitter position and tab container are straightforward.

private void FitToScreen()
{
    if (this.Width > Screen.PrimaryScreen.WorkingArea.Width)
    {
        this.Width = Screen.PrimaryScreen.WorkingArea.Width;
    }
    if (this.Height > Screen.PrimaryScreen.WorkingArea.Height)
    {
        this.Height = Screen.PrimaryScreen.WorkingArea.Height;
    }
}   
private void LoadPreferences()
{
    // Called from Form.OnLoad

    // Remember the initial window state and set it to Normal before sizing the form
    FormWindowState initialWindowState = this.WindowState;
    this.WindowState = FormWindowState.Normal;
    this.Size = UserPreferencesManager.LoadSetting("_Size", this.Size);
    _currentFormSize = Size;
    // Fit to the current screen size in case the screen resolution
    // has changed since the size was last persisted.
    FitToScreen();
    bool isMaximized = UserPreferencesManager.LoadSetting("_Max", initialWindowState == FormWindowState.Maximized);
    WindowState = isMaximized ? FormWindowState.Maximized : FormWindowState.Normal;
}
private void SavePreferences()
{
    // Called from Form.OnClosed
    UserPreferencesManager.SaveSetting("_Size", _currentFormSize);
    UserPreferencesManager.SaveSetting("_Max", this.WindowState == FormWindowState.Maximized);
    ... save other settings
}

x

Solution 3

I make a Setting for each value I want to save, and use code like this:

private void MainForm_Load(object sender, EventArgs e) {
  RestoreState();
}

private void MainForm_FormClosing(object sender, FormClosingEventArgs e) {
  SaveState();
}

private void SaveState() {
  if (WindowState == FormWindowState.Normal) {
    Properties.Settings.Default.MainFormLocation = Location;
    Properties.Settings.Default.MainFormSize = Size;
  } else {
    Properties.Settings.Default.MainFormLocation = RestoreBounds.Location;
    Properties.Settings.Default.MainFormSize = RestoreBounds.Size;
  }
  Properties.Settings.Default.MainFormState = WindowState;
  Properties.Settings.Default.SplitterDistance = splitContainer1.SplitterDistance;
  Properties.Settings.Default.Save();
}

private void RestoreState() {
  if (Properties.Settings.Default.MainFormSize == new Size(0, 0)) {
    return; // state has never been saved
  }
  StartPosition = FormStartPosition.Manual;
  Location = Properties.Settings.Default.MainFormLocation;
  Size = Properties.Settings.Default.MainFormSize;
  // I don't like an app to be restored minimized, even if I closed it that way
  WindowState = Properties.Settings.Default.MainFormState == 
    FormWindowState.Minimized ? FormWindowState.Normal : Properties.Settings.Default.MainFormState;
  splitContainer1.SplitterDistance = Properties.Settings.Default.SplitterDistance;
}

Keep in mind that recompiling wipes the config file where the settings are stored, so test it without making code changes in between a save and a restore.

Solution 4

The simplest solution I've found is to use data binding with the application settings. I bind the location and clientSize properties on the window along with the splitterDistance on the splitter.

Drawbacks:

  • If you close the window while minimized, it opens hidden the next time. It's really hard to get the window back.
  • If you close the window while maximized, it opens filling the whole screen, but not maximized (minor issue).
  • Resizing the window using the top-right corner or the bottom-left corner is just ugly. I guess the two databound properties are fighting each other.

If you'd like to experiment with the strange behaviour, I posted a sample solution using this technique.

Solution 5

Based on the accepted answer by Don Kirkby and the WindowSettings class he wrote, you could derive a CustomForm from the standard one to reduce the amount of identical code written for each and every form, maybe like this:

using System;
using System.Configuration;
using System.Reflection;
using System.Windows.Forms;

namespace CustomForm
{
  public class MyCustomForm : Form
  {
    private ApplicationSettingsBase _appSettings = null;
    private string _settingName = "";

    public Form() : base() { }

    public Form(ApplicationSettingsBase settings, string settingName)
      : base()
    {
      _appSettings = settings;
      _settingName = settingName;

      this.Load += new EventHandler(Form_Load);
      this.FormClosing += new FormClosingEventHandler(Form_FormClosing);
    }

    private void Form_Load(object sender, EventArgs e)
    {
      if (_appSettings == null) return;

      PropertyInfo settingProperty = _appSettings.GetType().GetProperty(_settingName);
      if (settingProperty == null) return;

      WindowSettings previousSettings = settingProperty.GetValue(_appSettings, null) as WindowSettings;
      if (previousSettings == null) return;

      previousSettings.Restore(this);
    }

    private void Form_FormClosing(object sender, FormClosingEventArgs e)
    {
      if (_appSettings == null) return;

      PropertyInfo settingProperty = _appSettings.GetType().GetProperty(_settingName);
      if (settingProperty == null) return;

      WindowSettings previousSettings = settingProperty.GetValue(_appSettings, null) as WindowSettings;
      if (previousSettings == null)
        previousSettings = new WindowSettings();

      previousSettings.Record(this);

      settingProperty.SetValue(_appSettings, previousSettings, null);

      _appSettings.Save();
    }
  }
}

To use this, pass your application settings class and setting name in the constructor:

CustomForm.MyCustomForm f = new CustomForm.MyCustomForm(Properties.Settings.Default, "formSettings");

This uses Reflection to get/set the previous settings from/to the settings class. It may not be optimal to put the Save call into the Form_Closing routine, one could remove that and save the settings file whenever the main app exits.

To use it as a regular form, just use the parameterless constructor.

Share:
20,179
Don Kirkby
Author by

Don Kirkby

Python, Java, and C# developer working in AIDS research. Hobbies include designing board games and puzzles, as well as learning Chinese. If you just want to see the codez, check out GitHub. To contact me, use Twitter or e-mail [email protected] .

Updated on July 22, 2022

Comments

  • Don Kirkby
    Don Kirkby almost 2 years

    It seems like a standard requirement: next time the user launches the application, open the window in the same position and state as it was before. Here's my wish list:

    • Window position same as it was
      • Unless the screen has resized and the old position is now off screen.
    • Splitters should retain their position
    • Tab containers should retain their selection
    • Some dropdowns should retain their selection
    • Window state (maximize, minimize, normal) is the same as it was.
      • Maybe you should never start minimized, I haven't decided.

    I'll add my current solutions as an answer along with the limitations.