Платформа .NET и Visual Studio предлагает относительно развитые возможности для простого использования настроек приложения. Это, например, разделение настроек на пользовательские (UserScope) и программные (AppScope), их статическая типизация, наличие встроенного в Visual Studio дизайнера и прочие вкусности. Неплохой обзор для VS 2005, но все еще актуальный, можно посмотреть здесь:
http://msdn.microsoft.com/en-us/library/aa730869(v=vs.80).aspx
Всего этого достаточно, чтобы покрыть большинство потребностей небольшого приложения. Но если вам когда-нибудь понадобиться выйти за границы статического объявления настроек и/или захочется хранить данные в своем формате и тем не менее оставаться в рамках стандартных механизмов .NET, вам, скорее всего, придется разбираться с классами ConfigurationManager и ConfigurationSection:
http://msdn.microsoft.com/en-us/library/system.configuration.configurationmanager(v=vs.100).aspx
Так случилось и со мной, мне понадобилось хранить группы определенных настроек, количество которых определялось во время выполнения программы. Группы нужно было идентифицировать по некоторому ключу, в моем случае им была уникальная строка. Но в своем решении я избежал сложностей испльзования ConfigurationManager, а просто “вложил” свою группу настроек в одну пользовательскую установку. Решение получилось достаточно простым, но не совсем очевидным, поэтому с удовольствием спешу поделиться этим знанием :)
Итак, дизайнер настроек позволяет определять различные предопределенные типы настроек. Последним пунктом в этом списке можно выбрать пользовательский тип из некоторого количества предоставленных имен пространств. Список этот ограничен, но можно вписать полный путь к произвольному типу вручную.
Здесь-то мы и добавим наш “контейнер” групп, но для начала нужно его объявить.
Для многократного использования общей конструкции я использовал общий (generic) класс. В моей задаче мне нужно было ко всему прочему иметь “глобальное” значение, которое бы использовалось как стандартное для всех новых и/или неопределенных значений. Ниже код:
public class SettingSetBase<T>
{
public SettingSetBase() { // ctor
KeyValList = new List<KeyVal<T>>();
DefaultValue = default(T);
}
public SettingSetBase(T defVal) { // ctor
KeyValList = new List<KeyVal<T>>();
DefaultValue = defVal;
}
public T DefaultValue { get; set; }
public List<KeyVal<T>> KeyValList { get; set; }
public T GetValue(string key) {
KeyVal<T> keyVal = KeyValList.FirstOrDefault(x => String.Equals(x.Key, key, StringComparison.CurrentCultureIgnoreCase));
if (keyVal == null) return DefaultValue;
else return keyVal.Val;
}
public void SetValue(String key, T val) {
KeyVal<T> keyVal = KeyValList.FirstOrDefault(x => String.Equals(x.Key, key, StringComparison.CurrentCultureIgnoreCase));
if (keyVal == null)
KeyValList.Add(new KeyVal<T>() { Key = key, Val = val });
else
keyVal.Val = val;
}
}
public class KeyVal<T>
{
public String Key { get; set; }
public T Val { get; set; }
}
* This source code was highlighted with Source Code Highlighter.
Не каждый тип может быть использован в качестве типа установки, важное условие - он должен быть Xml-сериализуемым. Избегая излишних сложностей сериализации словарей (Dictionary) применил List
Теперь этот generic-класс можно “конкретизировать” любым сериализуемым типом:
// Например для String:
public class SettingSetString : SettingSetBase<String>
{
public SettingSetString() { }
public SettingSetString(String defVal) : base(defVal) { }
}
// Или для булевых значений:
public class SettingSetBool : SettingSetBase<bool>
{
public SettingSetBool() { }
public SettingSetBool(bool defVal) : base(defVal) { }
}
// Или для глобального уникального идентификатора (GUID):
public class SettingSetGuid : SettingSetBase<Guid>
{
public SettingSetGuid() { }
public SettingSetGuid(Guid defVal) : base(defVal) { }
}
// Или даже для его обнулямого варианта:
public class SettingSetGuidNullable : SettingSetBase<Guid?>
{
public SettingSetGuidNullable() { }
public SettingSetGuidNullable(Guid? defVal) : base(defVal) { }
}
* This source code was highlighted with Source Code Highlighter.
Конечно же, элементарными типами дело не ограничивается и можно использовать комплексные xml-сериализуемые типы, как например этот для хранения параметров окон:
public class WinPosSett
{
public Point Location { get; set; }
public int Width { get; set; }
public int Height { get; set; }
}
* This source code was highlighted with Source Code Highlighter.
Теперь все готово, можно объявить контейнеры настроек в дизайнере. Ограничусь для демонстрации двумя видами - SettingSetString и SettingSetWinPosSett:
Замечание! Что бы эти типы стали доступными для выбора, необходимо пересоздать проект.
Как вы может быть уже заметили, для таких типов в дизайнере невозможно определить стандартное значение (колонка “Value”, или “Wert“ у меня в немецкой студии). Поэтому просто, не усложняя, расширим созданный генератором класс настроек следующим методом:
internal sealed partial class Settings
{
public void InitDefaults() {
if (MyStringSet == null)
MyStringSet = new SettingSetString("DefaultValue");
if (MyWinPosSet == null)
MyWinPosSet = new SettingSetWinPosSett(new WinPosSett() { Height = 300, Width = 300, Location = new Point(100, 100) });
}
}
* This source code was highlighted with Source Code Highlighter.
Ну все, можно пользоваться:
Properties.Settings.Default.InitDefaults();
String strGroup1 = Properties.Settings.Default.MyStringSet.GetValue("Group1"); // "DefaultValue"
Properties.Settings.Default.MyStringSet.SetValue("Group1", "My new Value");
Properties.Settings.Default.Save();
String strGroup1NewValue = Properties.Settings.Default.MyStringSet.GetValue("Group1"); // "My new Value"
String strGroup2 = Properties.Settings.Default.MyStringSet.GetValue("Group2"); // "DefaultValue"
* This source code was highlighted with Source Code Highlighter.
Вот как выглядит часть файла user.config после выполненных операций:
<setting name="MyStringSet" serializeAs="Xml">
<value>
<SettingSetString xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<DefaultValue>DefaultValue</DefaultValue>
<KeyValList>
<KeyValOfString>
<Key>Group1</Key>
<Val>My new Value</Val>
</KeyValOfString>
</KeyValList>
</SettingSetString>
</value>
</setting>
* This source code was highlighted with Source Code Highlighter.
Неплохо! Теперь попробуем использовать комплексный тип WinPosSett:
WinPosSett WinPosMainWindow = Properties.Settings.Default.MyWinPosSet.GetValue("MainForm");
// .. используем значения для установки размеров и позиции окна
// ..
// .. окно закрывается, сохраняем значения:
Properties.Settings.Default.MyWinPosSet.SetValue("MainForm", new WinPosSett() { Height = 400, Width = 200, Location = new Point(10, 10) });
Properties.Settings.Default.Save();
* This source code was highlighted with Source Code Highlighter.
Сохраненные настройки выглядят так:
<setting name="MyWinPosSet" serializeAs="Xml">
<value>
<SettingSetWinPosSett xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<DefaultValue>
<Location>
<X>100</X>
<Y>100</Y>
</Location>
<Width>300</Width>
<Height>300</Height>
</DefaultValue>
<KeyValList>
<KeyValOfWinPosSett>
<Key>MainForm</Key>
<Val>
<Location>
<X>10</X>
<Y>10</Y>
</Location>
<Width>200</Width>
<Height>400</Height>
</Val>
</KeyValOfWinPosSett>
</KeyValList>
</SettingSetWinPosSett>
</value>
</setting>
* This source code was highlighted with Source Code Highlighter.
Пример приложения можно скачать здесь:
https://sites.google.com/site/selosite/GroupUserSettings.zip