Стили в WinRT

173

Вы уже видели, что кисти могут определяться как ресурсы для совместного использования элементами. Бесспорно, самым частым применением ресурсов является определение стилей - экземпляров класса Style. Стиль по сути представляет собой коллекцию определений свойств, которые могут совместно использоваться разными элементами. Применение стилей не только сокращает объем повторяющейся разметки, но и упрощает глобальные изменения.

Ранее мы добавили в наш тестовый проект словарь ресурсов, в котором определили кисть rainbowBrush, а также размеры и тип шрифта. Давайте объединим данные о шрифте в отдельный стиль:

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:WinRTTestApp">
    
    <x:String x:Key="title">Настройки кистей</x:String>

    <LinearGradientBrush x:Key="rainbowBrush">
        <GradientStop Offset="0" Color="Red" />
        <GradientStop Offset="0.17" Color="Orange" />
        <GradientStop Offset="0.34" Color="Yellow" />
        <GradientStop Offset="0.5" Color="Green" />
        <GradientStop Offset="0.66" Color="Blue" />
        <GradientStop Offset="0.83" Color="Indigo" />
        <GradientStop Offset="1" Color="Violet" />
    </LinearGradientBrush>

    <Style x:Key="rainbowStyle" TargetType="TextBlock">
        <Setter Property="FontFamily" Value="Arial" />
        <Setter Property="FontSize" Value="80" />
        <Setter Property="Foreground" Value="{StaticResource rainbowBrush}" />
    </Style>

</ResourceDictionary>

Как и у всех ресурсов, начальный тег Style включает атрибут x:Key. У элемента Style также должен быть задан атрибут TargetType, обозначающий либо FrameworkElement, либо класс, производный от FrameworkElement. Стили могут применяться только к классам, производным от FrameworkElement.

В тело Style включен набор тегов Setter, в каждом из которых задаются атрибуты Property и Value. Обратите внимание: в последнем теге атрибуту Value задается StaticResource для ранее определенной кисти LinearGradientBrush. Чтобы эта ссылка работала, определение Style должно располагаться в файле XAML после определения кисти, хотя оно также может находиться в другой секции Resources глубже в визуальном дереве.

Для ссылок на стили, как и на другие ресурсы, элемент использует расширение разметки StaticResource в своем свойстве Style:

<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
        <TextBlock Text="{StaticResource title}"
                   FontSize="42"
                   HorizontalAlignment="Center"
                   VerticalAlignment="Center" />

        <TextBlock Text="Текст сверху"
                   Style="{StaticResource rainbowStyle}"
                   HorizontalAlignment="Center"
                   VerticalAlignment="Top" />

        <TextBlock Text="Текст слева"
                   Style="{StaticResource rainbowStyle}"
                   HorizontalAlignment="Left"
                   VerticalAlignment="Center" />

        <TextBlock Text="Текст справа"
                   Style="{StaticResource rainbowStyle}"
                   HorizontalAlignment="Right"
                   VerticalAlignment="Center" />

        <TextBlock Text="Текст снизу"
                   Style="{StaticResource rainbowStyle}"
                   HorizontalAlignment="Center"
                   VerticalAlignment="Bottom" />
</Grid>
Использование стилей вместо ресурсов для достижения того же эффекта

Визуальное оформление приложения не изменилось. Также существует альтернативный способ включения кисти LinearGradientBrush в это конкретное определение Style. По аналогии с использованием синтаксиса элементов свойств для определения объектов со сложной разметкой можно использовать синтаксис элементов свойств со свойством Value класса Setter:

<Style x:Key="rainbowStyle" TargetType="TextBlock">
        <Setter Property="FontFamily" Value="Arial" />
        <Setter Property="FontSize" Value="80" />
        <Setter Property="Foreground">
            <Setter.Value>
                <LinearGradientBrush>
                    <GradientStop Offset="0" Color="Red" />
                    <GradientStop Offset="0.17" Color="Orange" />
                    <GradientStop Offset="0.34" Color="Yellow" />
                    <GradientStop Offset="0.5" Color="Green" />
                    <GradientStop Offset="0.66" Color="Blue" />
                    <GradientStop Offset="0.83" Color="Indigo" />
                    <GradientStop Offset="1" Color="Violet" />
                </LinearGradientBrush>
            </Setter.Value>
        </Setter>
