пятница, 12 октября 2007 г.

Обратите внимание!

Прочитал сегодня интересный пост События и многопоточный код. Стоит запомнить...

среда, 10 октября 2007 г.

Страсти-то какие!

Цитирую cnews.ru

"Ужесточение борьбы с нелегальным программным обеспечением не могло не сказаться на росте доходов производителей ПО. Так, по итогам первого полугодия 2007 г. «Лаборатория Касперского» в России, СНГ и Балтии продемонстрировала рост по сравнению с аналогичным периодом 2006 г. на 130%. Рост продаж Microsoft Russia в 2007 финансовом году вырос на рекордные 107%, продажи Autodesk в странах СНГ во II квартале финансового года составили 160%, по итогам третьего квартала 2007 финансового года продажи Adobe в России выросли на 260%."

Нет слов.

И тут я задумался: а ведь программисты (ну понятно, что "хорошие программисты" :) ) в этом отношении самые счастливые люди в стране. Нам ничего не нужно из программ.

Фактически, тот софт, что я использую может быть или безболезненно заменен на бесплатные альтернативы или же написан самостоятельно.

Зачем мне Photoshop, когда есть Paint.Net?
Зачем мне, например, WebSite Watcher, если подобная программа может быть написана на коленке за один вечер? Ну естественно, менее функциональная - так ведь там в любом случае будет всё что мне нужно - я же ее напишу-то :).
А уж если покопаться в своих экспериментальных проектах - столько всего полезного можно нарыть... :)

Да, неудобно. В том же Ворде2007 есть много интересных вещей, без которых обойтись, конечно, можно, но...

Пиратство будет жить до тех пор, пока цены на ПО не будут приведены в соответствие с его качеством и популярностью.

И если купить приличный комп сейчас себе может позволить практически любой (8-15 тыс. руб. это не такие уж большие деньги. За полгода-год их вполне реально собрать при любой зарплате), то снабдить его полностью лицензионным ПО себе позволить может только крупная фирма.

Примечание: кстати, вот в моем университете есть требование сдавать чертежи распечатанными на плоттере. Нарисованные вручную - не принимают. Скажите мне, какой студент сможет себе позволить купить AutoCad для этих целей? Те кто может - учатся совсем в других университетах...

Enum'ы с человеческим лицом!

Сегодня речь я поведу о том, как облегчить себе жизнь при работе с enum-типами. То, что использовать их нужно понимают, я думаю, все. Это удобно: код становится понятнее, вероятность совершить ошибку (если использовать вместо enum «магические» числа) – ниже и т.д. и т.п.
Все это, конечно, хорошо… Но что делать, если необходимо отображать пользователю списки (например, combobox'ы) состоящие из этих самых enum (или сводящиеся к ним)? Естественно, что пользователь не знает английского, да и в любом случае – адекватно воспринимать замену пробела символом подчеркивания он точно не будет :)
Если вспомнить про то, что юникод для дотнета – это «родное», а студия еще и корректно работает с русскими именами функций и переменных, то можно, конечно, называть значения в нашем enum русскими именами. Но! Это ж умучаешься раскладки переключать :)
Поэтому самое простое неправильное решение – это, как правило, использование строкового массива, в котором задаются отображаемые пользователю русские имена.
Можно пойти дальше и использовать вместо массива, например, Dictionary. Этот способ как минимум более надежен и шанс , что в программе появится странная ошибка если мы решим добавить еще несколько элементов в enum, резко уменьшится.
Кстати, этот способ имеет право на жизнь. Однако – он неудобен. Намного удобнее, когда описания элементов enum хранятся вместе с ними. Даже очень удобно :)
Как этого добиться? (И вот тут все должны громко воскликнуть – «Ну конечно атрибуты!»)
Первое что нам понадобится – это атрибут, через который мы будем задавать описание:

[AttributeUsage(AttributeTargets.Field)]
public class EnumDescriptionAttribute: Attribute
{
    public readonly string description;

    public EnumDescriptionAttribute(string description)
    {
        this.description = description;
    }
}

public enum etest
{
    [EnumNameAttribute("Значение 1")]
    value1,
    [EnumNameAttribute("Значение 2")]
    value2,
    …
}
Как видите – здесь все просто :)
А теперь самое сложное: получение этих описаний (чтобы отдать их пользователю).

