Введение в Microsoft Silverlight 2

Мультимедиа, рукописный ввод и Deep Zoom

Разбить на страницы
Показывать лекцию целиком

В предыдущих лекциях вы познакомились с основными элементами управления, используемыми для создания насыщенных интерактивных Интернет-приложений. В данной главе будет рассмотрено три элемента управления, предоставляющие специальную функциональность. Сначала будет представлен элемент управления MediaElement, который обеспечивает API для управления аудио- и видеоданными, позволяя тем самым создавать замечательные мультимедийные приложения. Затем вы рассмотрите элемент управления InkPresenter (Элемент отображения рукописного ввода), который позволяет вводить рукописное или рисованное содержимое посредством рукописных примечаний, т.е. обеспечивает возможность использования приложения на компьютерах с перьевым вводом. Наконец, вашему вниманию будет представлен достаточно новый элемент управления Silverlight 2, MultiScaleImage (Масштабируемое изображение), который добавляет в ваши приложения новое измерение - возможность глубокого масштабирования.

Элемент управления MediaElement

Одно из наиболее важных применений Silverlight в Веб - обеспечение поддержки мультимедиа следующего поколения, которое может воспроизводиться на разных платформах. Для этого Silverlight поддерживает элемент управления MediaElement, которому посвящен данный раздел. В нем поэтапно рассматривается процесс создания простого медиаплейера, обеспечивающего возможность прогрессивной загрузки и воспроизведения видеоданных. Кроме того, вы научитесь закрашивать поверхности с помощью видео-кисти, что позволит добавлять интересные графические эффекты. Элемент управления MediaElement поддерживает следующие форматы:

