Introduction
I often use project settings and in particular project user scope settings. I also use application scope settings but don't find them as
useful since they are read only. This article illustrates how to create and use custom writable application scope settings in a simple yet versatile way.
Using the code
I wanted to implement custom settings so they could be used much in the same way as existing project settings. Here is a small example of
setting a property with a custom settings class MySettings
and a property on the settings class
OptionA
:
CustomProperties<MySettings>.Settings.Default.OptionA = true;
The class diagram of CustomProperties<T>
where T
is the settings class:
The class CustomProperties
is created as a singleton through the
Settings
property. The property Default
references an instance
of the type T
(the settings class).
public class CustomProperties<T> where T : class
{
private static CustomProperties<T> settings;
public T Default { get; private set; }
public static CustomProperties<T> Settings
{
get { return settings ?? (settings = new CustomProperties<T>()); }
}
...
Test application
To further illustrate the use of Application Scope settings, I've made a small test application.
The application uses a sample settings class MySettings
with two
Boolean
options and a List<string>
of selected options.
Loading settings in the test application:
private void InitializeSettings()
{
collection = new ObservableCollection<string>(CustomProperties<MySettings>.Settings.Default.SelectedOptions);
listBoxSelectedOptions.ItemsSource = collection;
checkBoxOptionA.IsChecked = CustomProperties<MySettings>.Settings.Default.OptionA;
checkBoxOptionB.IsChecked = CustomProperties<MySettings>.Settings.Default.OptionB;
SettingsFile.NavigateUri = new Uri(CustomProperties<MySettings>.Settings.SettingsFile);
}
and saving the settings:
private void buttonSave_Click(object sender, RoutedEventArgs e)
{
CustomProperties<MySettings>.Settings.Default.OptionA = checkBoxOptionA.IsChecked == true;
CustomProperties<MySettings>.Settings.Default.OptionB = checkBoxOptionB.IsChecked == true;
CustomProperties<MySettings>.Settings.Default.SelectedOptions = new List<string>(collection);
CustomProperties<MySettings>.Settings.Save();
}
The settings are stored using serialization to the following path: C:\[CommonAppData]\[ProductName]\[name of T].xml.
If the product name is empty or cannot be found, the name of the entry assembly or executing assembly is used.
For the test application, the path resolves to: C:\ProgramData\ApplicationScopeSettingsTest\MySettings.xml.
Load and save of the settings is performed using XmlSerializer
.
private T Load()
{
if (!settingsFile.Exists) return default(T);
var serializer = new XmlSerializer(typeof(T));
using (XmlReader reader = new XmlTextReader(File.OpenRead(settingsFile.FullName)))
{
return (T)serializer.Deserialize(reader);
}
}
public void Save()
{
var serializer = new XmlSerializer(typeof(T));
using (XmlWriter writer = new XmlTextWriter(File.Create(settingsFile.FullName), Encoding.UTF8))
{
serializer.Serialize(writer, Default);
}
}
Default values
When CustomProperties
is loaded and no previous settings has been found, it will create an instance of the settings (type
T
) with its default values. Creating a
instance of the settings with default values:
Default = (T)Activator.CreateInstance(typeof(T));
If any values are assigned to lists or collections in the constructor of the settings class, it will cause extra items to load when the settings are
deserialized (loaded). To workaround the problem I have created another constructor on the MySettings
class that takes a Boolean
value
so I have two ways of creating the settings. Like this:
const bool initializeObject = true;
Default = (T)Activator.CreateInstance(typeof(T), new object[] { initializeObject });
The modified settings class MySettings
:
public MySettings() : this(false)
{
}
public MySettings(bool initializeValues)
{
OptionA = false;
OptionB = true;
SelectedOptions = new List<string>();
if (!initializeValues) return;
SelectedOptions.AddRange(new List<string> { "Red", "Blue" });
}
This will make sure that both deserializing the settings from file and creating an instance of the settings class with default values will work.
If you won't assign any values to lists or collections, you can safely delete the overload and modify the CreateInstance<T>
method to omit the value.
Using CustomProperties<T> for User Scope settings
CustomProperties<T>
can also be used to create custom user scope settings. This can simply be done by altering the placement of the settings
file to reference Environment.SpecialFolder.ApplicationData
. The paths ApplicationData and CommonAppData can be written to as a restricted user.
Upgrading the settings between versions
Since the settings have been serialized, it is reasonably easy to add or remove properties on the settings class. Additional
properties will be assigned default values when loaded the first time. Removed properties will be ignored the first time the settings class loads.
History
This is version 1.0.0.0.