Переворачивание страниц в Silverlight

PageTurn – это образцовая демонстрационная версия Silverlight. Я использую ее каждый раз, когда мне надо довести до новичков назначение Silverlight. Но загляните внутрь PageTurn и вы поймете, что создание собственных приложений для переворачивания страниц – непростая задача. PageTurn полагается на преобразования, отсеченные области, динамически создаваемые объекты XAML и многое другое; чтобы разобраться в исходном коде нужны время и усилия (и немалые знания о Silverlight). Он искусно демонстрирует некоторые из самых плодотворных возможностей Silverlight, но не обязательно предназначен для использования в общих целях.

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

Приложение PageTurnDemo

Перед тем, как я представлю инфраструктуру, рассмотрим приложение, построенное вокруг нее. Приложение PageTurnDemo изображенное на рис. 1 позволяет пролистать первые несколько страниц выпуска журнала Microsoft Systems Journal (теперь известного как MSDN® Magazine) за ноябрь 1988 года. (Как избежать проблем с авторским правом при воспроизведении страниц из журнала? Используйте страницы из журнала, с которым работаете, да еще из статьи, которую писали сами!) Каждая их страниц является отсканированным изображением, но на одну из них (последнюю страницу) накладывается текст расширяемого языка разметки приложений (XAML). Я включил текст, чтобы подкрепить ключевой тезис: , страницы, помещаемые в среду, не исчерпываются изображениями; они могут содержать почти любой необходимый XAML (с небольшими ограничениями), включая страницы Image («Изображение»), TextBlock («Блок текста»), MediaElement («Элемент мультимедиа») и прочие.

 

Рис. 1 PageTurnDemo показывает частично перевернутую страницу

Демонстрационную версию можно запустить, загрузив исходный код и запустив его из Visual Studio® 2008, также ее можно просмотреть на wintellect.com/silverlight/pageturndemo. После появления обложки журнала (индикатор выполнения уведомляет о ходе загрузки, получающей все, используемые в приложении, изображения), используйте левую кнопку мыши, чтобы перетащить мышь через обложку справа налево, для открытия следующей страницы. Можно продолжить перетаскивать налево на страницах справа, чтобы открыть новые страницы, или наоборот, перетаскивать слева направо на страницах слева, чтобы вернуться к предыдущим.

Насколько сложно было создать этот образец? Если не считать сканирования, обрезания и подгонки под нужный размер изображений, с последующей упаковкой их в файл ZIP, совсем не сложно. Тремя ключевыми файлами исходного кода (которые можно найти в загружаемом файле, прилагающемся к этой статье) являются файл HTML, связанный с ним файл JavaScript и файл XAML. Если убрать код, использующий объект загрузчика Silverlight для загрузки изображений, исходного кода останется совсем немного. На деле, функции переворачивания страниц посвящено не более 10 строк.

Использование инфраструктуры переворачивания страниц

PageTurnDemo демонстрирует четыре базовых действия, которые нужно выполнить для использования инфраструктуры переворачивания страниц. Первым действием является включение PageTurn.js (файла сценария, содержащего реализацию структуры) – в файл HTML. Вот нужная строка в Default.html:

<script ... src="PageTurn.js"></script>

Вторым этапом является создание экземпляра инфраструктуры. Поскольку инфраструктура переворачивания страниц заключена в класс JavaScript, именуемый PageTurnFramework, нижеследующий оператор в Default.html.js создает ее экземпляр:

_ptf = new PageTurnFramework(_control,
  _control.content.findName('PageTurnCanvas'));

Первым параметром, передаваемым конструктору PageTurnFramework, является ссылка на элемент управления Silverlight. Вторым параметром является ссылка на полотно, содержащее страницы. В документе XAML PageTurnDemo это полотно именуется "PageTurnCanvas".

Третий этап – зарегистрировать страницы в инфраструктуре. Каждая страница представлена полотном, а PageTurnFramework предоставляет метод addPage, который можно использовать для регистрации страниц. AddPage принимает два параметра:

_ptf.addPage(_control.content.findName('EvenPage0'),
  _control.content.findName('OddPage0'));

