Блочные элементы
113WPF --- Периферия WPF --- Блочные элементы
Создать простейший документ нетрудно, но чтобы получить действительно полезный результат, потребуется овладеть разнообразными элементами. Среди них — пять блочных элементов, которые будут описаны ниже.
Элементы Paragraph и Run
Вы уже знакомы с элементом Paragraph (абзац), который представляет абзац текста. С технической точки зрения этот элемент не содержит текст: он содержит коллекцию строковых элементов Paragraph.Inlines.
Из этого факта можно сделать два вывода. Во-первых, это означает, что абзац может содержать отнюдь не только текст. Во-вторых, это означает, что для того, чтобы абзац мог содержать текст, он должен содержать строковый элемент Run. А элемент Run уже содержит сам текст:
<Paragraph>
<Run>Hello, world!</Run>
</Paragraph>
Этот более точный синтаксис не был задействован в примере из предыдущей статьи потому, что класс Paragraph сам неявно создает элемент Run, если поместить в него текст.
Но в некоторых случаях бывает важно понимать, как ведет себя абзац. Предположим, что требуется извлечь текст из абзаца программным образом, и имеется следующая разметка:
((Run)paragraph.Inlines.FirstInline).Text = "Привет, мир!";
Читабельность этого кода можно улучшить с помощью элемента Span, который позволяет заключить подлежащий изменению текст. Затем этому элементу можно присвоить имя и обращаться к нему напрямую.
В WPF 4 введено небольшое усовершенствование в элемент Run. В предыдущих версиях свойство Run.Text было обычным свойством и поэтому не поддерживало привязку данных. В WPF 4 свойство Run.Text является свойством зависимости, что позволяет установить его с помощью выражения привязки данных.
Класс Paragraph содержит свойство TextIndent, которое позволяет задавать величину отступа первой строки в не зависящих от устройства единицах. (По умолчанию оно имеет нулевое значение.)
Кроме этого, класс Paragraph содержит еще несколько свойств, которые определяют разбиение строк в местах разрывов колонок и страниц.
В отличие от HTML, в WPF нет блочных элементов для заголовков. Вместо них нужно просто использовать абзацы с другими размерами шрифтов.
Элемент List
Элемент List (список) представляет маркированный или нумерованный список. Формат списка определяется с помощью свойства MarkerStyle. Возможные параметры показаны на рисунке ниже. Кроме того, свойство MarkerOffset позволяет задать расстояние между каждым элементом списка и его маркером.
В элемент List заключаются элементы ListItem, представляющие отдельные элементы списка. Однако каждый элемент ListItem должен содержать подходящий блочный элемент (например, Paragraph). Ниже показан пример, в котором создаются два списка, один с маркерами, а другой с цифрами:
<Paragraph>Языки программирования</Paragraph>
<List>
<ListItem>
<Paragraph>C#</Paragraph>
</ListItem>
<ListItem>
<Paragraph>C++</Paragraph>
</ListItem>
<ListItem>
<Paragraph>Perl</Paragraph>
</ListItem>
<ListItem>
<Paragraph>PHP</Paragraph>
</ListItem>
</List>
<Paragraph>Второй список:</Paragraph>
<List MarkerStyle="Decimal">
<ListItem>
<Paragraph>Основы работы</Paragraph>
</ListItem>
<ListItem>
<Paragraph>Практическое руководство</Paragraph>
</ListItem>
</List>
Элемент Table
Элемент Table (таблица) предназначен для отображения табличной информации. Он был создан по подобию элемента <table> из языка HTML. Чтобы создать таблицу, необходимо выполнить следующие действия:
Поместите в Table элемент TableRowGroup. Элемент TableRowGroup хранит группу строк, и каждая таблица состоит из одного или более элементов TableRowGroup. Сам по себе TableRowGroup ничего не делает. Однако если использовать несколько групп с разным форматированием, вы без труда сможете изменить общий внешний вид таблицы, не занимаясь форматированием каждой строки.
В элемент TableRowGroup поместите элементы TableRow для каждой строки.
В каждый элемент TableRow поместите элементы TableCell, представляющие элементы строки.
В каждый элемент TableCell поместите какой-либо блочный элемент (как правило, Paragraph). И вот в этот блочный элемент можно занести содержимое данной ячейки.
Ниже представлены первые две строки таблицы, приведенной на первом рисунке:
<Table>
<Table.Columns>
<TableColumn Width="2*"></TableColumn>
<TableColumn Width="*"></TableColumn>
</Table.Columns>
<TableRowGroup>
<TableRow FontWeight="Bold">
<TableCell>
<Paragraph>MarkerStyle</Paragraph>
</TableCell>
<TableCell>
<Paragraph>Вид</Paragraph>
</TableCell>
</TableRow>
<TableRow>
<TableCell>
<Paragraph Margin="0,10,0,0">Disc</Paragraph>
</TableCell>
<TableCell>
<List MarkerStyle="Disc" Margin="0,10,0,0">
<ListItem/>
</List>
</TableCell>
</TableRow>
...
В отличие от Grid, ячейки в элементе Table заполняются по их позициям. Необходимо включить элемент TableCell для каждой ячейки в таблице и поместить каждую строку и значение в том порядке, в котором они должны отображаться.
Если не указать явным образом ширину столбцов, WPF равномерно распределит пространство между всеми столбцами. Это поведение можно изменить, присвоив свойству Table.Rows набор объектов TableColumn и определив для каждого из них ширину Width, как было показано в предыдущем примере.
С таблицей можно выполнять и другие действия. Свойства ColumnSpan и RowSpan позволяют растягивать ячейки на несколько строк. С помощью свойства Cellspacing таблицы можно задать величину промежутка между ячейками. Кроме того, к отдельным ячейкам можно применить индивидуальное форматирование (например, разные цвета текста и фона).
А вот поддержка рамки таблицы выполнена слабее. Можно воспользоваться свойствами BorderThickness и BorderBrush элемента TableCell, однако при этом прорисовывается отдельная рамка вокруг каждой ячейки. Эти рамки выглядят не очень аккуратно, если использовать их для групп смежных ячеек. У элемента Table имеются свойства BorderThickness и BorderBrush, но они позволяют нарисовать рамку только вокруг всей таблицы. Если вы надеялись получить более эффектное представление (например, добавить линии между колонками), то это не получится.
Другое ограничение состоит в том, что размеры колонок нужно задавать явно или пропорционально (при помощи уже показанного синтаксиса со звездочкой). Однако применять два подхода одновременно нельзя. Например, невозможно создать две колонки фиксированной ширины и одну пропорциональную колонку, чтобы занять оставшееся пространство, как в элементе Grid.
Некоторые элементы вывода содержимого похожи на другие элементы, не связанные с содержимым. Однако элементы вывода содержимого предназначены исключительно для использования внутри потокового документа. К примеру, нет смысла заменять элемент Grid элементом Table. Элемент Grid предназначен для наиболее эффективной компоновки элементов управления в окне, a Table служит для представления текста в документе наиболее удобным для чтения способом.
Элемент Section
Элемент Section (раздел) не имеет собственного встроенного форматирования. Он используется для упаковки других блочных элементов и позволяет применить единый формат ко всей порции документа. Например, если нужно, чтобы несколько смежных абзацев имели одинаковый цвет фона и шрифт, можно упаковать эти абзацы в один раздел и задать свойство Section.Background:
<Section FontFamily="Calibri" Background="LightBlue">
<Paragraph>Hello, world!</Paragraph>
<Paragraph>Это первый абзац</Paragraph>
<Paragraph>Языки программирования</Paragraph>
</Section>
Здесь используется то, что параметры шрифта наследуются содержащимися в разделе абзацами. Значение фона не наследуется, но фон каждого абзаца по умолчанию является прозрачным, и сквозь него виден фон раздела.
Более того, можно задать свойство Section.Style, чтобы отформатировать раздел с помощью стиля:
<Section Style="StyleForText">
Элемент Section аналогичен элементу <div> в языке HTML.
Во многих потоковых документах стили применяются для классификации форматирования содержимого на основе его типа. (Например, на книжном сайте с возможностью предварительного просмотра книг можно создать отдельные стили для заголовков, текста, для выделения врезок и фамилий авторов.) Затем для получения нужного форматирования достаточно определить эти стили.
Элемент BlockUIContainer
BlockUIContainer позволяет помещать элементы, не связанные с содержимым (классы-наследники UIElement), в документ, где должен помещаться блочный элемент.
Например, с помощью BlockUIContainer можно добавить в документ кнопки, флажки и даже целые контейнеры компоновки наподобие StackPanel и Grid. Единственное ограничение — BlockUIContainer может иметь только один дочерний элемент.
Вы можете удивиться: а зачем вообще помещать в документ элементы управления? Разве не лучше применять контейнеры компоновки для частей интерфейса, предназначенных для интерактивной связи с пользователем, и потоковую компоновку для объемных блоков содержимого, доступных только для чтения?
Однако в реальных приложениях существует много типов документов, которые как-то должны взаимодействовать с пользователем (кроме элемента вывода содержимого Hyperlink). Например, если система потоковой компоновки использована для создания страниц оперативной справки, то можно добавить кнопку, запускающую некоторое действие.
Вот пример расположения кнопки под абзацем:
<Paragraph>Настройка параметров</Paragraph>
<BlockUIContainer>
<Button HorizontalAlignment="Left" Padding="5" Margin="0,5" FontSize="13">Параметры</Button>
</BlockUIContainer>
Обработчик события подключается к событию Button.Click обычным образом.
Смешанное применение элементов вывода содержимого и обычных элементов, не связанных с содержимым, имеет смысл в документах, поддерживающих интерактивную связь с пользователем. Например, при создании приложения для опроса, в котором пользователи могут вводить ответы, можно воспользоваться расширенной компоновкой текста из модели потокового документа — тогда пользователь сможет вводить и/или выбирать значения с помощью обычных элементов управления.