devxlogo

Exploring Secrets of Persistent Application Settings

Exploring Secrets of Persistent Application Settings

he MSDN documentation includes a lot of material on persisting application settings in a Windows application (see Application Settings for Windows Forms ). But like much documentation, it’s primarily a reference rather than a practical implementation guide, giving the complex and intricate usage details of settings without giving a clear and concise discussion of typical simple uses?exactly what a large number of users will be interested in. This article attempts to provide some tips on implementation.

You generally implement application settings because you want your program to remember changes users made, such as setting toolbar visibility, tracking menu checkboxes, maintaining split-window positions, or storing recent selections from an options dialog. Ideally, you’d want to store such settings simply and easily while writing minimal custom code. It’s best to start with the basics. Here’s the scoop in a nutshell.

Solution: Simple Uses of Settings
You create settings from the Settings tab of your project properties. As shown in the illustration, open the project Properties from the Solutions Explorer (or from the Project menu), then open the Settings pane, resulting in a table of settings in the middle pane. The table defaults to a single entry named “Setting” of type string with user scope, as shown in Figure 1.

?
Figure 1: Application Settings: You can enter or alter application settings from the Settings tab of your project Properties dialog.

After creating a setting, you can bind it to a Form control property using the “ApplicationSettings” section in the Form’s properties pane. Alternatively, you can create and bind it in one step using the “ApplicationSettings” section in the project properties pane. You’ll see this technique later when I discuss binding.

The property binding provides two-way automatic updates; that is, when the settings are loaded during startup the framework updates the control properties bound to them. Similarly, when the control properties change, the settings are updated, making the binding two-way. Before discussing binding though, consider the basics of settings first.

By walking through the process of adding a few settings you’ll see how easy it is to connect settings to a Windows Form program.

?
Figure 2: Sample Settings: The figure shows the AppSettings editor with four settings of varying types defined.

Figure 2 shows four settings?two strings, one simple object (DateTime), and one Collection object (ArrayList). The Name value may of course be any descriptive term you care to use; that is how you will reference it in your program. The Type value may be any type of object that can be serialized as text, because the settings are stored in an XML format. The Scope may be either User or Application; Figure 2 shows string settings with each scope. You should think of Application-scoped settings as permanent, read-only, and global to all users. In contrast, User-scoped settings are modifiable values local to each user. The value field then, is the one-and-only value that an application-scoped setting will ever have. For a user-scoped setting, however, it is merely an initial or default value that may or may not change during a given invocation of the program. Once changed, a user-scoped setting may or may not retain that change for subsequent invocations, depending upon how you decide to treat it in your code.

So in Figure 2, the sample application has initial values for the strings but the more complicated objects are left as empty or null values. The application will initialize those in program code, although you could initialize them here. This is in fact a whiz-bang feature of Visual Studio. Clicking in the value field brings up an appropriate designer wizard for the specific data type. If you click on the LastRunTime value field, for example, you’ll see the designer wizard shown in Figure 3.

?
Figure 3. DateTime Designer Editor: Visual Studio uses a Calendar editor to edit System.DateTime settings values.
?
Figure 4. ArrayList Designer Editor: Visual Studio uses the Object Collection Editor for ArrayList settings.

And clicking on the value field for ItemList yields the wizard in Figure 4.

For now, just leave both the LastRunTime and ItemList values blank; the application will populate them during execution.

Exploring the Sample GUI Application
Figure 5 shows the simple GUI for the sample application described in this article to illustrate using various settings.

?
Figure 5. Sample GUI: Here’s a design-time view of the sample application.

The application will fill in the “TBD” text shown in labels and text box fields at runtime. The DataGridView, shown with a single column in Figure 5, provides a visual outlet for the ItemList ArrayList created earlier. The Save button is there as a convenience; you could use it to update persistent settings without quitting the program (it will call exactly the same method that is called when actually quitting).