Первый параметр является ссылкой на полотна, представляющие левую страницу на развороте; второй – ссылкой на полотно, представляющее правую страницу. Вызывать addPage можно столько раз, сколько нужно для регистрации всех пар страниц. И, наконец, за финальным вызовом к addPage должен следовать вызов к initializeFramework:

_ptf.initializeFramework();

Метод initializeFramework инициализирует внутреннее состояние инфраструктуры. Помимо прочего, он создает несколько объектов XAML, используемых для обрезки и поворота страниц, а также регистрирует обработчики для ключевых событий.

Четвертым и последним требованием к использованию инфраструктуры является тщательная очистка. Чтобы избежать утечек памяти, приложения на основе обозревателя, программно регистрирующие обработчики событий, должны также отменять их регистрацию. PageTurnFramework включает метод dispose («ликвидировать»), который отменяет регистрацию всех обработчиков событий, зарегистрированных addPage и initializeFramework. В PageTurnDemo, атрибут onunload в элементе <body> вызывает локальную функцию ликвидации при выгрузке страницы:

<body ... onunload="dispose()">

Эта функция, пребывающая в Default.html.js, вызывает метод устранения инфраструктуры:

if (_ptf != null)
  _ptf.dispose(); 

Я использовал событие DOM, чтобы активировать вызовы к устранению, поскольку Silverlight не запускает событий выгрузки. При желании, можно с тем же успехом использовать события window.unload.

Класс PageTurnFramework предоставляет шесть открытых методов, которые можно вызывать для добавления функции переворачивания страниц к приложениям (см. рис. 2). PageTurnDemo использует три из них: addPage, initializeFramework и dispose. Другие методы можно использовать для добавления дополнительных функций. Например, если требуется включить в приложение полосу переходов, состоящую из эскизов страниц (как это делает демонстрационная версия Silverlight PageTurn), можно вызвать PageTurnFrame work.goToPage при щелчке по эскизу, для прямого перехода к соответствующей странице.

Рис. 2 API-интерфейс Page-TurnFramework

МетодОписание
addPageРегистрирует пару страниц внутри структуры.
DisposeВысвобождает ресурсы, удерживаемые структурой, для правильной очистки.
getCurrentPageIndexВозвращает основанный на 0 индекс отображаемой пары страниц.
getPageCountВозвращает число пар страниц, добавленное с помощью addPage.
goToPageОтображает указанную пару страниц.
initializeFrameworkИнициализирует структуру переворачивания страниц.

Структура XAML

Инфраструктура переворачивания страниц налагает несколько базовых требований на структуру документов XAML. Эти требования таковы:

  1. Представлять каждую страницу в паре с помощью полотна XAML («полотна страницы»).
  2. Включить полотно «переворота страницы», являющееся контейнером для всех полотен страниц.
  3. Назначить ширину, высоту и цвет фона (даже если это «прозрачный») корневому полотну.

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

Для оптимизации процесса, все приложения Silverlight 1.0, которые фиксируют положение курсора мыши, как это делает инфраструктура переворачивания страниц при начале переворота, должны обрабатывать события MouseLeave, выдаваемые корневым полотном и использовать эту возможность для высвобождения мыши. А чтобы элемент мог получать события мыши, такие как MouseEnter и MouseLeave, элемент должен быть «тестируемым на нажатие» внутри Silverlight. Это проделывается путем проверки наличия у элемента (в данном случае, полотна) размера и «ненулевого» фона.

С учетом этих требований, рис. 3 показывает общую структуру документа XAML, используемого с инфраструктурой переворачивания страниц. Полотно переворота страницы – это то, что передается конструктору класса PageTurnFramework. Полотна страниц передаются PageTurnFramework.addPage. На полотне переворота страницы и полотнах страниц, как правило, должны быть пометки точной ширины и высоты, чтобы гарантировать верный запуск событий мыши, используемых инфраструктурой, вне зависимости от содержимого XAML в полотнах. Все прочее – это просто XAML.

Рис. 3 Структура XAML