Видео

  • WMV1: Windows Media Video 7
  • WMV2: Windows Media Video 8
  • WMV3: Windows Media Video 9
  • WMVA: Windows Media Video Advanced Profile, не-VC-1
  • WMVC1: Windows Media Video Advanced Profile, VC-1
  • Аудио

  • WMA7: Windows Media Audio 7
  • WMA8: Windows Media Audio 8
  • WMA9: Windows Media Audio 9
  • MP3: ISO/MPEG Layer 3
  • Моно или стерео
  • Частоты дискретизации от 8 до 48 кГц
  • Скорости передачи двоичных данных от 8 до 320 Кбит/с
  • Переменная скорость передачи двоичных данных
  • Кроме этих форматов, элемент управления MediaElement также поддерживает списки воспроизведения ASX и протоколы http, https и mms.

    Если говорить о потоковой передаче видео- и/или аудио, MediaElement поддерживает потоковое вещание и потоковую передачу по запросу от Windows Media Server. Если для URI задан протокол mms, потоковая передача включена; в противном случае, загрузка и воспроизведение файла будут осуществляться путем прогрессивной загрузки, при которой воспроизведение начинается только после заполнения буфера воспроизведения и затем эти два процесса выполняются параллельно.

    Если определен протокол http или https, происходит обратное. MediaElement сначала делает попытку выполнить прогрессивную загрузку, и уже в случае неудачи пытается применить потоковую передачу файла.

    Использование элемента управления MediaElement

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

    Простое воспроизведение видео с помощью элемента управления MediaElement

    Чтобы начать работу с элементом управления MediaElement, добавьте его на свою страницу и задайте в качестве значения его атрибута Source URL видеофайл, который хотите воспроизвести. Рассмотрим пример XAML-кода Silverlight 2 приложения:

    <UserControl 
       xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
       xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
       x:Class="SilverlightApplication5.Page"
       Width="640" Height="480">
      <Grid x:Name="LayoutRoot" Background="White">
        <MediaElement Source="balls.wmv" />
      </Grid>
    </UserControl>

    Это обеспечит автоматическую загрузку и воспроизведение видеоданных. Размер области воспроизведения мультимедиа определяется следующими правилами:

  • Если заданы свойства Height и Width элемента управления MediaElement, MediaElement использует их.
  • Если задано одно из этих свойств, элемент управления MediaElement растянет мультимедиа, сохраняя пропорции изображения.
  • Если не заданы ни Height, ни Width, элемент управления MediaElement будет воспроизводить видео в стандартных размерах. Если они превышают размер области просмотра элемента управления Silverlight, MediaElement обрежет видео соответственно размеру области просмотра.
  • Рассмотрим пример. Используемая в данной лекции (и доступная для скачивания на Веб-сайте компании) видеозапись balls.wmv имеет размер 480 х 360. Если указать MediaElement воспроизводить это видео и не задать его Height и Width, воспроизводимое видеоизображение будет размером 480 х 360. Если у UserControl выставить размер 200 х 200, на экране будет видна только часть видеоизображения размером 200 х 200 пикселов, начиная от верхнего левого угла. Обрезанная таким образом часть видео представлена на рис. 11.1.

    (рис 11.1) Видеоизображение, обрезанное по размеру элемента управления Silverlight

    Размеры элемента управления MediaElement

    Как было показано в предыдущем разделе, размер MediaElement важен при воспроизведении видео. Если размеры элемента управления не заданы, и видеоизображение превышает размеры элемента управления Silverlight, оно будет обрезано.

    Управление шириной и высотой самого MediaElement осуществляется через его свойства Height и Width. При формировании визуального представления элемента управления, мультимедиа будет растягиваться (или сжиматься) так, чтобы поместиться в элемент управления. Если заданный размер элемента управления мультимедиа больше элемента управления Silverlight, изображение будет обрезано размерами элемента управления Silverlight.

    Далее представлен пример элемента управления MediaElement, для которого заданы размеры 200 х 200. На рис. 11.2 можно увидеть, как будет отображаться видео:

    <MediaElement Source="balls.wmv" 
              Height="200" Width="200" />
    (рис 11.2) Отображение видео после задания размеров MediaElement

    Растяжение мультимедиа

    В предыдущем примере видеоизображение сворачивалось до размера элемента управления MediaElement (200 х 200). Как видно на рис. 11.2, размер видеоизображения (исходный размер которого, как вы помните, 480 х 360 пикселов) меняется так, чтобы оно поместилось в MediaElement, но с сохранением его пропорций. Это может привести к появлению черных полей вверху и внизу видеоизображения, создавая эффект просмотра широкоформатного видео. Переопределить это поведение можно с помощью свойства Stretch элемента управления MediaElement. Это свойство принимает четыре разных значения:

  • (рис 11.3) Эффект задания свойству Stretch значения None
  • Uniform Это режим растяжения по умолчанию, он обеспечивает сохранение пропорций видеоизображения и добавление полей вверху, внизу или по бокам в случае необходимости.
  • (рис 11.4) Использование режима растяжения UniformToFill
  • (рис 11.5) Использование значения Fill свойства Stretch
  • Прозрачность видеоизображения

    Регулировать прозрачность MediaElement позволяет свойство Opacity. Это нормализованное свойство, т.е. значение 0 эквивалентно полной прозрачности, 1 - полной непрозрачности, все остальные значения в этом диапазоне представляют разные степени прозрачности. Видеоизображение будет отображаться с заданной прозрачностью, при этом элементы, находящиеся позади элемента мультимедиа на экране, будут видны.

    Далее представлен пример XAML, обеспечивающий отображение красного прямоугольника и MediaElement. Для MediaElement задана прозрачность 0,5, что делает видеоизображение полупрозрачным. Поскольку визуальное представление MediaElement формируется вторым, он размещается ближе в направлении по оси Z и располагается поверх прямоугольника. На рис. 11.6 можно увидеть результат выполнения этого примера.

    <Rectangle Fill="Red" Height="100" Width="200" /> 
    <MediaElement Source="balls.wmv" Height="200" Width="200" 
                       Stretch="Fill" Opacity="0.5" />
    (рис 11.6) Применение свойства Opacity для видеоизображения

    Использование трансформаций с элементом управления MediaElement

    Подробно трансформации рассматриваются в лекц. 5, "Трансформация и анимация в XAML", здесь хотелось бы отметить лишь то, что трансформации могут применяться и к элементу управления MediaElement. При этом отображаемое видео также трансформируется. Это может привести к созданию некоторых очень интересных эффектов. Например, далее представлен MediaElement, к которому применена трансформация наклонение:

    <MediaElement Source="balls.wmv" Height="200" Width="200" 
           Stretch="Fill" >
      <MediaElement.RenderTransform> <SkewTransform AngleX="45"/>
      </MediaElement.RenderTransform> 
    </MediaElement>

    Как это выглядит, можно увидеть на рис. 11.7.

    (рис 11.7) Наклонение видео с помощью SkewTransform

    Расположение содержимого поверх видеоизображения

    Silverlight позволяет размещать содержимое, включая текст и графические элементы, поверх видеоизображения. Это можно сделать либо с помощью свойств ZOrder (Z-порядок) элементов пользовательского интерфейса (UI) (смотрите лекц. 1, "Введение в Silverlight 2"), либо просто размещать элементы UI в том же месте, что и элемент мультимедиа, и объявлять их в XAML после него. Далее представлен пример MediaElement, и Canvas с прямоугольником и текстовым блоком, который перекрывает видеоизображение:

    <MediaElement Source="balls.wmv" Height="200"  
                 Width="200" Stretch="Fill" /> 
    <Canvas Margin="20,140,0,0">
      <Rectangle Fill= "Red" Height="40" Width="160" />
      <TextBlock>Subtitle on Video</TextBlock> 
    </Canvas>

    На рис. 11.8 показано, как это будет выглядеть на экране.

    (рис 11.8) Отображение содержимого поверх видео

    Вырезание мультимедиа геометрическими формами

    В лекц. 4, "Основы XAML" были рассмотрены вырезание и геометрические элементы. Эти возможности также могут применяться к MediaElement, для которого с помощью фигур или контуров можно определить контур вырезания. Например, следующий XAML в качестве контура вырезания для MediaElement определяет эллипс:

    <MediaElement Source="balls.wmv" Height="200" 
                Width="200" Stretch="Fill" >
      <MediaElement.Clip>
        <EllipseGeometry RadiusX="100" RadiusY="75" Center="100,75"/>
      </MediaElement.Clip> 
    </MediaElement>

    Результаты представлены на рис. 11.9.

    (рис 11.9) Вырезание мультимедиа геометрическими формами

    Автоматическое воспроизведение

    По умолчанию, если задан источник, MediaElement автоматически воспроизводит мультимедиа. Этим поведением можно управлять посредством свойства AutoPlay (Автоматическое воспроизведение). По умолчанию его значение true, но его можно переопределить и задать false. Можно воспроизвести мультимедиа позже, используя метод Play (Воспроизвести). Этот и другие методы и события, используемые при программировании элемента мультимедиа, рассматриваются в следующем разделе.

    Управление воспроизведением звука

    С помощью свойства IsMuted (Без звука) элемента управления MediaElement можно определить, будет ли аудиозапись сопровождать воспроизведение или нет. Это свойство Булевого типа, и если задать ему значение true, звук будет отключен.

    Кроме того, с помощью свойства Volume (Громкость) можно управлять громкостью звука. Оно принимает нормализованное значение, где 0 эквивалентно отсутствию звука, 1 - полной громкости, и промежуточные значения представляют относительные уровни громкости. Итак, например, значение 0,43 обозначало бы воспроизведение звука с громкостью, составляющей 43% от полной громкости.

    Наконец, балансом звука можно управлять с помощью свойства Balance (Баланс), которое принимает значения в диапазоне от -1 до +1. Значение -1 приводит к смещению баланса звука полностью влево, т.е. левый динамик будет воспроизводить звук со 100% громкостью, и в правом динамике будет 0% громкости. Значение +1 обеспечивает прямо противоположный эффект - баланс звука будет полностью смещен вправо, при этом правый динамик будет воспроизводить звук со 100% громкостью. Значение 0 обеспечивает равномерное распределение громкости между двумя динамиками.

    Например, свойству Balance задано значение 0,8, тогда правый динамик будет воспроизводить звук с 80% громкостью и левый динамик- с 20%. Если используется значение -0,8, левый динамик будет воспроизводить звук с громкостью 80%, и правый динамик - с громкостью 20%.

    Далее представлен некоторый XAML, определяющий, что звук не выключен, общая громкость составляет 50% и что баланс звука смещен вправо:

    <MediaElement x:Name="vid" Source="balls.wmv" 
                 Height="200" Width="200" Stretch = "Fill" 
                 IsMuted = "False" Volume="0.5" Balance="0.8" />

    Программирование Media Element

    MediaElement предлагает насыщенную модель программирования, которая позволяет управлять воспроизведением методами Play, Stop (Остановить) и Pause (Приостановить). Также вы имеете возможность взаимодействовать с видео, отслеживая процесс буферизации и загрузки, а также отвечать на маркеры, размещенные в видеофайле. Кроме того, можно обработать события для перехвата, такие как поведение мыши.

    Базовые элементы управления воспроизведением

    Основными методами управления видео являются Play, Stop и Pause. Если свойству AutoPlay элемента управления MediaElement задано значение false, для начала воспроизведения видео необходимо применить Play. Даже если AutoPlay задано значение true, и воспроизведение начинается автоматически, с помощью этих методов его можно остановить или приостановить. Далее представлен пример XAML, в котором имеется элемент мультимедиа и три простых элемента управления воспроизведением, реализованных как элементы TextBlock:

    <MediaElement x:Name="vid" Source="balls.wmv" Height="200" 
                 Width = "200" Stretch="Fill" /> 
    <Canvas Canvas.Top="160">
      <Rectangle Fill= "Black" Width = "200" Height="24" Opacity="0.7"/>
      <TextBlock Foreground= "White" Canvas.Left="20">Play</TextBlock>
      <TextBlock Foreground="White" Canvas. Left="80">Stop</TextBlock>
      <TextBlock Foreground="White" Canvas. Left=" 140" >Pause</TextBlock> 
    </Canvas>

    На рис. 11.10 показано, как эти элементы управления будут выглядеть на видеоизображении.

    (рис 11.10) Добавление элементов управления в видеоизображение

    Для создания элементов управления вы задаете имя функции, которая должна выполняться в ответ на событие мыши, используя атрибут самого элемента управления видео. Намного более подробно обработка событий Silverlight с помощью JavaScript рассматривается в лекц. 6, "Элемент управления браузера Silverlight". Однако, в данном случае, просто требуется начать, остановить или приостановить воспроизведение видео, когда пользователь щелкнет соответствующий текстовый блок. Это реализуется путем обработки события MouseLeftButtonDown текстового блока, которое ассоциируется с функциями, воспроизводящими, приостанавливающими или останавливающими воспроизведение.

    При программировании на JavaScript необходимо обеспечить, чтобы эти функции были доступны со страницы, на которой размещается элемент управления Silverlight. Итак, они могут быть реализованы либо путем использования элементов JavaScript на странице, либо в файле js, который включен в страницу с помощью элемента JavaScript, свойству Src которого задано соответствующее значение.

    Если для сведения воедино содержимого XAML используется Expression Blend, он обеспечивает для Page.xaml псевдо-файл выделенного кода Page.xaml.js. Это идеальное место для реализации функциональности JavaScript.

    Создадим проект Silverlight 1.0 Site. Далее представлен XAML, описывающий похожий UI, как был создан в предыдущем примере, но с объявлениями обработчиков событий:

    <Canvas
      xmlns="http://schemas.microsoft.com/client/2007"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      Width="640" Height="480" Background="White" x:Name="Page">
      <MediaElement x:Name="vid" Source="balls.wmv"
                    Height="200" Width="200" Stretch="Fill" />
      <Canvas Canvas.Top="160">
        <Rectangle Fill="Black" Width="200" Height="24" Opacity="0.7"/>
        <TextBlock MouseLeftButtonDown="doPlay"
                   Foreground="White"
                   Canvas.Left="20">Play</TextBlock>
        <TextBlock MouseLeftButtonDown="doStop"
                   Foreground="White"
                   Canvas.Left="80">Stop</TextBlock>
        <TextBlock MouseLeftButtonDown="doPause"
                   Foreground="White"
                   Canvas.Left="140">Pause</TextBlock>
      </Canvas>
    </Canvas>

    Теперь можно написать кода на JavaScript для начала, остановки и приостановки воспроизведения. Вот этот код:

    function doPlay(sender, args) 
    {
      var meVid = sender.findName("vid");
      meVid.Play(); 
    }
    function doStop(sender, args) 
    {
      var meVid = sender.findName("vid");
      meVid.Stop(); 
    }
    function doPause(sender, args) 
    {
      var meVid = sender.findName("vid");
      meVid.Pause(); 
    }

    Когда функция JavaScript определяется как обработчик события, она должна принимать два параметра. Первый, sender, - это объект, который формирует событие. Второй, args, содержит аргументы, которые включаются как часть события.

    Теперь в этой JavaScript-функции необходимо получить ссылку на объект MediaElement, управление которым осуществляется. Выполняется это с помощью метода findName объекта sender. Хотя отправителем является текстовый блок, который щелкнул пользователь, выполнение его метода findName все равно обеспечит выполнение поиска по всему XAML-документу до тех пор, пока не будет найден элемент vid (который был создан путем применения атрибута x:Name к нашему объекту MediaElement ). Если вернуться к XAML, можно увидеть, что в MediaElement был элемент x:Name со значением vid, так что поиск должен быть успешным. Ссылка на соответствующий объект MediaElement будет сохранена в переменной JavaScript meVid. Теперь для управления воспроизведением осталось просто вызывать методы Play, Stop или Pause для этого объекта.

    При создании проекта приложения для Silverlight 2 и использовании .NET все даже еще проще: не надо использовать findName для доступа к элементу, и ему уже присвоено имя vid, так что код будет выглядеть так:

    private void doPlay(object sender, MouseButtonEventArgs e) 
    {
      vid.Play(); 
    }
    private void doPause(object sender, MouseButtonEventArgs e) 
    {
      vid.Pause(); 
    }
    private void doStop(object sender, MouseButtonEventArgs e) 
    {
      vid.Stop(); 
    }

    Управление буферизацией и загрузкой

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

    Когда буфер полон на 100%, начинается воспроизведение. Воспроизведение может прерываться при изменении состояния связи и дозагрузки буфера. Silverlight позволяет отслеживать это поведение с помощью события BufferingProgressChanged (Показатель процесса буферизации изменился) и свойства BufferingProgress (Показатель процесса буферизации). Используя эти событие и свойство, можно показывать пользователям текущее состояние буферизации или выполнять логику для улучшения взаимодействия с пользователем (UX) как результат условий буферизации. Например, в условиях плохого соединения буферизация может не превышать 50%. Вы можете перехватить это значение и предоставить соответствующую обратную связь своему клиенту.

    Для управления буферизацией задайте в MediaElement обработчик событий, определяющий функцию для обработки события BufferingProgressChanged:

    <MediaElement x:Name="vid" Source="balls.wmv" Height="200" Width="200"
    Stretch="Fill" Buffering Prog ressChanged = "doBuff"/> <TextBlock x:Name="txtBuff"></TextBlock>

    Таким образом, функция doBuff будет выполняться каждый раз при изменении наполненности буфера. Это событие тесно связано со свойством BufferingProgress. Данное свойство принимает значения в диапазоне от 0 до 1, где 0 соответствует пустому буферу, и 1 - полному. Событие будет формироваться при изменении наполнения буфера на 5% (т.е. 0,05) или более и когда буфер заполнен полностью.

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

    Сначала JavaScript-версия:

    function doBuff(sender, args) 
    {
      var theText = sender.findName("txtBuff");
      var meVid = sender.findName("vid");
      var prog = meVid.BufferingProgress * 100;
      prog = "Buffering % " + prog;
      theText.Text = prog; 
    }

    И вот, как это было бы сделано в выделенном коде .NET (на C#):

    private void doBuff(object sender, RoutedEventArgs e) 
    {
      double prog = vid.BufferingProgress * 100;
      txtBuff.Text = "Buffering % " + prog; 
    }

    Если у вас не видно текст с отображением буферизации в Silverlight-приложении, обратите внимание на используемый элемент компоновки. Если вы использовали <Grid>, текст может находиться под "кнопками" Play, Stop и Pause. В этом случае задайте для текстовых блоков небольшой отступ с помощью свойства Margin.

    Можно переопределить автоматическую буферизацию, задавая конкретную продолжительность буферизованного видео. Например, если необходимо всегда иметь 10-секундный буфер видео и, таким образом, снизить риск прерывания воспроизведения из-за повторной синхронизации буфера при ухудшении состояния связи, можно задать свойство BufferingTime (Размер буфера). В качестве его значения задается временной диапазон. Так, например, чтобы задать 10-секундный буфер, BufferingTime задается значение 0:0:10, как показано в следующем примере:

    <MediaElement x:Name="vid" Source="balls.wmv"
              Height="200" Width = "200" Stretch="Fill"
              BufferingProgressChanged = "doBuff"
              BufferingTime="0:0:10"/> 
    <TextBlock x:Name="txtDown"></TextBlock>

    Если прогрессивная загрузка недоступна или не поддерживается, придется загружать весь видеофайл целиком и только потом начинать его воспроизведение. В этом случае событие DownloadPro-gressChanged и свойство DownloadProgress могут использоваться для получения состояния загрузки. Принципы работы с ними аналогичны функциям буферизации. Дале представлен XAML, определяющий событие DownloadProgressChanged:

    <MediaElement x:Name="vid" Source="balls.wmv"
               Height="200" Width="200" Stretch="Fill"
               BufferingProgressChanged = "doBuff"
               BufferingTime="0:0:10"
               DownloadProgressChanged = "doDown"/>
    <TextBlock x:Name="txtDown"></TextBlock>

    И теперь код функции doDown, обрабатывающей это событие. Опять же, сначала рассмотрим JavaScript-версию:

    function doDown(sender, args) 
    {
      var theText = sender.findName("txtDown");
      var meVid = sender.findName("vid");
      var prog = meVid.DownloadProgress * 100;
      prog = "Downloading % " + prog;
      theText.Text = prog; 
    }

    И BOT.NET-версия:

    private void doDown(object sender, RoutedEventArgs e) 
    {
     double prog = vid.DownloadProgress * 100;
     txtBuff.Text = "Downloading % " + prog;
    }

    Управление текущим состоянием видео

    Silverlight представляет свойство CurrentState (Текущее состояние) и связанное с ним событие Cur-rentStateChanged (Текущее состояние изменилось), которое может использоваться для ответа на изменение состояния мультимедиа.

    Действительными состояниями свойства CurrentState являются:

  • Buffering ( Буферизация ) Буфер не заполнен на 100%, поэтому воспроизведение приостановлено до полного заполнения буфера.
  • Closed ( Закрыто ) Мультимедиа закрыто.
  • Error ( Ошибка ) Возникла проблема с загрузкой, буферизацией или воспроизведением мультимедиа.
  • Opening ( Открывается ) Мультимедийный файл найден, и буферизация или загрузка должны вот-вот начаться.
  • Paused ( Приостановлено ) Воспроизведение приостановлено.
  • Playing ( Воспроизводится ) Идет воспроизведение мультимедиа.
  • Stopped ( Остановлено ) Воспроизведение остановлено.
  • Вот как событие CurrentStateChanged элемента управления MediaElement определяется в XAML:

    <MediaElement x:Name="vid" Source="balls.wmv" 
              Height="200" Width="200" Stretch="Fill" 
              CurrentStateChanged= "doState" BufferingTime="0:0:10" />

    Здесь задается функция doState, которая будет вызываться в ответ на изменение текущего состояния. Далее представлен пример такой JavaScript-функции, которая использует значение свойства Cur-rentState элемента управления MediaElement в окне уведомления:

    function doState(sender, args) 
    {
      var meVid = sender.findName("vid");
      alert(meVid.CurrentState); 
    }

    В C#, вместо окна уведомления MessageBox, для вывода содержимого свойства CurrentState на экран можно использовать TextBlock:

    private void doState(object sender, RoutedEventArgs e) 
    {
      txtBuff.Text = vid.CurrentState.ToString(); 
    }

    Управление положением курсора воспроизведения

    Для управления текущим положением курсора воспроизведения используются свойства NaturalDuration (Номинальная продолжительность) и Position (Положение) элемента мультимедиа. После того, как свойство CurrentState устанавливается в значение Opened, становится возможным получение значения и свойства NaturalDuration. Продолжительность видео в секундах можно получить с помощью Natu-ralDuration.Seconds и потом преобразовать это значение в часы, минуты и секунды.

    В этом примере событие CurrentStateChanged элемента управления MediaElement ассоциировано с функцией doState (из предыдущего примера). Однако теперь эта функция перехватывает значение свойства NaturalDuration. В JavaScript-версии используется вспомогательная функция convertDT для преобразования возвращаемого значения в строку. Рассмотрим код на JavaScript:

    function doState(sender, args) 
    {
       var meVid = sender.findName("vid");
       var txtStat = sender.findName("txtStat");
       var datetime = new Date(0, 0, 0, 0, 0, meVid.naturalDuration.Seconds)
       durationString = convertDT(datetime);
       txtStat.Text = durationString.toString(); 
    }
    
    function convertDT(datetime) 
    {
      var hours = datetime.getHours();
      var minutes = datetime.getMinutes();
      var seconds = datetime.getSeconds();
      if (seconds < 10)  {
        seconds = "0" + seconds;
      }
      if (minutes < 10) {
        minutes = "0" + minutes; 
      }
      var durationString; 
      if (hours > 0) {
        durationString = hours.toString() + ":" + minutes + ":" + seconds; 
      } else {
        durationString = minutes + ":" + seconds; 
      }
      return durationString; 
    }

    Далее следует замечательный пример того, как .NET Framework в Silverlight 2 существенно упрощает разработку: все это может быть реализовано одной строкой кода на C#:

    private void doState(object sender, RoutedEventArgs e)
    { txtStat.Text = vid.NaturalDuration.ToString();
    }

    Вот XAML, В котором используется представленная выше функция:

    <Canvas x:Name="sample9" Opacity="1"> 
      <MediaElement x:Name="vid" Source="balls.wmv" 
              Height="200" Width="200" Stretch = "Fill" 
              CurrentStateChanged="doState" BufferingTime="0:0:10" /> 
      <TextBlock x:Name="txtStat">
      </TextBlock> 
    </Canvas>

    Получить текущее положение курсора воспроизведения можно с помощью свойства Position. В этом примере позиция выводится в текстовом блоке при приостановке воспроизведения (Для тестирования можете оставить в XAML-коде три "кнопки" Play, Stop и Pause и соответствующие им обработчики событий doPlay и doStop с предыдущего примера, но закомментировать тело функции doState ). Необходимый код на JavaScript для функции doPause выглядел бы примерно так:

    function doPause(sender, args) {
     var meVid = sender.findName("vid");
     meVid.Pause();
     var txtStat = sender.findName("txtStat");
     var datetime = new Date(0,0,0,0,0, meVid.Position.Seconds);
     positionString = convertDT(datetime);
     txtStat.Text = positionString.toString(); 
    }

    И, как и ранее, эквивалентный код на C# намного проще:

    private void doPause(object sender, MouseButtonEventArgs e) {
    vid.Pause();
    txtStat.Text = vid.Position.ToString(); }

    Использование маркеров временной шкалы мультимедиа

    Маркер временной шкалы - это элемент метаданных, ассоциированный с конкретной точкой на временной шкале мультимедиа. Обычно эти маркеры создаются и кодируются в мультимедиа заранее с помощью такого ПО, как Expression Encoder, и часто применяются для установления в видео поисковых меток.

    Silverlight поддерживает эти маркеры и формирует событие MarkerReached (Маркер достигнут) при встрече с маркером в ходе воспроизведения. Это событие можно перехватывать и использовать как триггер для действий, которые должны выполняться при достижении данной отметки.

    Далее представлен фрагмент XAML, в котором с помощью атрибута MarkerReached задается обработчик события встречи с маркером, JavaScript-функция handleMarker:

    <MediaElement x:Name="vid" Source="balls.wmv" Height="200" Width = "200" MarkerReached = "handleMarker" />

    Среди аргументов данного события имеется объект marker (маркер). Он содержит объект TimeSpan (Диапазон времени) с меткой времени, в которой установлен маркер. В предыдущем разделе, "Управление положением курсора воспроизведения", представлен пример преобразования объекта Time-Span в формат, удобный для восприятия пользователем. Также marker содержит свойство Type (Тип).

    Его значением является строка, которую задает человек, кодирующий видео. Наконец, имеется параметр Text, допускающий в качестве своего значения текст в свободном формате и обычно используемый для описания параметра. Далее представлен JavaScript-код для чтения всех трех параметров и создания строки, отображаемой в окне уведомления:

    function handleMarker(sender, args) {
     var strMarkerStatus = args.marker.time.seconds.toString();
     strMarkerStatus += "   :   ";
     strMarkerStatus += args.marker.type;
     strMarkerStatus += "   :   ";
     strMarkerStatus += args.marker.text;
     alert(strMarkerStatus); }

    Код на C# очень похож на предыдущий; заметьте, что args типа TimeLineMarkerRoutedEventArgs:

    private void handleMarker(object sender, TimelineMarkerRoutedEventArgs e) {
     string strMarkerStatus = e.Marker.Time.ToString();
     strMarkerStatus += "   :   ";
     strMarkerStatus += e.Marker.Type;
     strMarkerStatus += "   :   ";
     strMarkerStatus += e.Marker.Text; }

    Как это выглядит, можно увидеть на рис. 11.11.

    (рис 11.11) Перехват маркеров временной шкалы в Silverlight

    Маркеры временной шкалы можно добавлять в медиафайл также динамически, используя код на Silverlight. При этом поисковые метки можно задавать через процентное соотношение положения метки к общей продолжительности файла, например.

    Далее представлен пример, в котором XAML для MediaElement определяет функцию handleOpen (Обработка открытия), выполняющуюся при открытии файла мультимедиа. Она вставляет новый объект временной шкалы в видео на 10-секундной отметке. Этот элемент не сохраняется в видео постоянно, только до завершения сеанса.

    <MediaElement x:Name="vid" Source="balls.wmv" 
                Height="200" Width = "200" 
                MarkerReached = "handleMarker" MediaOpened= "handleOpened" />

    Обработчик должен создать новый элемент временной шкалы в XAML и добавить его в коллекцию маркеров MediaElement. В данном случае, задается маркер временной шкалы на отметке 10 секунд, для которого Type определен как 'My Temp Marker' (мой временный маркер) и Text- 'Dynamically Added Marker' (Динамически добавляемый маркер).

    Вот как это выглядело бы в JavaScript:

    function handleOpened(sender, args) {
     var marker =
     sender.getHost().content.createFromXam l(
     "<TimelineMarker Time='0:0:10'" +
              " Type='My Temp Marker' 
                         Text='Dynamically Added Marker' />"); 
     sender. markers.add(marker); 
    }

    И вот аналогичный код в .NET:

    private void handleOpened(object sender, RoutedEventArgs e) 
    {
     TimelineMarker t = new TimelineMarker();
     t.Time = new TimeSpan(0, 0, 0, 10);
     t.Type = "My Temp Marker";
     t.Text = "Dynamically Added Marker";
     vid.Markers.Add(t); 
    }

    Коллекцию маркеров MediaElement составляют объекты TimeLineMarker (Маркер временной шкалы), поэтому для введения нового маркера просто создается новый объект TimeLineMarker, задаются его свойства Time, Type и Text, и этот объект добавляется в коллекцию. Silverlight имеет достаточно развитую логику и знает, когда должно быть сформировано событие, исходя из заданного Time, поэтому при создании новых маркеров нет необходимости добавлять их в порядке расположения на временной шкале.

    Теперь, когда MediaElement достигает 10-секундной отметки при воспроизведении, появляется диалоговое окно уведомления, как показано на рис. 11.12.

    (рис 11.12) Использование динамически добавляемого маркера

    Закрашивание поверхностей видеоизображением с помощью VideoBrush

    Особенно замечательная возможность Silverlight - использование видео для заливки поверхности с помощью VideoBrush. Сделать это очень просто. Во-первых, необходим элемент мультимедиа, который загрузит видео. Он должен быть скрыт и не должен принимать события мыши. Для этого его прозрачность задается равной 0 и свойству IsHitTestVisible (Объект виден для механизма обработки кликов) присваивается значение false. Также элементу мультимедиа должно быть задано имя посредством свойства x:Name. Рассмотрим пример:

    <MediaElement x:Name="vid" Source="balls.wmv" Opacity="0" IsHitTestVisible="False" />

    Теперь объект VideoBrush можно применять к любому объекту точно так же, как любую другую кисть. В качестве источника кисти задается MediaElement (вот почему ему обязательно должно быть присвоено имя). Также можно задать свойство Stretch для управления визуальными эффектами кисти.

    Например, здесь представлен TextBlock, для закрашивания Foreground которого используется Video-Brush:

    <TextBlock FontFamily="Verdana" FontSize="80" 
                FontWeight="Bold" TextWrapping = "Wrap" 
                Text="Video"> 
      <TextBlock.Foreground >
        <VideoBrush SourceName="vid"/> 
      </TextBlock.Foreground > 
    </TextBlock>

    Silverlight теперь будет формировать визуальное представление текста, используя VideoBrush. На рис. 11.13 показано, как это будет выглядеть.

    (рис 11.13) Использование VideoBrush для закрашивания текста

    Элемент управления InkPresenter

    Как было показано в данном курсе до сих пор, Silverlight расширяет возможности следующего поколения Веб-приложений, предоставляя инструменты для добавления насыщенных видео, аудио, векторной графики, анимации и других элементов, которые улучшают UX. Рукописные примечания - создание рукописного или нарисованного от руки содержимого с использованием устройств, разработанных для этих целей, - еще один замечательный способ сделать приложения еще более интерактивными и индивидуальными. Обеспечиваемая Silverlight поддержка программирования рукописного содержимого приносит эту функциональность в Веб. В данном разделе мы рассмотрим поддержку рукописных примечаний в Silverlight.

    Существует несколько разных типов устройств, которые могут использоваться для приложений с поддержкой рукописных примечаний:

  • Ввод с помощью пера Обычно преобразователи графической информации, вводимой пером поддерживаются Планшетными ПК (Tablet PC), но существуют также настольные компьютеры, которые поддерживают подключение внешних планшетов. Эти устройства используют возможности ввода с помощью пера, предоставляемые в Silverlight. Они создают Ink (Рукописный фрагмент), который может быть интегрирован в Веб-страницы Silverlight, таким образом, обеспечивая поддержку рукописного ввода, рисования, рукописных примечаний и прочих форматов ввода в Веб.
  • Сенсорный ввод Сенсорные экраны широко применяются в интерактивных терминалах или других местах, где использование стилуса или клавиатуры было бы слишком громоздким или неуместным. Рукописные примечания в Silverlight обеспечивают поддержку сенсорных экранов, что позволяет создавать насыщенные Интернет-приложения с возможностью сенсорного ввода.
  • Ввод посредством мыши Имитировать рукописный ввод, подобный вводу пером Планшетного ПК, можно с помощью мыши. Однако такой ввод будет иметь боле низкое разрешение по сравнению с использованием пера настоящего Планшетного ПК.
  • Пример рукописных примечаний в Silverlight

    На сайте http://silverlight.net предлагается замечательный пример приложения, поддерживающего рукописные примечания. Это приложение для чтения книг можно найти по адресу http://silverlight.net/samples/1.0/Page-Turn/default.html и увидеть на рис. 11.14.

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

    (рис 11.14) Пример PageTurn на Silverlight(рис 11.15) Комментирование изображения в Silverlight

    Классы рукописного ввода в Silverlight для разработчиков на JavaScript

    Поддержка рукописного ввода в Silverlight очень проста. Каждый раз при проведении устройством ввода по экрану формируется один или более объектов StylusPoint (Штрих стилуса). Эти объекты объединяются в коллекцию StylusPointCollection (Коллекция штрихов стилуса), которая формирует основу Stroke. Обводки собираются в коллекцию StrokeCollection (Коллекция обводок). Эта коллекция составляет список графических элементов, используемых для создания рукописного ввода, визуальное предоставление которого формирует InkPresenter. Итак, если взглянуть на рис. 11.15, буква "С" в слове COOL - это обводка, образованная рядом точек, объединенных в StrokeCollection. Обе буквы "О", буква "L", линия и точка в восклицательном знаке, и линии, образующие стрелку и круг, - все это обводки. Каждая из этих обводок является участником коллекции StrokeCollection в InkPresenter, и каждая из них образована объектами StylusPoint, входящими в состав коллекции StylusPointCollection соответствующей обводки. Каждый из этих типов предоставляет объектно-ориентированный интерфейс со свойствами и методами, обеспечивающими возможность их программирования. Рассмотрим основные типы и некоторые их свойства и методы.

    Тип StrokeCollection

    Объект InkPresenter имеет свойство Strokes типа StrokeCollection. Эта коллекция, в свою очередь, содержит все метаданные, необходимые для представления рукописного ввода пользователя.

    Свойства StrokeCollection

    При использовании JavaScript для программирования Silverlight, StrokeCollection предоставляет свойство Count (Количество), которое возвращает количество обводок, находящихся в коллекции в настоящий момент.

    Методы StrokeCollection

    StrokeCollection предоставляет следующие методы:

  • Add ( Добавить ) Позволяет добавлять новую обводку в коллекцию.
  • Clear ( Очистить ) Очищает коллекцию обводок, что приводит к немедленному повторному формированию визуального отображения (т.е. удалению рукописного фрагмента, созданного ранее).
  • GetBounds ( Получить границы ) Возвращает прямоугольник (в виде структуры Rect ), который представляет блок, ограничивающий обводки.
  • GetItem(индекс) ( Получить элемент ) Обеспечивает извлечение обводки, хранящейся под заданным индексом.
  • HitTest ( Проверка совпадения ) Если этот метод передается в StylusPointCollection, он возвращает подмножество обводок в StrokeCollection, которые пересекаются с данными штрихами.
  • Insert ( Вставить ) Аналогичен Add за исключением того,что позволяет вставлять новые обводки в коллекцию по заданному индексу.
  • Remove ( Удалить ) Обеспечивает удаление заданного элемента из StrokeCollection.
  • ) Обеспечивает удаление элемента из StrokeCollection по заданному индексу.
  • Тип Stroke

    Представленный в предыдущем разделе StrokeCollection - это коллекция объектов Stroke. Объект Stroke представляет коллекцию штрихов обводки; каждый штрих соответствует одному движению стилуса: стилус опускается (stylus-down), стилус перемещается (stylus-move), стилус поднимается (stylus-up). Эти объекты, в свою очередь, обладают собственным набором свойств и методов.

    Свойства Stroke

    Объект Stroke предоставляет следующие свойства:

  • DrawingAttributes ( Атрибуты рисования ) Каждая обводка имеет собственную независимую высоту, ширину, цвет и цвет контура. Эти параметры задаются посредством типа DrawingAttributes. Чтобы использовать этот тип, создается экземпляр объекта DrawingAttributes, задаются значения свойств, и новый объект DrawingAttributes добавляется в обводку.
  • StylusPoints ( Штрихи стилуса ) Это StylusPointCollection, содержащий коллекцию объектов StylusPoint, образующих обводку. StylusPoint обсуждается в следующем разделе.
  • Методы Stroke

    Объект Stroke обладает следующими методами для работы с рукописным вводом:

  • GetBounds Возвращает ограничивающий прямоугольник Stroke в виде структуры Rect.
  • HitTest Если передать этот метод в StylusPointCollection и если какой-либо из этих штрихов пересекает Stroke, метод возвратит true; в противном случае, возвращается false.
  • Тип StylusPointCollection

    Эта коллекция объединяет в себе объекты StylusPoint. Она используется для хранения штрихов стилуса для Stroke или, в некоторых случаях, может передаваться в такие методы, как HitTest, для выявления пересечения обводок.

    Свойства StylusPointCollection

    StylusPointCollection предоставляет свойство - Count, которое возвращает количество штрихов стилуса в данной коллекции.

    Методы StylusPointCollection

    Объект StylusPointCollection имеет следующие методы:

  • Add Этот метод позволяет добавлять новый StylusPoint в конец коллекции.
  • AddStylusPoints ( Добавить штрихи стилуса ) Этот метод позволяет добавлять существующий объект StylusPointCollection в конец данной коллекции.
  • Clear Этот метод удаляет все объекты StylusPoint из коллекции.
  • GetItem Этот метод получает заданный StylusPoint из коллекции. Используется со значением типа integer, представляющим индекс интересующего элемента, например, getItem(5 ).
  • Insert Этот метод вставляет новый StylusPoint в коллекцию по заданному индексу.
  • Remove Этот метод удаляет заданный StylusPoint из коллекции.
  • RemoveAt Этот метод удаляет из коллекции StylusPoint по заданному индексу.
  • Тип StylusPoint

    Тип StylusPoint представляет отдельный штрих, создаваемый, когда пользователь выполняет ввод с помощью устройства рукописного ввода: пера, мыши или сенсорного экрана. Штрих предоставляет ряд свойств и методов.

    Свойства StylusPoint

    StylusPoint предоставляет следующие свойства:

  • Свойство Name позволяет задавать имя штриха. Это уникальный идентификатор. Когда штрихи формируются в процессе ввода пользователем, имена им не присваиваются.
  • Свойство PressureFactor (Коэффициент нажима) обозначает нажим, с которым пользователь водит пером или касается экрана при формировании штриха. При использовании мыши применяется нажим по умолчанию. Это значение типа double в диапазоне от 0.0 до 1.0, значение по умолчанию - 0,5. На основании коэффициента нажима можно менять высоту и ширину Stroke программно через свойство DrawingAttributes, чтобы предоставить обратную связь пользователю.
  • Координаты обводки можно получить с помощью свойств X и Y. Они измеряются в пикселах.

    Аргументы событий мыши и рукописный ввод

    При использовании рукописного ввода формируемые устройством ввода события трактуются как события мыши, и аргументы, получаемые обработчиками событий, будут типа MouseEventArgs.

    Объект MouseEventArgs содержит методы, которые позволяют запрашивать сведения об устройстве ввода (является ли оно мышью, стилусом или каким-то другим устройством) и коллекцию объектов StylusPoint, связанную с этим событием.

    Обратите внимание, что это тот же объект MouseEventArgs, который используется с событиями Mou-seEnter, MouseLeave, MouseLeftButtonDown, MouseLeftButtonUp и MouseMove.

    Свойства MouseEventArgs

    Объект MouseEventArgs предоставляет следующие свойства Булевого типа: ctrl и shift. Они принимают значение true, если пользователь удерживает соответствующую клавишу при формировании события.

    Методы MouseEventArgs

    Объект MouseEventArgs предоставляет следующие методы:

  • Метод GetPosition (Получить положение) принимает в качестве параметра элемент и возвращает объект Point, который представляет координаты х и у курсора мыши относительно этого элемента. Если вызов метода осуществляется без параметра, Point содержит координаты относительно положения элемента управления, сформировавшего событие.
  • Метод GetStylusInfo (Получить сведения о стилусе) возвращает объект StylusInfo (Сведения о стилусе), содержащий информацию о состоянии стилуса. StylusInfo имеет следующие свойства: 1.
  • IsInverted (Инвертированный):Если перо инвертировано, это указывает на то, что пользователь хочет использовать его для удаления штрихов, а не для их отрисовки. Это свойство возвращает true в этом случае. 2.
  • DeviceType (Тип устройства):Возвращает строку, содержащую тип устройства -"Mouse", "Stylus" или "Touch".
  • Метод GetStylusPoints возвращает копию штрихов стилуса, добавленных в коллекцию с момента формирования последнего события мыши. Это будет объект типа StylusPointCollection.
  • Программирование для рукописного ввода в Silverlight

    При написании приложений инструментами Microsoft под термином рукописный ввод (Ink) обычно подразумевается надпись или рисунок от руки, создаваемый пользователем с помощью описанных ранее устройств рукописного ввода, таких как цифровое перо, сенсорный экран или мышь. При использовании в приложении Silverlight эти устройства обеспечивают заполнение объекта StrokeCollec-tion отдельными объектами Stroke. В свою очередь, Stroke выполняет запись действий устройства, например пера, к которым относятся действия: перо опускается (pen-down), перо перемещается (pen-move) и перо поднимается (pen-up). Stroke может представлять точку, прямую линию или кривую. Это осуществляется с помощью объекта StylusPointCollection, который содержит объекты StylusPoint, создаваемые преобразователем графической информации, связанным с пером, сенсорным экраном или мышью. Атрибуты рукописного ввода содержаться в классе DrawingAttributes.

    Как говорилось, Silverlight принимает элементы рукописного ввода с помощью класса InkPresenter. Это подкласс элемента Canvas, который также содержит коллекцию обводок в StrokeCollection. При добавлении обводок в StrokeCollection, InkPresenter автоматически формирует их визуальное представление, используя соответствующие DrawingAttributes.

    Обычно InkPresenter добавляется в XAML приложения во время разработки дизайна, но объекты Stroke, составляющие StrokeCollection, будут добавляться во время выполнения с помощью JavaScript.

    Далее представлен пример использования перекрывающего изображение InkPresenter на странице:

    <Canvas xmlns="http://schemas.microsoft.com/client/2007"
      xmlns:x=" http://schemas.microsoft.com/winfx/2006/xaml">
      <Image Source="sushi.jpg" Width="600" Height="400"></Image>
      <InkPresenter x:Name="inkEl" Background="transparent" 
                    Width="600" Height="400" 
                    MouseLeftButtonDown="inkMouseDown" 
                    MouseMove="inkMouseMove" 
                    MouseLeftButtonUp="inkMouseUp"/>
    </Canvas>

    InkPresenter определяет обработчики событий MouseLeftButtonDown, MouseMove и MouseUp. Мы рассмотрим, как это делается в JavaScript, для чего создадим простое приложение для рукописного ввода. Затем далее в этом разделе то же самое будет сделано на C#. Сходство необыкновенное! Для этих событий понадобятся функции-обработчики действий "начало рукописного ввода", "рукописный ввод" и "прекращение рукописного ввода".

    Примечание Хотя рукописный ввод может быть выполнен пером, с помощью сенсорного экрана или мыши, в документации API используется термин "Мышь".

    Прежде чем перейти к функциям-обработчикам событий JavaScript, требуется выполнить некоторые вспомогательные операции по объявлению глобальных переменных, необходимых для поддержки этих действий:

    var theInk;       // Ссылка на модуль отображения рукописного ввода
    var newStroke;  // Ссылка на обводку
    var theControl;  // Ссылка на элемент управления Silverlight
    function handleLoad(control, userContext, rootElement)
    {
     // Событие Load возвращает ссылку на элемент управления,
     // но другие обработчики событий нет. Поэтому мы собираемся
     // создать ссылку на элемент управления здесь
     theControl = control;
     // Здесь мы создадим ссылку на элемент рукописного ввода 
     theInk = control.content. findName("inkEl"); 
    }

    Эти вспомогательные переменные для InkPresenter, текущей обводки и самого элемента управления Silverlight будут использоваться функциями, поддерживающими описанные выше действия рукописного ввода. При загрузке элемент управления Silverlight запускает функцию handleLoad. В качестве параметра она принимает ссылку на элемент управления Silverlight, но поскольку реализованные нами обработчики событий мыши не делают этого, данную ссылку необходимо сохранить в handleLoad. В ходе выполнения handleLoad можно также получить ссылку на InkPresenter, проведя его поиск по имени ( in-kEl ). Тогда не придется вызывать getHost для получения ссылки на родительский элемент UI, чтобы найти InkPresenter, при каждом выполнении обработчика событий.

    Теперь можно приступать к рассмотрению обработчиков событий. Сначала разберемся, что происходит при формировании события MouseLeftButtonDown, результатом чего является выполнение функции inkMouseDown (Нажатие кнопки мыши при рукописном вводе). Мы будем отслеживать перемещения мыши аналогично тому, как это происходило при обработке перемещения методом "drag-and-drop", что рассматривалось в лекц. 6. При получении входных данных от мыши создается новый объект Stroke, содержащий объект DrawingAttributes, который определяет визуальные характеристики этой обводки. В представленном здесь примере DrawingAttributes будет обеспечивать следующие параметры обводки: Width - 2, Height-2, цвет заливки - White (Белый), и цвет контура - White. Тип MouseEven-tArgs в Silverlight поддерживает метод getStylusPoints, как было показано ранее в этой лекции, который принимает InkPresenter в качестве единственного параметра. Этот метод возвращает тип StylusPointCol-lection, который используется в методе AddStylusPoints обводки. Теперь можно добавить обводку в StrokesCollection InkPresenter. Посмотрим на код:

    function inkMouseDown(sender,args) 
    {
     // Перехватываем событие мыши. 
     theInk.CaptureMouse();
     // Создаем новую обводку.
     newStroke = theControl.content.createFromXaml('<Stroke/>');
     // Задаем новый элемент атрибутов рисования для обводки. 
     // Он, как следует из его имени, определяет внешний вид обводки 
     var da =    theControl.content.CreateFromXaml('<DrawingAttributes/>'); 
     newStroke.DrawingAttributes = da;
     // Теперь, когда обводка имеет атрибуты рисования, 
     // зададим их значения... 
     newStroke.DrawingAttributes.Width = 2; 
     newStroke.DrawingAttributes.Height = 2; 
     newStroke.DrawingAttributes.Color = "White"; 
     newStroke.DrawingAttributes.OutlineColor = "White";
     newStroke.StylusPoints.Add StylusPoints(args.GetS tylusPoints(theInk)); 
     theInk.Strokes.Add (newStroke); 
    }

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

    // Добавляем новые штрихи в Stroke, с которым работаем в данный момент.
    function inkMouseMove(sender,args)
    {
     if (newStroke != null)
     { 
      newStroke.StylusPoints.AddStylusPoints(args.GetStylusPoints(theInk));
     }
    }

    Наконец, по завершении отрисовки обводки, когда вы отпускаете кнопку мыши (или отрываете перо от экрана), формируется событие MouseLeftButtonUp. После этого требуется очистить обводку и прекратить отслеживание мыши. После того, как переменной newStroke задается значение null, движение мыши (или пера) по экрану больше не будет обеспечивать добавление штрихов в обводку, и, следовательно, обводка не будет отрисовываться. Рассмотрим код:

    function inkMouseUp(sender,args) 
    {
     // Задаем newStroke значение null
     newStroke = null;
     // Прекращаем отслеживать перемещения мыши 
     theInk.releaseMouseCapture(); 
    }

    На рис. 11.16 показан пример приложения до добавления в него рукописного примечания. На рис. 11ly представлено то же приложение с рукописным примечанием, которое было отрисовано с помощью мыши или пера.

    (рис 11.16) Выполнение приложения Silverlightc возможностью рукописного ввода(рис 11.17) Приложение Silverlight с возможностью рукописного ввода с рукописным примечанием

    В предыдущем примере было продемонстрировано, как программировать рукописный ввод, используя Silverlight и JavaScript. Конечно же, то же самое можно сделать с помощью управляемого кода. Вот полный выделенный код на C#, обеспечивающий создание такого же приложения:

    using System;
    using System. Collections.Generic;
    using System.Linq;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Animation;
    using System.Windows.Shapes;
    using System.Windows.Ink;
    namespace NetInkSample 
    {
     public partial class Page : UserControl 
     {
      Stroke newStroke; public Page() 
      {
       InitializeComponent(); 
      }
      private void inkEl  MouseLeftButtonDown( object sender, MouseButtonEventArgs e) 
      {
      inkEl.CaptureMouse();
      // Создаем новую обводку.
      newStroke = new Stroke();
      DrawingAttributes da = new DrawingAttributes();
      newStroke.DrawingAttributes = da;
      newStroke.DrawingAttributes.Width=2;
      newStroke.DrawingAttributes.Height=2;
      newStroke.DrawingAttributes.Color= Colors.White;
      newStroke.DrawingAttributes.OutlineColor= Colors. White;
      newStroke. StylusPoints.Add(e.Sty lusDevice.GetStylusPoints(inkEl));
      inkEl.Strokes.Add (newStroke); }
      private void inkEl  MouseMove(object sender, MouseEventArgs e) {
      if (newStroke != null) {
       newStroke.StylusPoints.Add ( e.Sty lusDevice. GetS tylusPoints(inkEl));
       }
      }
      private void inkEl  MouseLeftButtonUp(object sender, MouseButtonEventArgs e) 
      {
       newStroke = null; inkEl.ReleaseMouseCapture(); 
      } 
     } 
    }

    И в XAML-коде, соответственно, необходимо сделать изменения:

    <Image Source="sushi.jpg" Width="600" Height="400">
    </Image> 
    <InkPresenter x:Name="inkEl" Background="transparent" 
               Width="600" Height="400" 
               MouseLeftButtonDown="inkEl   MouseLeftButtonDown" 
               MouseMove="inkEl  MouseMove" 
               MouseLeftButtonUp="inkEl   MouseLeftButtonUp"/>

    Кроме демонстрации гибкости Silverlight, который позволяет разрабатывать приложения как на языке сценариев JavaScript, так и на компилируемых языках программирования .NET, это также показывает простоту перехода от JavaScript к .NET, что позволяет без труда обновить существующие Silverlight-приложения на JavaScript до Silverlight 2 и перейти к управляемому коду.

    Реализация Deep Zoom с помощью элемента управления MultiScaleImage

    Deep Zoom -достаточно новая технология, введенная в Silverlight 2 и обеспечивающая новый уникальный способ работы с изображениями в приложениях. Она реализована в XAML посредством элемента MultiScaleImage, который, как следует из его имени, предоставляет возможность управлять размером и масштабом изображений, a Silverlight, в свою очередь, обеспечивает огромное виртуальное пространство, в котором эти изображения могут отрисовываться.

    Лучше всего возможность Deep Zoom рассмотреть на примере. На рис. 11.18 представлена иллюстрация к детскому научному проекту.

    (рис 11.18) Изображение в Deep Zoom

    Пока что ничего сверхъестественного, казалось бы, обычная фотография. Так чем же вызван весь этот шум вокруг Deep Zoom? В этом приложении можно менять масштаб изображения, прокручивая колесо мыши. Итак, на рис. 11.19 представлен другой вид этого изображения. Я уменьшил масштаб, чтобы показать весь рисунок. Теперь можно увидеть первое изображение относительно всей фотографии.

    (рис 11.19) Исходное изображение в другом масштабе

    Посмотрите внимательно на апельсин на рис. 11.19. В его центре можно увидеть изображение с рис. 11.18. Увеличьте масштаб, и вы опять получите изображение, представленное на рис. 11.20.

    (рис 11.20) Еще более глубокое масштабирование

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

    Использование редактора Deep Zoom

    Но как создать приложение, которое позволит уменьшать и увеличивать масштаб изображений таким образом? Базовое приложение для этого написать довольно просто. Возьмем элемент управления MultiScaleImage и ассоциируем его с файлом, содержащим метаданные изображений. Этот файл создается в редакторе Deep Zoom, который можно загрузить в Центре загрузки Microsoft. Редактор Deep Zoom представлен на рис. 11.21.

    (рис 11.21) Редактор Deep Zoom

    Этот инструмент выполняет простую последовательность операций, включающую Import (Импорт), за которым следует Compose (Компоновка) с последующим Export (Экспорт). Итак, начинаем с вкладки Import, щелкаем Add Image (Добавить изображение), чтобы выбрать изображение, и повторяем этот

    шаг для всех изображений, которые вы хотите использовать. На рис. 11.21 можно увидеть, что выбраны три изображения.

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

    (рис 11.22) Компоновка изображений для просмотра с разными уровнями увеличения в редакторе Deep Zoom

    Теперь, следующее изображение с его нормальным разрешением будет помещено на данное, масштаб которого увеличен. Итак, если посмотреть на рис. 11.22, вы увидите, куда вставлено второе изображение (в зрачок глаза). Позже, при выполнении приложения, увидеть это изображение можно будет, только увеличив первое. На рис. 11.23 показано, куда помещается второе изображение на первом.

    (рис 11.23) Добавление нового изображения на увеличенное первое

    Это простой пример, мы просто добавили одно изображение, которое можно увидеть, только увеличив первое. Deep Zoom позволяет создавать намного боле сложные приложения, данный пример лишь дает представление о возможностях этой технологии. Теперь мы готовы перейти к третьему этапу - экспорту. Для этого будем использовать закладку Custom вкладки Export редактора Deep Zoom (рис. 11.24).

    (рис 11.24) Экспорт метаданных Deep Zoom

    Чтобы экспортировать метаданные изображений, скомпонованных в Deep Zoom, необходимо присвоить имя проекту, выбрать в качестве Output type (Тип результата) - "Images" (Изображения), в качестве Image Settings (Настройки изображения) - "Export as Composition" (Экспортировать как композицию) и экспортировать проект в заданный каталог. При этом в выходном каталоге будут созданы два файла и папка. Один из файлов называется SparseImageSceneGraph.xml и является конфигурационным файлом, который определяет каждое изображение и его местоположение на других изображениях с разными уровнями увеличения. Например, рассмотрим граф сцены для XAML с двумя изображениями:

    <?xml version = "1.0"?> <SceneGraph version = "1"> 
      <AspectRatio>1.33333333333334</AspectRatio> <SceneNode>
    <FileName>C:\Code\SLBook\Chapter11\DZCSample \source images\
      dzcsample\DSCN2961JPG</FileName> <x>0</x> <y>0</y>
    < Width >1</Width> <Height>1</Height> <ZOrder> 1 </ZOrder>
    </SceneNode>
    <SceneNode>
    <FileName>C:\Code\SLBook\Chapter11\DZCSample
    \source images\dzcsample\DSCN2959JPG</FileName>
    <x>0.451782754964542</x>
    <y> 0.313488814592021</y>
    < Width >0.00099432659277551</Width> <Height>0.00099432659277551</Height> 
      <ZOrder>2</ZOrder>
    </SceneNode> </SceneGraph>

    Как видите, это довольно простой XAML-код. В нем указывается соотношение сторон основного изображения (полученное, исходя из размеров первого изображения), и каждое изображение превращается в SceneNode (Узел сцены). Первое изображения является первым SceneNode. Оно располагается в позиции (0,0) и является нормализованным изображением, т.е. его ширина и высота принимаются за 1. Размеры и расположение всех остальных изображений устанавливаются относительного первого.

    Второе изображение размещается в точке с координатами 0,45 по оси х и 0,31 по оси у. Его размер составляет, примерно, 0,00099 размеров первого изображения. Таким образом, если увеличить первое изображение, примерно, в 10000 раз, можно будет увидеть второе изображение. Его Z-порядок равен 2 (Z-порядок первого изображения - 1), т.е. он будет отрисовываться поверх первого изображения.

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

    Другой экспортируемый файл будет называется dzc output.xml. В нем содержатся все сведения о фрагментах и их положении относительно основного изображения. Он будет располагаться в каталоге, созданном редактором Deep Zoom, в котором вы также найдете ряд подпапок с изображениями.

    Первый проект Deep Zoom

    Чтобы использовать Deep Zoom, создадим новое приложение Silverlight. Прежде чем делать что-либо еще, необходимо скомпилировать приложение. При этом будет создан каталог ClientBin в проекте Веб-приложения. Сделав это, закройте решение.

    Теперь в Windows Explorer скопируйте каталог, содержащий файл dzc output.xml и подпапки с фраг-ментированными изображениями, в папку ClientBin Веб-приложения. Сделав это, вновь откройте решение. Solution Explorer вашего проекта будет выглядеть примерно так, как показано на рис. 11.25.

    (рис 11.25) Добавление данных Deep Zoom в Веб приложение

    Теперь, чтобы сформировать визуальное представление содержимого Deep Zoom, необходимо просто добавить MultiScaleImage в Page.xaml и в качестве значения его свойства Source задать путь к файлу dzc output.xml (в данном случае, этот файл располагается в /dzcsample/dzc output.xml ), а также необходимые ширину и высоту. Посмотрим, как будет выглядеть ваш Page.xaml:

    <UserControl x:Class="DZCSampleApp.Page"
    xmlns="http://schemas.microsoft.com/client/2007"
    xm lns:x=" http://schemas.microsoft.com/winfx/2006/xam l"
    Width = "400" Height="300">
    <Canvas>
    <MultiScaleImage Source="dzcsample/dzc_output.xml" Height="300" Width="400" /> </Canvas> </UserControl>

    Итак, при выполнении этого приложения элемент управления MultiScaleImage будет формировать визуальное представление верхнего элемента в SceneGraph, увеличивая его от оригинального масштаба изображения до заданных ширины и высоты (в данном случае, 400 * 300).

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

    Использование мыши и логических координат в Deep Zoom

    Как и все остальные элементы управления Silverlight, MultiScaleImage может объявлять функции для обработки событий. Например, для перемещения изображения используются обычные события мыши: MouseLeftButtonDown, MouseLeftButtonUp и Mouse Move, - и обрабатываются они так же, как это делается при перемещении любого элемента методом "drag-and-drop". Сначала, рассмотрим XAML для MultiScaleImage, определяющий эти события:

    <UserControl x:Class="DeepZoomSample.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xa ml/presentation" 
      xm lns:x=" http://schemas.microsoft.com/winfx/2006/xam l" Width = "640" Height="480"> <Canvas>
    <MultiScaleImage x:Name="dz" Source="dzcsample/dzc_output.xml" 
      MouseLeftButtonDown = "MultiScaleImage  MouseLeftButtonDown" MouseLeftButtonUp="MultiScaleImage  MouseLeftButtonUp" 
      MouseMove="MultiScaleImage  MouseMove" Height="480" Width = "640"></MultiScaleImage> </Canvas> </UserControl>

    И теперь рассмотрим каждый из этих обработчиков событий более подробно. Прежде всего, есть код, общий для них всех, который обеспечивает отслеживание текущего состояния мыши и просматриваемых в настоящий момент координат MultiScaleImage:

    bool dragging = false; double dx = 0; double dy = 0; Point p0; Point p1; Point pLast;

    Теперь посмотрим, что происходит, когда пользователь щелкает изображение кнопкой мыши:

    private void MultiScaleImage MouseLeftButtonDown( object sender, MouseButtonEventArgs e) 
    {
     dragging = true;
     p0 = dz.ElementToLogicalPoint(new Point(0, 0));
     p1 = dz.ElementToLogicalPoint(new Point(640, 480));
     dx = 0;
     dy = 0;
     double x = e.GetPosition(null).X;
     double y = e.GetPosition(null).Y;
     pLast = dz.ElementToLogicalPoint(new Point(x, y));
    }

    Предполагается, что пользователь удерживает кнопку мыши и двигает мышью, поэтому задаем Буле-вому свойству dragging (перетягивание) значение true. Теперь необходимо получить текущие координаты видимой части изображения. Вспомним, что для верхнего изображения задана позиция (0,0), или x = 0 and y = 0, и его высота и ширина заданы равными 1. Это логические координаты и логические размеры.

    В Silverlight логические координаты верхнего левого угла и нижнего правого угла окна можно получить с помощью метода ElementToLogicalPoint (Логическая точка элемента), передав в него физические координаты. Итак, представим, что изображение было увеличено и немного передвинуто. Теперь если вызвать этот API для физической точки (0,0), т.е. верхнего левого угла видимой в данный момент части изображения,будет возвращена логическая точка, представляющая положение это точки на полном изображении. Чтобы получить координаты нижнего правого угла, делаем то же самое для точки, координаты которой соответствуют ширине и высоте физического экрана (в данном случае, 640 * 480).

    Мы хотим отслеживать изменение размеров относительно осей х и у, поэтому задаем переменным dx и dy значение 0. Зачем это сделано, станет понятно через мгновение.

    Кроме того, необходимо получать логические координаты курсора мыши. Для этого вызывается API ElementToLogicalPoint, в него передается текущее положение мыши (которое можно получить из объекта MouseEventArgs, передаваемого в эту функцию) и возвращаемые результаты сохраняются в переменной pLast.

    Итак, что происходит, когда пользователь начинает перетягивание, удерживая кнопку мыши нажатой? Мы хотим получить эффект перемещения по изображению. Вот код, реализующий это:

    private void MultiScaleImage  MouseMove(
    object sender, MouseEventArgs e) { if (dragging) { if (e!=null) 
      { double x = e.GetPosition(null).X; double y = e.GetPosition(null).Y;
    Point pCurrent = dz.ElementToLogicalPoint(new Point(x, y)); if (pLast! = null) 
      { dx += (pCurrent.X - pLast.X); dy += (pCurrent.Y - pLast.Y); Point origin = 
         new Point(p0.X - dx, p0.Y - dy); dz.ViewportOrigin = origin; } pLast = pCurrent; } } }

    Событие MouseMove формируется независимо от того, удерживается кнопка мыши или нет, поэтому для обозначения перетягивания используем переменную dragging. Итак, если пользователь выполняет перетягивание, и MouseEventArgs не null, будет выполняться весь остальной код.

    В данном случае, мы извлекаем текущие координаты мыши и сохраняем их логические эквиваленты (полученные с помощью метода ElementToLogicalPoint элемента управления) в точке pCurrent. Теперь если pLast не null, значит, пользователь выполняет перетягивание, и мышь переместилась относительно ее предыдущего положения. Итак, чтобы получить изменение логических координат, произошедшее в результате перемещения мыши из предыдущей точки в текущую, мы просто вычисляем разницу (дельту) между предыдущими и текущими координатами относительно обеих осей (х и у). После этого задаем для свойства Origin (Начало отсчета) элемента управления MultiScaleImage исходные координаты с учетом дельта по х и у. Это обеспечит эффект "перемещения" изображения при перемещении мышью, хотя мы, по сути, просто изменяем координаты верхнего левого угла отображаемой части изображения. По завершении перемещения необходимо просто задать положение Last (Последний) как Current, чтобы, когда пользователь вновь начнет двигать мышью, вычисления производились относительно этой точки, а не той, в которой пользователь нажмет кнопку мыши.

    Наконец, необходимо, чтобы после того, как пользователь отпустит кнопку мыши, все вернулось в исходное состояние. Сделать это довольно просто. Рассмотрим код:

    private void MultiScaleImage  MouseLeftButtonUp( object sender, MouseButtonEventArgs e) {
    dragging = false;
    p0 = new Point();
    p1 = new Point();
    pLast = new Point();
    dx = 0;
    dy = 0; }

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

    Создание функциональности масштабирования с использованием колеса мыши

    Проблемой при создании приложений Deep Zoom является то, что стандартным средством изменения масштаба, де факто, является колесо мыши, но Silverlight и .NET не обрабатывают события колеса мыши. Что же можно сделать? Существует два варианта. Первый - использовать JavaScript вместо C#, потому что браузер может перехватывать перемещения колеса мыши и формировать события в ответ на его прокручивание. Второй вариант - использовать браузер как мост к Silverlight, т.е. браузер должен будет перехватывать событие и затем информировать .NET об этом. Вся дальнейшая обработка будет осуществляться в .NET. Все намного проще, чем кажется!

    Прежде всего, необходимо обеспечить возможность использования API браузера в Silverlight, поэтому не забудьте включить в код следующую строку:

    using System.Windows.Browser;

    Затем в конструкторе Page элемент управления должен быть зарегистрирован как поддерживающий сценарии. Методы элементы управления, обозначенные как ScriptableMember (Участник, поддерживающий сценарии), доступны из JavaScript. Вот как это делается:

    public Page() {
    InitializeComponent();
    HtmlPage.RegisterScriptableObject("MySilverlightObject", this); }

    Далее требуется перехватывать события колеса мыши на этой странице. Для этого сначала добавляем вызов handleLoad в тег <Body>, чтобы обеспечить выполнение JavaScript-кода при формировании визуального представления страницы:

    <body style=" height: 100%; margin :0;" onload = "handleLoad();">
    ...
    </body>

    Затем в JavaScript-функции handleLoad задается событие:

    function handleLoad() 
    {
     window.onmousewheel = document.onmousewheel = onMouseWheel;
     if (window.addEventListener)
     { 
      window.addEventListener('DOMMouseScroll', onMouseWheel, false);
     } 
    }

    Тем самым определяем, что JavaScript-функция onMouseWheel (При прокручивании колеса мыши) будет выполняться при любом прокручивании колеса мыши.

    function onMouseWheel() 
    {
     if(!event)
     { event = window.event;
     }
     var slPlugin = $get("Xaml1");
     slPlugin.content.MySilverlightObject.dz_MouseWheel( event.clientX, event.clientY, event.wheelDelta);
    }

    Эта функция просто принимает ссылку на элемент управления Silverlight (в данном случае, при создании ему присваивается имя Xaml1) и затем вызывает функцию выделенного кода, определенную для него. Для вызова используется синтаксис <ИмяКомпонента>.content.<ИмяОбъекта>.<ИмяФункции>.

    <ИмяКомпонента> - это переменная, определенная в JavaScript как ссылка на объект Silverlight. <ИмяОбъекта> - это имя, заданное для объекта при его регистрации (ее можно увидеть в конструкторе Page() ). <ИмяФункции> - это имя вызываемой функции. Эта функция должна быть соответствующим образом описана, чтобы JavaScript "видел" ее. В данном случае, вызывается функция dz MouseWheel, рассмотрим ее:

    [ScriptableMember]
    public void dz  MouseWheel(double x, double y, int delta)
    {
     double dZoomFactor=1.33;
     if (delta < 0) dZoomFactor= 1/1.33;
     Point pz = dz.ElementToLogicalPoint(new Point(x, y));
     dz.ZoomAboutLogicalPoint(dZoomFactor, pz.X, pz.Y); 
    }

    Прежде всего, как видите, она обозначена ScriptableMember, т.е. JavaScript может видеть и вызывать ее. Функция JavaScript передает текущие координаты мыши и дельта, возникающие в результате изменения масштаба. Дельта будет иметь положительное значение при прокручивании колеса вперед и отрицательное значение при прокручивании назад.

    Масштабный коэффициент каждого прокручивания колеса задан равным 33% (но он может быть любым), таким образом, переменной dZoomFactor (Масштабный коэффициент) задается значение 1.33 при увеличении и 1/1.33 при уменьшении масштаба.

    Далее требуется, чтобы изменение размеров происходило вокруг текущего положения курсора мыши на изображении. Это очень просто сделать, преобразовав координаты курсора мыши в логическую точку с помощью метода ElementToLogicalPoint элемента управления MultiScaleImage. Имея координаты и коэффициент масштабирования, вызываем метод ZoomAboutLogicalPoint (Масштабирование вокруг логической точки), чтобы обеспечить изменение размеров MultiScaleImage.

    Коллекции в Deep Zoom

    Кроме возможности увеличения и уменьшения изображения, можно также создавать коллекции изображений и работать с ними в масштабируемой среде.

    Возможно, вы заметили, что при экспорте изображений из редактора Deep Zoom всегда предлагалась опция Create Collection (Создать коллекцию). Как следует из ее имени, эта опция обеспечивает создание коллекции изображений. Посмотрим на рис. 11.26. Как видите, я разместил в рабочей области множество изображений. Теперь давайте посмотрим, как их можно экспортировать в виде коллекции.

    Для работы с изображениями коллекции Silverlight предлагает коллекцию SubImages (Множество изображений). Обратите внимание, что при формировании визуального представления страницы полный набор изображений не будет загружаться, поэтому эта коллекция будет пустой в этот момент. Чтобы иметь возможность работать с коллекцией, необходимо описать событие ImageOpenSucceeded (Изображение открыто успешно). Это событие будет формироваться в случае успешной загрузки и отображения.

    (рис 11.26) Создание коллекции Deep Zoom

    Рассмотрим XAML:

    <MultiScaleImage x:Name="dz"
               Source="dzcsample/dzc_output .xml"
               ImageOpenSucceeded = "dz ImageOpenSucceeded"
               Height="480" Width="640">
    </MultiScaleImage>

    В этот момент коллекция SubImages будет заполнена и может использоваться для работы с отдельными рисунками, ее составляющими. Рассмотрим пример изменения месторасположения изображений:

    private void dz ImageOpenSucceeded(object sender, RoutedEventArgs e) 
    {
      int nImages = dz.SubImages.Count;
      for (int lp = 0; lp < nImages; lp++)
      { 
       dz.SubImages[lp].ViewportOrigin = new Point(lp*0.1, lp*0.1);
      }
    }

    Как видите, процесс абсолютно аналогичен тому, который использовался ранее для работы с одним рисунком. В данном случае, каждое изображение перемещается путем использования SubImages[lp,], где lp - это переменная цикла, которая может принимать значения от 0 до числа, соответствующего количеству изображений в коллекции. Результаты представлены на рис. 11.27. Обратите внимание на разницу между этой и исходной компоновками, которая представлена на рис. 11.26.

    (рис 11.27) Компоновка коллекции изображений

    Заключение

    В этой длинной лекции представлены три наиболее мощных элемента управления, предлагаемые Silver-light 2. Сначала был подробно рассмотрен элемент управления MediaElement и то, как можно использовать Silverlight для воспроизведения и трансформации видео- и/или аудиосодержимого, включая программирование прозрачности, маркеры, динамические маркеры и т.д. Затем вы познакомились с рукописными примечаниями и устройствами, поддерживающими их, а также тем, как Silverlight может обеспечить поддержку рукописного ввода на Веб-страницах. Наконец, вашему вниманию была представлена технология Deep Zoom и реализация этой новой возможности в Silverlight. Вы научились создавать сцены и коллекции, расширенные функциональностью глубокого масштабирования.

    Страницы:

    В предыдущих лекциях вы познакомились с основными элементами управления, используемыми для создания насыщенных интерактивных Интернет-приложений. В данной главе будет рассмотрено три элемента управления, предоставляющие специальную функциональность. Сначала будет представлен элемент управления MediaElement, который обеспечивает API для управления аудио- и видеоданными, позволяя тем самым создавать замечательные мультимедийные приложения. Затем вы рассмотрите элемент управления InkPresenter (Элемент отображения рукописного ввода), который позволяет вводить рукописное или рисованное содержимое посредством рукописных примечаний, т.е. обеспечивает возможность использования приложения на компьютерах с перьевым вводом. Наконец, вашему вниманию будет представлен достаточно новый элемент управления Silverlight 2, MultiScaleImage (Масштабируемое изображение), который добавляет в ваши приложения новое измерение - возможность глубокого масштабирования.

    Элемент управления MediaElement

    Одно из наиболее важных применений Silverlight в Веб - обеспечение поддержки мультимедиа следующего поколения, которое может воспроизводиться на разных платформах. Для этого Silverlight поддерживает элемент управления MediaElement, которому посвящен данный раздел. В нем поэтапно рассматривается процесс создания простого медиаплейера, обеспечивающего возможность прогрессивной загрузки и воспроизведения видеоданных. Кроме того, вы научитесь закрашивать поверхности с помощью видео-кисти, что позволит добавлять интересные графические эффекты. Элемент управления MediaElement поддерживает следующие форматы:

    Видео

  • WMV1: Windows Media Video 7
  • WMV2: Windows Media Video 8
  • WMV3: Windows Media Video 9
  • WMVA: Windows Media Video Advanced Profile, не-VC-1
  • WMVC1: Windows Media Video Advanced Profile, VC-1
  • Аудио

  • WMA7: Windows Media Audio 7
  • WMA8: Windows Media Audio 8
  • WMA9: Windows Media Audio 9
  • MP3: ISO/MPEG Layer 3
  • Моно или стерео
  • Частоты дискретизации от 8 до 48 кГц
  • Скорости передачи двоичных данных от 8 до 320 Кбит/с
  • Переменная скорость передачи двоичных данных
  • Кроме этих форматов, элемент управления MediaElement также поддерживает списки воспроизведения ASX и протоколы http, https и mms.

    Если говорить о потоковой передаче видео- и/или аудио, MediaElement поддерживает потоковое вещание и потоковую передачу по запросу от Windows Media Server. Если для URI задан протокол mms, потоковая передача включена; в противном случае, загрузка и воспроизведение файла будут осуществляться путем прогрессивной загрузки, при которой воспроизведение начинается только после заполнения буфера воспроизведения и затем эти два процесса выполняются параллельно.

    Если определен протокол http или https, происходит обратное. MediaElement сначала делает попытку выполнить прогрессивную загрузку, и уже в случае неудачи пытается применить потоковую передачу файла.

    Использование элемента управления MediaElement

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

    Простое воспроизведение видео с помощью элемента управления MediaElement

    Чтобы начать работу с элементом управления MediaElement, добавьте его на свою страницу и задайте в качестве значения его атрибута Source URL видеофайл, который хотите воспроизвести. Рассмотрим пример XAML-кода Silverlight 2 приложения:

    <UserControl 
       xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
       xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
       x:Class="SilverlightApplication5.Page"
       Width="640" Height="480">
      <Grid x:Name="LayoutRoot" Background="White">
        <MediaElement Source="balls.wmv" />
      </Grid>
    </UserControl>

    Это обеспечит автоматическую загрузку и воспроизведение видеоданных. Размер области воспроизведения мультимедиа определяется следующими правилами:

  • Если заданы свойства Height и Width элемента управления MediaElement, MediaElement использует их.
  • Если задано одно из этих свойств, элемент управления MediaElement растянет мультимедиа, сохраняя пропорции изображения.
  • Если не заданы ни Height, ни Width, элемент управления MediaElement будет воспроизводить видео в стандартных размерах. Если они превышают размер области просмотра элемента управления Silverlight, MediaElement обрежет видео соответственно размеру области просмотра.
  • Рассмотрим пример. Используемая в данной лекции (и доступная для скачивания на Веб-сайте компании) видеозапись balls.wmv имеет размер 480 х 360. Если указать MediaElement воспроизводить это видео и не задать его Height и Width, воспроизводимое видеоизображение будет размером 480 х 360. Если у UserControl выставить размер 200 х 200, на экране будет видна только часть видеоизображения размером 200 х 200 пикселов, начиная от верхнего левого угла. Обрезанная таким образом часть видео представлена на рис. 11.1.

    (рис 11.1) Видеоизображение, обрезанное по размеру элемента управления Silverlight

    Размеры элемента управления MediaElement

    Как было показано в предыдущем разделе, размер MediaElement важен при воспроизведении видео. Если размеры элемента управления не заданы, и видеоизображение превышает размеры элемента управления Silverlight, оно будет обрезано.

    Управление шириной и высотой самого MediaElement осуществляется через его свойства Height и Width. При формировании визуального представления элемента управления, мультимедиа будет растягиваться (или сжиматься) так, чтобы поместиться в элемент управления. Если заданный размер элемента управления мультимедиа больше элемента управления Silverlight, изображение будет обрезано размерами элемента управления Silverlight.

    Далее представлен пример элемента управления MediaElement, для которого заданы размеры 200 х 200. На рис. 11.2 можно увидеть, как будет отображаться видео:

    <MediaElement Source="balls.wmv" 
              Height="200" Width="200" />
    (рис 11.2) Отображение видео после задания размеров MediaElement

    Растяжение мультимедиа

    В предыдущем примере видеоизображение сворачивалось до размера элемента управления MediaElement (200 х 200). Как видно на рис. 11.2, размер видеоизображения (исходный размер которого, как вы помните, 480 х 360 пикселов) меняется так, чтобы оно поместилось в MediaElement, но с сохранением его пропорций. Это может привести к появлению черных полей вверху и внизу видеоизображения, создавая эффект просмотра широкоформатного видео. Переопределить это поведение можно с помощью свойства Stretch элемента управления MediaElement. Это свойство принимает четыре разных значения:

  • (рис 11.3) Эффект задания свойству Stretch значения None
  • Uniform Это режим растяжения по умолчанию, он обеспечивает сохранение пропорций видеоизображения и добавление полей вверху, внизу или по бокам в случае необходимости.
  • (рис 11.4) Использование режима растяжения UniformToFill
  • (рис 11.5) Использование значения Fill свойства Stretch
  • Прозрачность видеоизображения

    Регулировать прозрачность MediaElement позволяет свойство Opacity. Это нормализованное свойство, т.е. значение 0 эквивалентно полной прозрачности, 1 - полной непрозрачности, все остальные значения в этом диапазоне представляют разные степени прозрачности. Видеоизображение будет отображаться с заданной прозрачностью, при этом элементы, находящиеся позади элемента мультимедиа на экране, будут видны.

    Далее представлен пример XAML, обеспечивающий отображение красного прямоугольника и MediaElement. Для MediaElement задана прозрачность 0,5, что делает видеоизображение полупрозрачным. Поскольку визуальное представление MediaElement формируется вторым, он размещается ближе в направлении по оси Z и располагается поверх прямоугольника. На рис. 11.6 можно увидеть результат выполнения этого примера.

    <Rectangle Fill="Red" Height="100" Width="200" /> 
    <MediaElement Source="balls.wmv" Height="200" Width="200" 
                       Stretch="Fill" Opacity="0.5" />
    (рис 11.6) Применение свойства Opacity для видеоизображения

    Использование трансформаций с элементом управления MediaElement

    Подробно трансформации рассматриваются в лекц. 5, "Трансформация и анимация в XAML", здесь хотелось бы отметить лишь то, что трансформации могут применяться и к элементу управления MediaElement. При этом отображаемое видео также трансформируется. Это может привести к созданию некоторых очень интересных эффектов. Например, далее представлен MediaElement, к которому применена трансформация наклонение:

    <MediaElement Source="balls.wmv" Height="200" Width="200" 
           Stretch="Fill" >
      <MediaElement.RenderTransform> <SkewTransform AngleX="45"/>
      </MediaElement.RenderTransform> 
    </MediaElement>

    Как это выглядит, можно увидеть на рис. 11.7.

    (рис 11.7) Наклонение видео с помощью SkewTransform

    Расположение содержимого поверх видеоизображения

    Silverlight позволяет размещать содержимое, включая текст и графические элементы, поверх видеоизображения. Это можно сделать либо с помощью свойств ZOrder (Z-порядок) элементов пользовательского интерфейса (UI) (смотрите лекц. 1, "Введение в Silverlight 2"), либо просто размещать элементы UI в том же месте, что и элемент мультимедиа, и объявлять их в XAML после него. Далее представлен пример MediaElement, и Canvas с прямоугольником и текстовым блоком, который перекрывает видеоизображение:

    <MediaElement Source="balls.wmv" Height="200"  
                 Width="200" Stretch="Fill" /> 
    <Canvas Margin="20,140,0,0">
      <Rectangle Fill= "Red" Height="40" Width="160" />
      <TextBlock>Subtitle on Video</TextBlock> 
    </Canvas>

    На рис. 11.8 показано, как это будет выглядеть на экране.

    (рис 11.8) Отображение содержимого поверх видео

    Вырезание мультимедиа геометрическими формами

    В лекц. 4, "Основы XAML" были рассмотрены вырезание и геометрические элементы. Эти возможности также могут применяться к MediaElement, для которого с помощью фигур или контуров можно определить контур вырезания. Например, следующий XAML в качестве контура вырезания для MediaElement определяет эллипс:

    <MediaElement Source="balls.wmv" Height="200" 
                Width="200" Stretch="Fill" >
      <MediaElement.Clip>
        <EllipseGeometry RadiusX="100" RadiusY="75" Center="100,75"/>
      </MediaElement.Clip> 
    </MediaElement>

    Результаты представлены на рис. 11.9.

    (рис 11.9) Вырезание мультимедиа геометрическими формами

    Автоматическое воспроизведение

    По умолчанию, если задан источник, MediaElement автоматически воспроизводит мультимедиа. Этим поведением можно управлять посредством свойства AutoPlay (Автоматическое воспроизведение). По умолчанию его значение true, но его можно переопределить и задать false. Можно воспроизвести мультимедиа позже, используя метод Play (Воспроизвести). Этот и другие методы и события, используемые при программировании элемента мультимедиа, рассматриваются в следующем разделе.

    Управление воспроизведением звука

    С помощью свойства IsMuted (Без звука) элемента управления MediaElement можно определить, будет ли аудиозапись сопровождать воспроизведение или нет. Это свойство Булевого типа, и если задать ему значение true, звук будет отключен.

    Кроме того, с помощью свойства Volume (Громкость) можно управлять громкостью звука. Оно принимает нормализованное значение, где 0 эквивалентно отсутствию звука, 1 - полной громкости, и промежуточные значения представляют относительные уровни громкости. Итак, например, значение 0,43 обозначало бы воспроизведение звука с громкостью, составляющей 43% от полной громкости.

    Наконец, балансом звука можно управлять с помощью свойства Balance (Баланс), которое принимает значения в диапазоне от -1 до +1. Значение -1 приводит к смещению баланса звука полностью влево, т.е. левый динамик будет воспроизводить звук со 100% громкостью, и в правом динамике будет 0% громкости. Значение +1 обеспечивает прямо противоположный эффект - баланс звука будет полностью смещен вправо, при этом правый динамик будет воспроизводить звук со 100% громкостью. Значение 0 обеспечивает равномерное распределение громкости между двумя динамиками.

    Например, свойству Balance задано значение 0,8, тогда правый динамик будет воспроизводить звук с 80% громкостью и левый динамик- с 20%. Если используется значение -0,8, левый динамик будет воспроизводить звук с громкостью 80%, и правый динамик - с громкостью 20%.

    Далее представлен некоторый XAML, определяющий, что звук не выключен, общая громкость составляет 50% и что баланс звука смещен вправо:

    <MediaElement x:Name="vid" Source="balls.wmv" 
                 Height="200" Width="200" Stretch = "Fill" 
                 IsMuted = "False" Volume="0.5" Balance="0.8" />

    Программирование Media Element

    MediaElement предлагает насыщенную модель программирования, которая позволяет управлять воспроизведением методами Play, Stop (Остановить) и Pause (Приостановить). Также вы имеете возможность взаимодействовать с видео, отслеживая процесс буферизации и загрузки, а также отвечать на маркеры, размещенные в видеофайле. Кроме того, можно обработать события для перехвата, такие как поведение мыши.

    Базовые элементы управления воспроизведением

    Основными методами управления видео являются Play, Stop и Pause. Если свойству AutoPlay элемента управления MediaElement задано значение false, для начала воспроизведения видео необходимо применить Play. Даже если AutoPlay задано значение true, и воспроизведение начинается автоматически, с помощью этих методов его можно остановить или приостановить. Далее представлен пример XAML, в котором имеется элемент мультимедиа и три простых элемента управления воспроизведением, реализованных как элементы TextBlock:

    <MediaElement x:Name="vid" Source="balls.wmv" Height="200" 
                 Width = "200" Stretch="Fill" /> 
    <Canvas Canvas.Top="160">
      <Rectangle Fill= "Black" Width = "200" Height="24" Opacity="0.7"/>
      <TextBlock Foreground= "White" Canvas.Left="20">Play</TextBlock>
      <TextBlock Foreground="White" Canvas. Left="80">Stop</TextBlock>
      <TextBlock Foreground="White" Canvas. Left=" 140" >Pause</TextBlock> 
    </Canvas>

    На рис. 11.10 показано, как эти элементы управления будут выглядеть на видеоизображении.

    (рис 11.10) Добавление элементов управления в видеоизображение

    Для создания элементов управления вы задаете имя функции, которая должна выполняться в ответ на событие мыши, используя атрибут самого элемента управления видео. Намного более подробно обработка событий Silverlight с помощью JavaScript рассматривается в лекц. 6, "Элемент управления браузера Silverlight". Однако, в данном случае, просто требуется начать, остановить или приостановить воспроизведение видео, когда пользователь щелкнет соответствующий текстовый блок. Это реализуется путем обработки события MouseLeftButtonDown текстового блока, которое ассоциируется с функциями, воспроизводящими, приостанавливающими или останавливающими воспроизведение.

    При программировании на JavaScript необходимо обеспечить, чтобы эти функции были доступны со страницы, на которой размещается элемент управления Silverlight. Итак, они могут быть реализованы либо путем использования элементов JavaScript на странице, либо в файле js, который включен в страницу с помощью элемента JavaScript, свойству Src которого задано соответствующее значение.

    Если для сведения воедино содержимого XAML используется Expression Blend, он обеспечивает для Page.xaml псевдо-файл выделенного кода Page.xaml.js. Это идеальное место для реализации функциональности JavaScript.

    Создадим проект Silverlight 1.0 Site. Далее представлен XAML, описывающий похожий UI, как был создан в предыдущем примере, но с объявлениями обработчиков событий:

    <Canvas
      xmlns="http://schemas.microsoft.com/client/2007"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      Width="640" Height="480" Background="White" x:Name="Page">
      <MediaElement x:Name="vid" Source="balls.wmv"
                    Height="200" Width="200" Stretch="Fill" />
      <Canvas Canvas.Top="160">
        <Rectangle Fill="Black" Width="200" Height="24" Opacity="0.7"/>
        <TextBlock MouseLeftButtonDown="doPlay"
                   Foreground="White"
                   Canvas.Left="20">Play</TextBlock>
        <TextBlock MouseLeftButtonDown="doStop"
                   Foreground="White"
                   Canvas.Left="80">Stop</TextBlock>
        <TextBlock MouseLeftButtonDown="doPause"
                   Foreground="White"
                   Canvas.Left="140">Pause</TextBlock>
      </Canvas>
    </Canvas>

    Теперь можно написать кода на JavaScript для начала, остановки и приостановки воспроизведения. Вот этот код:

    function doPlay(sender, args) 
    {
      var meVid = sender.findName("vid");
      meVid.Play(); 
    }
    function doStop(sender, args) 
    {
      var meVid = sender.findName("vid");
      meVid.Stop(); 
    }
    function doPause(sender, args) 
    {
      var meVid = sender.findName("vid");
      meVid.Pause(); 
    }

    Когда функция JavaScript определяется как обработчик события, она должна принимать два параметра. Первый, sender, - это объект, который формирует событие. Второй, args, содержит аргументы, которые включаются как часть события.

    Теперь в этой JavaScript-функции необходимо получить ссылку на объект MediaElement, управление которым осуществляется. Выполняется это с помощью метода findName объекта sender. Хотя отправителем является текстовый блок, который щелкнул пользователь, выполнение его метода findName все равно обеспечит выполнение поиска по всему XAML-документу до тех пор, пока не будет найден элемент vid (который был создан путем применения атрибута x:Name к нашему объекту MediaElement ). Если вернуться к XAML, можно увидеть, что в MediaElement был элемент x:Name со значением vid, так что поиск должен быть успешным. Ссылка на соответствующий объект MediaElement будет сохранена в переменной JavaScript meVid. Теперь для управления воспроизведением осталось просто вызывать методы Play, Stop или Pause для этого объекта.

    При создании проекта приложения для Silverlight 2 и использовании .NET все даже еще проще: не надо использовать findName для доступа к элементу, и ему уже присвоено имя vid, так что код будет выглядеть так:

    private void doPlay(object sender, MouseButtonEventArgs e) 
    {
      vid.Play(); 
    }
    private void doPause(object sender, MouseButtonEventArgs e) 
    {
      vid.Pause(); 
    }
    private void doStop(object sender, MouseButtonEventArgs e) 
    {
      vid.Stop(); 
    }

    Управление буферизацией и загрузкой

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

    Когда буфер полон на 100%, начинается воспроизведение. Воспроизведение может прерываться при изменении состояния связи и дозагрузки буфера. Silverlight позволяет отслеживать это поведение с помощью события BufferingProgressChanged (Показатель процесса буферизации изменился) и свойства BufferingProgress (Показатель процесса буферизации). Используя эти событие и свойство, можно показывать пользователям текущее состояние буферизации или выполнять логику для улучшения взаимодействия с пользователем (UX) как результат условий буферизации. Например, в условиях плохого соединения буферизация может не превышать 50%. Вы можете перехватить это значение и предоставить соответствующую обратную связь своему клиенту.

    Для управления буферизацией задайте в MediaElement обработчик событий, определяющий функцию для обработки события BufferingProgressChanged:

    <MediaElement x:Name="vid" Source="balls.wmv" Height="200" Width="200"
    Stretch="Fill" Buffering Prog ressChanged = "doBuff"/> <TextBlock x:Name="txtBuff"></TextBlock>

    Таким образом, функция doBuff будет выполняться каждый раз при изменении наполненности буфера. Это событие тесно связано со свойством BufferingProgress. Данное свойство принимает значения в диапазоне от 0 до 1, где 0 соответствует пустому буферу, и 1 - полному. Событие будет формироваться при изменении наполнения буфера на 5% (т.е. 0,05) или более и когда буфер заполнен полностью.

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

    Сначала JavaScript-версия:

    function doBuff(sender, args) 
    {
      var theText = sender.findName("txtBuff");
      var meVid = sender.findName("vid");
      var prog = meVid.BufferingProgress * 100;
      prog = "Buffering % " + prog;
      theText.Text = prog; 
    }

    И вот, как это было бы сделано в выделенном коде .NET (на C#):

    private void doBuff(object sender, RoutedEventArgs e) 
    {
      double prog = vid.BufferingProgress * 100;
      txtBuff.Text = "Buffering % " + prog; 
    }

    Если у вас не видно текст с отображением буферизации в Silverlight-приложении, обратите внимание на используемый элемент компоновки. Если вы использовали <Grid>, текст может находиться под "кнопками" Play, Stop и Pause. В этом случае задайте для текстовых блоков небольшой отступ с помощью свойства Margin.

    Можно переопределить автоматическую буферизацию, задавая конкретную продолжительность буферизованного видео. Например, если необходимо всегда иметь 10-секундный буфер видео и, таким образом, снизить риск прерывания воспроизведения из-за повторной синхронизации буфера при ухудшении состояния связи, можно задать свойство BufferingTime (Размер буфера). В качестве его значения задается временной диапазон. Так, например, чтобы задать 10-секундный буфер, BufferingTime задается значение 0:0:10, как показано в следующем примере:

    <MediaElement x:Name="vid" Source="balls.wmv"
              Height="200" Width = "200" Stretch="Fill"
              BufferingProgressChanged = "doBuff"
              BufferingTime="0:0:10"/> 
    <TextBlock x:Name="txtDown"></TextBlock>

    Если прогрессивная загрузка недоступна или не поддерживается, придется загружать весь видеофайл целиком и только потом начинать его воспроизведение. В этом случае событие DownloadPro-gressChanged и свойство DownloadProgress могут использоваться для получения состояния загрузки. Принципы работы с ними аналогичны функциям буферизации. Дале представлен XAML, определяющий событие DownloadProgressChanged:

    <MediaElement x:Name="vid" Source="balls.wmv"
               Height="200" Width="200" Stretch="Fill"
               BufferingProgressChanged = "doBuff"
               BufferingTime="0:0:10"
               DownloadProgressChanged = "doDown"/>
    <TextBlock x:Name="txtDown"></TextBlock>

    И теперь код функции doDown, обрабатывающей это событие. Опять же, сначала рассмотрим JavaScript-версию:

    function doDown(sender, args) 
    {
      var theText = sender.findName("txtDown");
      var meVid = sender.findName("vid");
      var prog = meVid.DownloadProgress * 100;
      prog = "Downloading % " + prog;
      theText.Text = prog; 
    }

    И BOT.NET-версия:

    private void doDown(object sender, RoutedEventArgs e) 
    {
     double prog = vid.DownloadProgress * 100;
     txtBuff.Text = "Downloading % " + prog;
    }

    Управление текущим состоянием видео

    Silverlight представляет свойство CurrentState (Текущее состояние) и связанное с ним событие Cur-rentStateChanged (Текущее состояние изменилось), которое может использоваться для ответа на изменение состояния мультимедиа.

    Действительными состояниями свойства CurrentState являются:

  • Buffering ( Буферизация ) Буфер не заполнен на 100%, поэтому воспроизведение приостановлено до полного заполнения буфера.
  • Closed ( Закрыто ) Мультимедиа закрыто.
  • Error ( Ошибка ) Возникла проблема с загрузкой, буферизацией или воспроизведением мультимедиа.
  • Opening ( Открывается ) Мультимедийный файл найден, и буферизация или загрузка должны вот-вот начаться.
  • Paused ( Приостановлено ) Воспроизведение приостановлено.
  • Playing ( Воспроизводится ) Идет воспроизведение мультимедиа.
  • Stopped ( Остановлено ) Воспроизведение остановлено.
  • Вот как событие CurrentStateChanged элемента управления MediaElement определяется в XAML:

    <MediaElement x:Name="vid" Source="balls.wmv" 
              Height="200" Width="200" Stretch="Fill" 
              CurrentStateChanged= "doState" BufferingTime="0:0:10" />

    Здесь задается функция doState, которая будет вызываться в ответ на изменение текущего состояния. Далее представлен пример такой JavaScript-функции, которая использует значение свойства Cur-rentState элемента управления MediaElement в окне уведомления:

    function doState(sender, args) 
    {
      var meVid = sender.findName("vid");
      alert(meVid.CurrentState); 
    }

    В C#, вместо окна уведомления MessageBox, для вывода содержимого свойства CurrentState на экран можно использовать TextBlock:

    private void doState(object sender, RoutedEventArgs e) 
    {
      txtBuff.Text = vid.CurrentState.ToString(); 
    }

    Управление положением курсора воспроизведения

    Для управления текущим положением курсора воспроизведения используются свойства NaturalDuration (Номинальная продолжительность) и Position (Положение) элемента мультимедиа. После того, как свойство CurrentState устанавливается в значение Opened, становится возможным получение значения и свойства NaturalDuration. Продолжительность видео в секундах можно получить с помощью Natu-ralDuration.Seconds и потом преобразовать это значение в часы, минуты и секунды.

    В этом примере событие CurrentStateChanged элемента управления MediaElement ассоциировано с функцией doState (из предыдущего примера). Однако теперь эта функция перехватывает значение свойства NaturalDuration. В JavaScript-версии используется вспомогательная функция convertDT для преобразования возвращаемого значения в строку. Рассмотрим код на JavaScript:

    function doState(sender, args) 
    {
       var meVid = sender.findName("vid");
       var txtStat = sender.findName("txtStat");
       var datetime = new Date(0, 0, 0, 0, 0, meVid.naturalDuration.Seconds)
       durationString = convertDT(datetime);
       txtStat.Text = durationString.toString(); 
    }
    
    function convertDT(datetime) 
    {
      var hours = datetime.getHours();
      var minutes = datetime.getMinutes();
      var seconds = datetime.getSeconds();
      if (seconds < 10)  {
        seconds = "0" + seconds;
      }
      if (minutes < 10) {
        minutes = "0" + minutes; 
      }
      var durationString; 
      if (hours > 0) {
        durationString = hours.toString() + ":" + minutes + ":" + seconds; 
      } else {
        durationString = minutes + ":" + seconds; 
      }
      return durationString; 
    }

    Далее следует замечательный пример того, как .NET Framework в Silverlight 2 существенно упрощает разработку: все это может быть реализовано одной строкой кода на C#:

    private void doState(object sender, RoutedEventArgs e)
    { txtStat.Text = vid.NaturalDuration.ToString();
    }

    Вот XAML, В котором используется представленная выше функция:

    <Canvas x:Name="sample9" Opacity="1"> 
      <MediaElement x:Name="vid" Source="balls.wmv" 
              Height="200" Width="200" Stretch = "Fill" 
              CurrentStateChanged="doState" BufferingTime="0:0:10" /> 
      <TextBlock x:Name="txtStat">
      </TextBlock> 
    </Canvas>

    Получить текущее положение курсора воспроизведения можно с помощью свойства Position. В этом примере позиция выводится в текстовом блоке при приостановке воспроизведения (Для тестирования можете оставить в XAML-коде три "кнопки" Play, Stop и Pause и соответствующие им обработчики событий doPlay и doStop с предыдущего примера, но закомментировать тело функции doState ). Необходимый код на JavaScript для функции doPause выглядел бы примерно так:

    function doPause(sender, args) {
     var meVid = sender.findName("vid");
     meVid.Pause();
     var txtStat = sender.findName("txtStat");
     var datetime = new Date(0,0,0,0,0, meVid.Position.Seconds);
     positionString = convertDT(datetime);
     txtStat.Text = positionString.toString(); 
    }

    И, как и ранее, эквивалентный код на C# намного проще:

    private void doPause(object sender, MouseButtonEventArgs e) {
    vid.Pause();
    txtStat.Text = vid.Position.ToString(); }

    Использование маркеров временной шкалы мультимедиа

    Маркер временной шкалы - это элемент метаданных, ассоциированный с конкретной точкой на временной шкале мультимедиа. Обычно эти маркеры создаются и кодируются в мультимедиа заранее с помощью такого ПО, как Expression Encoder, и часто применяются для установления в видео поисковых меток.

    Silverlight поддерживает эти маркеры и формирует событие MarkerReached (Маркер достигнут) при встрече с маркером в ходе воспроизведения. Это событие можно перехватывать и использовать как триггер для действий, которые должны выполняться при достижении данной отметки.

    Далее представлен фрагмент XAML, в котором с помощью атрибута MarkerReached задается обработчик события встречи с маркером, JavaScript-функция handleMarker:

    <MediaElement x:Name="vid" Source="balls.wmv" Height="200" Width = "200" MarkerReached = "handleMarker" />

    Среди аргументов данного события имеется объект marker (маркер). Он содержит объект TimeSpan (Диапазон времени) с меткой времени, в которой установлен маркер. В предыдущем разделе, "Управление положением курсора воспроизведения", представлен пример преобразования объекта Time-Span в формат, удобный для восприятия пользователем. Также marker содержит свойство Type (Тип).

    Его значением является строка, которую задает человек, кодирующий видео. Наконец, имеется параметр Text, допускающий в качестве своего значения текст в свободном формате и обычно используемый для описания параметра. Далее представлен JavaScript-код для чтения всех трех параметров и создания строки, отображаемой в окне уведомления:

    function handleMarker(sender, args) {
     var strMarkerStatus = args.marker.time.seconds.toString();
     strMarkerStatus += "   :   ";
     strMarkerStatus += args.marker.type;
     strMarkerStatus += "   :   ";
     strMarkerStatus += args.marker.text;
     alert(strMarkerStatus); }

    Код на C# очень похож на предыдущий; заметьте, что args типа TimeLineMarkerRoutedEventArgs:

    private void handleMarker(object sender, TimelineMarkerRoutedEventArgs e) {
     string strMarkerStatus = e.Marker.Time.ToString();
     strMarkerStatus += "   :   ";
     strMarkerStatus += e.Marker.Type;
     strMarkerStatus += "   :   ";
     strMarkerStatus += e.Marker.Text; }

    Как это выглядит, можно увидеть на рис. 11.11.

    (рис 11.11) Перехват маркеров временной шкалы в Silverlight

    Маркеры временной шкалы можно добавлять в медиафайл также динамически, используя код на Silverlight. При этом поисковые метки можно задавать через процентное соотношение положения метки к общей продолжительности файла, например.

    Далее представлен пример, в котором XAML для MediaElement определяет функцию handleOpen (Обработка открытия), выполняющуюся при открытии файла мультимедиа. Она вставляет новый объект временной шкалы в видео на 10-секундной отметке. Этот элемент не сохраняется в видео постоянно, только до завершения сеанса.

    <MediaElement x:Name="vid" Source="balls.wmv" 
                Height="200" Width = "200" 
                MarkerReached = "handleMarker" MediaOpened= "handleOpened" />

    Обработчик должен создать новый элемент временной шкалы в XAML и добавить его в коллекцию маркеров MediaElement. В данном случае, задается маркер временной шкалы на отметке 10 секунд, для которого Type определен как 'My Temp Marker' (мой временный маркер) и Text- 'Dynamically Added Marker' (Динамически добавляемый маркер).

    Вот как это выглядело бы в JavaScript:

    function handleOpened(sender, args) {
     var marker =
     sender.getHost().content.createFromXam l(
     "<TimelineMarker Time='0:0:10'" +
              " Type='My Temp Marker' 
                         Text='Dynamically Added Marker' />"); 
     sender. markers.add(marker); 
    }

    И вот аналогичный код в .NET:

    private void handleOpened(object sender, RoutedEventArgs e) 
    {
     TimelineMarker t = new TimelineMarker();
     t.Time = new TimeSpan(0, 0, 0, 10);
     t.Type = "My Temp Marker";
     t.Text = "Dynamically Added Marker";
     vid.Markers.Add(t); 
    }

    Коллекцию маркеров MediaElement составляют объекты TimeLineMarker (Маркер временной шкалы), поэтому для введения нового маркера просто создается новый объект TimeLineMarker, задаются его свойства Time, Type и Text, и этот объект добавляется в коллекцию. Silverlight имеет достаточно развитую логику и знает, когда должно быть сформировано событие, исходя из заданного Time, поэтому при создании новых маркеров нет необходимости добавлять их в порядке расположения на временной шкале.

    Теперь, когда MediaElement достигает 10-секундной отметки при воспроизведении, появляется диалоговое окно уведомления, как показано на рис. 11.12.

    (рис 11.12) Использование динамически добавляемого маркера

    Закрашивание поверхностей видеоизображением с помощью VideoBrush

    Особенно замечательная возможность Silverlight - использование видео для заливки поверхности с помощью VideoBrush. Сделать это очень просто. Во-первых, необходим элемент мультимедиа, который загрузит видео. Он должен быть скрыт и не должен принимать события мыши. Для этого его прозрачность задается равной 0 и свойству IsHitTestVisible (Объект виден для механизма обработки кликов) присваивается значение false. Также элементу мультимедиа должно быть задано имя посредством свойства x:Name. Рассмотрим пример:

    <MediaElement x:Name="vid" Source="balls.wmv" Opacity="0" IsHitTestVisible="False" />

    Теперь объект VideoBrush можно применять к любому объекту точно так же, как любую другую кисть. В качестве источника кисти задается MediaElement (вот почему ему обязательно должно быть присвоено имя). Также можно задать свойство Stretch для управления визуальными эффектами кисти.

    Например, здесь представлен TextBlock, для закрашивания Foreground которого используется Video-Brush:

    <TextBlock FontFamily="Verdana" FontSize="80" 
                FontWeight="Bold" TextWrapping = "Wrap" 
                Text="Video"> 
      <TextBlock.Foreground >
        <VideoBrush SourceName="vid"/> 
      </TextBlock.Foreground > 
    </TextBlock>

    Silverlight теперь будет формировать визуальное представление текста, используя VideoBrush. На рис. 11.13 показано, как это будет выглядеть.

    (рис 11.13) Использование VideoBrush для закрашивания текста

    Элемент управления InkPresenter

    Как было показано в данном курсе до сих пор, Silverlight расширяет возможности следующего поколения Веб-приложений, предоставляя инструменты для добавления насыщенных видео, аудио, векторной графики, анимации и других элементов, которые улучшают UX. Рукописные примечания - создание рукописного или нарисованного от руки содержимого с использованием устройств, разработанных для этих целей, - еще один замечательный способ сделать приложения еще более интерактивными и индивидуальными. Обеспечиваемая Silverlight поддержка программирования рукописного содержимого приносит эту функциональность в Веб. В данном разделе мы рассмотрим поддержку рукописных примечаний в Silverlight.

    Существует несколько разных типов устройств, которые могут использоваться для приложений с поддержкой рукописных примечаний:

  • Ввод с помощью пера Обычно преобразователи графической информации, вводимой пером поддерживаются Планшетными ПК (Tablet PC), но существуют также настольные компьютеры, которые поддерживают подключение внешних планшетов. Эти устройства используют возможности ввода с помощью пера, предоставляемые в Silverlight. Они создают Ink (Рукописный фрагмент), который может быть интегрирован в Веб-страницы Silverlight, таким образом, обеспечивая поддержку рукописного ввода, рисования, рукописных примечаний и прочих форматов ввода в Веб.
  • Сенсорный ввод Сенсорные экраны широко применяются в интерактивных терминалах или других местах, где использование стилуса или клавиатуры было бы слишком громоздким или неуместным. Рукописные примечания в Silverlight обеспечивают поддержку сенсорных экранов, что позволяет создавать насыщенные Интернет-приложения с возможностью сенсорного ввода.
  • Ввод посредством мыши Имитировать рукописный ввод, подобный вводу пером Планшетного ПК, можно с помощью мыши. Однако такой ввод будет иметь боле низкое разрешение по сравнению с использованием пера настоящего Планшетного ПК.
  • Пример рукописных примечаний в Silverlight

    На сайте http://silverlight.net предлагается замечательный пример приложения, поддерживающего рукописные примечания. Это приложение для чтения книг можно найти по адресу http://silverlight.net/samples/1.0/Page-Turn/default.html и увидеть на рис. 11.14.

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

    (рис 11.14) Пример PageTurn на Silverlight(рис 11.15) Комментирование изображения в Silverlight

    Классы рукописного ввода в Silverlight для разработчиков на JavaScript

    Поддержка рукописного ввода в Silverlight очень проста. Каждый раз при проведении устройством ввода по экрану формируется один или более объектов StylusPoint (Штрих стилуса). Эти объекты объединяются в коллекцию StylusPointCollection (Коллекция штрихов стилуса), которая формирует основу Stroke. Обводки собираются в коллекцию StrokeCollection (Коллекция обводок). Эта коллекция составляет список графических элементов, используемых для создания рукописного ввода, визуальное предоставление которого формирует InkPresenter. Итак, если взглянуть на рис. 11.15, буква "С" в слове COOL - это обводка, образованная рядом точек, объединенных в StrokeCollection. Обе буквы "О", буква "L", линия и точка в восклицательном знаке, и линии, образующие стрелку и круг, - все это обводки. Каждая из этих обводок является участником коллекции StrokeCollection в InkPresenter, и каждая из них образована объектами StylusPoint, входящими в состав коллекции StylusPointCollection соответствующей обводки. Каждый из этих типов предоставляет объектно-ориентированный интерфейс со свойствами и методами, обеспечивающими возможность их программирования. Рассмотрим основные типы и некоторые их свойства и методы.

    Тип StrokeCollection

    Объект InkPresenter имеет свойство Strokes типа StrokeCollection. Эта коллекция, в свою очередь, содержит все метаданные, необходимые для представления рукописного ввода пользователя.

    Свойства StrokeCollection

    При использовании JavaScript для программирования Silverlight, StrokeCollection предоставляет свойство Count (Количество), которое возвращает количество обводок, находящихся в коллекции в настоящий момент.

    Методы StrokeCollection

    StrokeCollection предоставляет следующие методы:

  • Add ( Добавить ) Позволяет добавлять новую обводку в коллекцию.
  • Clear ( Очистить ) Очищает коллекцию обводок, что приводит к немедленному повторному формированию визуального отображения (т.е. удалению рукописного фрагмента, созданного ранее).
  • GetBounds ( Получить границы ) Возвращает прямоугольник (в виде структуры Rect ), который представляет блок, ограничивающий обводки.
  • GetItem(индекс) ( Получить элемент ) Обеспечивает извлечение обводки, хранящейся под заданным индексом.
  • HitTest ( Проверка совпадения ) Если этот метод передается в StylusPointCollection, он возвращает подмножество обводок в StrokeCollection, которые пересекаются с данными штрихами.
  • Insert ( Вставить ) Аналогичен Add за исключением того,что позволяет вставлять новые обводки в коллекцию по заданному индексу.
  • Remove ( Удалить ) Обеспечивает удаление заданного элемента из StrokeCollection.
  • ) Обеспечивает удаление элемента из StrokeCollection по заданному индексу.
  • Тип Stroke

    Представленный в предыдущем разделе StrokeCollection - это коллекция объектов Stroke. Объект Stroke представляет коллекцию штрихов обводки; каждый штрих соответствует одному движению стилуса: стилус опускается (stylus-down), стилус перемещается (stylus-move), стилус поднимается (stylus-up). Эти объекты, в свою очередь, обладают собственным набором свойств и методов.

    Свойства Stroke

    Объект Stroke предоставляет следующие свойства:

  • DrawingAttributes ( Атрибуты рисования ) Каждая обводка имеет собственную независимую высоту, ширину, цвет и цвет контура. Эти параметры задаются посредством типа DrawingAttributes. Чтобы использовать этот тип, создается экземпляр объекта DrawingAttributes, задаются значения свойств, и новый объект DrawingAttributes добавляется в обводку.
  • StylusPoints ( Штрихи стилуса ) Это StylusPointCollection, содержащий коллекцию объектов StylusPoint, образующих обводку. StylusPoint обсуждается в следующем разделе.
  • Методы Stroke

    Объект Stroke обладает следующими методами для работы с рукописным вводом:

  • GetBounds Возвращает ограничивающий прямоугольник Stroke в виде структуры Rect.
  • HitTest Если передать этот метод в StylusPointCollection и если какой-либо из этих штрихов пересекает Stroke, метод возвратит true; в противном случае, возвращается false.
  • Тип StylusPointCollection

    Эта коллекция объединяет в себе объекты StylusPoint. Она используется для хранения штрихов стилуса для Stroke или, в некоторых случаях, может передаваться в такие методы, как HitTest, для выявления пересечения обводок.

    Свойства StylusPointCollection

    StylusPointCollection предоставляет свойство - Count, которое возвращает количество штрихов стилуса в данной коллекции.

    Методы StylusPointCollection

    Объект StylusPointCollection имеет следующие методы:

  • Add Этот метод позволяет добавлять новый StylusPoint в конец коллекции.
  • AddStylusPoints ( Добавить штрихи стилуса ) Этот метод позволяет добавлять существующий объект StylusPointCollection в конец данной коллекции.
  • Clear Этот метод удаляет все объекты StylusPoint из коллекции.
  • GetItem Этот метод получает заданный StylusPoint из коллекции. Используется со значением типа integer, представляющим индекс интересующего элемента, например, getItem(5 ).
  • Insert Этот метод вставляет новый StylusPoint в коллекцию по заданному индексу.
  • Remove Этот метод удаляет заданный StylusPoint из коллекции.
  • RemoveAt Этот метод удаляет из коллекции StylusPoint по заданному индексу.
  • Тип StylusPoint

    Тип StylusPoint представляет отдельный штрих, создаваемый, когда пользователь выполняет ввод с помощью устройства рукописного ввода: пера, мыши или сенсорного экрана. Штрих предоставляет ряд свойств и методов.

    Свойства StylusPoint

    StylusPoint предоставляет следующие свойства:

  • Свойство Name позволяет задавать имя штриха. Это уникальный идентификатор. Когда штрихи формируются в процессе ввода пользователем, имена им не присваиваются.
  • Свойство PressureFactor (Коэффициент нажима) обозначает нажим, с которым пользователь водит пером или касается экрана при формировании штриха. При использовании мыши применяется нажим по умолчанию. Это значение типа double в диапазоне от 0.0 до 1.0, значение по умолчанию - 0,5. На основании коэффициента нажима можно менять высоту и ширину Stroke программно через свойство DrawingAttributes, чтобы предоставить обратную связь пользователю.
  • Координаты обводки можно получить с помощью свойств X и Y. Они измеряются в пикселах.

    Аргументы событий мыши и рукописный ввод

    При использовании рукописного ввода формируемые устройством ввода события трактуются как события мыши, и аргументы, получаемые обработчиками событий, будут типа MouseEventArgs.

    Объект MouseEventArgs содержит методы, которые позволяют запрашивать сведения об устройстве ввода (является ли оно мышью, стилусом или каким-то другим устройством) и коллекцию объектов StylusPoint, связанную с этим событием.

    Обратите внимание, что это тот же объект MouseEventArgs, который используется с событиями Mou-seEnter, MouseLeave, MouseLeftButtonDown, MouseLeftButtonUp и MouseMove.

    Свойства MouseEventArgs

    Объект MouseEventArgs предоставляет следующие свойства Булевого типа: ctrl и shift. Они принимают значение true, если пользователь удерживает соответствующую клавишу при формировании события.

    Методы MouseEventArgs

    Объект MouseEventArgs предоставляет следующие методы:

  • Метод GetPosition (Получить положение) принимает в качестве параметра элемент и возвращает объект Point, который представляет координаты х и у курсора мыши относительно этого элемента. Если вызов метода осуществляется без параметра, Point содержит координаты относительно положения элемента управления, сформировавшего событие.
  • Метод GetStylusInfo (Получить сведения о стилусе) возвращает объект StylusInfo (Сведения о стилусе), содержащий информацию о состоянии стилуса. StylusInfo имеет следующие свойства: 1.
  • IsInverted (Инвертированный):Если перо инвертировано, это указывает на то, что пользователь хочет использовать его для удаления штрихов, а не для их отрисовки. Это свойство возвращает true в этом случае. 2.
  • DeviceType (Тип устройства):Возвращает строку, содержащую тип устройства -"Mouse", "Stylus" или "Touch".
  • Метод GetStylusPoints возвращает копию штрихов стилуса, добавленных в коллекцию с момента формирования последнего события мыши. Это будет объект типа StylusPointCollection.
  • Программирование для рукописного ввода в Silverlight

    При написании приложений инструментами Microsoft под термином рукописный ввод (Ink) обычно подразумевается надпись или рисунок от руки, создаваемый пользователем с помощью описанных ранее устройств рукописного ввода, таких как цифровое перо, сенсорный экран или мышь. При использовании в приложении Silverlight эти устройства обеспечивают заполнение объекта StrokeCollec-tion отдельными объектами Stroke. В свою очередь, Stroke выполняет запись действий устройства, например пера, к которым относятся действия: перо опускается (pen-down), перо перемещается (pen-move) и перо поднимается (pen-up). Stroke может представлять точку, прямую линию или кривую. Это осуществляется с помощью объекта StylusPointCollection, который содержит объекты StylusPoint, создаваемые преобразователем графической информации, связанным с пером, сенсорным экраном или мышью. Атрибуты рукописного ввода содержаться в классе DrawingAttributes.

    Как говорилось, Silverlight принимает элементы рукописного ввода с помощью класса InkPresenter. Это подкласс элемента Canvas, который также содержит коллекцию обводок в StrokeCollection. При добавлении обводок в StrokeCollection, InkPresenter автоматически формирует их визуальное представление, используя соответствующие DrawingAttributes.

    Обычно InkPresenter добавляется в XAML приложения во время разработки дизайна, но объекты Stroke, составляющие StrokeCollection, будут добавляться во время выполнения с помощью JavaScript.

    Далее представлен пример использования перекрывающего изображение InkPresenter на странице:

    <Canvas xmlns="http://schemas.microsoft.com/client/2007"
      xmlns:x=" http://schemas.microsoft.com/winfx/2006/xaml">
      <Image Source="sushi.jpg" Width="600" Height="400"></Image>
      <InkPresenter x:Name="inkEl" Background="transparent" 
                    Width="600" Height="400" 
                    MouseLeftButtonDown="inkMouseDown" 
                    MouseMove="inkMouseMove" 
                    MouseLeftButtonUp="inkMouseUp"/>
    </Canvas>

    InkPresenter определяет обработчики событий MouseLeftButtonDown, MouseMove и MouseUp. Мы рассмотрим, как это делается в JavaScript, для чего создадим простое приложение для рукописного ввода. Затем далее в этом разделе то же самое будет сделано на C#. Сходство необыкновенное! Для этих событий понадобятся функции-обработчики действий "начало рукописного ввода", "рукописный ввод" и "прекращение рукописного ввода".

    Примечание Хотя рукописный ввод может быть выполнен пером, с помощью сенсорного экрана или мыши, в документации API используется термин "Мышь".

    Прежде чем перейти к функциям-обработчикам событий JavaScript, требуется выполнить некоторые вспомогательные операции по объявлению глобальных переменных, необходимых для поддержки этих действий:

    var theInk;       // Ссылка на модуль отображения рукописного ввода
    var newStroke;  // Ссылка на обводку
    var theControl;  // Ссылка на элемент управления Silverlight
    function handleLoad(control, userContext, rootElement)
    {
     // Событие Load возвращает ссылку на элемент управления,
     // но другие обработчики событий нет. Поэтому мы собираемся
     // создать ссылку на элемент управления здесь
     theControl = control;
     // Здесь мы создадим ссылку на элемент рукописного ввода 
     theInk = control.content. findName("inkEl"); 
    }

    Эти вспомогательные переменные для InkPresenter, текущей обводки и самого элемента управления Silverlight будут использоваться функциями, поддерживающими описанные выше действия рукописного ввода. При загрузке элемент управления Silverlight запускает функцию handleLoad. В качестве параметра она принимает ссылку на элемент управления Silverlight, но поскольку реализованные нами обработчики событий мыши не делают этого, данную ссылку необходимо сохранить в handleLoad. В ходе выполнения handleLoad можно также получить ссылку на InkPresenter, проведя его поиск по имени ( in-kEl ). Тогда не придется вызывать getHost для получения ссылки на родительский элемент UI, чтобы найти InkPresenter, при каждом выполнении обработчика событий.

    Теперь можно приступать к рассмотрению обработчиков событий. Сначала разберемся, что происходит при формировании события MouseLeftButtonDown, результатом чего является выполнение функции inkMouseDown (Нажатие кнопки мыши при рукописном вводе). Мы будем отслеживать перемещения мыши аналогично тому, как это происходило при обработке перемещения методом "drag-and-drop", что рассматривалось в лекц. 6. При получении входных данных от мыши создается новый объект Stroke, содержащий объект DrawingAttributes, который определяет визуальные характеристики этой обводки. В представленном здесь примере DrawingAttributes будет обеспечивать следующие параметры обводки: Width - 2, Height-2, цвет заливки - White (Белый), и цвет контура - White. Тип MouseEven-tArgs в Silverlight поддерживает метод getStylusPoints, как было показано ранее в этой лекции, который принимает InkPresenter в качестве единственного параметра. Этот метод возвращает тип StylusPointCol-lection, который используется в методе AddStylusPoints обводки. Теперь можно добавить обводку в StrokesCollection InkPresenter. Посмотрим на код:

    function inkMouseDown(sender,args) 
    {
     // Перехватываем событие мыши. 
     theInk.CaptureMouse();
     // Создаем новую обводку.
     newStroke = theControl.content.createFromXaml('<Stroke/>');
     // Задаем новый элемент атрибутов рисования для обводки. 
     // Он, как следует из его имени, определяет внешний вид обводки 
     var da =    theControl.content.CreateFromXaml('<DrawingAttributes/>'); 
     newStroke.DrawingAttributes = da;
     // Теперь, когда обводка имеет атрибуты рисования, 
     // зададим их значения... 
     newStroke.DrawingAttributes.Width = 2; 
     newStroke.DrawingAttributes.Height = 2; 
     newStroke.DrawingAttributes.Color = "White"; 
     newStroke.DrawingAttributes.OutlineColor = "White";
     newStroke.StylusPoints.Add StylusPoints(args.GetS tylusPoints(theInk)); 
     theInk.Strokes.Add (newStroke); 
    }

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

    // Добавляем новые штрихи в Stroke, с которым работаем в данный момент.
    function inkMouseMove(sender,args)
    {
     if (newStroke != null)
     { 
      newStroke.StylusPoints.AddStylusPoints(args.GetStylusPoints(theInk));
     }
    }

    Наконец, по завершении отрисовки обводки, когда вы отпускаете кнопку мыши (или отрываете перо от экрана), формируется событие MouseLeftButtonUp. После этого требуется очистить обводку и прекратить отслеживание мыши. После того, как переменной newStroke задается значение null, движение мыши (или пера) по экрану больше не будет обеспечивать добавление штрихов в обводку, и, следовательно, обводка не будет отрисовываться. Рассмотрим код:

    function inkMouseUp(sender,args) 
    {
     // Задаем newStroke значение null
     newStroke = null;
     // Прекращаем отслеживать перемещения мыши 
     theInk.releaseMouseCapture(); 
    }

    На рис. 11.16 показан пример приложения до добавления в него рукописного примечания. На рис. 11ly представлено то же приложение с рукописным примечанием, которое было отрисовано с помощью мыши или пера.

    (рис 11.16) Выполнение приложения Silverlightc возможностью рукописного ввода(рис 11.17) Приложение Silverlight с возможностью рукописного ввода с рукописным примечанием

    В предыдущем примере было продемонстрировано, как программировать рукописный ввод, используя Silverlight и JavaScript. Конечно же, то же самое можно сделать с помощью управляемого кода. Вот полный выделенный код на C#, обеспечивающий создание такого же приложения:

    using System;
    using System. Collections.Generic;
    using System.Linq;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Animation;
    using System.Windows.Shapes;
    using System.Windows.Ink;
    namespace NetInkSample 
    {
     public partial class Page : UserControl 
     {
      Stroke newStroke; public Page() 
      {
       InitializeComponent(); 
      }
      private void inkEl  MouseLeftButtonDown( object sender, MouseButtonEventArgs e) 
      {
      inkEl.CaptureMouse();
      // Создаем новую обводку.
      newStroke = new Stroke();
      DrawingAttributes da = new DrawingAttributes();
      newStroke.DrawingAttributes = da;
      newStroke.DrawingAttributes.Width=2;
      newStroke.DrawingAttributes.Height=2;
      newStroke.DrawingAttributes.Color= Colors.White;
      newStroke.DrawingAttributes.OutlineColor= Colors. White;
      newStroke. StylusPoints.Add(e.Sty lusDevice.GetStylusPoints(inkEl));
      inkEl.Strokes.Add (newStroke); }
      private void inkEl  MouseMove(object sender, MouseEventArgs e) {
      if (newStroke != null) {
       newStroke.StylusPoints.Add ( e.Sty lusDevice. GetS tylusPoints(inkEl));
       }
      }
      private void inkEl  MouseLeftButtonUp(object sender, MouseButtonEventArgs e) 
      {
       newStroke = null; inkEl.ReleaseMouseCapture(); 
      } 
     } 
    }

    И в XAML-коде, соответственно, необходимо сделать изменения:

    <Image Source="sushi.jpg" Width="600" Height="400">
    </Image> 
    <InkPresenter x:Name="inkEl" Background="transparent" 
               Width="600" Height="400" 
               MouseLeftButtonDown="inkEl   MouseLeftButtonDown" 
               MouseMove="inkEl  MouseMove" 
               MouseLeftButtonUp="inkEl   MouseLeftButtonUp"/>

    Кроме демонстрации гибкости Silverlight, который позволяет разрабатывать приложения как на языке сценариев JavaScript, так и на компилируемых языках программирования .NET, это также показывает простоту перехода от JavaScript к .NET, что позволяет без труда обновить существующие Silverlight-приложения на JavaScript до Silverlight 2 и перейти к управляемому коду.

    Реализация Deep Zoom с помощью элемента управления MultiScaleImage

    Deep Zoom -достаточно новая технология, введенная в Silverlight 2 и обеспечивающая новый уникальный способ работы с изображениями в приложениях. Она реализована в XAML посредством элемента MultiScaleImage, который, как следует из его имени, предоставляет возможность управлять размером и масштабом изображений, a Silverlight, в свою очередь, обеспечивает огромное виртуальное пространство, в котором эти изображения могут отрисовываться.

    Лучше всего возможность Deep Zoom рассмотреть на примере. На рис. 11.18 представлена иллюстрация к детскому научному проекту.

    (рис 11.18) Изображение в Deep Zoom

    Пока что ничего сверхъестественного, казалось бы, обычная фотография. Так чем же вызван весь этот шум вокруг Deep Zoom? В этом приложении можно менять масштаб изображения, прокручивая колесо мыши. Итак, на рис. 11.19 представлен другой вид этого изображения. Я уменьшил масштаб, чтобы показать весь рисунок. Теперь можно увидеть первое изображение относительно всей фотографии.

    (рис 11.19) Исходное изображение в другом масштабе

    Посмотрите внимательно на апельсин на рис. 11.19. В его центре можно увидеть изображение с рис. 11.18. Увеличьте масштаб, и вы опять получите изображение, представленное на рис. 11.20.

    (рис 11.20) Еще более глубокое масштабирование

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

    Использование редактора Deep Zoom

    Но как создать приложение, которое позволит уменьшать и увеличивать масштаб изображений таким образом? Базовое приложение для этого написать довольно просто. Возьмем элемент управления MultiScaleImage и ассоциируем его с файлом, содержащим метаданные изображений. Этот файл создается в редакторе Deep Zoom, который можно загрузить в Центре загрузки Microsoft. Редактор Deep Zoom представлен на рис. 11.21.

    (рис 11.21) Редактор Deep Zoom

    Этот инструмент выполняет простую последовательность операций, включающую Import (Импорт), за которым следует Compose (Компоновка) с последующим Export (Экспорт). Итак, начинаем с вкладки Import, щелкаем Add Image (Добавить изображение), чтобы выбрать изображение, и повторяем этот

    шаг для всех изображений, которые вы хотите использовать. На рис. 11.21 можно увидеть, что выбраны три изображения.

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

    (рис 11.22) Компоновка изображений для просмотра с разными уровнями увеличения в редакторе Deep Zoom

    Теперь, следующее изображение с его нормальным разрешением будет помещено на данное, масштаб которого увеличен. Итак, если посмотреть на рис. 11.22, вы увидите, куда вставлено второе изображение (в зрачок глаза). Позже, при выполнении приложения, увидеть это изображение можно будет, только увеличив первое. На рис. 11.23 показано, куда помещается второе изображение на первом.

    (рис 11.23) Добавление нового изображения на увеличенное первое

    Это простой пример, мы просто добавили одно изображение, которое можно увидеть, только увеличив первое. Deep Zoom позволяет создавать намного боле сложные приложения, данный пример лишь дает представление о возможностях этой технологии. Теперь мы готовы перейти к третьему этапу - экспорту. Для этого будем использовать закладку Custom вкладки Export редактора Deep Zoom (рис. 11.24).

    (рис 11.24) Экспорт метаданных Deep Zoom

    Чтобы экспортировать метаданные изображений, скомпонованных в Deep Zoom, необходимо присвоить имя проекту, выбрать в качестве Output type (Тип результата) - "Images" (Изображения), в качестве Image Settings (Настройки изображения) - "Export as Composition" (Экспортировать как композицию) и экспортировать проект в заданный каталог. При этом в выходном каталоге будут созданы два файла и папка. Один из файлов называется SparseImageSceneGraph.xml и является конфигурационным файлом, который определяет каждое изображение и его местоположение на других изображениях с разными уровнями увеличения. Например, рассмотрим граф сцены для XAML с двумя изображениями:

    <?xml version = "1.0"?> <SceneGraph version = "1"> 
      <AspectRatio>1.33333333333334</AspectRatio> <SceneNode>
    <FileName>C:\Code\SLBook\Chapter11\DZCSample \source images\
      dzcsample\DSCN2961JPG</FileName> <x>0</x> <y>0</y>
    < Width >1</Width> <Height>1</Height> <ZOrder> 1 </ZOrder>
    </SceneNode>
    <SceneNode>
    <FileName>C:\Code\SLBook\Chapter11\DZCSample
    \source images\dzcsample\DSCN2959JPG</FileName>
    <x>0.451782754964542</x>
    <y> 0.313488814592021</y>
    < Width >0.00099432659277551</Width> <Height>0.00099432659277551</Height> 
      <ZOrder>2</ZOrder>
    </SceneNode> </SceneGraph>

    Как видите, это довольно простой XAML-код. В нем указывается соотношение сторон основного изображения (полученное, исходя из размеров первого изображения), и каждое изображение превращается в SceneNode (Узел сцены). Первое изображения является первым SceneNode. Оно располагается в позиции (0,0) и является нормализованным изображением, т.е. его ширина и высота принимаются за 1. Размеры и расположение всех остальных изображений устанавливаются относительного первого.

    Второе изображение размещается в точке с координатами 0,45 по оси х и 0,31 по оси у. Его размер составляет, примерно, 0,00099 размеров первого изображения. Таким образом, если увеличить первое изображение, примерно, в 10000 раз, можно будет увидеть второе изображение. Его Z-порядок равен 2 (Z-порядок первого изображения - 1), т.е. он будет отрисовываться поверх первого изображения.

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

    Другой экспортируемый файл будет называется dzc output.xml. В нем содержатся все сведения о фрагментах и их положении относительно основного изображения. Он будет располагаться в каталоге, созданном редактором Deep Zoom, в котором вы также найдете ряд подпапок с изображениями.

    Первый проект Deep Zoom

    Чтобы использовать Deep Zoom, создадим новое приложение Silverlight. Прежде чем делать что-либо еще, необходимо скомпилировать приложение. При этом будет создан каталог ClientBin в проекте Веб-приложения. Сделав это, закройте решение.

    Теперь в Windows Explorer скопируйте каталог, содержащий файл dzc output.xml и подпапки с фраг-ментированными изображениями, в папку ClientBin Веб-приложения. Сделав это, вновь откройте решение. Solution Explorer вашего проекта будет выглядеть примерно так, как показано на рис. 11.25.

    (рис 11.25) Добавление данных Deep Zoom в Веб приложение

    Теперь, чтобы сформировать визуальное представление содержимого Deep Zoom, необходимо просто добавить MultiScaleImage в Page.xaml и в качестве значения его свойства Source задать путь к файлу dzc output.xml (в данном случае, этот файл располагается в /dzcsample/dzc output.xml ), а также необходимые ширину и высоту. Посмотрим, как будет выглядеть ваш Page.xaml:

    <UserControl x:Class="DZCSampleApp.Page"
    xmlns="http://schemas.microsoft.com/client/2007"
    xm lns:x=" http://schemas.microsoft.com/winfx/2006/xam l"
    Width = "400" Height="300">
    <Canvas>
    <MultiScaleImage Source="dzcsample/dzc_output.xml" Height="300" Width="400" /> </Canvas> </UserControl>

    Итак, при выполнении этого приложения элемент управления MultiScaleImage будет формировать визуальное представление верхнего элемента в SceneGraph, увеличивая его от оригинального масштаба изображения до заданных ширины и высоты (в данном случае, 400 * 300).

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

    Использование мыши и логических координат в Deep Zoom

    Как и все остальные элементы управления Silverlight, MultiScaleImage может объявлять функции для обработки событий. Например, для перемещения изображения используются обычные события мыши: MouseLeftButtonDown, MouseLeftButtonUp и Mouse Move, - и обрабатываются они так же, как это делается при перемещении любого элемента методом "drag-and-drop". Сначала, рассмотрим XAML для MultiScaleImage, определяющий эти события:

    <UserControl x:Class="DeepZoomSample.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xa ml/presentation" 
      xm lns:x=" http://schemas.microsoft.com/winfx/2006/xam l" Width = "640" Height="480"> <Canvas>
    <MultiScaleImage x:Name="dz" Source="dzcsample/dzc_output.xml" 
      MouseLeftButtonDown = "MultiScaleImage  MouseLeftButtonDown" MouseLeftButtonUp="MultiScaleImage  MouseLeftButtonUp" 
      MouseMove="MultiScaleImage  MouseMove" Height="480" Width = "640"></MultiScaleImage> </Canvas> </UserControl>

    И теперь рассмотрим каждый из этих обработчиков событий более подробно. Прежде всего, есть код, общий для них всех, который обеспечивает отслеживание текущего состояния мыши и просматриваемых в настоящий момент координат MultiScaleImage:

    bool dragging = false; double dx = 0; double dy = 0; Point p0; Point p1; Point pLast;

    Теперь посмотрим, что происходит, когда пользователь щелкает изображение кнопкой мыши:

    private void MultiScaleImage MouseLeftButtonDown( object sender, MouseButtonEventArgs e) 
    {
     dragging = true;
     p0 = dz.ElementToLogicalPoint(new Point(0, 0));
     p1 = dz.ElementToLogicalPoint(new Point(640, 480));
     dx = 0;
     dy = 0;
     double x = e.GetPosition(null).X;
     double y = e.GetPosition(null).Y;
     pLast = dz.ElementToLogicalPoint(new Point(x, y));
    }

    Предполагается, что пользователь удерживает кнопку мыши и двигает мышью, поэтому задаем Буле-вому свойству dragging (перетягивание) значение true. Теперь необходимо получить текущие координаты видимой части изображения. Вспомним, что для верхнего изображения задана позиция (0,0), или x = 0 and y = 0, и его высота и ширина заданы равными 1. Это логические координаты и логические размеры.

    В Silverlight логические координаты верхнего левого угла и нижнего правого угла окна можно получить с помощью метода ElementToLogicalPoint (Логическая точка элемента), передав в него физические координаты. Итак, представим, что изображение было увеличено и немного передвинуто. Теперь если вызвать этот API для физической точки (0,0), т.е. верхнего левого угла видимой в данный момент части изображения,будет возвращена логическая точка, представляющая положение это точки на полном изображении. Чтобы получить координаты нижнего правого угла, делаем то же самое для точки, координаты которой соответствуют ширине и высоте физического экрана (в данном случае, 640 * 480).

    Мы хотим отслеживать изменение размеров относительно осей х и у, поэтому задаем переменным dx и dy значение 0. Зачем это сделано, станет понятно через мгновение.

    Кроме того, необходимо получать логические координаты курсора мыши. Для этого вызывается API ElementToLogicalPoint, в него передается текущее положение мыши (которое можно получить из объекта MouseEventArgs, передаваемого в эту функцию) и возвращаемые результаты сохраняются в переменной pLast.

    Итак, что происходит, когда пользователь начинает перетягивание, удерживая кнопку мыши нажатой? Мы хотим получить эффект перемещения по изображению. Вот код, реализующий это:

    private void MultiScaleImage  MouseMove(
    object sender, MouseEventArgs e) { if (dragging) { if (e!=null) 
      { double x = e.GetPosition(null).X; double y = e.GetPosition(null).Y;
    Point pCurrent = dz.ElementToLogicalPoint(new Point(x, y)); if (pLast! = null) 
      { dx += (pCurrent.X - pLast.X); dy += (pCurrent.Y - pLast.Y); Point origin = 
         new Point(p0.X - dx, p0.Y - dy); dz.ViewportOrigin = origin; } pLast = pCurrent; } } }

    Событие MouseMove формируется независимо от того, удерживается кнопка мыши или нет, поэтому для обозначения перетягивания используем переменную dragging. Итак, если пользователь выполняет перетягивание, и MouseEventArgs не null, будет выполняться весь остальной код.

    В данном случае, мы извлекаем текущие координаты мыши и сохраняем их логические эквиваленты (полученные с помощью метода ElementToLogicalPoint элемента управления) в точке pCurrent. Теперь если pLast не null, значит, пользователь выполняет перетягивание, и мышь переместилась относительно ее предыдущего положения. Итак, чтобы получить изменение логических координат, произошедшее в результате перемещения мыши из предыдущей точки в текущую, мы просто вычисляем разницу (дельту) между предыдущими и текущими координатами относительно обеих осей (х и у). После этого задаем для свойства Origin (Начало отсчета) элемента управления MultiScaleImage исходные координаты с учетом дельта по х и у. Это обеспечит эффект "перемещения" изображения при перемещении мышью, хотя мы, по сути, просто изменяем координаты верхнего левого угла отображаемой части изображения. По завершении перемещения необходимо просто задать положение Last (Последний) как Current, чтобы, когда пользователь вновь начнет двигать мышью, вычисления производились относительно этой точки, а не той, в которой пользователь нажмет кнопку мыши.

    Наконец, необходимо, чтобы после того, как пользователь отпустит кнопку мыши, все вернулось в исходное состояние. Сделать это довольно просто. Рассмотрим код:

    private void MultiScaleImage  MouseLeftButtonUp( object sender, MouseButtonEventArgs e) {
    dragging = false;
    p0 = new Point();
    p1 = new Point();
    pLast = new Point();
    dx = 0;
    dy = 0; }

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

    Создание функциональности масштабирования с использованием колеса мыши

    Проблемой при создании приложений Deep Zoom является то, что стандартным средством изменения масштаба, де факто, является колесо мыши, но Silverlight и .NET не обрабатывают события колеса мыши. Что же можно сделать? Существует два варианта. Первый - использовать JavaScript вместо C#, потому что браузер может перехватывать перемещения колеса мыши и формировать события в ответ на его прокручивание. Второй вариант - использовать браузер как мост к Silverlight, т.е. браузер должен будет перехватывать событие и затем информировать .NET об этом. Вся дальнейшая обработка будет осуществляться в .NET. Все намного проще, чем кажется!

    Прежде всего, необходимо обеспечить возможность использования API браузера в Silverlight, поэтому не забудьте включить в код следующую строку:

    using System.Windows.Browser;

    Затем в конструкторе Page элемент управления должен быть зарегистрирован как поддерживающий сценарии. Методы элементы управления, обозначенные как ScriptableMember (Участник, поддерживающий сценарии), доступны из JavaScript. Вот как это делается:

    public Page() {
    InitializeComponent();
    HtmlPage.RegisterScriptableObject("MySilverlightObject", this); }

    Далее требуется перехватывать события колеса мыши на этой странице. Для этого сначала добавляем вызов handleLoad в тег <Body>, чтобы обеспечить выполнение JavaScript-кода при формировании визуального представления страницы:

    <body style=" height: 100%; margin :0;" onload = "handleLoad();">
    ...
    </body>

    Затем в JavaScript-функции handleLoad задается событие:

    function handleLoad() 
    {
     window.onmousewheel = document.onmousewheel = onMouseWheel;
     if (window.addEventListener)
     { 
      window.addEventListener('DOMMouseScroll', onMouseWheel, false);
     } 
    }

    Тем самым определяем, что JavaScript-функция onMouseWheel (При прокручивании колеса мыши) будет выполняться при любом прокручивании колеса мыши.

    function onMouseWheel() 
    {
     if(!event)
     { event = window.event;
     }
     var slPlugin = $get("Xaml1");
     slPlugin.content.MySilverlightObject.dz_MouseWheel( event.clientX, event.clientY, event.wheelDelta);
    }

    Эта функция просто принимает ссылку на элемент управления Silverlight (в данном случае, при создании ему присваивается имя Xaml1) и затем вызывает функцию выделенного кода, определенную для него. Для вызова используется синтаксис <ИмяКомпонента>.content.<ИмяОбъекта>.<ИмяФункции>.

    <ИмяКомпонента> - это переменная, определенная в JavaScript как ссылка на объект Silverlight. <ИмяОбъекта> - это имя, заданное для объекта при его регистрации (ее можно увидеть в конструкторе Page() ). <ИмяФункции> - это имя вызываемой функции. Эта функция должна быть соответствующим образом описана, чтобы JavaScript "видел" ее. В данном случае, вызывается функция dz MouseWheel, рассмотрим ее:

    [ScriptableMember]
    public void dz  MouseWheel(double x, double y, int delta)
    {
     double dZoomFactor=1.33;
     if (delta < 0) dZoomFactor= 1/1.33;
     Point pz = dz.ElementToLogicalPoint(new Point(x, y));
     dz.ZoomAboutLogicalPoint(dZoomFactor, pz.X, pz.Y); 
    }

    Прежде всего, как видите, она обозначена ScriptableMember, т.е. JavaScript может видеть и вызывать ее. Функция JavaScript передает текущие координаты мыши и дельта, возникающие в результате изменения масштаба. Дельта будет иметь положительное значение при прокручивании колеса вперед и отрицательное значение при прокручивании назад.

    Масштабный коэффициент каждого прокручивания колеса задан равным 33% (но он может быть любым), таким образом, переменной dZoomFactor (Масштабный коэффициент) задается значение 1.33 при увеличении и 1/1.33 при уменьшении масштаба.

    Далее требуется, чтобы изменение размеров происходило вокруг текущего положения курсора мыши на изображении. Это очень просто сделать, преобразовав координаты курсора мыши в логическую точку с помощью метода ElementToLogicalPoint элемента управления MultiScaleImage. Имея координаты и коэффициент масштабирования, вызываем метод ZoomAboutLogicalPoint (Масштабирование вокруг логической точки), чтобы обеспечить изменение размеров MultiScaleImage.

    Коллекции в Deep Zoom

    Кроме возможности увеличения и уменьшения изображения, можно также создавать коллекции изображений и работать с ними в масштабируемой среде.

    Возможно, вы заметили, что при экспорте изображений из редактора Deep Zoom всегда предлагалась опция Create Collection (Создать коллекцию). Как следует из ее имени, эта опция обеспечивает создание коллекции изображений. Посмотрим на рис. 11.26. Как видите, я разместил в рабочей области множество изображений. Теперь давайте посмотрим, как их можно экспортировать в виде коллекции.

    Для работы с изображениями коллекции Silverlight предлагает коллекцию SubImages (Множество изображений). Обратите внимание, что при формировании визуального представления страницы полный набор изображений не будет загружаться, поэтому эта коллекция будет пустой в этот момент. Чтобы иметь возможность работать с коллекцией, необходимо описать событие ImageOpenSucceeded (Изображение открыто успешно). Это событие будет формироваться в случае успешной загрузки и отображения.

    (рис 11.26) Создание коллекции Deep Zoom

    Рассмотрим XAML:

    <MultiScaleImage x:Name="dz"
               Source="dzcsample/dzc_output .xml"
               ImageOpenSucceeded = "dz ImageOpenSucceeded"
               Height="480" Width="640">
    </MultiScaleImage>

    В этот момент коллекция SubImages будет заполнена и может использоваться для работы с отдельными рисунками, ее составляющими. Рассмотрим пример изменения месторасположения изображений:

    private void dz ImageOpenSucceeded(object sender, RoutedEventArgs e) 
    {
      int nImages = dz.SubImages.Count;
      for (int lp = 0; lp < nImages; lp++)
      { 
       dz.SubImages[lp].ViewportOrigin = new Point(lp*0.1, lp*0.1);
      }
    }

    Как видите, процесс абсолютно аналогичен тому, который использовался ранее для работы с одним рисунком. В данном случае, каждое изображение перемещается путем использования SubImages[lp,], где lp - это переменная цикла, которая может принимать значения от 0 до числа, соответствующего количеству изображений в коллекции. Результаты представлены на рис. 11.27. Обратите внимание на разницу между этой и исходной компоновками, которая представлена на рис. 11.26.

    (рис 11.27) Компоновка коллекции изображений

    Заключение

    В этой длинной лекции представлены три наиболее мощных элемента управления, предлагаемые Silver-light 2. Сначала был подробно рассмотрен элемент управления MediaElement и то, как можно использовать Silverlight для воспроизведения и трансформации видео- и/или аудиосодержимого, включая программирование прозрачности, маркеры, динамические маркеры и т.д. Затем вы познакомились с рукописными примечаниями и устройствами, поддерживающими их, а также тем, как Silverlight может обеспечить поддержку рукописного ввода на Веб-страницах. Наконец, вашему вниманию была представлена технология Deep Zoom и реализация этой новой возможности в Silverlight. Вы научились создавать сцены и коллекции, расширенные функциональностью глубокого масштабирования.

    Вернуться к учебному плану