Typically, you want to access settings from Windows Forms during specific events, such as startup, shutdown, or to respond to specific user events. The code below shows all three types:

   public partial class Form1 : Form   {      public Form1()      {         InitializeComponent();      }         private void Form1_FormClosing(object sender, FormClosingEventArgs e)      {         SaveToFile();      }         private void saveButton_Click(object sender, EventArgs e)      {         SaveToFile();      }         private void Form1_Load(object sender, EventArgs e)      {         LoadDataGridView(myDataGridView);         preferredPathTextBox.Text = Properties.Settings.Default.PreferredPath;         lastRunTimeLabel.Text =             Properties.Settings.Default.LastRunTime.ToString();         domainLabel.Text = Properties.Settings.Default.PlanetarySystem;      }         private void SaveToFile()      {         Properties.Settings.Default.ItemList = DGVtoList();         Properties.Settings.Default.PreferredPath = preferredPathTextBox.Text;         Properties.Settings.Default.LastRunTime = DateTime.Now;         Properties.Settings.Default.Save();      }      }
Author’s Note: I have omitted the DGVtoList and LoadDataGridView methods referenced in the preceding code, as they are not germane to the main focus, but they are available in the sample project code.

As noted earlier, terminating the program (form closing event) and clicking the Save button (button click event) both call the SaveToFile method.

When the form loads, it populates the TextBox, the two Labels, and the DataGridView from the values available in the application settings. These are automatically loaded by the .NET framework from the application configuration file. You access them via the Properties.Settings.Default. notation that you see in the code. Note that the settings are strongly typed. Thus, to set the Text property of the Label to the DateTime object stored in the LastRunTime setting, you must invoke its ToString method.

If you compile your project as an executable (.exe), its configuration file?stored in the same directory?will be called app.exe.config, where “app” is the name of your executable file. Recall that settings may be application-scoped or user-scoped. All application-scoped settings reside in the app.exe.config file. User-scoped settings are also in the app.exe.config file initially; the .NET framework loads them from app.exe.config the very first time you execute the program?and continues to load them from that file as long as you do not save any changed user-scoped settings.

Looking now at the SaveToFile method, note that it simply makes assignments into Properties.Settings.Default.. However, it stores only the user-scoped ItemList, PreferredPath, and LastRunTime settings, not the PlanetarySystem setting, because that’s application-scoped and immutable. The last line of the method invokes the Settings.Save method, causing the framework to:

  • Determine if any settings have indeed been changed.
  • Check if the user-scoped settings file exists and if not create it.
  • Write the current values of the user-scoped settings to the user’s settings file (user.config).

Executing the Sample Application
With the settings code in place, Figure 6 shows the application when it first launches.

?
Figure 6. First Launch: When you launch the application for the first time, you can see the default values appear in the Domain and Preferred Path fields, but the Last Run Time value is obviously incorrect, and the DataGridView is empty.
?
Figure 7. Second Launch: After closing the application, the Last Run Time value was saved and now displays properly upon a subsequent launch.

Remember that the application settings contain initial values for the PlanetarySystem and PreferredPath strings but not for the DateTime or the ArrayList settings. So, at first launch, the Last Run Time then shows the textual representation of no value and, as the ArrayList is empty, so is the DataGridView. Now terminate the program and then restart it, and it will look like Figure 7.

Closing the form runs the SaveToFile() method, which records the date and time when you terminated the program, creating a user.config file containing the new value for the user-scoped setting. Restarting the program recalls that persistent value and will never again show the default (null) unless and until you delete or rename the user-scoped settings file. This file, by the way, is always called just user.config. Unlike the application-scoped setting file app.exe.config, which is stored in the same folder as the executable, the location of user.config is much more convoluted. A typical path will be something like:

   Documents and Settingsyour-user-nameLocal Settings      Application Datayour-company-nameapp_xxx_yyy1.0.0.0user.config

More generally the path format is:

   __user.config

See the description of the components of this path at the Client Settings FAQ.