<Canvas
  xmlns="http://schemas.microsoft.com/client/2007"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Background="color" Width="width" Height="height">

  <!-- Other XAML content goes here -->

  <!-- Page turn canvas -->
  <Canvas>

  <!-- Canvases representing first page pair -->
  <Canvas>
  <!-- Content for left-hand page goes here -->
  </Canvas>
  <Canvas>
  <!-- Content for right-hand page goes here -->
  </Canvas>

  <!-- Canvases representing additional page pairs go here -->

  </Canvas>

  <!-- Other XAML content goes here -->

</Canvas>

Полотно переворота страницы может спокойно существовать рядом с любым другим насыщенным содержимым в документе XAML. Отдельные страницы могут состоять из простых изображений XAML, либо комплексных визуализаций XAML. Использования свойства полотен страниц Clip («Отсечение») следует, как правило, избегать, поскольку сама инфраструктура использует это свойство. Однако, всегда можно заявить подполотно внутри полотна страницы и использовать свойство подполотна Clip для определения отсеченных областей.

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

При сканировании страниц из книги или журнала для использования с инфраструктурой переворота страниц, как я сделал для PageTurnDemo, сосканированные изображения часто имеют тени на внутренних краях, что придает страницам более реалистичный вид. Если такие изображения не использовать, сымитировать тени поможет немного XAML. Для приложения, изображенного на рис. 4 (которое изображает некоторые из моих радиоуправляемых самолетов и может быть просмотрен на wintellect.com/silverlight/mymodels), я использовал прямоугольники XAML с рис. 5 для создания теней вокруг вертикального раздела в центре каждой пары страниц. Альфа значения в цветах GradientStop приводят к тому, что прямоугольники тускнеют справа налево на левосторонних страницах и слева направо на правосторонних страницах.

Рис. 5 XAML для создания теней внутри страницы

<!-- Shadow on left-hand page -->
<Rectangle Canvas.Left="380" Canvas.Top="0" Width="20" Height="600">
  <Rectangle.Fill>
  <LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
  <GradientStop Color="#00000000" Offset="0.0" />
  <GradientStop Color="#40000000" Offset="1.0" />
  </LinearGradientBrush>
  </Rectangle.Fill>
</Rectangle>

<!-- Shadow on right-hand page -->
<Rectangle Canvas.Left="0" Canvas.Top="0" Width="20" Height="600">
  <Rectangle.Fill>
  <LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
  <GradientStop Color="#40000000" Offset="0.0" />
  <GradientStop Color="#00000000" Offset="1.0" />
  </LinearGradientBrush>
  </Rectangle.Fill>
</Rectangle>

 

Рис. 4 Образец средства переворачивания страниц с тенями XAML внутри страницы

Если что-то из этого неясно, можно запустить приложение, создав надстройку над PageTurnDemo. Просто заменить мой XAML для отдельных страниц своим собственным XAML. Мои полотна страниц имеют размер 400 в ширину и 544 в высоту, но эти размеры можно изменить на любые желаемые.

Внутренние компоненты инфраструктуры

Инфраструктура переворачивания страниц реализована в PageTurn.js, который можно найти среди загружаемых файлов, прилагающихся к этому выпуску. Класс PageTurnFramework начинается через несколько строк сверху. JavaScript не поддерживает классы, но эта инфраструктура использует тот же прототип шаблона, что используется ASP.NET AJAX и многими приложениями Silverlight 1.0 для имитации классов в JavaScript. Это лишь иллюзия, но она эффективна.

Конструктор классов определяет все переменные образцов – эквивалент полей в C# – необходимые для сохранения внутреннего состояния. Например, оператор

   this._control = control;

заявляет «поле», именуемое _control и инициализирует его с помощью ссылки на элемент управления Silverlight, переданной конструктору. В целом, заявляются примерно 40 полей, которые используются, чтобы содержать все, от цифры процента выполнения для происходящего переворота страницы (_percent), до символов, представляющих зарегистрированные обработчики событий (которые устраняют использования, чтобы разрегистрировать разработчики). Поля также хранят ссылки на несколько объектов XAML, динамически создающихся в initializeFramework и на объекты XAML, зарегистрированные с вызовами к addPage.
PageTurnFramework.prototype содержит все методы PageTurnFramework. Методы можно разделить на три широкие категории: общие методы, такие как addPage и initializeFramework; обработчики событий, действующие в ответ на события мыши и события Storyboard.Completed, используемые структурой внутренне; наконец, частные методы, используемые инфраструктурой внутренне, но не предназначенные для ответов на вызовы извне. Это немало интересного кода, но, по соображениям пространства, чтобы увидеть его необходимо загрузить PageTurn.js.

