воскресенье, 13 марта 2011 г.

Лябда-выражения

Лябда-выражения являются реализацией замыканий. Это своего рода контейнер с функцией, который можно передавать в любую часть программы и вызвать с нужными аргументами. Кроме того лямбда-выражение связывает функцыю внутри себя с лексическим окружением. То есть лямбда-выражение видит все переменные и методы объекта в котором оно формируется.
Синтаксис лямбда-выражений очень простой состоит из дувух частей
 ([аргументы]) => {[тело функции]}
аргументы можно указывать с типами, но в большинстве случаев это не требуется. Следующая запись будет означать, что создаем лямбда-выражение с двумя аргументами num типа int и name типа string.
 (int num, string name) => {[тело функции]}
Если аргументов нет, тогда запись будет выглядеть так
 () => {[тело функции]}
Если аргумент один и не требуется указывать его тип, то можно опустить круглые скобки сократив запи
 arg => {[тело функции]}
Внутри блока {[тело функции]} можно вставлять любые операции дозволенные в текущем лексическом окружении. Если [тело функции] состоит из одной операции, то можно опустить фигурные
 ([аргументы]) => SomeFunction(...)
В этом случае лямбда-выражение вернет (если требуется) последнее значение со стека. В предыдущей записи это будет результат работы функции SomeFunction(...). А вот такая запись лямбда-выражения вернет а
 (int num, string name) => num
Ну вот и все, можно пользоваться.

воскресенье, 6 марта 2011 г.

Коротко о связываниях (binding) в WPF

Байдинги или привязки в WPF очень обширная тема, очень много статей в интернете рзаного размера побольше и поменьше, иногда попадаются удобные шпоргалки. Тем не менее на освоение этой простой технологии ушло без малого пара месяцев, хотя все очень просто.


Основные учасники привязки

Байндинги WPF связываю два объекта: Источник(Source) и Подписчик(Target). Все взаимодействие между source и target объектами укладывается в элементарную схему

На уровне xaml разметки подписчиком всегда будет контрол к которому прикручивается binding, а истоником будет оставшийся объект. Например:

<TextBlock Text="{Binding ElementName=Person}"/>

Здесь TextBlock является подписчиком, а источником будет некий элемент с именем Person.
Источником  может быть любой объект, главное правильно и корректно сосласться на него. Чаще всего приходится пользоваться такими конструкциями:
  • {Binding} В этом случае источником будет взят у контрола из поля DataContext
  • {Binding ElementName=Person} Привязываемся к контролу из xaml разметки с именем Person
  • {Binding RelativeSource={RelativeSource...}} Такая привязка используется, когда хотят привязаться к объекту по относительной связи. Подробнее здесь и здесь.
Чаще всего приходится привязываться не ко всему объекту, а к его свойствам. Для этого существует поле Path. Используется так же как и в коде. Например у нас есть класс

class Citizen
{
public string Name { get; set; }
public string Surname { get; set; }
public string Patronymic { get; set; }
}

Экземпляр лежит в DataContext контрола и мы хотим отобразить свойство Name, binding будет таким


<TextBlock Text="{Binding Path=Name}"/>

А если хотим вывести на экран длину имени, то это делается вот так

<TextBlock Text="{Binding Path=Name.Length}"/>


Направление привязки

В зависимости от ситуации может оказаться, что binding должен работать не в обе стороны, а в какую-то одну, за это поведение отвечает поле Mode:

<TextBlock Text="{Binding ElementName=Person, Mode=Default}"/>

Mode принемает одно из пяти значений:
  • Mode=TwoWay. Все изменения подписчика будут отправленны источнику и, наоборот, все изменения источника повлияют на подписчика. 

  • Mode=OneWay. Данные будут только браться из источника, но изменения подписчика никак не будут влиять на источник.


  • Mode=OneWayToSource. Данные будут браться от подписчика и устанавливаться источнику.  Будут игнорироваться изменения источника.

  • Mode=OneTime. Значение будет считано один раз из источника, дальнейшие изменения источника и подписчика игнорируются.



  • Mode=Default. Устанавливает в Mode значение по умолчанию, одно из четырех: TwoWay, OneWay, OneWayToSource или OneTime. Надо отметить, что для различных контролов в WPF значение поумолчанию разное.

Зачем нужен INotifyPropertyChanged