</Style>

Поначалу это выглядит немного странно, но определение кистей в стилях очень распространено. Обратите внимание: LinearGradientBrush теперь не имеет собственного атрибута x:Key. Только элементы, определяемые на корневом уровне коллекции Resources, могут иметь этот атрибут.

Объект Style также можно определить в коде:

Style style = new Style(typeof(TextBlock));
style.Setters.Add(new Setter(TextBlock.FontSizeProperty, 80));
style.Setters.Add(new Setter(TextBlock.FontFamilyProperty, "Arial"));

После этого фрагмент добавляется в коллекцию Resources объекта Page перед вызовом InitializeComponent(), чтобы он стал доступным для элементов TextBlock, определенных в файле XAML. Также можно задать объект Style непосредственно свойству Style объекта TextBlock. Впрочем, такое решение используется нечасто, потому что в коде доступны более удобные способы задания одинаковых свойств для нескольких разных элементов - а именно циклы for и foreach.

Обратите внимание на первый аргумент конструктора Setter в примере кода. Он определяется с типом DependencyProperty, и в нем передается статическое свойство типа DependencyProperty, определяемое (или унаследованное) целевым классом стиля. Это отличный пример того, как свойства зависимостей позволяют задавать свойство класса независимо от конкретного экземпляра этого класса.

Код также ясно показывает, что свойства, целевые для Style, могут быть только свойствами зависимостей. Ранее я уже упоминал о том, что свойства зависимостей устанавливают иерархию задания свойств. Допустим, в программе присутствует следующая разметка:

<TextBlock Text="Текст слева"
    Style="{StaticResource rainbowStyle}"
    HorizontalAlignment="Left"
    VerticalAlignment="Center"
    FontSize="36"/>

Style определяет значение FontSize, но свойство FontSize также задается локально для TextBlock. Как и следовало ожидать, локальное значение заменяет значение из Style, и оба значения заменяют значение FontSize, распространяемое по визуальному дереву.

После того как объект Style будет задан свойству Style элемента, его уже нельзя изменить. Позднее элементу можно будет задать другой объект Style, и вы можете изменять свойства объектов, ссылки на которые присутствуют в стиле (например, кистей), но вы не можете задавать или удалять объекты Setter или изменять их свойства Value.

Стили могут наследовать значения свойств от других стилей; для этого используется свойство Style с атрибутом BasedOn, которому обычно задается расширение разметки StaticResource со ссылкой на ранее определенный объект Style:

<Style x:Key="baseTextBlockStyle" TargetType="TextBlock">
        <Setter Property="FontFamily" Value="Arial" />
        <Setter Property="FontSize" Value="36" />
</Style>

<Style x:Key="gradientStyle" TargetType="TextBlock" BasedOn="{StaticResource baseTextBlockStyle}">
        <Setter Property="FontSize" Value="80" />
        <Setter Property="Foreground">
            <Setter.Value>
                <LinearGradientBrush>
                    <GradientStop Offset="0" Color="Red" />
                    <GradientStop Offset="1" Color="Violet" />
                </LinearGradientBrush>
            </Setter.Value>
        </Setter>
</Style>

Элемент Style с ключом gradientstyle в этом примере создается на основе другого элемента Style с ключом baseTextBlockStyle; это означает, что он наследует параметр FontFamily, переопределяет значение FontSize и определяет новое значение Foreground. Другой пример:

<Style x:Key="centerStyle" TargetType="FrameworkElement">
        <Setter Property="HorizontalAlignment" Value="Center" />
        <Setter Property="VerticalAlignment" Value="Center" />
</Style>