Одним из интересных аспектов архитектуры этой инфраструктуры является то, как она завершает перевороты страниц, если кнопка мыши отпущена (или курсор оставляет элемент управления), когда переворот еще не закончен. Во время инициализации, инфраструктура использует createFromXaml, чтобы создать объект раскадровки, для использования в качестве таймера. Затем она добавляет раскадровку к полотну переворота страницы (это одна из причин, по которой ссылку на полотно переворота страницы необходимо передать конструктору классов) и регистрирует обработчик для событий Storyboard.Completed.

Чтобы закончить незавершенный переворот страницы, инфраструктура вызывает Storyboard.begin для запуска таймера. На каждом такте таймера, он продвигает страницу на очередной отрезок (используя размер шага, хранящийся в _step field) и снова вызывает Storyboard.begin, если переворот страницы все еще не закончен. Это можно увидеть в действии, перевернув страницу наполовину и отпустив кнопку мыши. В зависимости от того, насколько страница была повернута, когда ее отпустили, она вернется в полностью закрытое или полностью открытое положение.

Определение XAML раскадровки хранится в переменной, именуемой _sb, в методе initializeFramework. Может возникнуть вопрос, почему это определение включает атрибут x:Name, значением которого является GUID. В Silverlight 1.0, раскадровки, созданные с помощью createFromXaml, должны были быть поименованы, или createFromXaml терпел сбой. Мне потребовалось дать имя раскадровке, но я хотел убедиться, что это имя не конфликтует с другими раскадровками, использованными в приложении. Поэтому я и дал ей имя GUID.

Другой интересной частью архитектуры является то, как инфраструктура использует события мыши. Для полотен, представляющих страницы addPage регистрирует обработчики для событий MouseLeftButtonDown, MouseMove и MouseLeftButtonUp. Поворот налево начинается по щелчку правой страницы и происходит по мере сдвига мыши влево с нажатой левой кнопкой. Аналогично, поворот направо начинается по щелчку левого полотна и происходит по мере сдвига мыши вправо. Ключевым методом, используемым обработчиками событий, является _turnTo, который передвигает частично перевернутую страницу в позицию, отмеченную параметром процента завершения.

Финальным аспектом инфраструктуры, который может потребоваться изучить, является то, как она использует преобразования и отсеченные области для изображения переворотов страниц. initializeFramework создает два объекта PathGeometry: один, который послужит отсеченной областью для правых страниц и второй для левых страниц. Он также создает объект TransformGroup, содержащий RotateTransform и TranslateTransform.

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

 

Рис. 6 Отсеченные области и преобразования, используемые для отображения переворотов страниц

RotateTransform и TranslateTransform объединяются, чтобы сориентировать страницу и установить ее позицию. (Да, это связано с уймой тригонометрических вычислений). По мере того, как курсор передвигается налево, вообразите, что желтый прямоугольник скользит налево и оборачивается в вертикальную позицию. Соедините это с мысленным образом синего треугольника, растущего по размеру, в то время как красный все более сужается и получится неплохое представление о работе переворота страницы.

Заключение

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

Не стесняйтесь использовать эту инфраструктуру в собственных проектах и изменять ее. Посылайте мне свои отзывы и если вам придут в голову полезные дополнений к набору функций и API-интерфейсу, дайте мне знать об этом. Я совмещу ваши идеи с моими собственными и постараюсь, чтобы версия 2.0 инфраструктуры переворачивания страниц работала еще лучше, чем версия 1.0.

Скачать исходники примеров 

Джефф Просайз (Jeff Prosise) является пишущим редактором журнала MSDN Magazine и автором нескольких книг, включая Programming Microsoft .NET («Программирование в Microsoft .NET»). Кроме того, он является соучредителем компании Wintellect (www.wintellect.com), оказывающей консультационные и образовательные услуги в области программного обеспечения и специализирующейся на .NET Framework.