суббота, 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. Но об этом чуть-чуть попозже :).

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

Комментариев нет:

Отправить комментарий