<Style x:Key="rainbowStyle" TargetType="TextBlock" BasedOn="{StaticResource centerStyle}">
        <Setter Property="FontFamily" Value="Arial" />
        <Setter Property="FontSize" Value="80" />
        <Setter Property="Foreground">
            <Setter.Value>
                <LinearGradientBrush>
                    <GradientStop Offset="0" Color="Red" />
                    <GradientStop Offset="1" Color="Violet" />
                </LinearGradientBrush>
            </Setter.Value>
        </Setter>
</Style>

В данном примере у первого элемента Style свойству TargetType задано значение FrameworkElement; это означает, что оно может включать только свойства, определенные или унаследованные FrameworkElement. Стиль может применяться к TextBlock, потому что TextBlock наследует от FrameworkElement. Второй элемент Style базируется на стиле с ключом centerStyle, но у него свойство TargetType равно TextBlock; это означает, что оно также может включать значения свойств, специфические для TextBlock. Значение TargetType должно относиться к типу BasedOn или быть производным от него.

Несмотря на все, что говорилось ранее об обязательности ключей для ресурсов, Style в действительности является единственным исключением из этого правила. Определение Style без атрибута x:Key - особый случай, называемый неявным стилем (implicit style). Например:

<Style TargetType="TextBlock">
        <Setter Property="FontSize" Value="80" />
        <Setter Property="Foreground">
            <Setter.Value>
                <LinearGradientBrush>
                    <GradientStop Offset="0" Color="Red" />
                    <GradientStop Offset="0.17" Color="Orange" />
                    <GradientStop Offset="0.34" Color="Yellow" />
                    <GradientStop Offset="0.5" Color="Green" />
                    <GradientStop Offset="0.66" Color="Blue" />
                    <GradientStop Offset="0.83" Color="Indigo" />
                    <GradientStop Offset="1" Color="Violet" />
                </LinearGradientBrush>
            </Setter.Value>
        </Setter>
</Style>

В действительности ключ создается, но это происходит незаметно. Это объект типа RuntimeType (не является открытым типом), обозначающий тип TextBlock.

Неявные стили обладают широкими возможностями. Любой элемент TextBlock, расположенный ниже по визуальному дереву, у которого не задано свойство Style, получает неявный стиль. Если ваша страница уже заполнена элементами TextBlock и вы решаете назначить им одинаковое оформление, при помощи неявных стилей это делается очень просто. Обратите внимание: ни у одного из следующих элементов TextBock свойства Style не задано:

<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
        <TextBlock Text="{StaticResource title}"
                   FontSize="42"
                   Foreground="{StaticResource ApplicationForegroundThemeBrush}"
                   HorizontalAlignment="Center"
                   VerticalAlignment="Center" />

        <TextBlock Text="Текст сверху"
                   HorizontalAlignment="Center"
                   VerticalAlignment="Top" />

        <TextBlock Text="Текст слева"
                   HorizontalAlignment="Left"
                   VerticalAlignment="Center"/>

        <TextBlock Text="Текст справа"
                   HorizontalAlignment="Right"
                   VerticalAlignment="Center" />

        <TextBlock Text="Текст снизу"
                   HorizontalAlignment="Center"
                   VerticalAlignment="Bottom" />
</Grid>

Разумеется, неявный стиль должен применяться к большинству элементов TextBlock на странице - кроме первого, который находится в центре. Если отдельные элементы страницы должны быть исключены из назначения неявного стиля, задайте им явный стиль или задайте локальные настройки, переопределяющие свойства из объекта Style, или задайте свойству Style значение null. (Вскоре я покажу, как это делается в XAML.) В этом примере я явно переопределил неявный стиль первого элемента TextBlock, задав ему явное значение FontSize и значение Foreground, основанное на предопределенном ресурсе.

Стиль не может определяться как производный от неявного стиля, однако неявный стиль может быть основан на явном стиле. Просто добавьте атрибуты TargetType и BasedOn, не указывая атрибут x:Key.

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

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

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