Наследование стилей/шаблонов WPF

Я пытаюсь изучить WPF на данный момент, и глядя на то, чтобы элемент управления .Net по умолчанию выглядел по-другому с помощью стиля. С помощью C#, как мой любимый язык, хотя все ниже кода разметки WPF.

Я настроил gmail сегодня с новой темой (см. изображение ниже) и, таким образом, поставил себе задачу, можно было бы сделать в WPF.

New GMail buttons

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

правая и левая кнопки очень похожи, но только 2 различия. Они имеют радиус угла 1 и 15 с левой или правой стороны, в то время как средняя кнопка имеет их оба установлены в 0.

вопросы!

Q1. вместо того, чтобы копировать весь стиль и изменять только эти 2 атрибута, это можно сделать с помощью некоторого типа наследования. Где расположены правая и левая кнопки на существующем стиле, но он делает эти 2 визуальных изменения. Я уже пробовал свойство BasedOn при создании нового стиля, но не смог отредактировать необходимые атрибуты.

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

Q3. наиболее трудный вопрос до последнего. Можно ли это сделать, поэтому, если к кнопке применен мой стиль. Затем, когда вы установите цвет фона, чтобы сказать синий. Затем кнопка поддерживает градиенты, но вместо того, чтобы они были белыми, они теперь имеют оттенок синего. т. е. фоновая линейная градиентная кисть основана на, а не перезаписывает цвет фона, который был применен к кнопке. Или они должны иметь отдельные стили. Я лично не могу видеть без некоторого типа кода за этим это может быть достигнуто, то есть создание градиентных кистей из одной кисти в разметке WPF.

т. е. кнопки, как ниже синей кнопки и серой/нормальной кнопки

Google buttons 2

mystyle может

<Style x:Key="GoogleMiddleButton" TargetType="{x:Type Button}">
        <Setter Property="Background">
            <Setter.Value>
                <LinearGradientBrush StartPoint="0,1" EndPoint="0,0">
                    <GradientStop Color="#F1F1F1" Offset="0"/>
                    <GradientStop Color="#F5F5F5" Offset="1"/>
                </LinearGradientBrush>
            </Setter.Value>
        </Setter>
        <Setter Property="Foreground" Value="#666666"/>
        <Setter Property="FontFamily" Value="Arial"/>
        <Setter Property="FontSize" Value="13"/>
        <Setter Property="FontWeight" Value="Bold"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="Button">
                    <Border Name="dropShadowBorder"
                        BorderThickness="0,0,0,1"
                        CornerRadius="1"
                        >
                        <Border.BorderBrush>
                            <SolidColorBrush Color="#00000000"/>
                        </Border.BorderBrush>
                    <Border Name="border" 
                    BorderThickness="{TemplateBinding BorderThickness}"
                    Padding="{TemplateBinding Padding}" 
                    CornerRadius="0" 
                    Background="{TemplateBinding Background}">
                        <Border.BorderBrush>
                            <SolidColorBrush Color="#D8D8D8"/>
                        </Border.BorderBrush>
                        <ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" 
                                  VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
                        </Border>
                    </Border>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsMouseOver" Value="True">
                            <Setter Property="BorderBrush" TargetName="border">
                                <Setter.Value>
                                    <SolidColorBrush Color="#939393"/>
                                </Setter.Value>
                            </Setter>
                            <Setter Property="BorderBrush" TargetName="dropShadowBorder">
                                <Setter.Value>
                                    <SolidColorBrush Color="#EBEBEB"/>
                                </Setter.Value>
                            </Setter>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
        <Style.Triggers>
            <Trigger Property="IsMouseOver" Value="True">
                <Setter Property="Foreground" Value="#333333"/>
            </Trigger>
            <Trigger Property="IsPressed" Value="True">
                <Setter Property="Background">
                    <Setter.Value>
                        <LinearGradientBrush StartPoint="0,1" EndPoint="0,0">
                            <GradientStop Color="#F1F1F1" Offset="1"/>
                            <GradientStop Color="#F5F5F5" Offset="0"/>
                        </LinearGradientBrush>
                    </Setter.Value>
                </Setter>
            </Trigger>
        </Style.Triggers>
    </Style>

