суббота, 19 декабря 2009 г.

SilverNotes: Drag'n'Drop + Print + RightClick

Привет, Друзья!

Сегодняшний пост посвящен сразу трем новинкам Silvelright 4.0: Printing, Drag'n'Drop и Right Click.

Надо отдать должное разработчикам Silverlight: они сделали настолько простой API для рассматриваемых новинок, что их описание влезло бы в два-три твита (интересно, твит - это уже стандартизированная единица размера информации :)? ).

В сумме эта тройка позволила мне за пару часов написать маленькое, но полезное приложение: SliverNotes (исходники, демо - для демо нужен 4-й сильверлайт). Эта программка позволяет сделать из обычного листка формата A4 карманную книжечку на 8 министраниц. Формат и содержание каждой странички определяется пользователем. Например, это может быть список покупок, подготовленный любимой женой забывчевому мужу, или же каждая страница может быть нотоносцем для талантливого музыканта, или... все что захотите. Вот пример распечатанной карманной книжки:



Итак к делу! (Иначе я рискую сделать введение самой длинной и сложной частью этого поста :)...)

Печать


Все, что нужно от разработчика для вывода содержимого на печать, это вызов функции Print() у объекта типа PrintDocument:
// PrintDocument - тот, кто нам нужен для печати.
var doc = new PrintDocument { DocumentName = "Notes" };

doc.PrintPage += (o, e) =>
                   {
                     e.PageVisual = uie;
                     e.HasMorePages = false;
                   };

doc.Print();
В обработчике события PrintPage  мы говорим Silverlight'у что именно нужно напечатать (e.PageVisual = uie). Здесь uie - это объект типа UIElement. Если бы мы хотели распечатать больше, чем одну страницу, то свойство e.HasMorePages нужно установить в true.

