-1

I have been tasked to make the UI of a Winforms application written in VB to save the position of several SplitContainer splitters and the window size. I will show the code for the SplitContainers below. The code for the window is very similar but addressing different properties. Note that all of the SplitContainer values are being saved as Integer and assigned to the User scope.

The code is pretty straight forward. When the form loads I check My.Settings.SettingsLoaded which defaults to False. If it is False, I grab the current default position and write save them.

Private Sub InitSettings()
    If My.Settings.SettingsLoaded <> True Then
        UpdateWindowSettingsData()
        UpdateSplitContainerSettingsData()
        My.Settings.SettingsLoaded = True
        My.Settings.Save()
    End If
    isLoading = False
    ScaleWindow()
    ScaleUIElements()
End Sub

The second part of that runs every time the form loads and positions the elements in question

Private Sub ScaleUIElements()
    isLoading = True
    SuspendLayout()
    SplitContainer3.SplitterDistance = My.Settings.SplitContainer3
    SplitContainer8.SplitterDistance = My.Settings.SplitContainer8
    SplitContainer10.SplitterDistance = My.Settings.SplitContainer10
    SplitContainer20.SplitterDistance = My.Settings.SplitContainer20
    SplitContainer21.SplitterDistance = My.Settings.SplitContainer21
    ResumeLayout()
    isLoading = False
End Sub

Then, I have attached several handlers to catch the user manipulations

Private Sub SplitterMoved(ByVal sender As System.Object, ByVal e As System.Windows.Forms.SplitterEventArgs) Handles SplitContainer3.SplitterMoved, SplitContainer8.SplitterMoved, SplitContainer20.SplitterMoved, SplitContainer21.SplitterMoved, SplitContainer10.SplitterMoved
    If isLoading Then
        Return
    End If
    UpdateSplitContainerSettingsData()
End Sub

The updating of the data is also pretty straight forward

Private Sub UpdateSplitContainerSettingsData()
    My.Settings.SplitContainer3 = SplitContainer3.SplitterDistance
    My.Settings.SplitContainer8 = SplitContainer8.SplitterDistance
    My.Settings.SplitContainer10 = SplitContainer10.SplitterDistance
    My.Settings.SplitContainer20 = SplitContainer20.SplitterDistance
    My.Settings.SplitContainer21 = SplitContainer21.SplitterDistance
    My.Settings.Save()
End Sub

As I work on this I have been monitoring the user.config file using Tail.exe. This allows me to see the settings update as they are saved. I even went as far as to set it up so that a sound plays when the document updates.

What I am seeing is that as I move the splitters around in the SplitContainers, I can see the events fire as they should by setting breakpoints. However, I can also watch it hit the break point, update the settings, run past the save line, and absolutely not update the document. It works about %40 of the time and seems to be completely random. I have put almost a full day into trying to get this to work and am in the exact same place I was when I wrote the code initially. I can't find anything indicating anyone has ever seen this behavior before and I am confident that my code is working as it should, but for some reason the app is not able to carry out the write to the file.

I have a pretty strong indication this is true. When My.Settings.Save() is called and the values do not update in Tail.exe, the bottom left corner status text says 'Waiting for file...'. When it does work, the text says 'Last Updated: XX:XX:XX'(timestamp). Waiting for file will never go away if I just leave it.

enter image description here

So, I was wondering if anyone else has ever encountered similar behavior. If so, how can I cause the settings to finish writing? Would I be better off using a 3rd party solution? Of course, if you can spot anything I am doing wrong when saving these settings, please let me know.

Thanks for any help!

