Подробнее об элементах управления содержимым (Content Controls) в Silverlight

четверг, 2 июня 2011, bobasoft

Играясь с шаблонами элементов в Silverlight становиться очевидно, что существует три типа элементов, которые используются в XAML. Проанализировав иерархическое дерево (часто называют визуальное дерево) страницы, выведем эти три типа:

  1. "Leaf" элементы - потомки класса Control. Это конечные точки в иерархии элементов, так как ничего не может быть в них добавлено.
  2. "Content" элементы - потомок класса ContentControl. Эти элементы могут содержать другие элементы. Это узлы иерархии.
  3. "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>

Шаблон еще не полностью закончен, так как еще нет части отвечающей за содержимое (контент). Графическое представление этой разметки можно увидеть ниже:

alt text

Пока это только базовый вид элемента в виде подсказки, которая часто используется в текстовых сообщениях, но я также добавил и заглавие. Большинство свойств элемента я привязал к внутренним элементам разметки - это позволяет изменять оформление без изменения шаблона.

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

Заключение

alt text

Я всегда поражаюсь мощностью шаблонов (Templated Controls) в Silverlight, а также в WPF. Они предоставляют простой способ создание частей интерфейса для многоразового использования с использованием всего набора инструментов доступных в языке. Элемент управления содержимым - шаг вперед к созданию чистой и понятной разметки XAML и эти глубокие познания улучшают программирования в Silverlight.

Компании из статьи


Microsoft Украина


Сайт:
http://www.microsoft.com/ukr/ua/

Microsoft Украина Украинское подразделение компании Microsoft.

Ищите нас в интернетах!

Комментарии

Свежие вакансии