Windows Forms 2.0 Programming (Microsoft .NET Development Series)
Thanks in large part to its ApplicationSettingsBase ancestor, the strongly typed class that's generated by the Designer for your settings files dramatically simplifies reading and writing settings, rollback, and migration. Let's take a look. Using the Settings Class
To use the strongly typed settings properties, you first instantiate the desired settings class: // MainForm.cs partial class MainForm : Form { ... ApplicationSettingsSample.Properties.Settings settings = new ApplicationSettingsSample.Properties.Settings(); void getSettingsButton_Click(object sender, EventArgs e) { int highScore = this.settings.HighScore; string assemblyPaths = this.settings.1AssemblyPaths; } }
This technique puts the onus on you to manage the lifetime of the Settings object instance and to make sure that all the forms and classes in your application have access to it. In trivial scenarios, this may be fine, but for more complex situations, you'll want to rely on the statically managed instance exposed by the generated settings class, as you saw earlier: // Settings.Designer.cs namespace ApplicationSettingsSample.Properties { [global::CompilerGenerated()] internal sealed partial class Settings : global::ApplicationSettingsBase { static Settings defaultInstance = new Settings(); public static Settings Default { get { return defaultInstance; } } ... } }
In this example, the statically managed Settings instance is created before it's requested and subsequently is retained in memory until the application session ends. The benefit for you is less worry and less code: // MainForm.cs partial class MainForm : Form { ... void getSettingsButton_Click(object sender, EventArgs e) { int highScore = Properties.Settings.Default.HighScore; string assemblyPaths = Properties.Settings.Default.AssemblyPaths; } }
Loading and Inspecting Settings
At some point before you use your first setting in client code, you'll likely want your settings loaded. In your favor, loading settings from the .config file requires no client code; the first time you inspect or update a property on the Settings class, it automatically loads all your settings into memory: // MainForm.cs partial class MainForm : Form { ... void getSettingsButton_Click(object sender, EventArgs e) { // No settings are loaded yet int highScore = Properties.Settings.Default.HighScore; // All settings are now loaded (caused by previous line) ... } } If you have client code that is dependent on when settings are loaded, you can handle the SettingsLoaded event exposed by the Settings class: // MainForm.cs using System.Configuration; ... partial class MainForm : Form { ... public MainForm() { ... Properties.Settings.Default.SettingsLoaded += DefaultSettings_SettingsLoaded; } ... void DefaultSettings_SettingsLoaded( object sender, SettingsLoadedEventArgs e) { MessageBox.Show("Settings loaded by " + e.Provider.Name); } }
The SettingsLoaded event handler accepts a SettingsLoadedEventArgs argument whose single interesting property is Provider, which returns an instance of a settings provider. Settings Providers
ApplicationSettingsBase uses a settings provider to manage the reading and writing of a setting to a data store, two tasks that are embodied by the SettingsProvider class: namespace System.Configuration { class SettingsLoadedEventArgs : EventArgs { public SettingsProvider Provider { get; } } }
By default, ApplicationSettingsBase uses LocalFileSettingsProvider, which manages settings persistence in .config files, as shown in Figure 15.15. Figure 15.15. SettingsLoaded Event and SettingsLoadedEventArgs in Action
The reason to abstract away the actual persistence part of the settings process is to support the creation of multiple settings providers, each capable of persisting settings to different data stores, such as the Registry, databases, and even web services.[12] It is possible for one settings file to use settings that are managed by more than one provider, and hence the provider distinction made by SettingsLoadedEventArgs. [12] Discussion of how to create setting providers can be found at MSDN Online, at http://msdn2.microsoft.com/en-us/library/8eyb2ct1.aspx (http://tinysells.com/28). Updating Settings
Whereas all settings can be inspected for their values, only user-scoped settings can be updated programmatically. This is as simple as setting a property value because they are exposed as strongly typed properties: Properties.Settings.Default.HighScore = int.Parse(this.settingsTextBox.Text); A user setting may be updated directly from user input via a Windows Form, in which case you can validate the data at the point of entry. However, some user settings may be set by other means, in which case you need another validation mechanism. This is provided by the SettingChanging event, which is exposed by the Settings class. The SettingChanging event handler is passed a SettingChangingEventArgs type: // UpdateSettingForm.cs partial class UpdateSettingForm : Form { public UpdateSettingForm() { ... // Handle SettingChanging event Properties.Settings.Default.SettingChanging += DefaultSettings_SettingChanging; ... } void updateSettingButton_Click(object sender, EventArgs e) { // Attempt to set value // Note: when the property is set, the SettingChanging // event is fired Properties.Settings.Default.HighScore = int.Parse(this.settingTextBox.Text); } void DefaultSettings_SettingChanging(object sender, SettingChangingEventArgs e) { // Validate HighScore if( e.SettingName == "HighScore" ) { if( ((int)e.NewValue) < 0 ) { MessageBox.Show("HighScore can't be less than 0."); e.Cancel = true; } } } } SettingChangingEventArgs provides the desired new value in the NewValue property. Additionally, it provides information about the setting that you need in order to determine which setting is about to be changed. Also, because SettingChangingEventArgs derives from CancelEventArgs, you can set its Cancel property to true to prevent an update of the setting if the new value is invalid. Note that the Cancel property is false by default, so you need to set it to true to prevent the invalid setting value from being applied. Figure 15.16 shows the result. Figure 15.16. Validating a Settings Change
If you need to respond to a successful settings update, you can handle the PropertyChanged event fired by the settings class. The passed PropertyChangedEventArgs simply details which setting was changed. Saving Settings
When user settings change, your user will want them to be saved. All updates to user settings are retained in memory until the Save method exposed by the settings class is invoked, which results in the modified values being persisted back to user.config. // MainForm.cs partial class MainForm : Form { ... public MainForm() { ... Properties.Settings.Default.SettingsSaving += DefaultSettings_SettingsSaving; ... } ... void MainForm_FormClosing(object sender, FormClosingEventArgs e) { Properties.Settings.Default.Save(); } void DefaultSettings_SettingsSaving(object sender, CancelEventArgs e) { // Ask whether user really wants to save changed settings DialogResult result = MessageBox.Show( "Save Settings before application shutdown?", Application.ProductName, MessageBoxButtons.YesNo, MessageBoxIcon.Question); e.Cancel = (result == DialogResult.No); } }
The SettingsSaving handler is passed a CancelEventArgs whose Cancel property you set to true to prevent settings persistence. The next section covers what happens when changed settings are persisted. Rolling Back Settings
Because settings can be updated and saved, there needs to be some recourse for users when new settings go awry. The settings system supports rolling back settings updates, and dealing with a total loss of the settings file. Rolling Back to Last Saved Settings
User settings are for users to change and use, to personalize an application to serve their visual and functional needs. Allowing users to update settings, however, means that things can turn pear-shaped. For example, a user might try out a new color scheme that, in the end, simply goes wrong. Even worse, the color scheme may be composed of so many colors that users can't easily recall the original colors. The settings system provides a simple mechanism by which you can allow users to refresh the current settings using the last saved settings values. This entails calling a single method, Reload, implemented by the Settings class: void saveRollbackToolStripMenuItem_Click(object sender, EventArgs e) { // Roll back to last saved settings Properties.Settings.Default.Reload(); // Retrieve this.userSettingTextBox.Text = Properties.Settings.Default.userSettingv1; }
Because settings are cached in memory by the Settings class, the Reload method simply refreshes the cache from user.config or app.exe.config, depending on whether user settings have been previously updated. Rolling Back to Original Settings
In more extreme scenarios, users may want to start with a clean slate and roll their settings back to their default values when the application was installed. In these cases, it is possible that settings have been repeatedly updated and saved many times since installation, removing all likelihood that anyone remembers the defaults. You can use the Reset method, implemented by Settings, to let users roll their settings back to installation defaults: void installRollbackToolStripMenuItem_Click( object sender, EventArgs e) { // Roll back to original settings Properties.Settings.Default.Reset(); // Retrieve this.userSettingTextBox.Text = Properties.Settings.Default.userSettingv1; }
Reset causes the settings system to pull settings values out of the app.exe.config file, and these values also happen to be the values you entered into the Settings Editor. If user.config happens to be deleted, an application automatically reverts to the settings values contained in app.exe.config, just as if the Reset method had been called. Dealing with .config File Deletion
As you've just seen, there's a lot of dependency on app.exe.config. Of course, it is also an obvious point of failure; what happens if the app.exe.config file itself is deleted? The settings system incorporates an additional layer of redundancy that ultimately provides a solid backup for the app.exe.config file: The value you enter for a setting when creating or editing it is considered its default value, which, by default, is added to a project's compiled assembly.[13] The happy result is that if app.exe.config is missing a setting or missing completely, the settings system pulls default values directly from code. [13] Whether setting and its value are included in the compiled output depends on there being a default value and on the GenerateDefaultValueInCode property being set to true. You can use the Settings Editor to set GenerateDefaultValueInCode from the Properties window for each setting, as discussed later in this chapter. Through the combination of user.config files, app.exe.config files, and compiled settings and values, .NET's settings system provides highly redundant support for rolling settings values back to previous versions. Migrating Settings
Settings rollback is only half the story. When a new version of an application is released, it can be useful to automatically migrate user-changed settings from the old version to save them the effort of manually changing the default settings deployed with the new version. .NET offers the ability to migrate settings from the previous version of an application to a new one, using either batch or selective migration as required. Determining When to Migrate Settings
Before a user's settings can be migrated, however, you must determine whether settings need to be migrated. Unfortunately, the settings system lacks a native mechanism for reporting whether settings need to be migrated, so you have to roll your own. A simple solution relies on the use of an additional Boolean flag setting that defaults to true and signals the new version to migrate settings from the previous version, if there was one. After a migration has occurred, the flag should be set to false to prevent further migration attempts. Even though the flag represents a setting that is used by the application only, it needs to be changed and must, therefore, be a user-scoped setting because it needs to be set to false after migration. Figure 15.17 shows the new setting, UpgradeRequired. Figure 15.17. UpgradeRequired Migration Flag
Next, you write the code to use the UpgradeRequired setting as just discussed. A good location for this code is an application's Main method:[14] [14] The UpgradeRequired trick was posted to Chris Sells's web site by the very wily Raghavendra Prabhu at http://www.sellsbrothers.com/news/showTopic.aspx?ixTopic=1537 (http://tinysells.com/29). // Program.cs static class Program { ... [STAThread] static void Main() { Application.EnableVisualStyles(); // Check whether upgrade required if( Properties.Settings.Default.UpgradeRequired ) { // Upgrade settings ... // Prevent further upgrades Properties.Settings.Default.UpgradeRequired = false; Properties.Settings.Default.Save(); } Application.Run(new MainForm()); } } With the upgrade required detection in place, we can employ one of two types of settings migration techniques. Migrating Settings Values in Bulk
If a new version of an application contains few or no changes in the settings it uses, or if the user simply doesn't need to participate in the process, you call the Upgrade method to migrate all settings values from the previous application version in one step: // Check whether upgrade required if( Properties.Settings.Default.UpgradeRequired ) { // Bulk migrate settings from previous version Properties.Settings.Default.Upgrade(); Properties.Settings.Default.Reload(); // Prevent further upgrades Properties.Settings.Default.UpgradeRequired = false; Properties.Settings.Default.Save(); } Upgrade copies all settings from the previous application version's user.config file and creates a new user.config under a folder for the new application version. Settings from the old version that do not appear in the new version are dropped because they need to be reloaded from the new user.config file, and hence the call to reload. One problem that upgrades might encounter is a missing user.config from the previous application's version, a file that Upgrade depends on. Although it's unlikely, this file may have been deleted or uninstalled. The Upgrade method throws a ConfigurationSettingsException if it can't find a previous version: // Program.cs static class Program { ... [STAThread] static void Main() { Application.EnableVisualStyles(); try { // Check whether upgrade required if( Properties.Settings.Default.UpgradeRequired ) {...} } catch( Exception ex ) { string msg = string.Format("{0}\n\nTry again next time?", ex.Message); DialogResult result = MessageBox.Show( msg, Application.ProductName, MessageBoxButtons.YesNo, MessageBoxIcon.Warning); if( result == DialogResult.No ) { // Prevent further upgrades Properties.Settings.Default.UpgradeRequired = false; Properties.Settings.Default.Save(); } } } Application.SetCompatibleTextRenderingDefault(false); Application.Run(new MainForm()); } ... }
When the settings upgrade can't take place, the sample code gives the user the option to prevent further upgrades or to try to upgrade the next time the application executes. Your application can take many approaches to handling a missing user.config file, but depending on application, business, user, and deployment requirements, this sample gives users two options: They can continue using the application and forget any further upgrade attempts or they can continue but try to upgrade the next time the application executes. Migrating Settings Values Selectively
Sometimes, the differences between the settings used by two versions might be nontrivial and only a few settings remain the same. Alternatively, you might like to give users the opportunity to choose which settings values to migrate from a previous application version. Either requirement would benefit from a selective style of migration, and the GetPreviousVersion method of the Settings class nicely provides this option. GetPreviousVersion simply retrieves the value of a specified setting from the previous version. This sample uses a batch migration approach to manage the entire upgrade process, albeit selective in this instance: // Program.cs static class Program { ... [STAThread] static void Main() { Application.EnableVisualStyles(); // Check whether upgrade required if( Properties.Settings.Default.UpgradeRequired ) { // Selectively migrate settings from previous version string oldValue = (string) Properties.Settings.Default.GetPreviousVersion("userSetting"); Properties.Settings.Default.userSettingv1 = oldValue; // Upgrade and prevent further attempts Properties.Settings.Default.UpgradeRequired = false; Properties.settings.Default.Save(); } Application.Run(new MainForm()); } }
When using the GetPreviousVersion method, you should consider two things. First, GetPreviousVersion returns an object, so you must always cast the result to the target setting's type. Second, GetPreviousVersion returns a return null if a previous version of the setting does not exist. |
Категории