WPF
Гибкое отображение информации с помощью документов нефиксированного формата
Маркус Эггер (Markus Egger)
В статье рассматривается:
- Windows Presentation Foundation
- Документы нефиксированного формата
- Отображение документов нефиксированного формата
- Редактирование документов нефиксированного формата
|
Продукты и технологии:
.NET Framework 3.0
|

Содержание
Windows
® Presentation Foundation (WPF) предоставляет великолепный набор возможностей. На самом деле, их так много, что некоторые, даже очень значимые, не получают и доли того внимания, которого они заслуживают. Превосходным примером являются документы нефиксированного формата, позволяющие разработчикам создавать документы прямо в WPF. В своей статье «Документы XPS: первый взгляд на интерфейсы API для создания документов XML Paper Specification» в выпуске журнала MSDN
® Magazine за январь 2006 г. Боб Уотсон (Bob Watson)
подробно рассмотрел документы XPS в WPF, но документы нефиксированного формата другие. Спецификация XPS (XML Paper Specification) ориентирована на вывод на печать и постраничную информацию, тогда как документы нефиксированного формата нацелены на чтение с экрана и предоставляют более динамичную и, возможно, более сложную модель. Документы нефиксированного формата подходят практически для любой текстовой информации, от описания продукции до целых книг.
Представление текста бесспорно является одной из наиболее важных возможностей пользовательского интерфейса. В интерфейсах WPF вы зачастую используете для отображения текста элементы управления, такие как Label. Однако в большом числе случаев вам необходимо большее, чем простое отображение нескольких слов. Документы нефиксированного формата предоставляют более сложный подход, хотя в основе своей они очень просты. Они определяют расположение текста способом, напоминающим документы HTML, но они обладают большими возможностями и предлагают значительно больше вариантов компоновки.
Обычно документы нефиксированного формата создаются с использованием расширяемого языка разметки приложений (XAML), стандартного языка разметки, основанного на XML. XAML особенно интуитивен для документов нефиксированного формата, в основном за счет схожести с HTML. В следующем примере документа нефиксированного формата создается абзац текста, где обычное полужирное форматирование применено к нескольким словам:
<FlowDocument
xmlns=’http://schemas.microsoft.com/winfx/2006/xaml/presentation’
xmlns:x=’http://schemas.microsoft.com/winfx/2006/xaml’>
<Paragraph>The quick <Bold>brown fox</Bold> jumps over the lazy dog.
</Paragraph>
</FlowDocument>
Как вы видите, схожесть с HTML даже более очевидна, чем в других пользовательских интерфейсах XAML. Названия элементов другие, но, по крайней мере для простых документов, концепция очень похожа. Обычно документы нефиксированного формата начинаются с корневого элемента FlowDocument, содержащего несколько блоков. Блоки являются элементами потока, обычно абзацами текста, как в приведенном примере (хотя существуют и другие типы блоков). В свою очередь, абзацы могут содержать другие элементы, такие как два выделенных полужирным шрифтом слова в этом примере. Обратите внимание, что, как и в любом другом XAML-документе, у корневого элемента должны быть специфические для XAML определения пространства имен, чтобы его распознать. Это деталь реализации, относящаяся исключительно к XAML и не имеющая отношения к документам нефиксированного формата. Отметьте, что задание пространства имен необходимо только для отдельных документов нефиксированного формата. (Они могут быть частью большего пользовательского интерфейса XAML, а в этом случае определения пространства имен достаются корневому элементу интерфейса).
Конечно, пользователи никогда не видят XAML для документов нефиксированного формата (в отличие от исходного текста HTML, который можно просмотреть в обозревателе), точно так же, как они не видят XAML для любого другого элемента пользовательского интерфейса. Вместо этого пользователи видят окончательное отображение документа. Для этого конкретного примера результат можно увидеть несколькими способами. Пожалуй, самым простым будет набрать пример в XamlPad, средстве, поставляемом в составе Windows SDK (см. рис. 1).
Рис. 1 Очень простой документ нефиксированного формата, отображаемый в XamlPad (Щелкните изображение, чтобы увеличить его)
Конечно же, это очень простой пример; определение документа и внутреннего размещения может быть значительно более сложным. Документы нефиксированного формата поддерживают все варианты форматирования, которых можно ожидать, такие как курсив, подчеркнутый шрифт, возможность задания цвета и гарнитуры и многое другое. На рис. 2 показан слегка более сложный пример, результат которого можно увидеть на рис. 3.