p.s. Если вы заметили какие-либо ошибки начинающих в WPF выше, не стесняйтесь указывать их мне.

3 ответов


я сделал это в прошлом, определив вложенное свойство под названием ExtendedProperties.CornerRadius. Затем я могу установить его в своем стиле:

<Style TargetType="Button">
    <Setter Property="local:ExtendedProperties.CornerRadius" Value="0"/>
    ...

и использовать его в шаблоне:

<Border CornerRadius="{Binding Path=(local:ExtendedProperties.CornerRadius), RelativeSource={RelativeSource TemplatedParent}">

затем я могу переопределить его локально так же, как я переопределяю любое другое свойство:

<Button Content="Archive" local:ExtendedProperties.CornerRadius="5,0,0,5"/>
<Button Content="Span"/>
<Button Content="Delete" local:ExtendedProperties.CornerRadius="0,5,5,0"/>

в моем случае, это дает мне (очевидно, моя тема-это темно -):

enter image description here

и просто настроив пару прикрепленных свойств моей темы, я создал такой эффект:

enter image description here

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

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

Итак, чтобы ответить на ваши конкретные вопросы:

Q1. Да, см. Мой ответ выше. Можно переопределить локально или определить новые стили, которые переопределяют свойство.

Q2. Это серая зона. На мой взгляд, если это чисто визуальный (а не поведенческий), то стили-это путь. Конечно, если он выходит из-под контроля, вы можете вместо этого подкласс все встроенные элементы управления и добавьте свои конкретные свойства. Но это делает вашу тему сложнее повторно использовать, а ваше приложение более обременительным для разработки (потому что вам нужно использовать свой набор элементов управления, а не стандартные).

Q3. Я бы сказал, что это возможно в коде, но не интуитивно, как управлять потребителем. Я думаю, вам лучше определить дополнительные свойства - например. ExtendedProperties.HoverBackground, ExtendedProperties.PressedBackground - и использовать их из вашего шаблона точно так же. Тогда у потребителей вашего контроля будет больше контроль над кистями, используемыми, когда Ваш контроль находится в различных состояниях. Я делал это в прошлом, но использовал более общие имена свойств (SecondaryBackground, TernaryBackground) так что я могу использовать эти свойства в других контекстах. Опять же, документирование вашей темы полезно.


Q1: нет, насколько я знаю

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

Q3: должно быть выполнимо с пользовательским преобразователем значений и вашим собственным стилем.

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

Edit: добавлен код и стиль для ответа Q3 выше. Я не уверен, что это именно то, что вы искали, но, возможно, здесь есть что-то, что может вас заинтересовать.

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

идея заключается в том, что я создаю пользовательское значение, которое преобразует кисть сплошного цвета в линейную градиентную кисть. Затем я использую этот конвертер для преобразования цвета фона кнопки из сплошной цветной кисти в линейную градиентную кисть.

вот пользовательский преобразователь значений:

class SolidColorBrushToGradientConverter : IValueConverter
{
    const float DefaultLowColorScale = 0.95F;

    public object Convert (object value, Type targetType, object parameter, CultureInfo culture)
    {
        var solidColorBrush = value as SolidColorBrush;

        if (!targetType.IsAssignableFrom (typeof (LinearGradientBrush)) || solidColorBrush == null)
        {
            return Binding.DoNothing;
        }

        var lowColorScale = ParseParameterAsDouble (parameter);

        var highColor = solidColorBrush.Color;
        var lowColor = Color.Multiply (highColor, lowColorScale);
        lowColor.A = highColor.A;

        return new LinearGradientBrush (
            highColor,
            lowColor,
            new Point (0, 0),
            new Point (0, 1)
            );
    }

