49
votes

I have a Windows Forms application with a normal window. Now when I close the application and restart it, I want that the main window appears at the same location on my screen with the same size of the moment when it was closed.

Is there an easy way in Windows Forms to remember the screen location and window size (and if possible the window state) or does everything have to be done by hand?

11
This question has been asked before and answered here: (It's a good answer. Be sure to give @Joe an upvote if you use it.) It's not an exact duplicate - that person didn't ask about window size, but you should be able to extrapolate from there. stackoverflow.com/questions/105932/…David
Deleted my comment along with your post... doh. I disagree as the solution uses a UserPreferencesManager which as far as I'm aware is a custom class being used. Believe the question is asking how you would implement such a UserPreferencesManager class.Ian
Please note that if user has variable screen size (e.g. he sometimes attaches monitor to nb or remote connects with different screen size), this attempt might result into frustrating state when your app appears out of screen boundary. Check stackoverflow.com/questions/105932/… for more complex solutions.jing

11 Answers

35
votes

You'll need to save the window location and size in your application settings. Here's a good C# article to show you how.

EDIT

You can save pretty much anything you want in the application settings. In the Type column of the settings grid you can browse to any .NET type. WindowState is in System.Windows.Forms and is listed as FormWindowState. There's also a property for FormStartPosition.

69
votes

If you add this code to your FormClosing event handler:

if (WindowState == FormWindowState.Maximized)
{
    Properties.Settings.Default.Location = RestoreBounds.Location;
    Properties.Settings.Default.Size = RestoreBounds.Size;
    Properties.Settings.Default.Maximised = true;
    Properties.Settings.Default.Minimised = false;
}
else if (WindowState == FormWindowState.Normal)
{
    Properties.Settings.Default.Location = Location;
    Properties.Settings.Default.Size = Size;
    Properties.Settings.Default.Maximised = false;
    Properties.Settings.Default.Minimised = false;
}
else
{
    Properties.Settings.Default.Location = RestoreBounds.Location;
    Properties.Settings.Default.Size = RestoreBounds.Size;
    Properties.Settings.Default.Maximised = false;
    Properties.Settings.Default.Minimised = true;
}
Properties.Settings.Default.Save();

It will save the current state.

Then add this code to your form's OnLoad handler:

if (Properties.Settings.Default.Maximised)
{
    Location = Properties.Settings.Default.Location;
    WindowState = FormWindowState.Maximized;
    Size = Properties.Settings.Default.Size;
}
else if (Properties.Settings.Default.Minimised)
{
    Location = Properties.Settings.Default.Location;
    WindowState = FormWindowState.Minimized;
    Size = Properties.Settings.Default.Size;
}
else
{
    Location = Properties.Settings.Default.Location;
    Size = Properties.Settings.Default.Size;
}

It will restore the last state.

It even remembers which monitor in a multi monitor set up the application was maximised to.

6
votes

I tried a few different methods; this is what ended up working for me. (In this case - on first launch - the defaults haven't been persisted yet, so the form will use the values set in the designer)

  1. Add the settings to the project (manually - don't rely on visual studio): Properties.Settings

  2. Add the following code to your form:

    private void Form1_Load(object sender, EventArgs e)
    {
        this.RestoreWindowPosition();
    }
    
    private void Form1_FormClosing(object sender, FormClosingEventArgs e)
    {
        this.SaveWindowPosition();
    }
    
    private void RestoreWindowPosition()
    {
        if (Settings.Default.HasSetDefaults)
        {
            this.WindowState = Settings.Default.WindowState;
            this.Location = Settings.Default.Location;
            this.Size = Settings.Default.Size;
        }
    }
    
    private void SaveWindowPosition()
    {
        Settings.Default.WindowState = this.WindowState;
    
        if (this.WindowState == FormWindowState.Normal)
        {
            Settings.Default.Location = this.Location;
            Settings.Default.Size = this.Size;
        }
        else
        {
            Settings.Default.Location = this.RestoreBounds.Location;
            Settings.Default.Size = this.RestoreBounds.Size;
        }
    
        Settings.Default.HasSetDefaults = true;
    
        Settings.Default.Save();
    }
    
6
votes

Previous solutions didn't work for me. After playing a while I ended up with following code which:

  • preserves maximised and normal state
  • replaces minimised state with default position
  • in case of screen size changes (detached monitor, remote connection,...) it will not get user into frustrating state with application open outside of screen.

    private void MyForm_Load(object sender, EventArgs e)
    {
        if (Properties.Settings.Default.IsMaximized)
            WindowState = FormWindowState.Maximized;
        else if (Screen.AllScreens.Any(screen => screen.WorkingArea.IntersectsWith(Properties.Settings.Default.WindowPosition)))
        {
            StartPosition = FormStartPosition.Manual;
            DesktopBounds = Properties.Settings.Default.WindowPosition;
            WindowState = FormWindowState.Normal;
        }
    }
    
    private void MyForm_FormClosing(object sender, FormClosingEventArgs e)
    {
        Properties.Settings.Default.IsMaximized = WindowState == FormWindowState.Maximized;
        Properties.Settings.Default.WindowPosition = DesktopBounds;
        Properties.Settings.Default.Save();
    }
    

user settings:

    <userSettings>
    <WindowsFormsApplication2.Properties.Settings>
        <setting name="WindowPosition" serializeAs="String">
            <value>0, 0, -1, -1</value>
        </setting>
        <setting name="IsMaximized" serializeAs="String">
            <value>False</value>
        </setting>
    </WindowsFormsApplication2.Properties.Settings>
</userSettings>

Note: the WindowsPosition is intentionally wrong, so during first launch application will use default location.

Note that IntersectsWith expects a Rectangle, not a Point. So unlike other answers, this answer is saving the DesktopBounds, not Location, into Properties.Settings.Default.WindowPosition

2
votes

Matt - to save the WindowState as a user setting, in the Settings Dialog, in the "Type" dropdown, scroll to the bottom and select "Browse".

In the "Select a Type" dialog, expand System.Windows.Forms and you can choose "FormWindowState" as the type.

(sorry, I don't see a button that allows me to comment on the comment...)

2
votes

If you have more than 1 form you can use something like this...

Add this part all form load void

var AbbA = Program.LoadFormLocationAndSize(this);
            this.Location = new Point(AbbA[0], AbbA[1]);
            this.Size = new Size(AbbA[2], AbbA[3]);
            this.FormClosing += new FormClosingEventHandler(Program.SaveFormLocationAndSize);

Save form location and size to app.config xml

public static void SaveFormLocationAndSize(object sender, FormClosingEventArgs e)
{
    Form xForm = sender as Form;
    Configuration config = ConfigurationManager.OpenExeConfiguration(Application.ExecutablePath);
    if (ConfigurationManager.AppSettings.AllKeys.Contains(xForm.Name))
        config.AppSettings.Settings[xForm.Name].Value = String.Format("{0};{1};{2};{3}", xForm.Location.X, xForm.Location.Y, xForm.Size.Width, xForm.Size.Height);
    else
        config.AppSettings.Settings.Add(xForm.Name, String.Format("{0};{1};{2};{3}", xForm.Location.X, xForm.Location.Y, xForm.Size.Width, xForm.Size.Height));
    config.Save(ConfigurationSaveMode.Full);
}

Load form location and size from app.config xml

public static int[] LoadFormLocationAndSize(Form xForm)
{
    int[] LocationAndSize = new int[] { xForm.Location.X, xForm.Location.Y, xForm.Size.Width, xForm.Size.Height };
    //---//
    try
    {
        Configuration config = ConfigurationManager.OpenExeConfiguration(Application.ExecutablePath);
        var AbbA = config.AppSettings.Settings[xForm.Name].Value.Split(';');
        //---//
        LocationAndSize[0] = Convert.ToInt32(AbbA.GetValue(0));
        LocationAndSize[1] = Convert.ToInt32(AbbA.GetValue(1));
        LocationAndSize[2] = Convert.ToInt32(AbbA.GetValue(2));
        LocationAndSize[3] = Convert.ToInt32(AbbA.GetValue(3));
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
    //---//
    return LocationAndSize;
}
2
votes

If you use the fabulous open source library - Jot, you can forget about the tedious .settings files and just do this:

public MainWindow()
{
    InitializeComponent();

    _stateTracker.Configure(this)
        .IdentifyAs("MyMainWindow")
        .AddProperties(nameof(Height), nameof(Width), nameof(Left), nameof(Top), nameof(WindowState))
        .RegisterPersistTrigger(nameof(Closed))
        .Apply();
}

There's a Nuget package as well, and you can configure pretty much everything about how/when/where data is stored.

Disclaimer: I'm the author, but the library is completely open source (under MIT license).

1
votes

You'll have to manually save the information somewhere. I'd suggest doing so as application settings, storing them in user specific isolated storage.

Once you load up, read the settings then resize/move your form.

1
votes

My answer is adapted from ChrisF♦'s answer, but I've fixed one thing I didn't like - if the window is minimized at the time of closing, it would appear minimized on next start.

My code handles that case correctly by remembering whether the window was maximized or normal at the time of its minimization, and setting the persistent state accordingly.

Unfortunately, Winforms doesn't expose that information directly, so I needed to override WndProc and store it myself. See Check if currently minimized window was in maximized or normal state at the time of minimization

partial class Form1 : Form
{
    protected override void WndProc(ref Message m)
    {
        if (m.Msg == WM_SYSCOMMAND)
        {
            int wparam = m.WParam.ToInt32() & 0xfff0;

            if (wparam == SC_MAXIMIZE)
                LastWindowState = FormWindowState.Maximized;
            else if (wparam == SC_RESTORE)
                LastWindowState = FormWindowState.Normal;
        }

        base.WndProc(ref m);
    }

    private const int WM_SYSCOMMAND = 0x0112;
    private const int SC_MAXIMIZE = 0xf030;
    private const int SC_RESTORE = 0xf120;
    private FormWindowState LastWindowState;

    private void Form1_FormClosing(object sender, FormClosingEventArgs e)
    {
        if (WindowState == FormWindowState.Normal)
        {
            Properties.Settings.Default.WindowLocation = Location;
            Properties.Settings.Default.WindowSize = Size;
        }
        else
        {
            Properties.Settings.Default.WindowLocation = RestoreBounds.Location;
            Properties.Settings.Default.WindowSize = RestoreBounds.Size;
        }

        if (WindowState == FormWindowState.Minimized)
        {
            Properties.Settings.Default.WindowState = LastWindowState;
        }
        else
        {
            Properties.Settings.Default.WindowState = WindowState;
        }

        Properties.Settings.Default.Save();
    }

    private void Form1_Load(object sender, EventArgs e)
    {
        if (Properties.Settings.Default.WindowSize != new Size(0, 0))
        {
            Location = Properties.Settings.Default.WindowLocation;
            Size = Properties.Settings.Default.WindowSize;
            WindowState = Properties.Settings.Default.WindowState;
        }
    }
1
votes

You could also save it in your (let's say) config.xml when you close the form:

private void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
    XmlDocument docConfigPath = new XmlDocument();
    docConfigPath.Load(XML_Config_Path);

    WriteNode(new string[] { "config", "Size", "Top", Top.ToString() }, docConfigPath);
    WriteNode(new string[] { "config", "Size", "Left", Left.ToString() }, docConfigPath);
    WriteNode(new string[] { "config", "Size", "Height", Height.ToString() }, docConfigPath);
    WriteNode(new string[] { "config", "Size", "Width", Width.ToString() }, docConfigPath);

    docConfigPath.Save(XML_Config_Path);
}

public static XmlNode WriteNode(string[] sNode, XmlDocument docConfigPath)
{
    int cnt = sNode.Length;
    int iNode = 0;
    string sNodeNameLast = "/" + sNode[0];
    string sNodeName = "";
    XmlNode[] xN = new XmlNode[cnt];

    for (iNode = 1; iNode < cnt - 1; iNode++)
    {
        sNodeName = "/" + sNode[iNode];
        xN[iNode] = docConfigPath.SelectSingleNode(sNodeNameLast + sNodeName);
        if (xN[iNode] == null)
        {
            xN[iNode] = docConfigPath.CreateNode("element", sNode[iNode], "");
            xN[iNode].InnerText = "";
            docConfigPath.SelectSingleNode(sNodeNameLast).AppendChild(xN[iNode]);
        }
        sNodeNameLast += sNodeName;
    }

    if (sNode[cnt - 1] != "")
        xN[iNode - 1].InnerText = sNode[cnt - 1];

    return xN[cnt - 2];
}

And the loading is on your:

private void Form1_Load(object sender, EventArgs e)
{
    XmlDocument docConfigPath = new XmlDocument();
    docConfigPath.Load(XML_Config_Path);
    XmlNodeList nodeList = docConfigPath.SelectNodes("config/Size");

    Height = ReadNodeInnerTextAsNumber("config/Size/Height", docConfigPath);
    Width = ReadNodeInnerTextAsNumber("config/Size/Width", docConfigPath);
    Top = ReadNodeInnerTextAsNumber("config/Size/Top", docConfigPath);
    Left = ReadNodeInnerTextAsNumber("config/Size/Left", docConfigPath);
}

The config.xml should contain the following:

<?xml version="1.0" encoding="utf-8"?>
<config>
  <Size>
    <Height>800</Height>
    <Width>1400</Width>
    <Top>100</Top>
    <Left>280</Left>
  </Size>
</config>
0
votes

I've been using this method so far and it's been working great. You don't have to fiddle around with application settings. Instead, it uses serialization to write a settings file to your working directory. I use JSON, but you can use .NET's native XML serialization or any serialization for that matter.

Put these static methods in a common extensions class. Bonus points if you have a common extensions project that you reference by multiple projects:

const string WINDOW_STATE_FILE = "windowstate.json";

public static void SaveWindowState(Form form)
{
    var state = new WindowStateInfo
    {
        WindowLocation = form.Location,
        WindowState = form.WindowState
    };

    File.WriteAllText(WINDOW_STATE_FILE, JsonConvert.SerializeObject(state));
}

public static void LoadWindowState(Form form)
{
    if (!File.Exists(WINDOW_STATE_FILE)) return;

    var state = JsonConvert.DeserializeObject<WindowStateInfo>(File.ReadAllText(WINDOW_STATE_FILE));

    if (state.WindowState.HasValue) form.WindowState = state.WindowState.Value;
    if (state.WindowLocation.HasValue) form.Location = state.WindowLocation.Value;
}

public class WindowStateInfo
{
    public FormWindowState? WindowState { get; set; }

    public Point? WindowLocation { get; set; }
}

You only need to write that code once and never mess with again. Now for the fun part: Put the below code in your form's Load and FormClosing events like so:

private void Form1_Load(object sender, EventArgs e)
{
    WinFormsGeneralExtensions.LoadWindowState(this);
}

private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
    WinFormsGeneralExtensions.SaveWindowState(this);
}

That is all you need to do. The only setup is getting those extensions into a common class. After that, just add two lines of code to your form's code and you're done.

This code will only really work if your WinForm's app has a single form. If it has multiple forms that you want to remember the positions of, you'll need to get creative and do something like this:

public static void SaveWindowState(Form form)
{
    var state = new WindowStateInfo
    {
        WindowLocation = form.Location,
        WindowState = form.WindowState
    };

    File.WriteAllText($"{form.Name} {WINDOW_STATE_FILE}", JsonConvert.SerializeObject(state));
}

I only save location and state, but you can modify this to remember form height and width or anything else. Just make the change once and it will work for any application that calls it.