Figure 2 Дополнительное форматирование и маркированный список
<FlowDocument
xmlns=’http://schemas.microsoft.com/winfx/2006/xaml/presentation’
xmlns:x=’http://schemas.microsoft.com/winfx/2006/xaml’>
<Paragraph FontFamily=”Calibri” FontWeight=”Bold” FontSize=”24”>
WPF Flow Documents</Paragraph>
<Paragraph>WPF Flow Documents are <Italic>quite sophisticated</Italic>.
They support all common layout options, as well as many you probably
do <Span Foreground=”Red”>not expect</Span>.</Paragraph>
<List>
<ListItem><Paragraph>First List Item</Paragraph></ListItem>
<ListItem><Paragraph>Second List Item</Paragraph></ListItem>
<ListItem><Paragraph>Third List Item</Paragraph></ListItem>
</List>
<Paragraph>Of course, Flow Documents also support the definition
of <Bold><Span FontFamily=”Comic Sans MS” FontSize=”24”
Foreground=”Blue”>in line</Span></Bold> font sizes and font faces.
</Paragraph>
</FlowDocument>
Рис. 3 Документ нефиксированного формата со слегка более сложным форматированием (Щелкните изображение, чтобы увеличить его)
В этом примере показаны несколько абзацев со встроенным форматированием. Он также демонстрирует первый пример другого типа блока, а именно List, который, что неудивительно, содержит список из нескольких элементов. Обратите внимание, что каждый из элементов списка, в свою очередь, является контейнером для других блоков. Поэтому вместо того чтобы поместить текст внутрь элемента списка, я добавил в каждый их них по абзацу. Я мог добавить по нескольку абзацев или блоков любого другого типа в каждый элемент. Это позволяет создавать сложные компоновки внутри одной позиции списка, что обычно является проблемой в форматах наподобие HTML, которые позволяют размещать только простые строки текста в каждой позиции списка.
Основы документов нефиксированного формата
Теперь, когда вы увидели несколько базовых документов нефиксированного формата, давайте вернемся назад и снова рассмотрим некоторые основные понятия. Как вы уже видели, документы нефиксированного формата являются коллекциями блоков. Внутри все блоки являются классами WPF, произведенными от класса System.Windows.Documents.Block. В свою очередь, класс Block произведен (несколько шагов далее по цепочке) от класса ContentElement, достаточно низкоуровневого класса WPF, специально оптимизированного для определения документов. Это чем-то напоминает используемые для создания интерфейсов WPF элементы управления, которые все произведены от класса UIElement. Оба дерева наследования схожи по концепции, но не абсолютно одинаковы. Это означает, что элементы управления WPF и блоки нельзя скомбинировать напрямую. Например, название кнопки не может быть абзацем текста, а абзац не может содержать кнопку. Есть тонкие различия между элементами управления и блоками, вызванные тем фактом, что компоновка внутри элемента управления и компоновка внутри блока работают достаточно по-разному. К счастью, разница между двумя типами элементов WPF, которую необходимо преодолеть, достаточно мала. Кнопки, например, могут содержать объекты TextBlock, состоящие из форматированного текста, а блоки могут содержать любой элемент управления WPF при помощи специального класса блока BlockUIContainer. Это означает, что документы нефиксированного формата могут содержать элементы WPF любого типа (включая интерактивные пользовательские интерфейсы, мультимедиа и трехмерные элементы), а с другой стороны, документы нефиксированного формата могут быть частью любого пользовательского интерфейса WPF либо в качестве сложного элемента разметки для информации элемента управления, либо как отдельный документ, такой как описание товара в торговом приложении.
Теоретически, список доступных блоков бесконечен, поскольку разработчики могут производить свои собственные классы блоков и таким образом создавать собственные расширения механизма отображения документов. Это предоставляет свободу, недоступную ни в каком другом механизме отображения документов, которые мне известны. Тем не менее, обычно создатели документов используют ограниченный набор типов блоков. Перечень наиболее важных типов блоков приведен на Рис. 4.