После всего вышенаписанного осталось непонятно, как источник сообщает о том, что он  изменился. Для этого достаточно отнаследовать класс модели от интерфейса INotifyPropertyChanged. Класс Citizen будет выглядеть примерно так


class Citizen : INotifyPropertyChanged
{
string _name;
public string Name
{
get
{
return _name;
}
set
{
_name = value;
OnPropertyChanged("Name");
}
}

//Surname и Patronymic будут переписаны аналогично Name
public string Surname { ... }
public string Patronymic { ... }

public event PropertyChangedEventHandler PropertyChanged;

protected void OnPropertyChanged(string propertyName)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}

Теперь когда мы изменим поле Name будет вызвано событие о том, что в этом объекте поле Name изменилось. По этому сообщению binding поймет в какой момент надо будет обновить интерфейс.


Когда изменения от подписчика дойдут до источника

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

  • UpdateSourceTrigger=PropertyChanged. Источник получит новое значение сразу же после изменения подписчика. Далеко не всегда удобный вариант. Например, если мы изменяем текст, то источнику будет присваиваться новое значение всякий раз когда мы вводим/удаляем символ. 
  • UpdateSourceTrigger=LostFocus. Все изменения передадутся источнику после того, как подписчик потеряет фокус. 
  • UpdateSourceTrigger=Default. В этом случае нужно будет лезть в документацию и смотреть какое значение ставиться. Совершенно бесполезное значение, почти всегда устанавливается как UpdateSourceTrigger=PropertyChanged
  • UpdateSourceTrigger=Explicit. Источник узнает об изменении подписчика только после вызова метода UpdateSource. Для этого необходимо иметь binding не в xaml разметке, а непосредственно в коде. подробнее рассказывать нехочу, так как используется редко. Здесь написано подробнее.


Конверторы в привязке

Совершенно прозрачна работа привязок с простыми типами данных (int, double, string). Однако этого недостаточно. Например, мы работаем с валютными операциями. По какой-то причине храним все в рублях, но пользователю нужно выдавать сумму в долларах. Привязки в WPF позволяют конвертировать сырые значения из модели в требуемые для представления. Для этого достаточно написать класс-наследник интерфейса IValueConverter. А потом подключить его в xaml разметке. Интерфейс IValueConverter требует обязательного наличия в классе двух методов.


object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)


Отвечает за преобразование значения из источника в нужный подписчику вид. И второй

object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)


Отвечает за обратное преобразование, значение от подписчику переводит в корректный для источника формат. На картинке это выглядит так


Для примера, впростой реализации конвертер валюты из рублей в доллары будет выглядеть так

class RublesToDollarsConverter : IValueConverter

{
//такой курс долллара был на момент написания поста
const double RublesPerDollar = 28.1717;

#region IValueConverter Members

public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is double)
{
return (double)value / RublesPerDollar;
}
return value;
}

public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is double)
{
return (double)value * RublesPerDollar;
}
return value;
}

#endregion
}


Подключить конвертер можно несколькими способами. Самый простой путь: добавить конвертер в список ресурсов xaml разметки контрола и задать этому ресурсу имя, например "ManeyConverter". Теперь там где мы хотим пользоваться конвертером в binding добавляем поле Converter={StaticResource ManeyConverter}. Привязка будет выглядеть примерно так:


{Binding ... Converter={StaticResource ResourceKey=}}.

Вот совершенно чудесная статья по конверторам.


Проверка корректности данных


Тут я ничего писать не буду, ибо в этой статье все прекрасно и коротко описано.

Ложка дегтя

Неcмотря на всю прелесть связываний, есть очень существенное ограничение. При формировании связывания нельзя ссылаться на динамические объекты. То есть внутри биндинга нельзя пользоваться еще одним биндингом или DynamicResource. Однако, это ограничение частично можно обойти через StaticResource, но об этом потом.

пятница, 4 марта 2011 г.

Вкратце об MVVM

О паттерне MVVM написано достаточно много статей тут и там, а еще здесь. Я постараюсь кратко на простом примере пояснить суть паттерна.

Чем же MVVM особенный?

MVVM очередной вариант классического паттерна MVC, создавался компанией Microsoft специально для WPF.  Про отличия MVC, MVP и MVVM можно почитать в этой статье. В MVVM приложение делится на три части: Model, View, View-Model.