Чуть-чуть деталей

  • Если элемент, который вы выводите на печать "обрезается" родительским контейнером, он будет обрезанным и на бумаге. Чтобы это обойти, просто измените родительский контейнер, например, на Canvas.
  • Если вы применяете трансформации прямо к элементу, который служит PageVisual'ом, то они не будут применены на печати. Чтобы это обойти, устанавливайте трансформации ниже по дереву: вместо самого PageVisual'a, трансформируйте его первого ребенка (жуть-то какая :) ).
  • PageVisual не обязательно должен находиться в Visual Tree. Это может быть просто объект в памяти.
  • Инициировать печать можно только по запросу пользователя (например, в обработчике Click'a). Эй, а где еще его можно инициировать-то, умник? - Ну, у меня было желание стартовать печать в событии Loaded, но Silverlight пожелал мне удачи в виде приветливого Exception'a. Даже если обработчик Loaded - это лямбда в обработчике Click, вы получите исключение...

Drag'n'Drop


Эта новинка позволяет пользователям перетаскивать в плагин [пока только] файлы из операционной системы. Стоит ли говорить, на какой уровень это повышает UX (user experience)? . Фантазия - ваш предел. В SilverNotes вы можете перетащить на пустую страничку картинку, тем самым сделав свой шаблон для печати:


1. Слева - окошко Windows Explorer'a. Я схватил фотографию леса из моего родного города Лебедина, и тащу ее на SilverNotes. Превью фото делает Explorer.

2. Теперь указатель мышки находится над пустой страничкой SilverNote's. Я замечаю это в событии DragEnter, и начинаю анимировать цвет фона, как бы говоря: "Давай, бросай!"
 
3. Ну и, наконец, я отпускаю левую кнопку, и фото оказывается в Silverlight приложении...

Для того, чтобы разрешить бросать файл на UIElement, достаточно установить свойство AllowDrop="True". После этого у UIElement'a будут срабатывать обработчики событий DragEnter, DragLeave, DragOver и Drop. Вот пример кода, который считывает брошенную картинку на UIElement с установленным AllowDrop'ом:

private void DoDrop(object sender, DragEventArgs e)
{
  if (e.Data == null)
  {
    return; // И почему мы так не любим nulls?
  }
  
  // Куда именно это мы бросаем?
  Point dropPosition = e.GetPosition(_cnvDropTarget);

  // Ну-ка, что нам тут подбросили?
  var files = e.Data.GetData(DataFormats.FileDrop) as FileInfo[];

  foreach (var file in files)
  {
    using (var stream = file.OpenRead())
    {
      // Обрабатываем файл:
      ProcessFileContent(stream, dropPosition);
    }
  }
}

Чуть-чуть деталей

  • Не пытайтесь получить доступ к данным во время событий DragEnter/DragLeave. Только разочарование и SecurityException ждут вас. Правильное место для считывания данных - обработчик события Drop.
  • Не пытайтесь перетаскивать файл на Silverlight приложение, с установленным windowless режимом. Также проблематичным будет использование Drag'n'Drop под Маками (см. детали).

ПКМ ака RightClick


О поддержке правого клика в сильверлайт приложениях раньше приходилось только мечтать (или извращаться с html'ем поверх сильверлайта). Теперь же, все что нужно сделать - это подписаться на событие MouseRightButtonDown (или MouseRightButtonUp). Если вы хотите показать свое контекстное меню по правому клику, то нужно... написать свое контекстное меню и показать его. Что?! В сильверлайте нет стандартной реализации класса ContextMenu? - Да, покамест нет. Но не удивлюсь, если с выходом четверки (не беты) оно там появится. Пока же Microsoft предлагает пользоваться классом Popup, и в него встраивать контекстное содержимое.

Для демонстрации на IT Jam'e мы воспользовались кодом Jesse Bishop'a, и чуть-чуть изменили его, сделав более похожим на стандартное WPF меню:



В SilverNotes контекстное меню для Grid'a устанавливает следующий код:

<Grid x:Name="LayoutRoot" Background="Transparent">
  <menu:ContextMenu.ContextMenu>
 <menu:ContextMenu>
   <menu:MenuItem Click="OnDeleteClick">
  <menu:MenuItem.Content>
    <StackPanel Orientation="Horizontal">
   <Image Margin="0, 0, 8, 0" Source="/SilverNotes;Component/Resources/delete.png" Width="16" Height="16"/>
   <TextBlock Text="Delete"/>
    </StackPanel>
  </menu:MenuItem.Content>
   </menu:MenuItem>
 </menu:ContextMenu>
  </menu:ContextMenu.ContextMenu>
  <!-- Содержимое Grid. -->
</Grid>

Вы также можете пользоваться и улучшать код проекта, скачав исходники SilverNotes.

Чуть-чуть деталей

  • Чтобы полностью скрыть стандартное меню сильверлайта, не забудьте установить e.Handled = true, в обработчике MouseRightButtonDown.
  • Событие MouseRightButtonUp не прийдет к вам, до тех пор пока вы не установите e.Handled = true в MouseRightButtonDown.

Итак


Все три новинки наглядно показаны в SilverNotes. Я выложил этот проект на Google Code. Если у вас есть желание усовершенствовать проект, дописать шаблоны для печати - добро пожаловать :). Кстати, создавать свои собственные шаблоны в SilverNotes так же просто, как создавать обычный UserControl на Silverlight'e. Шаблоны загружаются при помощи MEF (Managed Extensibility Framework), который так же входит в поставку четвертого Silverlight. Но об этом чуть-чуть попозже :).

Отличного настроения и прекрасного программирования в предновогодние морозы :)!

понедельник, 7 декабря 2009 г.

Webcam Fun

Привет, Друзья!

На всеукраинской сходке айтишников IT Jam, нам с Сергеем Лутаем посчастливилось делать доклад по новинкам четвертого Сильверлайта. Пока обрабатывается видео с презентации, выкладываю код, который мы использовали для демонстрации работы с веб камерой. Кроме кода, мы посмотрим на основы работы с камерой  и на несколько веселых эффектов, которые можно сделать при помощи пиксельных шейдеров.