Figure 4 Важные типы блоков
| Блок |
Описание |
| Paragraph |
Содержит текст, возможно форматированный. |
| List |
Содержит списки разного типа (нумерованный, маркированный и т. д.). |
| Table |
Содержит таблицы, похожие на таблицы Microsoft Word или HTML. |
| BlockUIContainer |
Содержит различные элементы пользовательского интерфейса, становящиеся частью размещения. |
| Section |
Содержит группу прочих блоков. Разделы удобны для применения общих атрибутов к группам блоков, например, одни и те же атрибуты шрифта для нескольких абзацев. |
При создании документов нефиксированного формата WPF в XAML вы просто создаете объекты определенных типов. Рассмотрим следующий фрагмент XAML (в дальнейшем я буду опускать задание пространства имен, чтобы сделать примеры проще):
<FlowDocument>
<Paragraph>Hello World!</Paragraph>
</FlowDocument>
Здесь создается экземпляр класса FlowDocument и экземпляр класса Paragraph (с текстом «Hello World!»). Этот абзац добавляется в коллекцию блоков класса FlowDocument. Обратите внимание, что, как и всегда в XAML, названия элементов чувствительны к регистру и точно совпадают с именами классов, доступных в WPF. Вы также могли бы создать этот же документ программно, вот так:
FlowDocument doc = new FlowDocument();
Paragraph para = new Paragraph();
para.Inlines.Add(“Hello World!”);
doc.Blocks.Add(para);
Конечно, это значительно менее интуитивно, чем предоставляемый XAML декларативный подход, поэтому к программному подходу прибегают лишь в специальных случаях. (Я иногда использую этот подход, когда мне необходимо создать сложно отформатированный отчет, больше похожий на настоящий документ, чем табличный вывод, создаваемый многими механизмами отчетов).
Во многих случаях сами абзацы содержат сложно отформатированную информацию, что также делается при помощи создания объектов, как здесь:
<Paragraph>Hello <Bold>World!</Bold></Paragraph>
Здесь абзац содержит два сегмента текста—«Hello» (использующий форматирование по умолчанию) и «World!» (полужирным шрифтом). Это интересно тем, что XAML не просто создает экземпляр абзаца и задает его текст простой строкой; вместо этого создается абзац с двумя дочерними сегментами, каждый из которых содержит текст с разным форматированием. В WPF такие сегменты называются внутристрочными элементами. Так же как документ нефиксированного формата может содержать несколько блоков разного типа, абзацы могут содержать внутристрочные элементы разных типов. Внутристрочные элементы бывают разными. Некоторые внутристрочные элементы, называемые Span, представляют собой сегмент текста с определенными параметрами форматирования. Элемент Bold, использованный в этом примере, является специальным случаем Span, где шрифт по умолчанию установлен полужирным. Другим типом внутристрочного элемента является Run, сегмент текста с форматированием по умолчанию. Поэтому вышеприведенный код XAML является просто сокращением для:
<Paragraph>
<Run>Hello </Run>
<Bold>World!</Bold>
</Paragraph>
Конечно, гораздо удобнее не иметь необходимости определять каждый внутристрочный элемент в XAML, но если вам придется создавать тот же пример программно, важно понимать концепцию внутристрочных элементов, поскольку их нельзя пропустить в коде. Вот программный эквивалент двух предыдущих примеров XAML:
Paragraph para = new Paragraph();
para.Inlines.Add(new Run(“Hello “));
Bold b = new Bold();
b.Inlines.Add(“World!”);
para.Inlines.Add(b);
Bold является специальной версией Span, где шрифт по умолчанию установлен полужирным; тип Bold произведен от класса Span и переопределяет свойство FontWeight. Есть похожие специальные элементы Span, такие как Italic и Underline. Однако эти специальные элементы Span не очень обязательны, поскольку можно использовать Span по умолчанию и установить подходящие свойства:
<Paragraph>Hello <Span FontWeight=”Bold”>World!</Span></Paragraph>
Конечно, возможность напрямую устанавливать атрибуты наподобие полужирного шрифта и курсива, помещая определенную секцию текста в теги Bold и Italic, очень удобна и интуитивна, поэтому значительно чаще используется «<Bold>» вместо «<Span FontWeight="Bold">». Тем не менее, элемент <Span> очень полезен, поскольку помимо простого полужирного шрифта существует значительное число свойств, которые можно установить, и у большинства из них нет индивидуального типа Span. Для достаточно большого числа распространенных вариантов форматирования нет специальных типов Span. Типичным примером является установка гарнитур шрифтов. В отличие от HTML, в документах нефиксированного формата нет элементов <Font>. Вместо этого управление шрифтами осуществляется следующим образом:
<Paragraph>Hello <Span FontFamily=”Comic Sans MS” FontSize=”24”>
World!</Span></Paragraph>
Многие из свойств, такие как FontFamily, наличествуют во всех без исключения классах для документов нефиксированного формата. Например, если бы вам хотелось установить шрифт для целого абзаца, а не для внутристрочного элемента, можно было бы обойтись без элемента Span:
<Paragraph FontFamily=”Comic Sans MS” FontSize=”24”>Hello World!</Paragraph>
Есть и другие внутристрочные элементы, помимо Span и Run. Вот некоторые из других наиболее интересных внутристрочных элементов:
Figure Элементы Figure слегка необычны, потому что они содержат блоки. В каком-то смысле элементы Figure выглядят почти как миниатюрные документы с нефиксированным форматом внутри документов с нефиксированным форматом. Элементы Figure часто используются для расширенных возможностей компоновки, таких как изображения внутри абзацев, когда обычный текст «обтекает» изображение.
Floater Элементы Floater являются облегченным вариантом элементов Figure. Они не поддерживают всех возможностей позиционирования элементов Figure, но если все, что вам нужно, — это возможность простого выравнивания помимо стандартного выравнивания абзаца, элементы Floater могут пригодиться.
LineBreak Элементы LineBreak выполняют в точности то, что предполагает их название: они дают возможность разрыва строки внутри абзаца.
InlineUIContainer Элемент InlineUIContainer является внутристрочным эквивалентом BlockUIContainer. Если необходимо скомбинировать элемент управления WPF любого типа с внутристрочными элементами (как, например, поместить кнопку внутри текста абзаца), то вам нужен InlineUIContainer.
Элементы Figure постоянно используются в документах нефиксированного формата (элементы LineBreak тоже, но они вряд ли заслуживают подробного описания). В следующем примере Figure используется для отображения изображения в составе большей композиции:
<Paragraph>
<Figure Width=”200”>
<BlockUIContainer>
<Image Source=”Pictures\Humpback Whale.jpg” />
</BlockUIContainer>
<Paragraph Foreground=”Blue” FontFamily=”Consolas”>
The Whale</Paragraph>
</Figure>
The quick brown fox jumps over the lazy dog. The quick brown...
</Paragraph>
Обратите внимание, что в документах нефиксированного формата WPF нет блока Image. Вместо этого изображения встраиваются в виде BlockUIContainer при помощи стандартных для WPF элементов управления Image. (Такой же подход используется для такого содержимого, как видео или интерактивные трехмерные модели внутри документа нефиксированного формата). На рис. 5 показано отображение документа, похожего на этот.
Рис. 5 Текст «обтекает» изображение и заголовок (Щелкните изображение, чтобы увеличить его)
Просмотр документов нефиксированного формата
Теперь вы знаете, как создавать простые документы нефиксированного формата и просматривать их в XamlPad. До этого момента я обходил стороной вопрос просмотра документов нефиксированного формата. В конце концов, вы не ожидаете, что ваши пользователи станут запускать XamlPad и вставлять туда код XAML документа. Один из вариантов просмотра документа нефиксированного формата XAML – сохранить его в виде файла с расширением XAML и открыть его двойным щелчком мыши в Проводнике Windows. Запустится приложение по умолчанию, ассоциированное с файлами XAML (обычно обозреватель Internet Explorer®), которое отобразит документ. На рис. 6 показан результат.
Рис. 6 Документ нефиксированного формата XAML в Internet Explorer (Щелкните изображение, чтобы увеличить его)
То, что Internet Explorer (и другие обозреватели) могут отображать содержимое XAML, представляет особенный интерес, потому что это позволяет вам отображать документы нефиксированного формата как часть ваших веб-приложений. Другими словами, если вы загрузите документы нефиксированного формата XAML на ваш веб-сервер и кто-то будет просматривать эти файлы, то он увидит нечто похожее на рис. 6 (в предположении, что у пользователя установлен пакет Microsoft® .NET Framework 3.0). Конечно же это работает и динамически. Если ваше веб-приложение ASP.NET (или любая другая технология на стороне сервера) создает документ нефиксированного формата XAML «на лету» и возвращает его в качестве своего результата (и в предположении, что тип содержимого правильно задан как «application/xaml+xml»), то пользователь увидит документ нефиксированного формата, как часть вашего веб-приложения, что во многих ситуациях весьма полезно. На рис. 7 показана простая страница ASP.NET, создающая документ нефиксированного формата.
Отображение документов нефиксированного формата
Вы могли заметить, что при отображении документ нефиксированного формата (и в обозревателе, и в XamlPad) отображается не только он. В частности, вдоль нижней части документа отображаются элементы управления. Как вы можете увидеть на рис. 8, по умолчанию документы нефиксированного формата отображаются при помощи элемента управления FlowDocumentReader, предоставляющего набор стандартных возможностей, таких как масштабирование, разбиение по страницам, различные режимы просмотра и даже возможность поиска. Выясняется, что документы нефиксированного формата необходимо размещать в каком-то элементе управления, который способен их отображать. По умолчанию средством просмотра документов нефиксированного формата является элемент управления FlowDocumentReader, экземпляр которого создается автоматически, когда вы явно используете другой элемент управления. На данный момент WPF предоставляет три разных элемента управления для просмотра документов нефиксированного формата:
Рис. 8. Управляющие кнопки в элементе управления FlowDocumentReader (Щелкните изображение, чтобы увеличить его)
FlowDocumentScrollViewer Этот элемент управления отображает документы непрерывным потоком с полосой прокрутки, похоже на веб-страницы или режим просмотра Web Layout в Microsoft Word. На рис. 9 показан документ в FlowDocumentScrollViewer.
Рис. 9 Использование элемента управления FlowDocumentScrollViewer (Щелкните изображение, чтобы увеличить его)
FlowDocumentPageViewer Этот элемент управления отображает документы нефиксированного формата в виде отдельных страниц с перелистыванием страниц вместо прокрутки. Это напоминает режим полноэкранного просмотра в Word. FlowDocumentPageViewer показан на рис. 10. Здесь документ с рис. 9 отображен в элементе управления FlowDocumentPageViewer и полоса прокрутки замещена механизмом пролистывания. Компоновка простого размещения была заменена более сложной многоколонной постраничной компоновкой.
Рис. 10. Использование элемента управления FlowDocumentPageViewer (Щелкните изображение, чтобы увеличить его)
FlowDocumentReader Этот элемент управления сочетает в себе оба предыдущих и позволяет пользователю переключаться между двумя подходами. Он является элементом управления по умолчанию для документов нефиксированного формата, и зачастую оказывается замечательным выбором для приложений, осуществляющих отображение сложных текстов. На рис. 11 документ, показанный на Рис. 9 и 10, отображен в элементе управления FlowDocumentReader, сочетающем подходы постраничного просмотра и просмотра с прокруткой. В дополнение, он включает возможность поиска, которая по умолчанию скрыта в других элементах управления (другие элементы управления для просмотра поддерживают поиск, это можно продемонстрировать, выполнив команду ApplicationCommands.Find или нажав сочетание клавиш «Ctrl+F» на клавиатуре). Он также поддерживает режим многостраничного просмотра, слегка изменяя постраничное отображение и способ отображения колонок и иллюстраций.
Рис. 11 Использование элемента управления FlowDocumentReader (Щелкните изображение, чтобы увеличить его)
Выбор элемента управления будет зависеть от ситуации, хотя FlowDocumentReader напрашивается для всех вариантов использования, кроме самых простых. Он разносторонен и мощен и поддерживает постраничную компоновку, которая во многих ситуациях лучше, чем прокрутка. Подробное обсуждение этого момента выходит за пределы данной статьи, но оказывается, что прокрутка и связанные с ней эффекты, наподобие удвоения, являются одной из основных причин, почему люди предпочитают печать цифровому тексту. Страничный подход зачастую более натурален и должен помочь сделать чтение цифровой информации более широко распространенным.
Как определить, какой элемент управления использовать? Простой, хотя скорее грубоватый, подход — добавить желаемый элемент управления в код XAML документа:
<FlowDocumentScrollViewer
xmlns=’http://schemas.microsoft.com/winfx/2006/xaml/presentation’
xmlns:x=’http://schemas.microsoft.com/winfx/2006/xaml’>
<FlowDocument>
<Paragraph>The quick <Bold>brown fox</Bold> jumps over the lazy
dog.</Paragraph>
</FlowDocument>
</FlowDocumentScrollViewer>
В этом примере корень документа был сделан тегом FlowDocumentScrollViewer. Это означает, что вы больше не просто определяете отдельный документ. Вместо этого вы определяете полный интерфейс XAML, который использует FlowDocumentScrollViewer в качестве корня. Содержимым FlowDocumentScrollViewer является документ нефиксированного формата из самого первого примера. (Заметьте, что теперь определения пространства имен теперь относятся к тегу FlowDocumentScrollViewer, а не к тегу документа). Рисунки с 9 по 11 были созданы с применением этого подхода с использованием различных элементов управления для просмотра в качестве корневого элемента.
Почему я считаю этот подход грубоватым? Потому что с точки зрения архитектуры у него есть проблемы, вызванные смешением определения пользовательского интерфейса с его конкретными данными. Гораздо желательнее держать документ отдельно от его интерфейса. Смешение средства просмотра с документом сродни созданию таблицы SQL Server™ и указанию каким-то образом, что она может быть отображена только в элементе Windows Forms DataGrid. Есть несколько способов хранить документ отдельно от определения пользовательского интерфейса. Если вы хотите отображать документы нефиксированного формата как часть веб-приложения, используя продемонстрированный подход ASP.NET, вы можете создать страницу ASP.NET с желаемым элементом управления для просмотра и наполнять ее содержимым (хранящимся отдельно, возможно, в базе данных), используя стандартный код ASP.NET.
В типовом приложении WPF, с другой стороны, вы можете просто определить свой пользовательский интерфейс, используя стандартные подходы WPF, Windows и XAML приложений обозревателя (XAML Browser Application, XBAP), а затем динамически загрузить свой документ. На рис. 12 показан простой пример, использующий вымышленную библиотеку моих статей, названия которых отображаются в списке в верхнем левом углу. Когда пользователь выбирает статью из списка, документ динамически загружается в элемент управления FlowDocumentReader, занимающий большую часть формы. Обратите внимание, что в этом решении используются стандартные приемы WPF, такие как альфа-смешивание. Вы увидите, что сам документ полупрозрачен и моя фотография на заднем плане просвечивает сквозь него. Отметьте также, что в приложении для создания библиотеки вымышленных статей задействованы раскрывающийся список, изображение, метка и элемент управления FlowDocumentReader.
Рис. 12 Использование элементов управления Listbox, Image, Label и FlowDocumentReader (Щелкните изображение, чтобы увеличить его)
Самым сложным в этом примере является загрузка документа в элемент управления для просмотра. Это делается при помощи класса System.Windows.Markup.XamlReader, позволяющего динамическую загрузку любого содержимого XAML, включая документы нефиксированного формата и не только их. Вот код, привязанный к событию смены выделения в раскрывающемся списке:
documentReader.Document =
(FlowDocument)XamlReader.Load(
File.OpenRead(fileName));
Метод Load возвращает объект, поскольку корневой элемент в XAML-файле может представлять множество различных типов. В моем примере я знаю, что возвращаемое значение FlowDocument, поэтому я просто произвожу преобразование типа и назначаю этот документ свойству Document элемента FlowDocumentReader (в этом примере я назвал экземпляр элемента управления documentReader). Пожалуйста, помните, что это лишь пример. Коду промышленного качества здесь также определенно необходима обработка ошибок.
Все, что вы знаете про WPF, применимо к этому примеру. Например, элементы управления просмотра являются стандартными элементами управления WPF, поддерживающими стилизацию. Это означает, что вы можете полностью изменить внешний вид элементов пользовательского интерфейса, таких как полоса масштабирования, переключатели режима просмотра и управление пролистыванием. (Единственный элемент, возможность управления которым ограничена, — это окно поиска, хотя вы можете вовсе не использовать его, если оно вам не нравится).
Кроме того, хотя в моем примере показано приложение Windows, то же самое приложение может быть развернуто как XBAP и запущено в обозревателе Интернета (опять же, в предположении, что у пользователя установлен пакет .NET Framework 3.0). Обратите внимание, что наличия Microsoft Silverlight™ (ранее называвшегося «WPF/E») недостаточно, так как Silverlight поддерживает лишь подмножество WPF и не поддерживает документов нефиксированного формата.
Создание документов нефиксированного формата
Как создаются документы нефиксированного формата? Конечно, разработчики всегда могут создавать документы нефиксированного формата при помощи низкоуровневых средств, таких как XamlPad. Тем не менее, в реальных условиях это маловероятно. Обычно документы нефиксированного формата создаются либо в редакторах WYSIWYG, либо посредством преобразования содержимого из существующего формата. Поскольку документы нефиксированного формата могут быть определены при помощи XAML, особенно легко преобразовывать существующие документы XML. Тем не менее, с разумными трудозатратами также возможно преобразование документов HTML и Word (хотя требуется программирование, поскольку для этого пока не появилось готовых средств).
Для редактирования WYSIWYG платформа WPF предоставляет готовый элемент управления. В элемент управления WPF RichTextBox встроена поддержка редактирования документов XAML. Название элемента управления заставляет сделать неверное предположение, что он предназначен для работы с RTF. Хотя он и поддерживает RTF, этот элемент управления в основном используется для документов нефиксированного формата. На самом деле, этот элемент управления повторяет возможности элементов управления для просмотра документов нефиксированного формата, за исключением того, что он поддерживает редактирование. Некоторые даже скажут, что элемент управления RichTextBox следует считать еще одним способом отображения документов нефиксированного формата.
Наберите следующий пример в XamlPad, чтобы увидеть RichTextBox в действии:
<RichTextBox
xmlns=’http://schemas.microsoft.com/
winfx/2006/xaml/presentation’
xmlns:x=’http://schemas.microsoft.com/winfx/2006/xaml’>
<FlowDocument>
<Paragraph>
The quick brown fox jumps over the lazy dog.
</Paragraph>
</FlowDocument>
</RichTextBox>
Точно так же, как элементы управления для просмотра, у RichTextBox есть свойство Document, которому можно автоматически назначить документ нефиксированного формата. Это создает пользовательский интерфейс, очень схожий с элементом управления FlowDocumentScrollViewer, за исключением того, что текст можно редактировать. Отметьте, что этот элемент управления всегда обрабатывает документы в режиме прокрутки. Способа редактировать документы нефиксированного формата с помощью RichTextBox в постраничном или многоколонном режиме нет. Однако результатом редактирования является стандартный документ нефиксированного формата, который может быть отображен при помощи любого из средств просмотра, которые вы уже видели, включая многоколонный и постраничный режимы.
Одной из стоящих упоминания возможностей RichTextBox является встроенная проверка орфографии. Ее можно включить так:
<RichTextBox SpellCheck.IsEnabled=”true”>
<FlowDocument>...</FlowDocument>
</RichTextBox>
На рис.13 показана проверка орфографии в работе.
Рис. 13 Элемент управления RichTextBox с проверкой орфографии (Щелкните изображение, чтобы увеличить его)
Единственной сложностью в работе с этим элементом управления являются загрузка и сохранение. В большинстве случаев вы, вероятно, не станете сохранять содержимое RichTextBox в пользовательский интерфейс XAML, как в одном из предыдущих примеров. Вместо этого вы загрузите и сохраните документ динамически. Операция загрузки текста в RichTextBox идентична загрузке документа в элемент управления для просмотра (см. выше). Сохранение документа является строго обратной операцией: вы берете документ и сериализуете его в XAML, что может быть сделано так:
System.Windows.Markup.XamlWriter.
Save(richTextBox.Document)
В результате будет возвращен код XAML в виде строки, которую вы затем можете сохранить в файле или в базе данных или использовать любым способом, который вы можете себе представить.
Хотя элемент RichTextBox очень удобен, стоит сказать и о его недостатках. Тогда как документы нефиксированного формата представляют собой самую сложную технологию для отображения документов на экране, элемент управления RichTextBox не так сложен. Он великолепно подходит для редактированию небольших документов и фрагментов текста, но вы не станете использовать его для написания книги, журнала или маркетинговой брошюры. Для таких длинных форматов его средства отображения чересчур упрощены, поскольку поддерживают только компоновку для прокрутки (что означает, что нет удобного визуального способа для создания сложных компоновок, о которых мы вскоре поговорим). Подход к сохранению документов зачастую также неудовлетворителен. Класс XmlWriter использует документ из памяти и преобразует его в XAML, но, к сожалению, он не следует многим концепциям, которые важны для масштабного производства документов нефиксированного формата, таким как стили. В результате XAML точно сохраняет внешний вид документа, но часто он неаккуратен и очень велик. Элемент управления RichTextBox, без сомнения, полезен, но не ожидайте, что он будет решением для публикации экранного содержимого (хотя такое приложение очень нужно).
Исследование возможностей компоновки
Теперь, когда вы знаете, как создавать и просматривать документы нефиксированного формата, давайте вернемся к самим документам и рассмотрим еще несколько возможностей. Документы нефиксированного формата очень сложны, и исследование всех доступных возможностей выходит за пределы этой статьи, но есть несколько вещей, которые я хотел бы обсудить.
Одна из возможностей, которая всегда поражает меня, называется оптимальным абзацем. Когда она включена, эта возможность старается распределить пробелы в данном абзаце так ровно, как это возможно, значительно повышая удобство чтения. Оптимальный абзац особенно хорошо работает в комбинации с встроенной возможностью по расстановке переносов, которая осуществляет расстановку переносов «на лету» для всего документа или отдельных абзацев.
Включение оптимального абзаца и расстановки переносов очень просто:
<FlowDocument IsOptimalParagraphEnabled=”true”
IsHyphenationEnabled=”true”>
На рис.14 показан один и тот же документ, отображенный с отключенными и включенными этими возможностями. Разница между двумя версиями незначительна, но важна. Обратите внимание, что версия слева выглядит более спокойной, в основном за счет более ровного распределения, но также и за счет уменьшения пробелов между словами. Эта кажущаяся незначительной разница критически важна, особенно при чтении большого количества текста с экрана.
Рис. 14 Оптимальный абзац и расстановка переносов (Щелкните изображение, чтобы увеличить его)
Как вы уже видели, элемент управления FlowDocumentReader применяет многоколонный подход к отображению текста. Это другая очень важная составляющая читаемости, поскольку людям неудобно читать одну строку поперек всего широкого экрана. Ширина колонок варьируется в зависимости от нескольких факторов, таких как вся ширина для отображения, заданный масштаб и заданная ширина колонки. По умолчанию, ширина колонки документа нефиксированного формата в 20 раз больше размера шрифта, что при размере шрифта по умолчанию составляет примерно 300 устройствонезависимых пикселов (3 1/8 дюйма на точном дисплее). Этот параметр по умолчанию легко переопределить:
<FlowDocument ColumnWidth=”400”>
Получатся колонки шириной порядка 400 пикселов. Однако есть другие факторы, влияющие на получающуюся ширину. При масштабе в 50 процентов, например, ширина колонки будет лишь 200 пикселов. К тому же, ширину колонки стоит воспринимать как минимальную ширину колонки. Это означает, что, если общая доступная ширина 900 пикселов, отображаемый результат содержит две колонки, достаточно широкие, чтобы заполнить все 900 пикселов, делая каждую из колонок шире, чем заданные 400 пикселов. Обычно это желательно, поскольку при этом результат вывода гораздо приятнее. Однако, если вам не нравится такое поведение и вы хотите, чтобы колонки были точно 400 пикселов в ширину, вы можете позаботиться, чтобы ширина колонок была неизменной:
<FlowDocument ColumnWidth=”400” IsColumnWidthFlexible=”false”>
Теперь колонки ровно 400 пикселов в ширину (при масштабе в 100 процентов), а остающееся место пустует.
Другим имеющим отношение к колонкам параметром, с которой вам может захотеться поиграть, является промежуток между колонками. Его можно настроить через свойство ColumnGap (параметр тоже основан на аппаратно-независимых пикселах):
<FlowDocument ColumnGap=”25”>
К этому имеет отношение и параметр графления колонок, позволяющий задавать визуальный элемент, отображаемый между колонками. Рассмотрим этот пример (результаты можно увидеть на рис. 15):
Рис. 15 Документ нефиксированного формата с простым графлением между колонками (Щелкните изображение, чтобы увеличить его)
<FlowDocument ColumnRuleWidth=”5” ColumnRuleBrush=”Red”>
Конечно, во многих публикациях документы не просто делятся на простые колонки. Зачастую присутствуют другие элементы, выпадающие из регулярного размещения. Вы уже видели примеры этому с изображениями, помещаемыми внутрь документов. На рис. 12 показано размещение, принятое у иллюстраторов. Изображение помещается между двух колонок, а текст «обтекает» его; в результате изображение находится прямо посреди текста, не нарушая значительно размещения ни в одной из колонок. Это распространенный вариант компоновки, который просто был недоступен в известных мне динамических средах экранного чтения до появления документов нефиксированного формата.
Ключом к созданию таких компоновок является блок Figure, позволяющий определить содержимое, не следующее размещению остального документа. Размещение изображения внутри тега Figure является одним из примеров, но элементы Figure можно использовать и по-другому. Например, можно использовать элемент Figure для определения заголовка во всю ширину документа:
<Paragraph>
<Figure HorizontalAnchor=”ContentLeft” VerticalAnchor=”ContentTop”
Width=”1Content”>
<Paragraph FontSize=”36” FontWeight=”Bold”>Go With
The Flow</Paragraph>
</Figure>
Windows Presentation Foundation in Windows Vista provides a great set
of features.
</Paragraph>
В этом коде Figure содержит другой абзац, используемый как текст заголовка. Отметьте, что есть удобные свойства, которые можно использовать для создания сложных и гибких документов. Например, рассмотрим ширину рисунка. Вместо установки ширины в определенное число пикселов, я устанавливаю ее в точности равной ширине содержимого, что автоматически подстроит ширину рисунка в зависимости от того, как широко содержимое.
Взгляните на рис. 16. Вы увидите, что заголовок (размещенный с помощью рисунка) настроен пересекать весь документ, что смещает все четыре колонки вниз. Изображение по горизонтали и вертикали привязано к центру страницы.
Рис. 16 Заголовок пересекает четыре колонки (Щелкните изображение, чтобы увеличить его)
Отметьте, что рисунки с шириной, заданной относительно содержимого, не обязаны быть так же широки, как содержимое. Следующий пример устанавливает ширину рисунка в 75 процентов ширины содержимого:
<Figure Width=”0.75Content”>
Ширина может быть также задана относительно других параметров, например ширины колонок. В следующем примере рисунок всегда шириной в две колонки (если не отображается лишь одна колонка, в этом случае ширина уменьшается до одной колонки):
Конечно же, высота рисунков может быть задана таким же способом (хотя часто рисунки просто растягиваются по высоте вместе с содержимым).
Другим важным параметром является положение рисунка. Во фрагменте кода он привязан к левому краю по горизонтали и к верхнему краю по вертикали. Это означает, что рисунок появляется в верхнем левом углу текущей страницы содержимого, независимо от того, где он определен. В этом примере рисунок и так был определен в качестве первого элемента документа, но даже если бы до этого заголовка было несколько абзацев, он был бы смещен вверх и влево из-за этих настроек. Фотографии на рис. 12 и рис. 16 были помещены между колонок аналогичным способом, установкой горизонтальной привязки в значение «PageCenter». (Доступные значения свойств для всех этих параметров можно найти в документации по WPF).
Вы могли заметить, что многое создавалось вручную. Например, когда необходимо изменить гарнитуру шрифта, вы добавляете эту информацию в блок или внутристрочный элемент. Пока что это не представляло большой проблемы, поскольку в большинстве примеры были маленькими. Однако если у вас есть 50-страничная глава книги и вы хотите изменить шрифт в каждом абзаце, то делать это каждый раз вручную становится утомительно. К счастью, есть лучший подход: как и все остальное в WPF, документы нефиксированного формата поддерживают стили. Стили могут быть определены как именованные ресурсы в самом документе. Вот стиль, определяющий информацию о шрифте:
<FlowDocument>
<FlowDocument.Resources>
<Style x:Key=”MyStyle”>
<Setter Property=”TextElement.FontSize” Value=”12” />
<Setter Property=”TextElement.FontFamily” Value=”Bodoni MT” />
</Style>
<FlowDocument.Resources>
...
</FlowDocument>
Стиль может затем быть применен к абзацам (и другим элементам) следующим образом:
<Paragraph Style=”{StaticResource MyStyle}”>The quick... </Paragraph>
Из-за природы документов нефиксированного формата стили очень широко используются. Я рекомендую, чтобы для всего, кроме простых ситуаций, вы определяли большую часть параметров форматирования в стилях, а не в атрибутах отдельных внутристрочных элементов. Стили помогают документам оставаться компактными и облегчают их поддержку.
Больше и шире
Я надеюсь, что эта статья предоставила вам чуть больше, чем базовое понимание документов нефиксированного формата и их возможностей, и затронула ваш интерес. Есть еще более сложные возможности, с которыми стоит ознакомиться, включая сложные стили элементов управления средств просмотра, наследование и расширение документов, блоков и внутристрочных элементов, цифровое управление правами, возможность текстовых и чернильных аннотаций и расширенное форматирование шрифтов.
Маркус Эггер (Markus Egger) является широко известным докладчиком и издателем журнала «CoDe Magazine». Маркус также занимает должности президента и главного архитектора ПО компании EPS Software Corp., оказывающей консультационные услуги по объектно-ориентированной разработке, разработке для Интернета, приложениям B2B и веб-службам. Его блог доступен по адресу
www.markusegger.com/blog.