пятница, 21 сентября 2007 г.

Сохранение настроек приложения

Решил поделиться опытом :) Проблема сохранения настроек своего приложения - вечная и каждый ее решает по-своему. Я рассмотрю три разных способа ее решения. Естественно, что я не претендую на истину в последней инстанции, но возможно, данная информация окажется кому-нибудь полезной.

1. Сохранение настроек в собственный файл используя сериализацию.

Данный подход обладает достаточно большой гибкостью и удобен в использовании. Однако требует наибольшее количество кода. Итак. Первое что нам понадобится - это класс с настройками. Для удобства использования, лучше всего если все члены класса будут статическими (Это, конечно, не самый лучший способ, однако, если наше приложение очень маленькое и не будет сильно расти/развиваться в дальнейшем, то можно и так. Более универсальный способ - это реализовать для класса с настройками паттерн singleton).
public class MySettings
{
   public static int Setting1;
   public static string Setting2;
}
Думаю - код класса понятен и проблем ни у кого не вызовет :) Для сохранения данных - сериализуем:
XmlSerializer ser = new XmlSerializer(typeof(MySettings));
TextWriter writer = new StreamWriter("settings.xml");
ser.Serialize(writer, MySettings);
writer.Close();
Упс!.. Ошибка: "'MySettings' is a 'type' but is used like a 'variable' (CS0118)" Переделываем:
MySettings settings=new MySettings();

XmlSerializer ser = new XmlSerializer(typeof(MySettings));
TextWriter writer = new StreamWriter("settings.xml");
ser.Serialize(writer, settings);
writer.Close();
Пожалуй стоит пояснить. Первой строкой мы создаем объект класса MySettings, но поскольку он содержит только статические члены - данные в переменных класса не изменятся, но мы получим объект (переменную), которую уже можно сериализовать :) Компилируем, запускаем, смотрим что получилось:

<?xml version="1.0" encoding="utf-8"?>
<MySettings xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"/>
Упс!.. А где данные? Проблема в том, что сериализовать статические члены класса нельзя! (В дополнение: xml-сериализация работает только с public-членами класса). Делаем "финт ушами":
public class MySettings
{
    public static int Setting1;
    public static string Setting2;
    public int _Setting1
    {
        get{ return Setting1;}
        set{ Setting1=value;}
    }
    public string _Setting2
    {
        get{ return Setting2;}
        set{ Setting2=value;}
    }
}
Компилируем, запускаем, смотрим:
<?xml version="1.0" encoding="utf-8"?>
<MySettings xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <_Setting1>5</_Setting1>
  <_Setting2>test</_Setting2>
</MySettings>
Вот оно! :) Суть в том, что через не-статические public-члены класса сериализатор получает доступ к статическим. :) Ну а теперь - самое легкое: десериализация.
XmlSerializer deser = new XmlSerializer(typeof(MySettings));
TextReader reader = new StreamReader("settings.xml");
settings=((MySettings)ser.Deserialize(reader));
reader.Close();

2. "Ручное" сохранение в файл.

Поклонникам C++ рекомендуется :) В смысле - смотрите и завидуйте! Итак. В .Net Framework в пространстве имен System.IO есть один замечательный класс - File, которым мы и воспользуемся. В частности, статическими функциями ReadAllLines и WriteAllLines. Первое что на надо сделать для сохранения данных, это подготовить и заполнить массив строк:
string[] settings = new string[5];
settings[0] = "setting1=" + setting1.ToString();
settings[2] = "setting1=" + setting2.ToString();
Примечание: Многие увидевшие такой код наверняка захотят оторвать мне руки :)) Однако, сделано это специально, для того, чтобы показать, что делать если настройки сохранить надо, времени мало (лень), да и настроек 2-3 штуки. Естественно, что в серьезном приложении так делать нельзя! После чего пишем одну строку кода(!!!) и все:
File.WriteAllLines("settings.txt", settings);
При желании, можно указать и кодировку:
File.WriteAllLines("settings.txt", settings, Encoding.GetEncoding(1251));
Теперь перейдем к самому ответственному - чтению настроек. :)
string[] settings=File.ReadAllLines("settings.txt");
foreach(string s in settings)
{
    string[] tmp=s.Split(new char[] {'='}, StringSplitOptions.RemoveEmptyEntries);
    if(tmp.Length==2) //на всякий случай 
    {
        switch(tmp[0])
        {
            case "setting1":
                Int32.TryParse(tmp[1], out setting1); //тоже, чтобы не падало :)
                break;
            case "setting2":
                setting2 = tmp[1];
                break;
        }
    }
}
Вообщем-то и все.