JRodd
  • 267
  • 4
  • 13
  • 1
    I would only save those settings on the FormClosing event. You wouldn't need that isLoading variable then. – LarsTech Apr 11 '19 at 20:13
  • I have looked into that also. In the Application.myapp file I have true. I have tried removing my save lines but the file never updates on close, at least in the automatic sense. I then put a line in the Form_Closing event to save the settings and I have never once had it update the file successfully. – JRodd Apr 11 '19 at 20:31
  • You can bind control properties to settings. In the Properties window, section *Data*, *ApplicationSettings*. But you will still have to save the settings in the form close event. See this [answer](https://stackoverflow.com/a/30047958/880990) on SO. – Olivier Jacot-Descombes Apr 11 '19 at 20:31
  • i always find mysettings weird...especially when you add columns...id recommend a separate config file...like something json – Ctznkane525 Apr 11 '19 at 20:41
  • @OlivierJacot-Descombes: I like your suggestion and so far it is working better than trying to manage this via code. However, I've now run into the problem discussed here: https://stackoverflow.com/questions/12634567/strange-split-container-behavior-when-properties-are-bound-to-settings – JRodd Apr 11 '19 at 21:47
  • This is not a discussion forum, so if you have another issue and the linked answer and other answers on SO do not answer it, you might have to post another question. – Olivier Jacot-Descombes Apr 12 '19 at 13:18
  • @OlivierJacot-Descombes: The issue remains that when I call My.Settings.Save from FormClosing or anywhere else it does not save the settings reliably. I have now proven this to be true in a C# Winforms app also. That is the original issue and if I can get help on that and it works then I will accept the answer. – JRodd Apr 12 '19 at 15:24

1 Answers1

0

I have moved over to using DataBindings to the User Settings as suggested by @OlivierJacot-Descombes. I now have only one save call in the Form_Closing function.

Come to find out what I was doing to track the changes was not adequate. Tail was not always updating to reflect the current data in the config file which was misleading. I have been using that for years to monitor log files and such and never saw it do that before. Also, I needed to make manual updates for the values I needed to be present when the save was done. This consisted of looping the SpitContainers and updating the user settings with their current SplitterDistance value in a ClientSize_Changed handler that is common to all of the SplitContainers. The ClientSize_Changed fires repeatedly as the form is loading, so I still had to track that with a loading bit so the SplitDistances were not updated until all of the measuring is done and I reload the settings.

It still does not always save the settings in debug mode. However, when I build in release and use the .exe to start the app (or run without debugging) it save much more reliably.

I also had to call My.Settings.Reload in the Form_Shown handler. This is because the form does a lot of measuring and walks over the values you've loaded. By reloading the values after the primary measuring is done I can see the saved values take effect.

Then to get rid of a ton of visible measuring I had to suspend the layout during form Resize by addressing Form_ResizeBegin and Form_ResizeEnd. This works to smooth out what the user sees while resizing, but the ugly remeasuring of the stack panel is still present.

From a working side project in C#:

public partial class Form1 : Form
{
    private bool loading = true;
    public Form1()
    {
        InitializeComponent();
    }

    private void Form1_FormClosing(object sender, FormClosingEventArgs e)
    {
        Properties.Settings.Default.Save();
    }

    private void SplitContainerClientSizeChanged(object sender, EventArgs e)
    {
        if (!loading)
        {
            UpdateAllSplitterDistance();
            Properties.Settings.Default.Save();
        }
    }

    private void UpdateAllSplitterDistance()
    {
        foreach (var i in this.Controls)
        {
            if (i is SplitContainer)
            {
                UpdateSplitterDistance(i as SplitContainer);
            }
        }
    }

    private void UpdateSplitterDistance(SplitContainer sc)
    {
        Properties.Settings.Default[string.Format("{0}_Dist", sc.Name)] = sc.SplitterDistance;
    }

    private void Form1_Shown(object sender, EventArgs e)
    {
        loading = false;
        scMain.Visible = false;
        scMain.SuspendLayout();
        Properties.Settings.Default.Reload();
        scMain.ResumeLayout();
        scMain.Visible = true;
    }

    private void Form1_ResizeBegin(object sender, EventArgs e)
    {
        scMain.Visible = false;
        scMain.SuspendLayout();
    }

    private void Form1_ResizeEnd(object sender, EventArgs e)
    {
        scMain.Visible = true;
        scMain.ResumeLayout();
    }
}

SplitContainer with a SplitContainer in both the right and left panels SplitContainer with a SplitContainer in both the right and left panels

User Settings User Settings

Thanks for the comments. Hopefully someone else can find this useful.

JRodd
  • 267
  • 4
  • 13