Шаблон окна

98

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

Первым делом нужно изучить предлагаемый по умолчанию шаблон элемента управления для класса Window. В основном он довольно прост, но включает в себя одну, возможно, неожиданную деталь — элемент AdornerDecorator. Этот элемент создает поверх остальной части клиентского содержимого окна специальную область рисования, называемую декоративным слоем (adorner layer). Элементы управления WPF могут использовать этот слой для прорисовывания содержимого, которое должно налагаться поверх других элементов.

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

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

<Style x:Key="CustomWindowChrome" TargetType="{x:Type Window}">
    <Setter Property="AllowsTransparency" Value="True"></Setter>
    <Setter Property="WindowStyle" Value="None"></Setter>
    <Setter Property="Background" Value="Transparent"></Setter>
    
    <Setter Property="Template">
      <Setter.Value>
        <ControlTemplate TargetType="{x:Type Window}">
          <Border Width="Auto" Height="Auto" Name="windowFrame"    
          BorderBrush="#395984"
          BorderThickness="1"
          CornerRadius="0,20,30,40" >
            <Border.Background>
              <LinearGradientBrush >
                <GradientBrush.GradientStops>
                  <GradientStopCollection>
                    <GradientStop Color="#E7EBF7" Offset="0.0"/>
                    <GradientStop Color="#CEE3FF" Offset="0.5"/>

                  </GradientStopCollection>
                </GradientBrush.GradientStops>
              </LinearGradientBrush>
            </Border.Background>
            <Grid>

              <Grid.RowDefinitions>
                <RowDefinition Height="Auto"></RowDefinition>
                <RowDefinition></RowDefinition>
                <RowDefinition Height="Auto"></RowDefinition>
              </Grid.RowDefinitions>

              <TextBlock Margin="1" Padding="5" Text="{TemplateBinding Title}" FontWeight="Bold"
                         MouseLeftButtonDown="titleBar_MouseLeftButtonDown"></TextBlock>
              <Button Style="{StaticResource CloseButton}" HorizontalAlignment="Right" Margin="0,5,25,0"
                      Click="cmdClose_Click"></Button>
              <Border Background="#B5CBEF" Grid.Row="1">
            
                <AdornerDecorator>
                  <ContentPresenter/>
                </AdornerDecorator>
                
              </Border>

              <ContentPresenter Grid.Row="2" Margin="10" 
                                HorizontalAlignment="Center"
                                Content="{TemplateBinding Tag}"></ContentPresenter>
              <!--<TextBlock HorizontalAlignment="Center" Grid.Row="2" Text="{TemplateBinding Tag}" Margin="1,10,1,1" Padding="5"></TextBlock>-->
              <ResizeGrip Name="WindowResizeGrip" Grid.Row="2" Margin="0,0,10,7"
                  HorizontalAlignment="Right"
                  VerticalAlignment="Bottom"
                  Visibility="Collapsed"
                  IsTabStop="false"/>
              
              <Rectangle Grid.Row="1" Grid.RowSpan="3"
       Cursor="SizeWE" Fill="Transparent" Width="5" VerticalAlignment="Stretch" HorizontalAlignment="Right"
                 MouseLeftButtonDown="window_initiateResizeWE"
                 MouseLeftButtonUp="window_endResize"
                 MouseMove="window_Resize"></Rectangle>
            

            <Rectangle Grid.Row="2" 
     Cursor="SizeNS" Fill="Transparent" Height="5" HorizontalAlignment="Stretch" VerticalAlignment="Bottom"
               MouseLeftButtonDown="window_initiateResizeNS"
               MouseLeftButtonUp="window_endResize"
               MouseMove="window_Resize"></Rectangle>
            </Grid>

          </Border>

          <ControlTemplate.Triggers>
            <Trigger Property="ResizeMode" Value="CanResizeWithGrip">
              <Setter TargetName="WindowResizeGrip" Property="Visibility" Value="Visible"/>
            </Trigger>
          </ControlTemplate.Triggers>
        </ControlTemplate>
      </Setter.Value>
    </Setter>
  </Style>

Элементом самого верхнего уровня в данном шаблоне является объект Border, который отвечает за рамку окна. Внутри него размещается элемент Grid с тремя строками. Содержимое элемента Grid распределяется следующим образом:

В этом коде разметки не показаны малоинтересные стили элемента захвата и изменения размера (просто создает небольшой узор из точек) и кнопки закрытия окна (рисует небольшой символ X в красном квадрате). Разметка также не включает детали форматирования наподобие градиентной кисти, отвечающей за прорисовку фона, и свойств, отвечающих за создание скругленных углов границы окна.

Шаблон окна применяется с использованием простого стиля. Стиль также устанавливает три ключевых свойства класса Window, которые делают его прозрачным. Это позволяет создать границу окна и фон с помощью элементов WPF.

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

Существует два возможных способа для добавления необходимого кода: расширение примера до специального класса, производного от Window, либо создание класса отделенного кода для словаря ресурсов. Подход с созданием специального класса лучше с точки зрения инкапсуляции и позволяет расширять общедоступный интерфейс окна (например, добавлять полезные методы и свойства, пригодные для использования в приложении).

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

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