Подробнее об элементах управления содержимым (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:
1 2 3 4 5 6 7 | public class Balloon : ContentControl { public Balloon() { this .DefaultStyleKey = typeof (Balloon); } } |
Для этого можно использовать мастера шаблонов в Visual Studio. После того, как мастер завершит создание, нужно поменять базовый класс с Control на ContentControl, так как мастер по умолчанию создает "leaf" элемент.
Теперь нужно создать шаблон по умолчанию. Если использовали мастера из Visual Studio, то он уже создал файл generic.xaml и пустой шаблон для элемента. В другом случае нужно вручную создать этот файл в папке Themes и добавить ControlTemplate.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | <ResourceDictionary 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, расположены один поверх другого.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | <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:
1 2 3 4 5 6 7 8 | <!-- 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, и он будет отображен там, где нужно.
1 2 3 4 5 6 7 8 9 10 11 12 | <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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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).
1 2 3 4 5 6 7 8 9 10 11 | <!-- 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 Украина | ![]() |