06 July 2014

Using Modern UI Icons

In my research, I stumbled across the excellent Modern UI Icons.

I downloaded them and put the XAML files in a test application. Eventually, I got the the point of adding them to a view. That's when the problems started. The XAML files can't be used as sources for an Image. And here I was thinking it was an image format like SVG.

Eventually, I was able to setup a useful style for a ContentControl. This can be resized and the color-changed as well.

Here is what I ended up with:

    <Style TargetType="ContentControl" x:Key="icon">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="ContentControl">
                    <Viewbox Height="{TemplateBinding Height}" Stretch="Uniform">
                        <ContentPresenter />
                    </Viewbox>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    <Canvas x:Shared="False" x:Key="icon-calculator"  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Name="appbar_calculator" Width="76" Height="76" Clip="F1 M 0,0L 76,0L 76,76L 0,76L 0,0">
        <Path Width="32" Height="42" Canvas.Left="22" Canvas.Top="17" Stretch="Fill" Fill="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContentControl}, Path=Foreground}" Data="..."/>
    </Canvas>

I left the data out of this example for brevity. The Path data is in the XAML files for the modern UI icon. The yellow stuff is what I had to add.

1) Setting x:Shared to False is necessary otherwise WPF will only display the icon once, no matter how many times you reference it.
2) Settings x:Key is also necessary so you can reference it.
3) The Fill binding allows me to change the brush color

So here's how to use the icon.

<ContentControl Style="{StaticResource icon}" Content="{StaticResource icon-calculator}" Height="32" Foreground="Black"/>

I could probably shorten this with a custom control. However, the only thing it would save is the Style declaration. So I haven't bothered yet.

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.