Исходники к этому посту можно скачать здесь. Живое демо находится здесь. Для запуска демо, вам, кроме камеры, понадобится установить Silverlight 4 Beta 1. Рантайм есть только для разработчиков, но ведь это про вас, верно :)?

Основы


Для старта нам понадобится... камера. Чтобы получить доступ к камере из Silverlight'a, достаточно спросить у пользователя разрешения, и получив оное, начать видеозахват:

private CaptureSource _captureSource;

private CaptureSource CaptureSource
{
  get { return _captureSource ?? (_captureSource = new CaptureSource()); }
}

private void StartCapture()
{
  // Можно мне попользоваться вашей камерой?
  if (CaptureDeviceConfiguration.AllowedDeviceAccess || CaptureDeviceConfiguration.RequestDeviceAccess())
  {
    // Урра! Начинаем захват: 
    CaptureSource.Start();
    
    var brush = new VideoBrush();
    brush.SetSource(CaptureSource);
    VideoBrush = brush; // К этому свойству можно привязать любой Brush: <Border Background="{Binding VideoBrush}" .../>
  }
}
Видео является обычной кисточкой (Brush), а значит, мы можем разукрасить выходом с камеры любой элемент, который можно разукрасить :). Пожалуй, это и все. Просто, не так ли?

Не Основы


К "продвинутым" темам можно отнести сохранение рисунка с камеры. Тут есть два варианта: воспользоваться функцией CaptureSource.AsyncCaptureImage() или сделать снимок UIElement'a, как обычно, при помощи WriteableBitmap'a: new WriteableBitmap(uie, null). Разница в том, что WriteableBitmap фотографирует содержимое со всеми эффектами, а CaptureSource выдаст именно то, что есть на выходе камеры, без трансформаций, эффектов и прочего.

Если есть несколько камер (скорее всего, у вас, как и у меня, их нет, но мало ли :)?), то можно всегда выбрать с какой камеры вести захват: CaptureDeviceConfiguration.GetAvailableVideoCaptureDevices();

Для того, чтобы сохранить WriteableBitmap в Jpeg формат нужно написать свой энкодер, который будет последоватльно, байт за байтом, запаковывать цвета в формат Jpeg. Для этого, конечно, нужно сначала достать спецификацию Jpeg'a, изучить ее, и заимплементить... Шутка :). Все уже написано до нас: библиотека FJCore позволяет легко сохранять jpeg'и. Для просмотра примера использования, загляните в исходники к этому посту.

Спецэффекты


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

Эффект находит границы на рисунке и закрашивает их черным цветом. Все остальное выводится белым. На этой фотке знакомые могут узнать Мишу Чалого :).
С этой фотки из паралельной вселенной дружественный привет землянам шлет головастый и чертовски умный двойник  автора этого блога :).
Этот эффект-страшилка просто инвертирует цвета. Как-то так выглядел мир по ту сторону кольца, когда Фродо надевал его :).
И, наконец, последний эффект, "скрытая фича Sivlerlight 4.0", он же знакомый нам по передачам типа "Прогноз погоды": подмена вывода. Как только цвет точки становится ярче определенного порога, вместа оригинала выводится точка из другого сэмплера (читай - brush'a). Здесь я направил включенный фонарик в ладошку, открывая секретный кусочек лица Билла.

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

Выводы


Пользоваться камерой в Silverlight  4.0 очень легко. Все операции с камерой не требуют  участия сервер-сайда. Можно легко сохранять изображения.

Для тех, кто любит просто читать с конца, еще раз привожу ссылку на исходники и демо. Не забудьте про рантайм: Silverlight 4 Beta 1. Отличного программирования и прекрасного настроения!

ЗЫЖ спасибо всем, кто пришел к нам на презентацию. И, конечно, всем тем, кто поставил за нас "наляпочки" на доске голосования :). Нам было очень приятно!