Модель (Model) содержит в себе всю логику приложение, при чем модель ничего не знает о том как ее будут использовать. Здесь не должно быть ничего лишнего, особенно это касается событий вроде "Модель изменилась - пора обновить графический интерфейс". В этом случае модель получается до ужаса простой, так как программисту не надо заботиться о дизайнере и наворачивать ненужные конструкции. Более того, модель проще тестировать.

VM (View-Model) прослойка, перегоняющая функционал модели в удобный для представления вид. А так же VM должна оповещать представление о том, что модель изменилась.

Представление (View) - обычный GUI. Тут надо отметить, что представление ничего не знает ни о модели, ни о VM. То есть, представление никак не связано с VM напрямую. То есть получается следующая связь:

Такое возможно только при наличии дусторонних связок, которые бы неявным образом синхронизировали поля у представления и VM. В WPF вкачестве таких связок будут байндинги (binding).

Давайте теперь разберем патерн на простеньком примере.

Надуманный пример. 

Сделаем список заметок, который можно редактировать. Добавлять и удалять записи НЕЛЬЗЯ, для этого нужны коммады, о них я расскажу в одном из следующих постов. У заметки будет название (Title) и описание (Description). Итак идем попорядку


Модель


class NoteModel
{
public String Title { get; set; }
public String Description { get; set; }

public NoteModel(string title = "untitled", string description = null)
{
Title = title;
Description = description;
}
}


View-Model


Все VM являются наследниками INotifyPropertyChanged. Этот интерфейс необходит для оповещения презентера обо всех изменениях в моделе.


class NotePresenter : INotifyPropertyChanged
{
NoteModel _model;

public string Title
{
get
{
return _model.Title;
}
set
{
_model.Title = value;
OnPropertyChanged("Title");
}
}

public string Description
{
get
{
return _model.Description;
}
set
{
_model.Description = value;
OnPropertyChanged("Description");
}
}

public NotePresenter(NoteModel model)
{
if (model == null) throw new ArgumentNullException("model");
_model = model;
}

#region INotifyPropertyChanged Members

public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string fieldName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(fieldName));
}
}

#endregion
}


Презентер

Особой красоты наводить не буду


<Window x:Class="Testing_MVVM.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Testing MVVM" Height="350" Width="525"
>
    <Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
Grid.ColumnDefinitions>
<ListBox Grid.Column="0" ItemsSource="{Binding Notes}" x:Name="ListOfNotes">
<ListBox.ItemTemplate>
<DataTemplate>
<Border CornerRadius="5" BorderBrush="Blue" Margin="5">
<StackPanel>
<TextBlock Text="{Binding Title}"/>
<TextBlock Text="{Binding Description}"/>
StackPanel>
Border>
DataTemplate>
ListBox.ItemTemplate>
ListBox>
<ContentControl Grid.Column="1" Content="{Binding ElementName=ListOfNotes, Path=SelectedValue}"
HorizontalAlignment="Center" VerticalAlignment="Center">
<ContentControl.ContentTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*" MinWidth="100"/>
Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="Title" 
  VerticalAlignment="Center"/>
<TextBox Grid.Row="0" Grid.Column="1" 
Text="{Binding Title, UpdateSourceTrigger=PropertyChanged}"/>
<TextBlock Grid.Row="1" Grid.Column="0" Text="Description" 
  VerticalAlignment="Center"/>
<TextBox Grid.Row="1" Grid.Column="1" 
Text="{Binding Description, UpdateSourceTrigger=PropertyChanged}"/>
Grid>
DataTemplate>
ContentControl.ContentTemplate>
ContentControl>
Grid>
Window>

И гланое .cs  файл привязанный к разметке


public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();

//по хорошему презентер должен создаваться и устанавливаться в другом месте
//или передаваться в конструктор
DataContext = new NotesListPresenter(
new NoteModel("Утро", "Еле встал"),
new NoteModel("Обед", "отлично поел"),
new NoteModel("Вечер", "Смотрел футбол"),
new NoteModel("Ночь", "Сладко-сладко поспал"));
}
}

Скриншот


В левой части мы выбираем запись, в правой редактируем, при этом поля изменяются одновременно.


Итог


В разметке xaml нет упонимания ни о моделе, ни о VM, а  главную VM мы подсовываем интерфейсу в качестве DataContext. Главное не запутаться в DataContext-ах. В целом мы добились нужной связи.
Исходники лежат здесь.

Почему плохо использовать lock(this) в C# ??


На днях на в фирме встал вопрос "Почему нельзя использовать lock(this) для синхронизации потоков?". На мелкомягком MSDN про это пишут так:

lock (this) может привести к проблеме, если к экземпляру допускается открытый доступ.
Возьмем простенький пример:

У нас есть некоторый объект, который асинхронно(в отдельном потоке) проверяет сам себя и каждые 3 секунды выдает на консоль сообщение "Все в порядке...продолжаю диагностику". Чтобы было поинтереснее, дадим нашему объекту имя и количество денег.

public class AutoTestingObj
{
protected String m_name;
protected int m_cash;

public String Name
{
  get
    {
       return m_name;
    }
    set
    {
       m_name = value;
    }
}

public int Cash
{
  get
    {
       return m_cash;
    }
    set
    {
       m_cash = value;
    }
}
}


Теперь добавим логику тестирования в отдельном потоке. При этом нужно как-то фиксировать переменные m_cash и m_name, чтобы никто из другого потока их не изменил во время тестирования. На первый взгляд логичнее и проще всего залочить весь объект и да будет на счастье. Код выглядит так:

public class AutoTestingObj
{
Thread m_thread;

protected String m_name;
protected int m_cash;

public String Name
{
  get
{
    return m_name;
    }
    set
    {
       m_name = value;
    }
}

public int Cash
{
  get
    {
       return m_cash;
    }
    set
    {
       m_cash = value;
    }
 }
public AutoTestingObj()
 {
    m_thread = new Thread(this.TestSystem);
}

public void Start()
{
  m_thread.Start();
}

public void TestSystem()
{
  for (; ; )
    {
       //лочим весь объект, чтобы никто не изменил
       lock (this)
       {
          //проводим какое-то тестирование в течении 3 секунд
          Thread.Sleep(3000);
          Console.WriteLine("Все в порядке...продолжаю диагностику");
       }
    }
}
}

Вроде бы все прекрасно. Обезопасили объект от многопоточного произвола. Правда мы не заметили нескольких неприятных моментов.
  1. Мы заблокировали объект для других потоков. Если этот объект понадобиться в другом потоке, надо будет ждать пока объект освободиться. Не очень удобно. Логичнее было бы добиться запрета на изменение(запись) полей, а чтение разрешить.
  2. Мы обезопасили объект только в случаем, когда везде перед переменной ставим lock(...). Вообще говоря, ничего мы этим способом не обезопасили, свойства Name и Cash легко можно изменить из любого потока обратившись к ним вне lock блока.
  3. Самая интересная причина. Если другой наш коллега захочет использовать экземпляр класса для синхронизации потоков, его ожидает бооольшой сюрприз. Поясню суть на примере. Написана вот такая программа

static void Main(string[] args)
{
AutoTestingObj inst = new AutoTestingObj();
inst.Start();

Thread.Sleep(100);

Console.WriteLine("Запланированна работа на 5 секунд");

DateTime start = DateTime.Now;
lock (inst)
{
  Thread.Sleep(5000);
}
TimeSpan delta = DateTime.Now - start;

Console.WriteLine("Работа выполнялась {0} секунд", delta.TotalSeconds);
}