Here’s the user.config file created when the program terminated for the first time:

                                      C:                             02/27/2007 15:32:02                                                                             
?
Figure 8: Modifying Values: The Preferred Path value has been modified, and several items were added to the DataGridView.

Note that the framework writes all the settings even though only one actually changed. The PreferredPath in this file still has the same value as the PreferredPath setting in the app.exe.config file, and the ItemList is still empty.

So, with the application still running, go ahead and enter a few values in the ItemList and change the path, as shown in Figure 8.

Press Save and then look at user.config again, which now looks something like this:

                                      C:etcdata                             02/27/2007 15:45:56                                                       line one              123456              four score and 7yrs                                               

That concludes the discussion of the basics of persistent settings. But it’s possible to reduce the amount of code you have to write even further by binding application settings to form controls, giving you the automatic, two-way updates described at the start of this article.

Binding Properties to Settings
Binding properties to settings is a simple, two-step process from the current code.

Step One: Create the Bindings
Returning to the visual designer, select the TextBox next to the Preferred Path label, then select Data >> PropertyBinding in the Properties pane, and finally click on the ellipsis. Figure 9 shows the three-step sequence.

?
Figure 9. Binding Controls to Settings: To bind a control to a setting, select the control, and then click the ellipsis next to the (PropertyBinding) item under the ApplicationSettings item in the Properties pane.
?
Figure 10: Binding Application Settings: This dialog lets you bind form controls to application settings.

Clicking the ellipsis opens a dialog for binding properties to application settings as shown in Figure 10.

?
Figure 11: Design-time Binding Results: After binding the Text property to the PreferredPath setting, the correct value appears in the Preferred Path TextBox?even at design-time.

Open the drop-down arrow next to the Text property and select the PreferredPath setting as shown in Figure 10, and then close the dialog. You should see two changes, as shown in Figure 11.

  1. The text in the TextBox will change from “TBD” (which was entered manually when creating the form) to the PreferredPath application setting value.
  2. The Text property binding appears in the Properties pane.

Do the same thing for the Domain field, binding it to the PlanetarySystem setting. But unfortunately, that’s as much as you can accomplish in the visual designer. As Figure 12 shows, only those two settings are available in the binding drop-down.

What happened to the other two settings? The answer is that they’re not available because they’re not simple strings?and a form can display only strings when it comes right down to it. That is why, if you look back at the code, that we had to convert the setting to a string:

   lastRunTimeLabel.Text =       Properties.Settings.Default.LastRunTime.ToString();
?
Figure 12. Available Settings: Only two of the four possible settings are available for binding at design-time.

Automatic design-time binding, then, is not available for all settings, but because so many properties are just strings, design-time binding works fine most of the time.