3. Использование класса Settings.

Внимание: данный способ работает только в Visual Studio. По крайней мере в Sharp Delevop 2.2 этого точно нет. Более того: данный способ доступен только для WinForms или консольных проектов. Первое что нам надо сделать - это добавить те настройки, что мы хотим сохранять. Делается это в разделе Properties списка файлов нашего проекта. Там дважды кликаем на раздел Settings и получаем в результате окно с таблицей. В этой таблице придумываем и вводим имена перменных (в которых и будут храниться наши настройки) их тип и значение по-умолчанию. Доступ к настройкам осуществляется так:
Properties.Settings.Default.имя_нашей_перменной = значение;
После компиляции рядом .exe файлом будет лежать файл с таким же именем и расширением .exe.config. В этом файле хранятся значения по-умолчанию для наших настроек. Об этом надо помнить! Сами же настройки приложения хранятся в
Documents and Settings\имя_пользователя\Local Settings\Application Data\имя_компании_из_AssemblyInfo.cs\имя_приложения.exe_StrongName_<разные левые числа>\номер_версии"
Причем номер версии берется в AssemblyInfo.cs из атрибута [assembly: AssemblyVersion("1.0.0.0")] Если его изменить - то путь для настроек также изменится и, соответсвенно - наше приложение запустится с настройками по-умолчанию. (Тому, кто это придумал в Microsoft, по-хорошему следовало бы оторвать руки...) К счастью - атрибут [assembly: AssemblyFileVersion("1.0.0.0")] можно менять как хочется :) Самое веселое, что сохранять текущие настройки куда-либо помимо указанного пути стандартными средствами нельзя! Настройки загружаются автоматически при старте программы. Сохранять их надо вручную, вот так:
Properties.Settings.Default.Save();
В случае, если вам надо сбросить все настройки в их значения по умолчанию, используется команда:
Properties.Settings.Default.Reset();
Ну и хватит наверное :) Более подробно - читаем МСДН.

понедельник, 17 сентября 2007 г.

Параллельное выполнение кода в .Net Framework 3.5

Вы не ждали? А мы пришли!

Э... О чем это я?

А!

Microsoft разрабатывает библиотеку для упрощения процесса написания программ, в которых отдельные элементы алгоритма можно выполнять параллельно. Более подробно (на русском!) об этой библиотеке можно почитать здесь

Последнее время все чаще встречаю статьи о том, что "несколько ядер/процессоров это конечно хорошо... но вот программировать под них...". Так вот, теперь это не просто, теперь это очень просто! :)

P.S.: а С++ пора начинать забывать. Как страшный сон.

суббота, 15 сентября 2007 г.

Полезный ListView

Совершенно случайно нашел то, что давно искал, а именно - модифицированный ListView, который позволяет группировать элементы. Да-да, я знаю, что стандартный ListView это тоже умеет, однако делает он это очень плохо. Контрол можно найти тут: http://www.codeproject.com/cs/miscctrl/outlooklistcontrol.asp Выглядит он вот так: Самое интересное, что возможностей у него очень и очень много. Кроме того, можно переопределять стандартную отрисовку любой части контрола, причем для этого даже не надо лезть и что-то исправлять в его исходном коде! Хотя нет. Вру :) Мне все-таки пришлось кое-что подправить. Видите воон ту желтую область слева? Вот ее-то и пришлось править - цвет области был жестко зашит в коде. Вообщем, теперь у меня это выглядит вот так (в моем втором более-менее крупном проекте MusicSorter):

воскресенье, 9 сентября 2007 г.

Новый движок для скриптов готов!

    Сегодня великий день :) Я окончательно доделал новый движок скриптов (скрипты даже компилируются и работают в основной программе-переводчике!). Теперь скрипты пишутся исключительно на на языке C#. Применяться он будет в составе алгоритма синтаксического анализа предложений вместо уже существующего.

    Наверное стоит рассказать, чем же меня не устраивала предыдущая версия. Пожалуй самая основная ее проблема в том, что предыдущий движок был декларативным. Фактически, любое правило описывало жестко заданный алгоритм: 

  1. поиск слов имеющих заданные параметры и замена в строке-представлении предложения символа в соответствующей позиции (равной номеру слова в предложении) на заданный симво
  2. поиск в предложении участка по заданному регулярному выражению
  3. если поиск увенчался успехом, то производятся заданные действия - например, установка у указанных слов определенных параметров, добавление связей между словами и т.п.

