locked
Windows forms position persistence RRS feed

  • Question

  • Hi all,

    I am working with VS 2015, C# and Windows 7/10

    I have a simple Windows Forms application where I need its position to persist across sessions. The user can size and position the app and then when it is exited and launched again, the size and position of the last session persist. To do this I did some research and successfully implemented it. However, on rare occasion, the app loses the last position and instead positions the app off-screen to coordinates -32,768, -32,768.

    I note that this happens sporadically. I have not been able to determine a specific pattern or reason why this happens. The following is the code that I found online and implemented in my own application. I would appreciate it if you can look over it and tell me if there is something incorrect about it. I greatly appreciate your feedback. Thank you for your time. Saga

    In the form's closing event I save the form's size and position:

    private void frmMDIMain_FormClosing(object sender, FormClosingEventArgs e) { //The user is exiting the application. //Save the size and location of the window in case it changed. //Save new size and location to Settings //Save the form size only if the form is not minimized, otherwise, //set the size to the restored window size. if (this.WindowState == FormWindowState.Normal) { //Form not minimized, save its size. Settings.Default.WindowSize = this.Size; } else { //Form is minimized, save the restored size. //Note: The restored size is the size of the window if it was in its //normal window state. Settings.Default.WindowSize = this.RestoreBounds.Size; } // Could the following line be saving invalid position coordinates? PENDING

    Settings.Default.WindowLocation = this.Location; //And persist the settings. Settings.Default.Save(); }

    When the application is launched I have the following code in the form's load event. Aside from setting the size and position from the saved settings, I also check to see if the user is holding down the Shift key. If so, then I don't use the saved settings and instead use the default settings for size and position. This is my hack to "fix" the issue when the app is displayed off-screen.

    private void frmMDIMain_Load(object sender, EventArgs e)
    {

      //Create an object that holds the virgin size of this window.
      var oInitWinSize = new System.Drawing.Size(0, 0);

    //Set initial form size if it is the first run or user pressed Shift key when launching app. //In either case use default values by getting screen size and using that to mostly fill the //screen with the MDI window. If it isn't the first run then just use the saved settings. if (System.Drawing.Size.Equals(oInitWinSize, Settings.Default.WindowSize) || Control.ModifierKeys == Keys.Shift) { //First run, set window size and location. //Make MDI form large, but able to fit inside screen, depending on screen size. this.Location = Screen.PrimaryScreen.WorkingArea.Location; this.Size = Screen.PrimaryScreen.WorkingArea.Size; //Save new size and location to Settings Settings.Default.WindowSize = this.Size; Settings.Default.WindowLocation = this.Location; //And persist the settings. Settings.Default.Save(); } else { //A default size exists, so use that to set the window size and location. this.Location = Settings.Default.WindowLocation; this.Size = Settings.Default.WindowSize; }

    }



    You can't take the sky from me

    Wednesday, November 11, 2020 6:27 PM

Answers

  • Thank you.

    I have updated the project at https://github.com/AlbertoPoblacion/SalvarUbicacionFormulario

    Now it uses serialization to save the settings. This allow you to declare the settings entry as String, instead of using a class name. Hopefully, this should resolve all the difficulties for configuring the project.

    I have used XML serialization because it is backwards compatible with old Framework versions. If you are using a new version that supports JSON serialization (or you don't mind adding a third-party library), feel free to modify the two methods that Serialize and Deserialize the data. Using JSON will provide a more compact representation than XML. Or you could use Binary Runtime Serialization, since the classes are marked as [Serializable]. This will be even more compact than JSON, but it will cause trouble if you want to deserialize older data into a newer version. Anyway, the volume of data to serialize is quite small, so it shouldn't be too problematic to use the XML serialization, even if this is not the most efficient option.

    • Marked as answer by SagaV9 Wednesday, November 25, 2020 10:54 PM
    Friday, November 20, 2020 6:07 PM

All replies

  • I wrote a small project that does exactly what you want. You can find it in GitHub here:

    https://github.com/AlbertoPoblacion/SalvarUbicacionFormulario/tree/master/SalvarUbicacionFormulario

    The comments and documentation are written in Spanish, but I can translate it into English if there is sufficient interest. Anyway, the code should be understandable even without reading the documentation. The solution includes a sample form that demonstrates how to use it.

    Basically it does the same thing that you did: It uses the Closing event to save the coordinates of the form and the Load event to restore it. It is a bit more sophisticated than what you wrote because it also supports environments with several monitors and supports closing the application and then reopening it from another computer that has a different screen size or different numbers of monitors (e.g., if you use Remote Desktop). It will save the coordinates of the form for each different screen size.

    I have tested it extensively and it has never failed so far, so you may wish to copy some or all of the code and use it in your program.

    Wednesday, November 11, 2020 7:24 PM
  • Thanks Alberto, much appreciated! I'll have a look at the project that you shared. For me at least, no worries about the language. I am fluent in Spanish. Saga

    You can't take the sky from me

    Wednesday, November 11, 2020 7:46 PM
  • Hi Alberto,

    Again, thanks for providing the sample project. I spent today implementing your code into my project. I placed the class file directly into the main app source folder, not into a subfolder like you did in Herramientas.

    The problem I am having is that when I go ahead and create the setting UbicacionesFormularios I don't have the ColeccionUbicaciones item type in the list of types. I've kept your code intact, only changing the class name from EstadoFormulario to FormState and of course using the app's namespace instead of Herramientas.

    Do you know why the serialized class is not appearing in the settings type list? Thanks! Saga


    You can't take the sky from me

    Saturday, November 14, 2020 3:59 AM
  • Do you know why the serialized class is not appearing in the settings type list? Thanks!

    The class only appears in Settings after it has been compiled. But you can't compile it because you get errors in the parts of the code that presume that the Setting has already been added :-(

    There are two solutions: One is to momentarily comment-out the parts that use the Setting and therefore are throwing errors. This will let you compile the class and add it to the Settings. The other solution is to add the Setting with an ordinary type such as "string". Then, edit the source code for the settings, which is a plain XML file, and replace the "string" with the fully qualified name ("namespace.classname") of the class that you need to serialize into the settings.

    Saturday, November 14, 2020 7:40 AM
  • Again, thanks!

    As you mentioned, I commented out the lines referencing the setting and compiled. Worked fine, but when I went back to the Properties/Settings dialog, the serializable class type was still not there.

    I then attempted to manually edit the source files. I looked at the settings files and found two occurrences of the setting:

    App.Config

      <userSettings>
        <Ralphie.Properties.Settings>
          <setting name="WindowSize" serializeAs="String">
            <value>0, 0</value>
          </setting>
          <setting name="WindowLocation" serializeAs="String">
            <value>0, 0</value>
          </setting>
          <setting name="UserCat" serializeAs="String">
            <value>0</value>
          </setting>
          <setting name="PositionCollecton" serializeAs="Ralphie.clsFormState.ColeccionUbicaciones">
            <value />
          </setting>
        </Ralphie.Properties.Settings>
      </userSettings>
    

    PositionCollection is the setting of interest. "Ralphie" is the namespace. The ColeccionUbicaciones is in a class called clsFormState.

    Settings.Designer.cs

            [global::System.Configuration.UserScopedSettingAttribute()]
            [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
            [global::System.Configuration.DefaultSettingValueAttribute("")]
            //public string PositionCollecton {
            public Ralphie.clsFormState.ColeccionUbicaciones PositionCollecton
            {
                get {
                    //return ((string)(this["PositionCollecton"]));
                    return ((Ralphie.clsFormState.ColeccionUbicaciones)(this["PositionCollecton"]));
                }
                set {
                    this["PositionCollecton"] = value;
                }
            }
    

    In theory, these changes should do it, but when I compile I get the following exception:

    Unhandled exception message

    The first thing that jumped at me is why this exception in WindowSize? I've seen VS generate exceptions in a code where the real problem is somewhere else. I rolled back the code t remove the class type and restore the string type and the app ran fine. Change it back to what I present here and this exception shows up.

    I am missing something, I just can't put my finger on it. Thanks for looking and possibly suggesting a course of action. Saga


    You can't take the sky from me

    Tuesday, November 17, 2020 8:33 PM
  • In theory, it should work by just changing the type the way that you are doing it. There must be something else going on which is not obvious from your description.

    I am thinking that the whole thing is too much trouble. Maybe a better approach is to just remove the class from the settings. Instead, I would serialize it into a string and then save the string in the Settings. This should simplify quite a lot the process of adding this feature to a new project.

    Would you like to try making the change to use serialization and sending a Pull Request to GitHub? Or do you prefer if I do it myself and update the project?

    Wednesday, November 18, 2020 3:55 PM
  • Thanks again!

    Go ahead and modify/update the project. I am going to linger on this issue a longer to determine why I was not able to implement your code.

    I tested your project with VS2013 and 2015 and it works perfectly. As we both mentioned, I am surely missing something. I will post back here with any updates. Regards, Saga


    You can't take the sky from me

    Thursday, November 19, 2020 8:57 PM
  • Thank you.

    I have updated the project at https://github.com/AlbertoPoblacion/SalvarUbicacionFormulario

    Now it uses serialization to save the settings. This allow you to declare the settings entry as String, instead of using a class name. Hopefully, this should resolve all the difficulties for configuring the project.

    I have used XML serialization because it is backwards compatible with old Framework versions. If you are using a new version that supports JSON serialization (or you don't mind adding a third-party library), feel free to modify the two methods that Serialize and Deserialize the data. Using JSON will provide a more compact representation than XML. Or you could use Binary Runtime Serialization, since the classes are marked as [Serializable]. This will be even more compact than JSON, but it will cause trouble if you want to deserialize older data into a newer version. Anyway, the volume of data to serialize is quite small, so it shouldn't be too problematic to use the XML serialization, even if this is not the most efficient option.

    • Marked as answer by SagaV9 Wednesday, November 25, 2020 10:54 PM
    Friday, November 20, 2020 6:07 PM
  • Hello Alberto,

    I finally came around and finished implementing your modified code in my project. All worked fine, as expected.

    I then needed to translate the Spanish language code to English, which was successful, except for the UbicacionFormulario class. When I renamed it, the functionality stopped working. After some debugging I found that the XML string that is serialized and deserialized insists on keeping the UbicacionFormulario name.

    What do I need to do so that the XML string will reflect the new name for the class?

    Again, thank you for your assistance. Saga


    You can't take the sky from me

    Wednesday, November 25, 2020 7:17 PM
  • If you open the user.config file under the AppData\Local folder, you will find that the content is similar to the following:

    <?xml version="1.0" encoding="utf-8"?>
    <configuration>
        <userSettings>
            <SalvarUbicacionFormulario.Properties.Settings>
                <setting name="UbicacionesFormulariosSerializadas" serializeAs="String">
                    <value>&lt;?xml version="1.0" encoding="utf-16"?&gt;
    &lt;ArrayOfUbicacionFormulario xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"&gt;
      &lt;UbicacionFormulario&gt;
        &lt;Nombre&gt;Form1&lt;/Nombre&gt;
        &lt;EstaMaximizado&gt;false&lt;/EstaMaximizado&gt;
        &lt;Size&gt;
          &lt;Width&gt;512&lt;/Width&gt;
          &lt;Height&gt;258&lt;/Height&gt;
        &lt;/Size&gt;
        &lt;Location&gt;
          &lt;X&gt;13&lt;/X&gt;
          &lt;Y&gt;40&lt;/Y&gt;
        &lt;/Location&gt;
        &lt;CodigoTamVentana&gt;54003200&lt;/CodigoTamVentana&gt;
      &lt;/UbicacionFormulario&gt;
    &lt;/ArrayOfUbicacionFormulario&gt;</value>
                </setting>
            </SalvarUbicacionFormulario.Properties.Settings>
        </userSettings>
    </configuration>

    The whole class is there as xml under the xml (therefore all the weird escapes). As you can see, all the XML elements have the same names as the class and property names in the source code. You can force these elements to serialize themselves with different names by applying attributes in the source code.

    But it's probably not worth the effort. Just delete the config file and it will be created anew using the new names. Or ignore the error that you get when reading the settings and run the code until it saves the new settings. They will overwrite the existing ones.

    In a real application you would not have this problem as long as you changed the version number with each release, because the user.config file is saved under a different subfolder for each version. So you could change the class names in a new release as long as you changed the version number for the application.

    Wednesday, November 25, 2020 7:40 PM
  • Thanks again!

    Yup, deleting the user.config file worked. Best regards, Saga


    You can't take the sky from me

    Wednesday, November 25, 2020 10:58 PM