Step Two: Remove Unneeded Code
Because two of the settings are now automatically bound, you can remove the code that manually bound those settings before. The following code has commented-out references to the PreferredPath and PlanetarySystem (two lines in the Form1_Load method and one line in SaveToFile):

   private void Form1_Load(object sender, EventArgs e)   {      LoadDataGridView(myDataGridView);      // preferredPathTextBox.Text =            Properties.Settings.Default.PreferredPath;      lastRunTimeLabel.Text =          Properties.Settings.Default.LastRunTime.ToString();      // domainLabel.Text =          Properties.Settings.Default.PlanetarySystem;   }      private void SaveToFile()   {      Properties.Settings.Default.ItemList = DGVtoList();      // Properties.Settings.Default.PreferredPath = preferredPathTextBox.Text;      Properties.Settings.Default.LastRunTime = DateTime.Now;      Properties.Settings.Default.Save();   }

As you can see, if this application had only string settings, you could eliminate all settings-related code in the Form1_Load method and you would need only one line of code in Form1_Closing (or in this case the SaveToFile method):

   Properties.Settings.Default.Save();
Author’s Note: If one line of code is still too much, then I’m afraid you will have to switch to Visual Basic for the final leap to zero lines of code, as C# doesn’t support this. Yes, with Visual Basic, you do not even need the Save method call (see point #7 in How to: Create Application Settings Using the Designer). But in C#, the typical course of action is to use the Designer to create and bind String settings, and add just the one line of code in your form’s FormClosing event handler.

Creating and Binding a Setting in One Step
The theme for this article is reducing the effort needed to create persistent settings. And you can reduce the effort even further by both creating and binding a setting in a single step. So this time, select the TextBox from the visual designer, and then select the drop-down of the Text property under the Application Settings item in the Properties pane (see Figure 13). Ignore the fact that we have already set the Text property to PreferredPath.

?
Figure 13. One-step Setting Creation and Binding: Open the dropdown on the Text property of Application Settings.
?
Figure 14. New Application Setting Dialog: This dialog appears when you click the “New…” link from the ApplicationSettings dropdown list.

This time, notice the “New?” link near the bottom of the dropdown; when you click that you’ll see a New Application Setting dialog (see Figure 14).

The fields should be familiar?name, scope, and value?but here the type field is absent because the value must be a string. If you did not already have the PreferredPath setting defined, you could both create it here and bind it here?all in one dialog. See How to: Add or Remove Application Settings for a nutshell summary on the steps discussed so far.

Additional Binding to Consider
Unfortunately, that easy settings procedure doesn’t include everything you might want. For example, one notable component that (oddly) cannot handle automatic settings is a ToolStrip. But fear not! Quoting from Microsoft’s Application Settings Overview documentation:

“Custom controls can also save their own settings by … the SaveSettings method. The Windows Forms ToolStrip control implements this interface to save the position of toolbars and toolbar items between application sessions.”

Therefore, if you want to persist toolbar settings for your application, you only need add these bits of code to the FormClosing and Load event handlers:

   private void MainForm_FormClosing(object sender, FormClosingEventArgs e)      ToolStripManager.SaveSettings(this);      Properties.Settings.Default.Save();      . . .   }      private void MainForm_Load(object sender, EventArgs e)   {      ToolStripManager.LoadSettings(this);      ...   }

Note that waiting until your program terminates introduces a slight chance that the tool strip settings might not be saved if your program crashes, for example, but the alternative is putting in separate hooks for each event that is fired from a tool strip change. Too much work and too messy, in my view.

This discussion thread is another useful source of information on form size and location saving, as is this article: Saving out a Form’s Size and Location using the Application Settings feature

Caveats on Bindings and Settings
The functionality and implementation discussed above fit the ubiquitous 80/20 rule; that is, these techniques will be useful?indeed, valuable?80 percent of the time. To be complete, though, it is important to be aware of limitations of bindings and limitations of settings. Since this falls in the 20 percent bucket, I will just state the issues to be aware of, as reported in MSDN; exploring the depths of these issues is beyond the scope of this article.

First, for bindings, components that you are binding to must have the “right stuff”, otherwise they will not quite do what is expected. The MSDN article Application Settings Architecture in the section called “Settings Bindings” describes it this way:

“You can only bind an application setting to a component that supports the IBindableComponent interface. Also, the component must implement a change event for a specific bound property, or notify application settings that the property has changed through the INotifyPropertyChanged interface. If the component does not implement IBindableComponent and you are binding through Visual Studio, the bound properties will be set the first time, but will not update. If the component implements IBindableComponent but does not support property change notifications, the binding will not update in the settings file when the property is changed.”

Settings also have restrictions as to what types of objects may be stored. The MSDN article Application Settings describes it thusly:

“Application settings can be stored as any data type that is XML serializable or has a TypeConverter that implements ToString/FromString. The most common types are String, Integer, and Boolean, but you can also store values [such] as Color, Object, or as a connection string.”

This is not a major limitation, though. In the sample code above, you saw objects of type string, DateTime, and ArrayList all worked just fine. I picked the DateTime and ArrayList quite at random, not from any careful perusal of what types might or might not work.