    static float ParseParameterAsDouble (object parameter)
    {
        if (parameter is float)
        {
            return (float)parameter;
        }
        else if (parameter is string)
        {
            float result;
            return float.TryParse(
                (string) parameter, 
                NumberStyles.Float, 
                CultureInfo.InvariantCulture, 
                out result
                        )
                        ? result
                        : DefaultLowColorScale
                ;
        }
        else
        {
            return DefaultLowColorScale;
        }
    }

    public object ConvertBack (object value, Type targetType, object parameter, CultureInfo culture)
    {
        return Binding.DoNothing;
    }
}

тогда я ссылаюсь на это в стиле, который я скопировал у вас (в основном то же самое, но я немного реструктурировал его), важная часть строки такова:

Background="{Binding Path=Background,Mode=OneWay,RelativeSource={RelativeSource Mode=TemplatedParent}, Converter={StaticResource SolidColorBrushToGradientConverter}, ConverterParameter=0.95}"

это означает, что мы привязываемся к шаблонному родительскому фону (т. е. кнопке.Фон), мы используем преобразователь SolidColorBrushToGradientConverter с параметром 0.95 (это определяет, насколько темнее должен быть "низкий" цвет по сравнению с "высоким" цветом).

полное стиль:

<local:SolidColorBrushToGradientConverter x:Key="SolidColorBrushToGradientConverter" />

<Style x:Key="GoogleMiddleButton" TargetType="{x:Type Button}">
    <Setter Property="Background" Value="#F5F5F5" />
    <Setter Property="Foreground" Value="#666666"/>
    <Setter Property="FontFamily" Value="Arial"/>
    <Setter Property="FontSize" Value="13"/>
    <Setter Property="FontWeight" Value="Bold"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="Button">
                <Border 
                    Name="dropShadowBorder"                        
                    BorderThickness="0,0,0,1"                        
                    CornerRadius="1"
                    >
                    <Border.BorderBrush>
                        <SolidColorBrush Color="#00000000"/>
                    </Border.BorderBrush>
                    <Border Name="border"                     
                            BorderThickness="{TemplateBinding BorderThickness}"                    
                            Padding="{TemplateBinding Padding}"                     
                            CornerRadius="0"                     
                            Background="{Binding Path=Background,Mode=OneWay,RelativeSource={RelativeSource Mode=TemplatedParent}, Converter={StaticResource SolidColorBrushToGradientConverter}, ConverterParameter=0.95}"
                            >
                        <Border.BorderBrush>
                            <SolidColorBrush Color="#D8D8D8"/>
                        </Border.BorderBrush>
                        <ContentPresenter 
                            HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"                                   
                            VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                            />
                    </Border>
                </Border>
                <ControlTemplate.Triggers>
                    <Trigger Property="IsMouseOver" Value="True">
                        <Setter Property="BorderBrush" TargetName="border">
                            <Setter.Value>
                                <SolidColorBrush Color="#939393"/>
                            </Setter.Value>
                        </Setter>
                        <Setter Property="BorderBrush" TargetName="dropShadowBorder">
                            <Setter.Value>
                                <SolidColorBrush Color="#EBEBEB"/>
                            </Setter.Value>
                        </Setter>
                    </Trigger>
                    <Trigger Property="IsPressed" Value="True">
                        <Setter Property="Background" Value="#4A8FF7" />
                        <Setter Property="Foreground" Value="#F5F5F5" />
                        <Setter Property="BorderBrush" TargetName="border">
                            <Setter.Value>
                                <SolidColorBrush Color="#5185D8"/>
                            </Setter.Value>
                        </Setter>
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

мне нравится этот вызов!

Q1: другие комментарии / ответы верны, шаблоны не могут быть изменены или унаследованы. Однако существуют способы передачи значений в шаблон для изменения их внешнего вида. Простым (но слегка хакерским способом) было бы пройти границу CornerRadius в шаблон с помощью тега enter code hereсобственность. Лучшим способом может быть кнопка подкласса для добавления свойства "location".

Q2: Да, вы на правильном пути с стили / шаблоны

