WPF: Создание универсального стиля для кнопки

245
21 февраля 2019, 18:00

Есть много кнопок с одинаковым стилем, в которых изменяется только иконка и название и toolTip. Как создать универсальный стиль для них?

Вот стиль:

<Style TargetType="{x:Type Button}">
    <Setter Property="SnapsToDevicePixels" Value="True"/>
    <Setter Property="VerticalAlignment" Value="Center"/>
    <Setter Property="HorizontalAlignment" Value="Stretch"/>
    <Setter Property="Width" Value="200"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type Button}">
                <Border Width="{TemplateBinding Width}">
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="auto"/>
                            <ColumnDefinition Width="*"/>
                            <ColumnDefinition Width="5"/>
                        </Grid.ColumnDefinitions>
                        <Rectangle Name="Rectagle"
                                   Grid.Column="0"
                                   Margin="2.5"
                                   Height="30"
                                   Width="30"
                                   Fill="White">
                            <Rectangle.OpacityMask>
                                <VisualBrush Visual="{TemplateBinding local:MyButtonExtension.Icon}"
                                             Stretch="Fill"/>
                            </Rectangle.OpacityMask>
                        </Rectangle>
                        <TextBlock Name="Name"
                                   Grid.Column="1"
                                   Margin="5"
                                   VerticalAlignment="Center"
                                   HorizontalAlignment="Stretch"
                                   Foreground="White"
                                   FontSize="12"
                                   Text="{TemplateBinding Content}"/>
                        <Rectangle Name="Flag"
                                   Grid.Column="2"
                                   Fill="Transparent"/>
                    </Grid>
                </Border>
                <ControlTemplate.Triggers>
                    <Trigger Property="IsMouseOver" Value="True">
                        <Setter TargetName="Flag" Property="Fill"
                                Value="{StaticResource MainBrush}"/>
                        <Setter TargetName="Rectagle" Property="Fill"
                                Value="{StaticResource MainBrush}"/>
                        <Setter TargetName="Name" Property="Foreground"
                                Value="{StaticResource MainBrush}"/>
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

И вот, как я его использую:

<Button Content="Создать конфигурацию"
        Command="{Binding CreateConfig}">

Как ПРАВИЛЬНО реализовать такой стиль, с возможностью передачи в Rectagle(внутри стиля) OpacityMask, в которой будет иконка.

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

UPD 1:

Использование проверти:

<Button local:MyButtonExtension.Text="Создать конфигурацию"
        local:MyButtonExtension.Icon="{StaticResource appbar_newspaper_create}"
        Command="{Binding CreateConfig}">
</Button>

Иконка:

<Canvas x:Key="appbar_newspaper_create" Width="76" Height="76" Clip="F1 M 0,0L 76,0L 76,76L 0,76L 0,0">
    <Path Width="48.0313" Height="43" Canvas.Left="17" Canvas.Top="14" Stretch="Fill" Fill="{DynamicResource BlackBrush}" Data="(удалил, чтобы не засорять)"/>
</Canvas>
Answer 1

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

Пишу первое приближение:

<Button Margin="2.5" VerticalContentAlignment="Stretch">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Image Source="play.png"/>
        <TextBlock Grid.Row="1" HorizontalAlignment="Center" Text="Play"/>
    </Grid>
</Button>

Всё просто и выполнено дословно то что требуется по условию.

Теперь идем дальше, мы хотим спрятать всё это поддерево Grid/Image/TextBlock в стиль и параметризировать их через свойства. Штатных таких свойств в WPF, естественно, нет, но есть средства для создания дополнительных свойств: Attached Property. Смотрим какого типа нужны свойства, Text – понятно, string, а тип Image.Source можно узнать поставив курсор на него в разметке и нажав F12, как выясняется, это ImageSource. Отлично, пишем класс с двумя AP:

public static class MyButtonExtension
{
    public static string GetText(DependencyObject obj)
        => (string)obj.GetValue(TextProperty);
    public static void SetText(DependencyObject obj, string value)
        => obj.SetValue(TextProperty, value);
    public static readonly DependencyProperty TextProperty =
        DependencyProperty.RegisterAttached("Text", typeof(string),
            typeof(MyButtonExtension), new PropertyMetadata(""));
    public static ImageSource GetImageSource(DependencyObject obj)
        => (ImageSource)obj.GetValue(ImageSourceProperty);
    public static void SetImageSource(DependencyObject obj, ImageSource value)
        => obj.SetValue(ImageSourceProperty, value);
    public static readonly DependencyProperty ImageSourceProperty =
        DependencyProperty.RegisterAttached("ImageSource", typeof(ImageSource),
            typeof(MyButtonExtension), new PropertyMetadata(null));
}

