05 July 2014

Bootstrap style WPF buttons

In my upcoming job, I will be writing a desktop app using WPF. Most of my work has previously been on the web, where there are many established libraries for layout and general behavior. One of those is called Bootstrap.

Some people dislike Bootstrap because many websites out there use the starting Bootstrap template, hence the notion that all Bootstrap sites look the same. Let me just say, they don't. It's probably fairer to say that website looks alike if their designers are lazy. Bootstrap itself is a great tool.

I commonly use Bootstrap buttons, because they come with good default styles which can give hints to the users. I mainly use the primary, default, and danger styles to indicate the kind of actions that are being performed. So today, I will be discussing how to get those Bootstrap-style buttons in WPF.

First, what do they look like? Here is a sample from the website (using Chrome):


This style matches pretty well with a so-called "modern ui" style.

Here is what my WPF attempt at these buttons looks like:


Here is the XAML style for these buttons, including disable, hover and click effects.

    <Style x:Key="btn" TargetType="Button">
        <Setter Property="FontFamily" Value="Helvetica Neue,Helvetica,Arial,sans-serif"/>
        <Setter Property="FontSize" Value="14"/>
        <Setter Property="Padding" Value="12,8"/>
        <Setter Property="BorderThickness" Value="1"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type ButtonBase}">
                    <Border Name="border" CornerRadius="4" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="True">
                        <Grid>
                            <Border Name="dropShadowBorder" CornerRadius="4" BorderBrush="Transparent" BorderThickness="0" Visibility="Hidden">
                                <Border.Background>
                                    <LinearGradientBrush StartPoint="0,0" EndPoint="0,0.16">
                                        <GradientStop Color="#22000000" Offset="0"/>
                                        <GradientStop Color="#00000000" Offset="1"/>
                                    </LinearGradientBrush>
                                </Border.Background>
                            </Border>
                            <ContentPresenter Name="contentPresenter" ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" ContentStringFormat="{TemplateBinding ContentStringFormat}" Focusable="False" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
                        </Grid>
                    </Border>
                    <ControlTemplate.Triggers>
                         <!--default button highlight--> 
                        <Trigger Property="Button.IsDefaulted" Value="True">
                            <Setter Property="BorderBrush" TargetName="border" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
                        </Trigger>
                         <!--inner drop shadow when pressed / checked--> 
                        <Trigger Property="IsPressed" Value="True">
                            <Setter Property="Visibility" TargetName="dropShadowBorder" Value="Visible"/>
                        </Trigger>
                        <Trigger Property="ToggleButton.IsChecked" Value="True">
                            <Setter Property="Visibility" TargetName="dropShadowBorder" Value="Visible"/>
                        </Trigger>
                        <Trigger Property="IsEnabled" Value="False">
                            <Setter Property="Opacity" TargetName="border" Value="0.60"/>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    <Style x:Key="btn-default" TargetType="Button" BasedOn="{StaticResource btn}">
        <Setter Property="Foreground">
            <Setter.Value>
                <SolidColorBrush Color="#333"/>
            </Setter.Value>
        </Setter>
        <Setter Property="Background">
            <Setter.Value>
                <SolidColorBrush Color="#fff"/>
            </Setter.Value>
        </Setter>
        <Setter Property="BorderBrush">
            <Setter.Value>
                <SolidColorBrush Color="#ccc"/>
            </Setter.Value>
        </Setter>
        <Style.Triggers>
            <Trigger Property="IsMouseOver" Value="True">
                <Setter Property="Background" Value="#e6e6e6"/>
                <Setter Property="BorderBrush" Value="#adadad"/>
            </Trigger>
            <Trigger Property="IsPressed" Value="True">
                <Setter Property="Background" Value="#e6e6e6"/>
                <Setter Property="BorderBrush" Value="#adadad"/>
            </Trigger>
            <Trigger Property="ToggleButton.IsChecked" Value="True">
                <Setter Property="Background" Value="#e6e6e6"/>
                <Setter Property="BorderBrush" Value="#adadad"/>
            </Trigger>
        </Style.Triggers>
    </Style>
    <Style x:Key="btn-primary" TargetType="Button" BasedOn="{StaticResource btn}">
        <Setter Property="Foreground">
            <Setter.Value>
                <SolidColorBrush Color="#fff"/>
            </Setter.Value>
        </Setter>
        <Setter Property="Background">
            <Setter.Value>
                <SolidColorBrush Color="#428bca"/>
            </Setter.Value>
        </Setter>
        <Setter Property="BorderBrush">
            <Setter.Value>
                <SolidColorBrush Color="#357ebd"/>
            </Setter.Value>
        </Setter>
        <Style.Triggers>
            <Trigger Property="IsMouseOver" Value="True">
                <Setter Property="Background" Value="#3071a9"/>
                <Setter Property="BorderBrush" Value="#285e8e"/>
            </Trigger>
            <Trigger Property="IsPressed" Value="True">
                <Setter Property="Background" Value="#3071a9"/>
                <Setter Property="BorderBrush" Value="#285e8e"/>
            </Trigger>
            <Trigger Property="ToggleButton.IsChecked" Value="True">
                <Setter Property="Background" Value="#3071a9"/>
                <Setter Property="BorderBrush" Value="#285e8e"/>
            </Trigger>
        </Style.Triggers>
    </Style>
    <Style x:Key="btn-success" TargetType="Button" BasedOn="{StaticResource btn}">
        <Setter Property="Foreground">
            <Setter.Value>
                <SolidColorBrush Color="#fff"/>
            </Setter.Value>
        </Setter>
        <Setter Property="Background">
            <Setter.Value>
                <SolidColorBrush Color="#5cb85c"/>
            </Setter.Value>
        </Setter>
        <Setter Property="BorderBrush">
            <Setter.Value>
                <SolidColorBrush Color="#4cae4c"/>
            </Setter.Value>
        </Setter>
        <Style.Triggers>
            <Trigger Property="IsMouseOver" Value="True">
                <Setter Property="Background" Value="#449d44"/>
                <Setter Property="BorderBrush" Value="#398439"/>
            </Trigger>
            <Trigger Property="IsPressed" Value="True">
                <Setter Property="Background" Value="#449d44"/>
                <Setter Property="BorderBrush" Value="#398439"/>
            </Trigger>
            <Trigger Property="ToggleButton.IsChecked" Value="True">
                <Setter Property="Background" Value="#449d44"/>
                <Setter Property="BorderBrush" Value="#398439"/>
            </Trigger>
        </Style.Triggers>
    </Style>
    <Style x:Key="btn-info" TargetType="Button" BasedOn="{StaticResource btn}">
        <Setter Property="Foreground">
            <Setter.Value>
                <SolidColorBrush Color="#fff"/>
            </Setter.Value>
        </Setter>
        <Setter Property="Background">
            <Setter.Value>
                <SolidColorBrush Color="#5bc0de"/>
            </Setter.Value>
        </Setter>
        <Setter Property="BorderBrush">
            <Setter.Value>
                <SolidColorBrush Color="#46b8da"/>
            </Setter.Value>
        </Setter>
        <Style.Triggers>
            <Trigger Property="IsMouseOver" Value="True">
                <Setter Property="Background" Value="#31b0d5"/>
                <Setter Property="BorderBrush" Value="#269abc"/>
            </Trigger>
            <Trigger Property="IsPressed" Value="True">
                <Setter Property="Background" Value="#31b0d5"/>
                <Setter Property="BorderBrush" Value="#269abc"/>
            </Trigger>
            <Trigger Property="ToggleButton.IsChecked" Value="True">
                <Setter Property="Background" Value="#31b0d5"/>
                <Setter Property="BorderBrush" Value="#269abc"/>
            </Trigger>
        </Style.Triggers>
    </Style>
    <Style x:Key="btn-warning" TargetType="Button" BasedOn="{StaticResource btn}">
        <Setter Property="Foreground">
            <Setter.Value>
                <SolidColorBrush Color="#fff"/>
            </Setter.Value>
        </Setter>
        <Setter Property="Background">
            <Setter.Value>
                <SolidColorBrush Color="#f0ad4e"/>
            </Setter.Value>
        </Setter>
        <Setter Property="BorderBrush">
            <Setter.Value>
                <SolidColorBrush Color="#eea236"/>
            </Setter.Value>
        </Setter>
        <Style.Triggers>
            <Trigger Property="IsMouseOver" Value="True">
                <Setter Property="Background" Value="#ec971f"/>
                <Setter Property="BorderBrush" Value="#d58512"/>
            </Trigger>
            <Trigger Property="IsPressed" Value="True">
                <Setter Property="Background" Value="#ec971f"/>
                <Setter Property="BorderBrush" Value="#d58512"/>
            </Trigger>
            <Trigger Property="ToggleButton.IsChecked" Value="True">
                <Setter Property="Background" Value="#ec971f"/>
                <Setter Property="BorderBrush" Value="#d58512"/>
            </Trigger>
        </Style.Triggers>
    </Style>
    <Style x:Key="btn-danger" TargetType="Button" BasedOn="{StaticResource btn}">
        <Setter Property="Foreground">
            <Setter.Value>
                <SolidColorBrush Color="#fff"/>
            </Setter.Value>
        </Setter>
        <Setter Property="Background">
            <Setter.Value>
                <SolidColorBrush Color="#d9534f"/>
            </Setter.Value>
        </Setter>
        <Setter Property="BorderBrush">
            <Setter.Value>
                <SolidColorBrush Color="#d43f3a"/>
            </Setter.Value>
        </Setter>
        <Style.Triggers>
            <Trigger Property="IsMouseOver" Value="True">
                <Setter Property="Background" Value="#c9302c"/>
                <Setter Property="BorderBrush" Value="#ac2925"/>
            </Trigger>
            <Trigger Property="IsPressed" Value="True">
                <Setter Property="Background" Value="#c9302c"/>
                <Setter Property="BorderBrush" Value="#ac2925"/>
            </Trigger>
            <Trigger Property="ToggleButton.IsChecked" Value="True">
                <Setter Property="Background" Value="#c9302c"/>
                <Setter Property="BorderBrush" Value="#ac2925"/>
            </Trigger>
        </Style.Triggers>
    </Style>

Then to utilize these, you only have to set the style on the button. It's style name matches the class name for the equivalent bootstrap button. For example:

<Button Content="Default" Style="{StaticResource btn-default}"/>

Notes: Xaml styling is not as robust as CSS, and is a lot more verbose. You can't set more than one style, but this capability wasn't needed in this case.

3 comments:

Technews Able said...

What is bootstrap and why should you use bootstrap?

Mark Tannehill said...

this is amazing. thank you. any more bootstrap mimics for me to use? :)

Kasey said...

None at this time. My foray into WPF has ended for now.