где разработчик правила мог указывать необходимый (и строго фиксированный!) набор параметров.

    Фактически я получил более-менее гибкую систему главной особенностью которой было то. что она полностью управлялась набором параметров (параметры описываются специальным классом). В результате - мало того, что на каждый новый параметр надо было писать отдельный алгоритм обработки, так еще и из-за того что данные параметры являются глобальными (и используются практически во всех частях переводчика), то о каком либо расширении набора параметров или изменении логики их обработки сторонними разработчиками можно было смело забыть.

На этот раз я постарался разработать более гибкую систему. Пока сложно сказать насколько она удачна, т.к. правил там на данный момент нет :)

На данный момент запланированы следующие задачи:

  1. реализовать поддержку нескольких библиотек скриптов в редакторе с соответствующей компиляцией в разные .dll
  2. сконвертировать все старые скрипты к новому виду - т.е. фактически написать алгоритм генерации класса по описанию правила.
  3. разработать набор визардов для автоматической генерации простых скриптов (лень - двигатель прогресса!)

    Ну и как не похвалиться? На скриншоте можно посмотреть структуру стандартного скрипта:

    Функция Init используется для инициализации. Там необходимо выполнять те действия, которые можно сделать один раз и потом пользоваться результатом В частности я предполагаю выполнять там действия по инициализации алгоритма поиска.

    Ну и самая главная функция - Run - которая вызывается в цикле до тех пор пока возвращает true (ну о том, как именно работает алгоритм анализа я расскажу по-позже).

четверг, 6 сентября 2007 г.

Nullable типы в C#

Разбираясь с примером по WWF наткнулся на странное объявление переменной. Выглядело это примерно так:

int? i;

Недолгие поиски в гугле привели меня на сайт MSDN.

Как оказалось - такие типы имеют еще одно значение - null, показывающее, что значение не установлено. Сделано это, в основном, для совместимости с логикой работы с типами в базах данных.

Как это работает.

Объявленный таким способом тип имеет помимо прочих еще два члена:

  • HasValue - показывает, не записано ли null в Value
  • Value - содержит само значение переменной нашего типа

В случае, если данные в переменную не записаны (т.е. фактически она равна null), то при попытке как-то взаимодействовать с такой переменной мы получаем InvalidOperationException. Если данные записаны - то все работает так же как и у переменных обычного типа.

Примеры:


int? x = 10;
if (x.HasValue)
    System.Console.WriteLine(x.Value);
else
    System.Console.WriteLine("Undefined");

int? y = 10;
if (y != null)
    System.Console.WriteLine(y.Value);
else
    System.Console.WriteLine("Undefined");

понедельник, 3 сентября 2007 г.

Xml-сериализация сложных коллекций

Ну я надеюсь что про простую xml-сериализацию вы уже знаете :) Однако постоянно встает вопрос - как сериализовать классы, содержащие коллекции типа Hastable, Dictionary или Hashset (новый тип, доступный в 3-ей версии .Net Framework). Итак. Первое что нам понадобится:
using System.Runtime.Serialization; 
Второе. Пометить сериализуемые классы (все! включая вложенные!) атрибутом [DataContract] и все члены класса, данные которых надо сериализовать артибутом [DataMember]:
[DataContract]
public class MySerializableClass
{
   [DataMember]
   public Dictionary MyCollection = new Dictionary();
   ...
}
Сериализация производится следующим образом:

public void SaveData(string path, MySerializableClass msc)
{
   XmlTextWriter xw = new XmlTextWriter(path, Encoding.UTF8);
   //а это чтобы красиво было :)
   xw.Formatting = Formatting.Indented;
   XmlDictionaryWriter writer = XmlDictionaryWriter.CreateDictionaryWriter(xw);
   DataContractSerializer ser = new DataContractSerializer(typeof(MySerializableClass));
   ser.WriteObject(writer, ds);
   writer.Close();
   xw.Close();
}
Ну и, соответственно, десериализация:

public MySerializableClass LoadData(string path)
{
   MySerializableClass msc = null;   using (FileStream fs = new FileStream(path, FileMode.Open))
   {
      XmlDictionaryReader reader = XmlDictionaryReader.CreateTextReader(fs, Encoding.UTF8, new XmlDictionaryReaderQuotas(), null);
      DataContractSerializer ser = new DataContractSerializer(typeof(MySerializableClass));
      msc = (MySerializableClass)ser.ReadObject(reader);
   }

   return msc;
}
P.S.: ну естественно, что все красоты с использованием отступов и кодировок можно опустить - но наша же цель состоит именно в том, чтобы получить более-менее читаемый файл...