Компилируем проект, чтобы дизайнер XAML узнал об этом классе. Теперь можно кнопке задать эти свойства:

c:MyButtonExtension.Text="Play"
c:MyButtonExtension.ImageSource="play.png"

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

<Button Margin="2.5" VerticalContentAlignment="Stretch"
        c:MyButtonExtension.Text="Play"
        c:MyButtonExtension.ImageSource="play.png">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Image Source="{Binding (c:MyButtonExtension.ImageSource), RelativeSource={RelativeSource AncestorType=Button}}"/>
        <TextBlock Grid.Row="1" HorizontalAlignment="Center"
                   Text="{Binding (c:MyButtonExtension.Text), RelativeSource={RelativeSource AncestorType=Button}}"/>
    </Grid>
</Button>

Убеждаемся что это работает, запускаем приложение:

Теперь можно вынести это всё в шаблон/стиль (я показываю лишь кусочек):

...
<Setter Property="Template">
    <Setter.Value>
        <ControlTemplate TargetType="{x:Type Button}">
            <Themes:ButtonChrome x:Name="Chrome" BorderBrush="{TemplateBinding BorderBrush}" Background="{TemplateBinding Background}" RenderMouseOver="{TemplateBinding IsMouseOver}" RenderPressed="{TemplateBinding IsPressed}" RenderDefaulted="{TemplateBinding IsDefaulted}" SnapsToDevicePixels="true">
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition/>
                        <RowDefinition Height="Auto"/>
                    </Grid.RowDefinitions>
                    <Image Source="{Binding (c:MyButtonExtension.ImageSource), RelativeSource={RelativeSource AncestorType={x:Type Button}}}"/>
                    <TextBlock Grid.Row="1" HorizontalAlignment="Center"
                               Text="{Binding (c:MyButtonExtension.Text), RelativeSource={RelativeSource AncestorType={x:Type Button}}}"/>
                </Grid>
            </Themes:ButtonChrome>
            ...

Отлично, ну и теперь, так как мы находимся внутри ControlTemplate, мы можем переписать эти привязки проще и удобнее, с использованием TemplateBinding:

...
<Setter Property="Template">
    <Setter.Value>
        <ControlTemplate TargetType="{x:Type Button}">
            <Themes:ButtonChrome x:Name="Chrome" BorderBrush="{TemplateBinding BorderBrush}" Background="{TemplateBinding Background}" RenderMouseOver="{TemplateBinding IsMouseOver}" RenderPressed="{TemplateBinding IsPressed}" RenderDefaulted="{TemplateBinding IsDefaulted}" SnapsToDevicePixels="true">
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition/>
                        <RowDefinition Height="Auto"/>
                    </Grid.RowDefinitions>
                    <Image Source="{TemplateBinding c:MyButtonExtension.ImageSource}"/>
                    <TextBlock Grid.Row="1" HorizontalAlignment="Center"
                               Text="{TemplateBinding c:MyButtonExtension.Text}"/>
                </Grid>
            </Themes:ButtonChrome>
            ...

Готово!

<UniformGrid Margin="5" Rows="3" Columns="4">
    <Button c:MyButtonExtension.Text="Play"
            c:MyButtonExtension.ImageSource="play.png"
            Style="{DynamicResource MyButtonStyle}"/>
    <Button c:MyButtonExtension.Text="Pause"
            c:MyButtonExtension.ImageSource="pause.png"
            Style="{DynamicResource MyButtonStyle}"/>
    <Button c:MyButtonExtension.Text="Stop"
            c:MyButtonExtension.ImageSource="stop.png"
            Style="{DynamicResource MyButtonStyle}"/>
</UniformGrid>

READ ALSO
Вернуть BITMAP из unmanaget dll

Вернуть BITMAP из unmanaget dll

Пробовал вернуть HBITMAP из unmanaged dll на c++, но его C# не хочет переваривать, описал его как возвращаемый IntPtr, пишет о невозможности преобразования...

154
Почему выводит не все флаги

Почему выводит не все флаги

Есть готовые enum-ы

186
C# WPF Prism занять один регион другим

C# WPF Prism занять один регион другим

Как в C# WPF Prism занять один регион другим? Пример на Word'e:

179
Как дождаться закрытия формы?

Как дождаться закрытия формы?

Пишу автономное приложение, я вызываю форму

167