.NET / Windows Forms: remember windows size and location
Asked Answered
M

11

54

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?

Methodology answered 9/12, 2009 at 12:44 Comment(3)
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. #106432Copestone
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.Slander
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 #106432 for more complex solutions.Vyborg
S
36

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.

Stoppage answered 9/12, 2009 at 12:55 Comment(6)
thanks, that is a good article. only thing left is to remember the windowstate (maximized, minimized, ...) how can i put that one to the settings?Methodology
yes thanks. i didnt realize that you can browse to all types of .NET in these settings at first.Methodology
thanks! one more thing: if the window is maximized, how can i remember which monitor it is maximized on in a multi-monitor setup?Methodology
Add the monitor id to the application settings as well.Stoppage
thanks, but i couldnt find a way to get the monitor id. i only found the Screen object, but it doesnt seem to have something unique associated with it.Methodology
@clamp, I expect if you set the window position and then set the window state to maximized, it will maximize to cover whichever monitor it is in. If you record the window position and window state at shut down, and then apply the window position before the window state at start up, then it should maximize back where it was.Epochmaking
L
83

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.

Libration answered 9/12, 2009 at 19:44 Comment(9)
For those of you that haven't used Properties.Settings before you'll need to go into your project properties and go to the settings tab. Create a default settings file and add a property called Location and one called Size.Tracheo
Your location storage code fails if the form is Minimized. You need to also check for FormWindowState.Minimized too. Is "Maximised" a typo for "Maximized"?Absolute
@Absolute - "Maximised" is the name of the property. I suppose it should be "Maximized" for consistency. Re checking for "Minimized" - it was something I didn't have in the app I took this from.Libration
@rob: Cheers for the reminder on settings. ALSO: I tried and failed to move this code into a static helper class. Has anyone had such luck?Savina
You need to set WindowState after setting Location. Otherwise, the window will always be maximized on the default monitor, since the maximized location is based on Location at the time you set WindowState. That means that when the user maximizes it on his secondary monitor, it'll restore on the primary monitor instead.Penult
what is RestoreBounds? Does not exist, can't find a reference eatherSil
@RageCompex - RestoreBounds is a property of the System.Windows.Forms.Form class, so your app needs to be derive from that in order to use it.Libration
IT WORKED! But with some extra work for adding ApplicationSettings -> PropertyBinding thing (I'm not into .NET etc and all got too complex). More info below. In 2017, using a Windows Application targeting .NET Framework 4.5.2 I had to follow the steps pointed in this blog post to create the needed properties xldennis.wordpress.com/2010/03/19/…Rutter
Good solution. If you saved WindowState as type System.Windows.Forms.FormWindowState in settings it would saving having the max and min flags. XAcreage
S
36

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.

Stoppage answered 9/12, 2009 at 12:55 Comment(6)
thanks, that is a good article. only thing left is to remember the windowstate (maximized, minimized, ...) how can i put that one to the settings?Methodology
yes thanks. i didnt realize that you can browse to all types of .NET in these settings at first.Methodology
thanks! one more thing: if the window is maximized, how can i remember which monitor it is maximized on in a multi-monitor setup?Methodology
Add the monitor id to the application settings as well.Stoppage
thanks, but i couldnt find a way to get the monitor id. i only found the Screen object, but it doesnt seem to have something unique associated with it.Methodology
@clamp, I expect if you set the window position and then set the window state to maximized, it will maximize to cover whichever monitor it is in. If you record the window position and window state at shut down, and then apply the window position before the window state at start up, then it should maximize back where it was.Epochmaking
V
8

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

Vyborg answered 14/9, 2015 at 9:8 Comment(1)
This one worked for me with the exception that if the window is maximized on the second screen it's restored maximized on the main screen. The desktop bounds should be restored before setting the state (if it intersects).Nichy
W
8

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();
    }
    
Wellgroomed answered 7/1, 2017 at 1:12 Comment(1)
BEWARE that this.Location must be put in Form1_Load() to be restored correctly. I made a mistake by putting it in Form1() below InitializeComponent() and the this.Location was always screwed.Sajovich
T
3

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).

Tremain answered 9/6, 2016 at 15:11 Comment(4)
Jot is amateur solution. Let your form to remember a position on a second monitor, disable the monitor and you won't see your form. In some cases you will never see it as Windows window positioning buttons may be ignored. Imagine happy customers and support teams now. I haven't researched how but it changes the layout of forms. WTF? Jot activities on the Location property will be ignored if StartPosition property isn't set to the Manual. And so on, and so on. But this is a nice try. Let it be.Roslynrosmarin
Hey. Multiple displays (the scenario with unplugging) is addressed in the github readme in the "real world form/window tracking" section as well as using callbacks for handling special cases like minimized windows. The above code is simplistic for illustration purposes. It's also a bit out of date. If you're up for submitting an issue (or a pull request) for any scenario in particular that'd be great.Tremain
I should have been consider it as a library, not as a framework. I'll read the readme. Many thanks.Roslynrosmarin
Yeah, it's ~500 lines of code without whitespace and comments, so it's fairly small. I'd definitely call it a library, even though it can integrate with ioc containers and work as infrastructure.Tremain
P
2

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...)

Pale answered 9/12, 2009 at 13:25 Comment(1)
Again, I'm sorry, I can't add a comment to the other thread... But Matt, by setting the location of the form, it will move it to the correct screen, and then you can set the WindowState to Maximized, etc, and it will be on the correct screen.Pale
P
2

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;
}
Pyroclastic answered 1/2, 2015 at 14:53 Comment(0)
S
1

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.

Slander answered 9/12, 2009 at 12:51 Comment(0)
D
1

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;
        }
    }
Divided answered 7/9, 2016 at 12:2 Comment(0)
T
1

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>
Totipalmate answered 7/9, 2018 at 10:7 Comment(0)
T
0

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.

Thorlay answered 31/8, 2019 at 2:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.