Примечание: и вот тут-то руки и опускаются – сразу после того, как мы слышим жуткое слово reflection :) Но не пугайтесь, все не так страшно ;)

Чтобы получить наш атрибут-описание для известного значения, надо вызвать следующий код:


((EnumDescriptionAttribute)typeof(etest).GetField(p.ToString()).GetCustomAttributes(typeof(EnumDescriptionAttribute), true)[0]).description;


Примечание: стра-а-ашно??? :)

Чтобы получить имена для всех значений enum (например, чтобы сформировать список, из которого будет производиться выбор нужного значения):

public static string[] GetAllEnumDescription()
{
    T[] vals=(T[])Enum.GetValues(typeof(T));
    string[] names = new string[vals.Length];
    for (int i = 0; i < vals.Length; i++)
        names[i] = GetEnumDescription(vals[i]);
    return names;
}

public static string GetEnumDescription(T p)
{
    return ((EnumDescriptionAttribute)typeof(T).GetField(p.ToString()).GetCustomAttributes(typeof(EnumDescriptionAttribute), true)[0]).description;
}


Примечание: я решил, что данный вариант (использующий generics) функции будет полезнее – этот вариант функции можно использовать для любого enum (при условии, что у его элементов есть наш атрибут). Кому не нравится – его легко можно переделать.

Вообщем-то и всё…

P.S.: ой, нет! Чуть не забыл: если в функции GetAllEnumDescription использовать конструкцию yeld return – то она будет еще более удобна :) и один new можно будет убрать…

пятница, 5 октября 2007 г.

"Продвинутое" использование класса Settings, а также немного о Databinding

В предыдущем посте среди прочих способов сохранения настроек я упомянул класс Settings. Однако написал я про него не очень много и как-то не очень внятно. Вряд ли по тому посту будет понятно насколько это хорошая штука :) Поэтому я решил рассказать про него поподробнее и на конкретном примере. Тем более, что в сети информация все больше общего характера – просто непонятно зачем этот класс нужен. Примечание: проект я буду делать в Visual Studio 2008 Beta 2 установленной на Windows Server 2008 RC0. Принципиального отличия от VS2005 в данном случае нет. В обоих версиях студии способ работы с классом Settings одинаков.

Начнем с создания нового проекта. Будет это Windows Forms Application с одной формой (я покажу только как разработывается форма с настройками приложения):


Кидаем на форму несколько контролов, через которые и будут редактироваться наши настройки:

Собственно, ничего особенного тут нет. Я думаю справитесь :)
А теперь начинается самое интересное :) Начинаем использовать класс Settings. Для этого надо для каждого контрола проделать следующие шаги:
1. Выделить его. Переключиться на вкладку Properties и найти раздел ApplicationSettings.
2. В данном случае - для checkBox - нам нужно сохранять свойство Checked.

3 И жмем ссылку new

Здесь можно ввести название переменной, в которой будет храниться состояние нашей настройки, значение по-умолчанию (True/False) и где она будет храниться (оставляем как есть).
4. А собственно и все :)
Ну и напоследок, посмотрим как это работает. В метод button1_Click пропишем добавим следующий код:
MessageBox.Show(Properties.Settings.Default.setting1.ToString());

И мы увидим, что всё работает :)

Ну и напоследок
Что делать, если необходимо добавить какую-либо проверку значений вводимых пользователем параметров или, например, осуществлять преобразование типов?
В этом случае придется немного пописать ручками :)
Добавим свойство с методами get/set через которое и будет осуществляться доступ к настройкам:

public string Setting3
{
  get
  {
      return Properties.Settings.Default.setting3;
  }
  set
  {
      string tmp = value.ToLower();
      Properties.Settings.Default.setting3 = tmp;
  }
}

Свяжем его с соответствующим свойством у контрола на форме:

this.textBox1.DataBindings.Add("Text", this, "Setting3");

В данном случае, первый параметр - это имя свойства у контрола которое будет связываться. Второй параметр - ссылка на объект где находится свойство с которым будет производиться связывание. Ну и третий параметр - это, соответственно, имя свойсва.
И всё! Дальше всё будет работать само.
Пожалуй на этом и закончу.

пятница, 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();
Ну и хватит наверное :) Более подробно - читаем МСДН.