Q3: измените шаблон, чтобы включить OpacityMask это имеет градиент, который вы хотите. Затем вы можете разместить элемент за этой маской или заставить замаскированный элемент принять цвет фона. Полный пример, показанный ниже:

enter image description here

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
  <Window.Resources>
    <Style x:Key="GoogleButton" TargetType="{x:Type Button}">      
      <Setter Property="Background" Value="White"/>
      <Setter Property="Foreground" Value="#666666"/>
      <Setter Property="Tag">
        <Setter.Value>
          <CornerRadius>0</CornerRadius>
        </Setter.Value>
      </Setter>
      <Setter Property="FontFamily" Value="Arial"/>
      <Setter Property="FontSize" Value="13"/>
      <Setter Property="FontWeight" Value="Bold"/>
      <Setter Property="Template">
        <Setter.Value>
          <ControlTemplate TargetType="Button">
            <Border Name="dropShadowBorder"
                    BorderThickness="0,0,0,1"
                    CornerRadius="1"
                    BorderBrush="Transparent"
                    Background="White">
              <Grid>
                <Border Name="backgroundFill" 
                        BorderBrush="Red"
                        Background="{TemplateBinding Background}"
                        CornerRadius="{TemplateBinding Tag}">
                  <Border.OpacityMask>
                    <LinearGradientBrush StartPoint="0,1" EndPoint="0,0">
                      <GradientStop Color="#FF000000" Offset="0"/>
                      <GradientStop Color="#00000000" Offset="1"/>
                    </LinearGradientBrush>
                  </Border.OpacityMask>
                </Border>
                <Border Name="border" 
                    BorderThickness="{TemplateBinding BorderThickness}"
                    Padding="{TemplateBinding Padding}" 
                    CornerRadius="{TemplateBinding Tag}" 
                    Background="Transparent">
                  <Border.BorderBrush>
                    <SolidColorBrush Color="#D8D8D8"/>
                  </Border.BorderBrush>
                  <ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" 
                                  VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
                </Border>
              </Grid>
            </Border>            
            <ControlTemplate.Triggers>
              <Trigger Property="IsMouseOver" Value="True">
                <Setter Property="BorderBrush" TargetName="border">
                  <Setter.Value>
                    <SolidColorBrush Color="#939393"/>
                  </Setter.Value>
                </Setter>
                <Setter Property="BorderBrush" TargetName="dropShadowBorder">
                  <Setter.Value>
                    <SolidColorBrush Color="#EBEBEB"/>
                  </Setter.Value>
                </Setter>
              </Trigger>
            </ControlTemplate.Triggers>
          </ControlTemplate>
        </Setter.Value>
      </Setter>
      <Style.Triggers>
        <Trigger Property="IsMouseOver" Value="True">
          <Setter Property="Foreground" Value="#333333"/>
        </Trigger>
        <Trigger Property="IsPressed" Value="True">
          <Setter Property="Background">
            <Setter.Value>
              <LinearGradientBrush StartPoint="0,1" EndPoint="0,0">
                <GradientStop Color="#F1F1F1" Offset="1"/>
                <GradientStop Color="#F5F5F5" Offset="0"/>
              </LinearGradientBrush>
            </Setter.Value>
          </Setter>
        </Trigger>
      </Style.Triggers>
    </Style>
  </Window.Resources>
  <Grid>
    <StackPanel Orientation="Horizontal"
                VerticalAlignment="Top">
      <Button Style="{StaticResource GoogleButton}" Content="Archive">
        <Button.Tag>
          <CornerRadius>2,0,0,2</CornerRadius>
        </Button.Tag>
      </Button>
      <Button Style="{StaticResource GoogleButton}" Content="Spam"
              Background="LightBlue"/>
      <Button Style="{StaticResource GoogleButton}" Content="Delete">
        <Button.Tag>
          <CornerRadius>0,2,2,0</CornerRadius>
        </Button.Tag>
      </Button>
    </StackPanel>
  </Grid>
</Window>