Блочные элементы

113

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

Элементы 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 позволяет задать расстояние между каждым элементом списка и его маркером.

Свойство MarkerStyle

В элемент 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>
                <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 обычным образом.

Использование контейнера BlockUIContainer

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

Пройди тесты
Лучший чат для C# программистов