Fecund Configuration Files
You potentially have multiple sets of configuration files to be aware of. If you run with debugging (F5) Visual Studio creates your configuration file in one location, but if you run without debugging (Ctrl-F5) it creates it in a different location! If you are not aware of this, you might think your program is behaving quite strangely if you innocuously switch from F5 to Ctrl-F5. Say, for example, you invoke the sample application above with F5. You make a change to a setting, exit, restart with F5 again, and you will find your change has persisted across invocations. But if you exit, then restart with Ctrl-F5 your change is apparently gone. You may see the program defaults again if this is the first time you executed with Ctrl-F5, or a different change if you had previously run with Ctrl-F5.

Earlier, I indicated that the user-scoped configuration file is stored in a path following this format:

   __   user.config

For the sample application running without debugging my actual file path is:

   C:Documents and SettingsmsorensLocal SettingsApplication Data   CleanCodeAppSettings.exe_Url_yjlhbxvvtjary1qdlqisscwfyewyrs211.0.0.0

It is important to understand the derivation of the file naming components. Open the Properties window, then the Application pane, then select “Assembly Information” to get to the assembly information dialog. The and the come from the fields indicated in Figure 15.

?
Figure 15. Filename Component Derivation: The user.config path consists of several components that depend on the values in this dialog, your user profile?and the method you use to deploy your application.

The refers to your user profile, which in my case is C:Documents and SettingsmsorensLocal SettingsApplication Data.

The advertised format is not quite complete though. Running with debugging and storing settings creates a user.config for me in this path:

   C:Documents and SettingsmsorensLocal SettingsApplication Data   CleanCodeAppSettings.vshost.exe_Url_yjlhbxvvtjary1qdlqisscwfyewyrs211.0.0.0

The only difference in the path name compared to the previous path is the added vshost portion (highlighted above). That is why there are at least two different user.config files. There may be even more, because the way you deploy your project affects the location of the user.config file as well. Whether or not you deploy with ClickOnce (i.e. the Publish tab of your project settings) affects the location. Quoting from ClickOnce and Application Settings:

“In Windows Forms applications not deployed using ClickOnce, an application’s app.exe.config file is stored in the application directory, while the user.config file is stored in the user’s Documents and Settings folder. In a ClickOnce application, app.exe.config lives in the application directory inside of the ClickOnce application cache, and user.config lives in the ClickOnce data directory for that application.”

So you need to consider debug vs. non-debug mode, ClickOnce vs. non-ClickOnce, and possibly other factors I haven’t managed to identify yet. Still, the multiple-config-file problem is significant primarily during development. It is typically not an issue after deployment because a user will not be able to make environmental changes that you as the developer can in Visual Studio.

Cleaning Up Settings Files

?
Figure 16. Cleaning Up Extraneous Settings Files: The numbered items show the procedure to “synchronize” (clean up) the various settings files that VS and the .NET framework create.

Visual Studio provides a convenient mechanism for cleaning up all the different manifestations of user.config. The easiest way to clean up any such differences is to open the Settings pane of your project properties and click the “Synchronize” button at the top. VS prompts you that it will erase any copies of the user.config file it finds (see Figure 16).

In Figure 16, there are just two extraneous files: the first is from a debugging execution, the second from a non-debugging execution, as discussed. However, I’ve seen up to 15 files show up in this list. In fact, one time I knew there was a stray copy out there though VS said there was not. I tried re-opening my project, re-opening VS, even rebooting, but it never did find the file. So I manually searched for the file and deleted it to clean it up.

Resetting User Settings
The previous section discussed cleaning up variations of the user-scoped configuration file user.config. But recall that there is also an application-scoped configuration file (app.exe.config) and re-synchronizing the two is useful at development time as well as after deployment. Visual Studio provides, as just mentioned, a button to throw away all versions of the user.config during development, so settings again all come from the app.exe.config. You could give the user of the deployed application the same flexibility and more. Here is how.