Сколько выполнялась работа? Ответ: примерно 7.9 секунды(думаю, понятно откуда такой результат). А чтобы все было совсем хорошо, представим что у автора программы нету исходников нашего класса AutoTestingObj. Не хотел бы я оказаться в таком положении.

    Чтобы избежать этих трех неприятностей лучше действовать по хорошо известной схеме: залачивать можно только private члены класса. Например так:

    public class Locker { }

    public class AutoTestingObj
    {
    Thread m_thread;

    private Locker m_locker; //Locker-экземпляр любого класса(не путать со struct)

    protected String m_name;
    protected int m_cash;

    public String Name
    {
      get
        {
           return m_name;
        }
        set
        {
           lock (m_locker)
           {
              m_name = value;
           }
        }
    }

    public int Cash
    {
      get
        {
           return m_cash;
        }
        set
        {
           lock (m_locker)
           {
              m_cash = value;
           }
        }
    }

    public AutoTestingObj()
    {
      m_locker = new Locker();
        Name = "No name";   
        m_thread = new Thread(this.TestSystem);
    }

    public void Start()
    {
      m_thread.Start();
    }

     public void TestSystem()
    {
      for (; ; )
        {
           lock (m_locker)
           {
              Thread.Sleep(3000);
              Console.WriteLine("Все в порядке...продолжаю диагностику");
           }
        }
    }
    }

    Все 3 проблемы решили. Но стоит помнить, что присвая новое значение свойству Name или Cash поток будет ждать пока освободиться m_locker.

    воскресенье, 20 июня 2010 г.

    OpenCV + XCode

    Про то, что такое OpenCV можно почитать здесь http://ru.wikipedia.org/wiki/OpenCV.
    Сегодня попытался подключить OpenCV 2.1 к XCode 3.2.1. Оказалось, что есть много подводных камней, про которые мало где пишут. Существует, по крайней мере 2 способа.
    Способ раз.
    Проще всего скачать primery framework например отсюда http://www.ient.rwth-aachen.de/cms/opencv/. Копируем OpenCV.framework в папку /Library/Frameworks. На самом деле это не обязательно. В XCode создадим новый Command Line Tool проект, язык программирования выбираем C++ stdc++
    Назовем проект FirstOpenCV. Жмем правой кнопкой мыши на проект, выбираем Add -> Existing Framework
    Выбираем в списке OpenCV.framework. Если его нет в списке, щелкаем по кнопке “Add other ...” вручную находим OpenCV.framework и жмем Add. В общем-то и все))) библиотека подключена и можно ей пользоваться. Хедеры подключаем вот так
    #include "OpenCV\нужный_хедер.h"
    Казалось бы все просто и логично, однако есть большая такая ложка неприятностей. Возмем простенькую функцию для просмотра видео.
    void ShowVideo(char* fileName)
    {
            cvNamedWindow(videoWindowName, CV_WINDOW_AUTOSIZE);
            CvCapture* capture = cvCreateFileCapture(fileName);
            
            IplImage* frame;
            for (; ; )
            {
                    frame = cvQueryFrame(capture);
                    if (!frame) break;
                    cvShowImage( videoWindowName, frame );
                    char c = cvWaitKey(33);
                    if (c==27) break;
                    printf("%c", c);
            }
            cvReleaseCapture(&capture);
            cvDestroyWindow(videoWindowName);
    }
    Проблема 1: При запуске видео очень дергается и тормазит, не знаю с чем это связано, скорее всего так framework написан.
    Проблема 2: Видео крутится ПО КРУГУ. То есть не срабатывает проверка
                    if (c==27) break;
    Более того, когда приглядимся поближе заметим, что функция
                    cvWaitKey(33);
    всегда возвращает -1.
    Проблема 3: Нельзя собрать проект под x64 архитектуру.
    Глубже я не копал, возможно есть еще глюки у этого способа (скорее всего). Так что мне пришлось отказаться от этого способа и искать более изощренный.
    Способ два.
    Описание этого способа можно найти здесь. Вкратце, качаем macports (очень полезный инструмент, много раз выручал). Набираем с командной строке
    sudo port selfupdate
    sudo port install opencv
    не забываем вводить пароль администратора, если потребуется. Ждем, пока macports скомпилят OpenCV и все что для них нужно. На этом установка завершена. Чтобы прикрутить библиотеку к проекту нужно сделать много магических манипуляций.
    Первое: Добавим нужные библиотеки. В меню Project->Add To Project идем в папку
    /opt/local/lib/ (или /usr/local/lib/)
    помечаем там следующие библиотеки
    libcxcore.dylib
    libcvaux.dylib
    libcv.dylib
    libhighgui.dylib
    libml.dylib
    жмем add. Следим, чтобы галочка у “Copy items to destination group`s older(if needed)” была снята. Снова жмем Add.
    Второе: Идем в настройки проекта (помечаем проект, жмем info). Идем на вкладку build. Среди весьма внушительного списка параметров ищем “Header Search Paths” (используйте строку поиска). Вводим туда
    /opt/local/include/opencv/ (или /usr/local/include/opencv/ зависит от того куда встал OpenCV)
    Третье: из графы “Valid Architectures” оставляем только x86_64
    Четвертое(самое неприятное): Проект может собираться только под ТЕКУЩУЮ архитектеру ситемы. То есть, если активен 64 разрядный режим то и собирать надо в 64 разрядном режиме!!! Если 32 битный, собираем в 32 разрядном. Если ошибемся с разрядностью, то будут вылазать левые ошибки в большом количестве, например таких
    Еще один не понятный момент, окошки, которые создает OpenCV почему-то открываются через X11. Зато все отрабатывает без тормазов и правильно.