Подробнее об элементах управления содержимым (Content Controls) в Silverlight
Играясь с шаблонами элементов в Silverlight становиться очевидно, что существует три типа элементов, которые используются в XAML. Проанализировав иерархическое дерево (часто называют визуальное дерево) страницы, выведем эти три типа:
- "Leaf" элементы - потомки класса Control. Это конечные точки в иерархии элементов, так как ничего не может быть в них добавлено.
- "Content" элементы - потомок класса ContentControl. Эти элементы могут содержать другие элементы. Это узлы иерархии.
- "Items" элементы - обычно наследованные от класса ItemsControl и могут содержать несколько элементов. Это тоже узлы иерархии только с множеством веток выходящих из них.
Граница между одним типом элементов и другим не всегда очевидна. Возьмем, к примеру, элемент Button, на первый взгляд он кажется элементом leaf, но это content элемент, и благодаря этому в кнопке можно поместить текст с изображением, а не только надпись.
При создании элемента управления очень важно сделать правильный выбор, от какого класса нужно наследоваться, так как это может повлиять на эффективность и возможность повторного использования этого элемента. В большинстве случаев можно наследоваться от элемента Control, но стоит обратить внимание и на элемент ContentControl. ContentControl может предоставить дополнительное преимущество в дальнейшем использовании этого элемента.
Создание СontentControl с нуля
При первом создании элемента, очень сложно понять концепцию контента (content). Кажется, что нет ничего не возможного при наследовании от элемента Control, объявив целый набор свойств для охвата нужных целей. В этом посте мы создадим элемент Balloon (подсказка), похожий на тот, что используется в чат-приложениях. Сам элемент это просто прямоугольник с уголком и текстом. Первое что приходит в голову - это наследоваться от элемента Control и объявить свойство "Text" для установки содержимого элемента. С одной стороны в этом решении нет ничего плохого, но как только мы решим добавить в текст поддержку смайлов - становиться сложнее. В принципе, учитывая то, что свойство "Text" содержит строку, можно использовать текстовые смайлы, а потом с помощью парсера заменить их на изображения. Но это все-таки не тривиальная задача. Все еще больше усложниться, если нужно будет добавить поддержку прикрепления изображений, разных шрифтов, стилей и т.д.
Но если создать элемент Balloon и наследоваться от ContentControl, то о контенте можно думать как о любой-другой ветке визуального дерева. В классе ContentControl есть свойство Content типа "object" куда можно добавить другие элементы. Поэтому, элемент становиться более универсальным, так как в нем можно разместить все что угодно. Первым делом, для создания такого типа элемента, нужно наследоваться от класса ContentControl:
public class Balloon : ContentControl { public Balloon() { this.DefaultStyleKey = typeof(Balloon); } }
Для этого можно использовать мастера шаблонов в Visual Studio. После того, как мастер завершит создание, нужно поменять базовый класс с Control на ContentControl, так как мастер по умолчанию создает "leaf" элемент.
Теперь нужно создать шаблон по умолчанию. Если использовали мастера из Visual Studio, то он уже создал файл generic.xaml и пустой шаблон для элемента. В другом случае нужно вручную создать этот файл в папке Themes и добавить ControlTemplate.
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:SLPG.Controls"> <Style TargetType="local:Balloon"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="local:Balloon"> <!-- Add here the default appearance of your control --> </ControlTemplate> </Setter.Value> </Setter> </Style> </ResourceDictionary>
Дальше создаем разметку нашего элемента. Для простоты примера, я создал шаблон из элемента Grid и двух колонок. Левая колонка содержит уголок с отрицательным сдвигом вправо. В другой колонке есть два элемента Border, расположены один поверх другого.
<ControlTemplate TargetType="local:Balloon"> <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="*" /> </Grid.ColumnDefinitions> <Border Grid.Column="1" BorderThickness="0" Background="{TemplateBinding BorderBrush}" /> <Polyline Points="20,-20 0,0 20,-10" Width="40" Height="40" Fill="{TemplateBinding Background}" Stroke="Transparent" Stretch="Fill" StrokeThickness="0" VerticalAlignment="Bottom" Margin="0,0,-20,0" /> <Border Grid.Column="1" Background="{TemplateBinding Background}" Margin="{TemplateBinding BorderThickness}" Padding="{TemplateBinding Padding}" BorderBrush="Transparent"> <StackPanel> <TextBlock FontWeight="Bold">Here goes the title</TextBlock> <!-- here goes the content --> </StackPanel> </Border> </Grid> </ControlTemplate>
Шаблон еще не полностью закончен, так как еще нет части отвечающей за содержимое (контент). Графическое представление этой разметки можно увидеть ниже:
Пока это только базовый вид элемента в виде подсказки, которая часто используется в текстовых сообщениях, но я также добавил и заглавие. Большинство свойств элемента я привязал к внутренним элементам разметки - это позволяет изменять оформление без изменения шаблона.
Теперь нужно разобраться, как же расположить в правильном месте разметки элементы, которые разработчик вставляет внутрь тега Balloon.
Роль элемента ContentPresenter
Разметка уже почти готова. Теперь нужно отобразить информацию (контент) в нужном месте. Для этого используется специальный элемент ContentPresenter. ContentPresenter потомок от FrameworkElement, поэтому он не контрол, но он может отобразить в себе контент из свойства. Можно считать, что ContentPresenter это заполнитель (placeholder) который будет заменен на контент при выполнении.
ContentPresenter настолько умный, что положив его в ContolTemplate, он автоматически определит контент для отображения. Но чтобы понять, как это работает, мы вручную свяжем его свойства с расширенной разметкой TemplateBinding:
<!-- continue previous ControlTemplate --> <StackPanel> <TextBlock FontWeight="Bold">Here goes the title</TextBlock> <ContentPresenter Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}" /> </StackPanel> <!-- continue previous ControlTemplate -->
В элементе ContentPresenter можно изменять выравнивание, границы и размеры, но он не имеет никаких "визуальных" свойств как Background, BorderBrush и т.д. Поэтому контент отображается в полностью прозрачном контейнере. Как только ContentPresenter расположили в нужном месте, мы можем писать контент прямо внутрь тега Balloon, и он будет отображен там, где нужно.
<ct:Balloon Background="Orange" Padding="5" HorizontalAlignment="Center" VerticalAlignment="Center" MaxWidth="200" BorderBrush="White" BorderThickness="3"> <Border Background="White" Padding="5"> <toolkit:WrapPanel> <TextBlock TextWrapping="Wrap">Hey boys, this sounds</TextBlock> <TextBlock>embarassing!</TextBlock> <Image Source="smile.PNG" Width="19" Height="19" VerticalAlignment="Top" /> </toolkit:WrapPanel> </Border> </ct:Balloon>
Управление несколькими контентами
Мы уже на финишной прямой. Еще нужно связать заголовок подсказки со свойством, позволив разработчику изменять его. Самый простой вариант - это соединить его с зависимым свойством (dependency property) Text. Но мы сделаем немного больше.
ControlTemplate может содержать больше чем один контейнер, поэтому мы объявим еще одно контент свойство (типа object) с именем HeaderContent и потом свяжем его с другим элементом ContentPresenter.
public class Balloon : ContentControl { public static readonly DependencyProperty HeaderContentProperty = DependencyProperty.Register( "HeaderContent", typeof(object), typeof(Balloon), new PropertyMetadata(null)); public object HeaderContent { get { return (object)GetValue(HeaderContentProperty); } set { SetValue(HeaderContentProperty, value); } } public Balloon() { this.DefaultStyleKey = typeof(Balloon); } }
Свойство HeaderContent должен быть типа object, потому как должно содержать любой элемент или обычный текст. После того как новое зависимое свойство готово, можно добавить еще один ContentPresenter в шаблон и использовать TemplateBinding для связи его контента со свойством. Так как уже два контейнера, то для второго нужно использовать TemplateBinding. Только один контейнер в шаблоне может не использовать явной связи (explicit binding).
<!-- continue previous ControlTemplate --> <StackPanel> <ContentPresenter Content="{TemplateBinding HeaderContent}" /> <ContentPresenter Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}" /> </StackPanel> <!-- continue previous ControlTemplate -->
Заключение
Я всегда поражаюсь мощностью шаблонов (Templated Controls) в Silverlight, а также в WPF. Они предоставляют простой способ создание частей интерфейса для многоразового использования с использованием всего набора инструментов доступных в языке. Элемент управления содержимым - шаг вперед к созданию чистой и понятной разметки XAML и эти глубокие познания улучшают программирования в Silverlight.
http://www.silverlightshow.net/items/Understanding-the-Content-Controls-in-Silverlight.aspx
Компании из статьи
Microsoft Украина | Украинское подразделение компании Microsoft. |