0
\$\begingroup\$

I needed a button with text and icon that did two things:

  • Text and icon went grey if the button was disabled.
  • Icon changes depending on Boolean property (e.g. connected / disconnected).

For the icon I used the XAML icons from here: http://modernuiicons.com/

An example of an icon is:

<?xml version="1.0" encoding="utf-8"?>
<Canvas xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    x:Name="appbar_connect" 
    Width="76" 
    Height="76" 
    Clip="F1 M 0,0L 76,0L 76,76L 0,76L 0,0">
    <Path 
        Width="36.7542" 
        Height="36.7542" 
        Canvas.Left="19.6229" 
        Canvas.Top="19.6229" 
        Stretch="Fill" 
        Fill="#FF000000" 
        Data="F1 M 25.7639,28.0031L 20.0866,22.3258C 19.4683,21.7075 19.4683,20.705 20.0866,20.0866C 20.705,19.4683 21.7075,19.4683 22.3258,20.0867L 28.0031,25.7639C 32.3443,22.5092 38.5302,22.856 42.4783,26.8042L 26.8041,42.4784C 22.856,38.5302 22.5092,32.3443 25.7639,28.0031 Z M 49.1958,33.5217C 53.144,37.4699 53.4908,43.6557 50.2361,47.9969L 55.9133,53.6742C 56.5317,54.2925 56.5317,55.295 55.9133,55.9134C 55.295,56.5317 54.2925,56.5317 53.6742,55.9134L 47.9969,50.2361C 43.6557,53.4908 37.4698,53.1441 33.5216,49.1959L 36.8804,45.8371L 34.0814,43.0381C 33.1539,42.1107 33.1539,40.6069 34.0814,39.6794C 35.0089,38.7519 36.5127,38.7519 37.4402,39.6794L 40.2392,42.4784L 42.4783,40.2392L 39.6794,37.4402C 38.7519,36.5127 38.7519,35.009 39.6794,34.0815C 40.6069,33.154 42.1106,33.154 43.0381,34.0815L 45.8371,36.8804L 49.1958,33.5217 Z "/>
</Canvas>

I put the icon in a resource dictionary, with some changes

  • Wrapped in a ControlTemplate to make it usable more than once.
  • Gave it a key
  • Unnecessary attributes removed
  • Fill changed to Fill="{Binding RelativeSource={RelativeSource AncestorType=Control}, Path=Foreground}". This solves the first issue, going grey when the button is disabled. The button will change it's foreground colour to grey when disabled, and so the icon will too. Furthermore, the icon can be changed in colour by changing the button foreground colour.

Code:

<ControlTemplate x:Key="ConnectButtonIconTemplate">
    <Canvas
            Width="76" 
            Height="76" 
            Clip="F1 M 0,0L 76,0L 76,76L 0,76L 0,0">
        <Path 
                Width="36.7542"
                Height="36.7542" 
                Canvas.Left="19.6229" 
                Canvas.Top="19.6229" 
                Stretch="Fill"  
                Fill="{Binding RelativeSource={RelativeSource AncestorType=Control}, Path=Foreground}" 
                Data="F1 M 25.7639,28.0031L 20.0866,22.3258C 19.4683,21.7075 19.4683,20.705 20.0866,20.0866C 20.705,19.4683 21.7075,19.4683 22.3258,20.0867L 28.0031,25.7639C 32.3443,22.5092 38.5302,22.856 42.4783,26.8042L 26.8041,42.4784C 22.856,38.5302 22.5092,32.3443 25.7639,28.0031 Z M 49.1958,33.5217C 53.144,37.4699 53.4908,43.6557 50.2361,47.9969L 55.9133,53.6742C 56.5317,54.2925 56.5317,55.295 55.9133,55.9134C 55.295,56.5317 54.2925,56.5317 53.6742,55.9134L 47.9969,50.2361C 43.6557,53.4908 37.4698,53.1441 33.5216,49.1959L 36.8804,45.8371L 34.0814,43.0381C 33.1539,42.1107 33.1539,40.6069 34.0814,39.6794C 35.0089,38.7519 36.5127,38.7519 37.4402,39.6794L 40.2392,42.4784L 42.4783,40.2392L 39.6794,37.4402C 38.7519,36.5127 38.7519,35.009 39.6794,34.0815C 40.6069,33.154 42.1106,33.154 43.0381,34.0815L 45.8371,36.8804L 49.1958,33.5217 Z "/>
    </Canvas>
</ControlTemplate>

The button is laid out like:

<Button x:Name="buttonConnect" Command="{Binding StartCameraCommand}" Style="{StaticResource ConnectToolbarButtonStyle}">
    <StackPanel>
        <Viewbox>
            <ContentControl />
        </Viewbox>
        <TextBlock/>
    </StackPanel>
</Button>

The base button style is:

<Style x:Key="ToolbarButtonStyle" TargetType="{x:Type Button}">
    <Setter Property="Width" Value="65"/>
    <Style.Resources>
        <Style TargetType="{x:Type Viewbox}">
            <Setter Property="Width" Value="40"/>
        </Style>
        <Style TargetType="{x:Type TextBlock}">
            <Setter Property="HorizontalAlignment" Value="Center"/>
        </Style>
    </Style.Resources>
</Style>

To make icon change according to a Boolean property, I added as style derived from it:

<Style x:Key="ConnectToolbarButtonStyle" TargetType="{x:Type Button}" BasedOn="{StaticResource ToolbarButtonStyle}">
    <Style.Resources>
        <Style TargetType="{x:Type ContentControl}">
            <Setter Property="Template" Value="{StaticResource ConnectButtonIconTemplate}"/>
            <Style.Triggers>
                <DataTrigger Binding="{Binding CameraConnected}" Value="False">
                    <Setter Property="Template" Value="{StaticResource ConnectButtonIconTemplate}"/>
                </DataTrigger>
                <DataTrigger Binding="{Binding CameraConnected}" Value="True">
                    <Setter Property="Template" Value="{StaticResource DisconnectButtonIconTemplate}"/>
                </DataTrigger>
            </Style.Triggers>
        </Style>
        <Style TargetType="{x:Type TextBlock}">
            <Setter Property="HorizontalAlignment" Value="Center"/>
            <Setter Property="Text" Value="Connect"/>
            <Style.Triggers>
                <DataTrigger Binding="{Binding CameraConnected}"  Value="True">
                    <Setter Property="Text" Value="Disconnect"/>
                </DataTrigger>
                <DataTrigger Binding="{Binding CameraConnected}" Value="False">
                    <Setter Property="Text" Value="Connect"/>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </Style.Resources>
</Style>

One thing is that I have to put <Setter Property="HorizontalAlignment" Value="Center"/> for the TextBlock in the derived style, I guess it gets overwritten.

Note that buttons that do not change icon can be defined like this:

<Button x:Name="buttonRefresh" Click="buttonRefresh_Click" Style="{StaticResource ToolbarButtonStyle}">
    <StackPanel>
        <Viewbox>
            <ContentControl Template="{StaticResource RefreshButtonIconTemplate}"/>
        </Viewbox>
        <TextBlock Text="Refresh"/>
    </StackPanel>
</Button>

For these, I assume it would be possible to do away with the writing the Viewbox and other child elements by using ControlTemplate targeting the button and then change the text and icon using a style.

\$\endgroup\$
  • \$\begingroup\$ Is there a specific reason you're using style to setup button content? I mean, I'd feel more natural simply using two value converters to bind the appropriate icon and text (converter which maybe one and use ConverterParameter if you, rightly IMO, want to keep everything in XAML). Also, IMO, this might be a good chance to create a custom control ToolbarButton. \$\endgroup\$ – Adriano Repetti Dec 29 '17 at 16:26
2
\$\begingroup\$

Based on Adriano's comment:

First, a generic converter class (copied from codeproject)

public class BoolToObjectConverter : IValueConverter
{
    public object TrueObject { get; set; }
    public object FalseObject { get; set; }
    public object NullObject { get; set; }

    public BoolToObjectConverter()
    {

    }

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value == null) return NullObject;
        bool boolValue = true;
        bool isBool = true;
        try
        {
            boolValue = (bool)value;
        }
        catch
        {
            isBool = false;
        }

        if (!isBool) return NullObject;
        return boolValue ? TrueObject : FalseObject;
    }

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

To use, we can apply inline:

<Viewbox>
    <ContentControl>
        <ContentControl.Template>
            <Binding Path="Stage.IsConnected"
                        FallbackValue="{StaticResource ConnectButtonIconTemplate}">
                <Binding.Converter>
                    <conv:BoolToObjectConverter 
                        TrueObject="{StaticResource DisconnectButtonIconTemplate}"
                        FalseObject="{StaticResource ConnectButtonIconTemplate}"
                        NullObject="{StaticResource ConnectButtonIconTemplate}"/>
                </Binding.Converter>
            </Binding>
        </ContentControl.Template>
    </ContentControl>
</Viewbox>

Or create a resource:

<conv:BoolToObjectConverter x:Key="BoolToConnect"
                                    TrueObject="{StaticResource DisconnectButtonIconTemplate}"
                                    FalseObject="{StaticResource ConnectButtonIconTemplate}"
                                    NullObject="{StaticResource ConnectButtonIconTemplate}"/>

and apply like so:

<Viewbox>
     <ContentControl Template="{Binding CameraConnected, Converter={StaticResource BoolToConnect}, FallbackValue={StaticResource ConnectButtonIconTemplate}}"/>
</Viewbox>

Note the FallbackValue to make sure the icon appears at design time.

Another trick is to make BoolToObjectConverter extend MarkupType. Then the values of TrueObject etc. can be defined inline... but only if the value is something simple, if it is, for example, {StaticResource ...} it won't work. Still, this is good to use for the text:

<TextBlock Text="{Binding Stage.IsConnected, Converter={conv:BoolToObjectConverter TrueObject=Disconnect, FalseObject=Connect, NullObject=Connect}, FallbackValue=Connect}"/>

Note there is no comma between the converter and the first parameter.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.