The .NET framework provides two methods to handle this. The first, Reload discards changes to the current session, (since the beginning of the program’s execution), so that even if your program saves the configuration upon exit, it will not have any changes to save. The second method, Reset, rewrites the user configuration file to match the application’s configuration file, effectively wiping out any changes in the user’s copy. You can call either with a single line, as shown below:

   // To discard changes during the current session:   Properties.Settings.Default.Reload();      // To restore settings to "out-of-the-box":   Properties.Settings.Default.Reset();

Maintaining Settings Between Program Versions
The astute reader will have noticed that, because the program version number is part of the path to the user.config file, if you issue a new release to the program (i.e. increment the version number), then all previously stored user.config settings for each user will be lost. That is true, but Microsoft provided an elegant workaround for this. For any settings from the current version that match settings in the prior version, this routine will import them into the current version’s user.config file:

   if (Properties.Settings.Default.UpgradeSettings)   {       Properties.Settings.Default.Upgrade();       Properties.Settings.Default.UpgradeSettings = false;   }

You will also need to create the referenced setting (UpgradeSettings) and initialize it to True. Then the above code fragment will update the current user.config just the first time the new version is executed. In the process, it will change the persistent setting to false, so the condition in the if statement will never again evaluate to true. To test what this does, insert the above code in the sample program initialization (e.g. in the form constructor or load event handler). Run the application, saving some custom values for the various settings. Next, increment the program version number in Visual Studio to (for example) 1.0.0.1, and re-run the program. You should find that your custom setting values are still present. Of course, that does not prove that this mechanism worked, because our custom values were there before! So for the skeptics among you, check your directory and you should now find a new directory with a new configuration file, as in:

   C:Documents and Settings...1.0.0.0user.config   C:Documents and Settings...1.0.0.1user.config

Note that when you change version numbers you are again proliferating copies of the user.config file, but Visual Studio’s synchronize function is clever enough to know this. Now that we have introduced a new version, three files show up when synchronizing: the earlier two from version 1.0.0.0 and the new one from version 1.0.0.1 (see Figure 17)

?
Figure 17. Synchronizing After an Upgrade: Visual Studio’s Synchronize function can also clean up extraneous config file copies on your development machine after a version number change to your application.

Final Thoughts
Bound settings provide a mechanism for automatically updating persistent setting values across program invocations. They are ideally suited for simple bound properties such as splitter positions, control sizes, visibility, and other object properties that users manipulate visually. But you may not actually want to bind all of your settings. For example, consider an Options dialog, where users may be setting things such as the Text property of a text field, or the Checked property of a radio button, etc., just like in the sample application earlier. As in any conventional Windows program, you probably want to do two things in this Options dialog where binding could get in the way?provide a Cancel button and perform validation on values the user has entered. For both of these, you may need to discard any changes rather than saving them. That is more easily done, though, from the opposite perspective: save the changes if they warrant it; otherwise do nothing and they will be discarded, as in this code:

   private void OptionForm_Load(object sender, EventArgs e)   {      uiAppTextBox.Text = Properties.Settings.Default.UIAppPath;      logPathTextBox.Text = Properties.Settings.Default.LogFilePath;      ...   }      private void okButton_Click(object sender, EventArgs e)   {      Properties.Settings.Default.UIAppPath = uiAppTextBox.Text;      Properties.Settings.Default.LogFilePath = logPathTextBox.Text;      ...   }

As the code fragment illustrates, you load the form values from settings during the Form_Load event. Store the form values into settings only when the user presses OK, which will presumably be enabled only when form validation succeeds, and do nothing if users press the Cancel button. For more details on form validation see my article Exploring Secrets of Form Validation ).

Whether bound or unbound, you now have the tools to create application settings that persist across invocations of a program, even across new versions of your program. This removes the tedium?as you have certainly encountered yourself in some programs?of having to re-establish your preferred settings each time you execute the program.

devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist