WPF CustomControl Image Button that changes with state and does not use ControlTemplate












4














Note: See Verticle WPF icon button that used Segoe MDL2 font for icon for an example using Segoe MDL2 for the icons.



I need a button that changes appearance based on the state of a Boolean. A previous attempt is here: WPF button with xaml-defined icon that changes with state



In this implementation, I do this creating a custom control based off Button that does NOT use a ControlTemplate. From what I understand, using ControlTemplate completely replaces the default control template, so you have to create the entire style again.



Another method is to put a button inside the ControlTemplate and bind the properties to the DependencyProperties of the custom control and the Button it is based on, e.g. https://www.codeproject.com/Tips/773386/WPF-ImageButton. The problem with this is that properties automatically set such as IsEnabled for a command do not get automatically applied to the internal Button



So I use ContentTemplate and trigger changes via DataTrigger instead of ControlTemplate and ControlTemplate.Triggers



This is the class with the dependency properties:



public class VerticalImageButton : Button
{
static VerticalImageButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(VerticalImageButton), new FrameworkPropertyMetadata(typeof(VerticalImageButton)));
}

public bool State
{
get { return (bool)GetValue(StateProperty); }
set { SetValue(StateProperty, value); }
}

public static readonly DependencyProperty StateProperty =
DependencyProperty.Register("State",
typeof(bool), typeof(VerticalImageButton), new PropertyMetadata(null));

public ControlTemplate IconOn
{
get { return (ControlTemplate)GetValue(IconOnProperty); }
set { SetValue(IconOnProperty, value); }
}
public static readonly DependencyProperty IconOnProperty =
DependencyProperty.Register("IconOn",
typeof(ControlTemplate), typeof(VerticalImageButton), new PropertyMetadata(null));

public ControlTemplate IconOff
{
get { return (ControlTemplate)GetValue(IconOffProperty); }
set { SetValue(IconOffProperty, value); }
}
public static readonly DependencyProperty IconOffProperty =
DependencyProperty.Register("IconOff",
typeof(ControlTemplate), typeof(VerticalImageButton), new PropertyMetadata(null));

public string ContentOn
{
get { return (string)GetValue(ContentOnProperty); }
set { SetValue(ContentOnProperty, value); }
}
public static readonly DependencyProperty ContentOnProperty =
DependencyProperty.Register("ContentOn",
typeof(string), typeof(VerticalImageButton), new PropertyMetadata(null));

public string ContentOff
{
get { return (string)GetValue(ContentOffProperty); }
set { SetValue(ContentOffProperty, value); }
}
public static readonly DependencyProperty ContentOffProperty =
DependencyProperty.Register("ContentOff",
typeof(string), typeof(VerticalImageButton), new PropertyMetadata(null));
}


Note that the icons are XAML resources wrapped in ControlTemplate, see: WPF button with xaml-defined icon that changes with state



Now in the Generic.xaml file that is created when you make a custom control in VS:



<Style TargetType="{x:Type local:VerticalImageButton}" BasedOn="{StaticResource {x:Type Button}}">
<Style.Resources>
<Style x:Key="buttonText" TargetType="{x:Type TextBlock}">
<Setter Property="Text" Value="{Binding ContentOff, RelativeSource={RelativeSource AncestorType=Button}}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding State, RelativeSource={RelativeSource AncestorType=Button}}" Value="True">
<Setter Property="Text" Value="{Binding ContentOn, RelativeSource={RelativeSource AncestorType=Button}}"/>
</DataTrigger>
<DataTrigger Binding="{Binding State, RelativeSource={RelativeSource AncestorType=Button}}" Value="False">
<Setter Property="Text" Value="{Binding ContentOff, RelativeSource={RelativeSource AncestorType=Button}}"/>
</DataTrigger>
</Style.Triggers>
</Style>
<Style x:Key="buttonIcon" TargetType="{x:Type ContentControl}">
<Setter Property="Template" Value="{Binding IconOff, RelativeSource={RelativeSource AncestorType=Button}}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding State, RelativeSource={RelativeSource AncestorType=Button}}" Value="True">
<Setter Property="Template" Value="{Binding IconOn, RelativeSource={RelativeSource AncestorType=Button}}"/>
</DataTrigger>
<DataTrigger Binding="{Binding State, RelativeSource={RelativeSource AncestorType=Button}}" Value="False">
<Setter Property="Template" Value="{Binding IconOff, RelativeSource={RelativeSource AncestorType=Button}}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Style.Resources>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Vertical" Margin="2" >
<Viewbox Width="36">
<ContentControl Style="{DynamicResource buttonIcon}"/>
</Viewbox>
<TextBlock Style="{DynamicResource buttonText}" VerticalAlignment="Center"/>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="Width" Value="61"/>
<Setter Property="Margin" Value="2"/>
</Style>


One problem is that because it is based on {StaticResource {x:Type Button}}, it won't inherit the style of the container - e.g. when putting into a Toolbar. For this case, you can just add the toolbar style, Style="{StaticResource {x:Static ToolBar.ButtonStyleKey}}" when laying out the buttons.



Finally, the use of the control is quite easy:



<local:VerticalImageButton
x:Name="TestButton"
Style="{StaticResource {x:Static ToolBar.ButtonStyleKey}}"
Command="{Binding TestCommand}"
IconOff="{StaticResource TestOnIcon}"
IconOn="{StaticResource TestOffIcon}"
ContentOff="Connect"
ContentOn="Disconnect"
State="{Binding IsTestOn}"/>


Two icons if someone wants to implement. Note the binding on the fill. This makes the icon change colour with the button, and is needed when binding the button to an ICommand so that the icon also goes gray if the command cannot be executed.



<ControlTemplate x:Key="TestOffIcon">
<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>
<ControlTemplate x:Key="TestOnIcon">
<Canvas
Width="76"
Height="76"
Clip="F1 M 0,0L 76,0L 76,76L 0,76L 0,0">
<Path
Width="35.9625"
Height="35.9625"
Canvas.Left="20.0187"
Canvas.Top="20.0187"
Stretch="Fill"
Fill="{Binding RelativeSource={RelativeSource AncestorType=Control}, Path=Foreground}"
Data="F1 M 27.6073,29.8464L 20.4825,22.7216C 19.8641,22.1033 19.8641,21.1008 20.4825,20.4825C 21.1008,19.8641 22.1033,19.8641 22.7216,20.4825L 29.8464,27.6073C 34.1877,24.3526 40.3735,24.6993 44.3217,28.6475L 28.6475,44.3217C 24.6993,40.3735 24.3526,34.1877 27.6073,29.8464 Z M 47.7483,32.0742C 51.6965,36.0223 52.0433,42.2082 48.7885,46.5494L 55.5175,53.2784C 56.1358,53.8967 56.1358,54.8992 55.5175,55.5175C 54.8992,56.1359 53.8967,56.1359 53.2783,55.5175L 46.5494,48.7886C 42.2081,52.0433 36.0223,51.6965 32.0741,47.7484L 35.4329,44.3896L 32.6339,41.5906C 31.7064,40.6631 31.7064,39.1594 32.6339,38.2319C 33.5614,37.3044 35.0652,37.3044 35.9927,38.2319L 38.7916,41.0308L 41.0308,38.7917L 38.2319,35.9927C 37.3044,35.0652 37.3044,33.5614 38.2319,32.634C 39.1594,31.7065 40.6631,31.7065 41.5906,32.6339L 44.3896,35.4329L 47.7483,32.0742 Z "/>
</Canvas>
</ControlTemplate>


Update



There were a few mistakes that have been fixed with my edit. Note that in the style one must use RelativeSource={RelativeSource AncestorType=Button} with the binding to make it refer to the button dependency properties.



Fixed on and off icon references.










share|improve this question





























    4














    Note: See Verticle WPF icon button that used Segoe MDL2 font for icon for an example using Segoe MDL2 for the icons.



    I need a button that changes appearance based on the state of a Boolean. A previous attempt is here: WPF button with xaml-defined icon that changes with state



    In this implementation, I do this creating a custom control based off Button that does NOT use a ControlTemplate. From what I understand, using ControlTemplate completely replaces the default control template, so you have to create the entire style again.



    Another method is to put a button inside the ControlTemplate and bind the properties to the DependencyProperties of the custom control and the Button it is based on, e.g. https://www.codeproject.com/Tips/773386/WPF-ImageButton. The problem with this is that properties automatically set such as IsEnabled for a command do not get automatically applied to the internal Button



    So I use ContentTemplate and trigger changes via DataTrigger instead of ControlTemplate and ControlTemplate.Triggers



    This is the class with the dependency properties:



    public class VerticalImageButton : Button
    {
    static VerticalImageButton()
    {
    DefaultStyleKeyProperty.OverrideMetadata(typeof(VerticalImageButton), new FrameworkPropertyMetadata(typeof(VerticalImageButton)));
    }

    public bool State
    {
    get { return (bool)GetValue(StateProperty); }
    set { SetValue(StateProperty, value); }
    }

    public static readonly DependencyProperty StateProperty =
    DependencyProperty.Register("State",
    typeof(bool), typeof(VerticalImageButton), new PropertyMetadata(null));

    public ControlTemplate IconOn
    {
    get { return (ControlTemplate)GetValue(IconOnProperty); }
    set { SetValue(IconOnProperty, value); }
    }
    public static readonly DependencyProperty IconOnProperty =
    DependencyProperty.Register("IconOn",
    typeof(ControlTemplate), typeof(VerticalImageButton), new PropertyMetadata(null));

    public ControlTemplate IconOff
    {
    get { return (ControlTemplate)GetValue(IconOffProperty); }
    set { SetValue(IconOffProperty, value); }
    }
    public static readonly DependencyProperty IconOffProperty =
    DependencyProperty.Register("IconOff",
    typeof(ControlTemplate), typeof(VerticalImageButton), new PropertyMetadata(null));

    public string ContentOn
    {
    get { return (string)GetValue(ContentOnProperty); }
    set { SetValue(ContentOnProperty, value); }
    }
    public static readonly DependencyProperty ContentOnProperty =
    DependencyProperty.Register("ContentOn",
    typeof(string), typeof(VerticalImageButton), new PropertyMetadata(null));

    public string ContentOff
    {
    get { return (string)GetValue(ContentOffProperty); }
    set { SetValue(ContentOffProperty, value); }
    }
    public static readonly DependencyProperty ContentOffProperty =
    DependencyProperty.Register("ContentOff",
    typeof(string), typeof(VerticalImageButton), new PropertyMetadata(null));
    }


    Note that the icons are XAML resources wrapped in ControlTemplate, see: WPF button with xaml-defined icon that changes with state



    Now in the Generic.xaml file that is created when you make a custom control in VS:



    <Style TargetType="{x:Type local:VerticalImageButton}" BasedOn="{StaticResource {x:Type Button}}">
    <Style.Resources>
    <Style x:Key="buttonText" TargetType="{x:Type TextBlock}">
    <Setter Property="Text" Value="{Binding ContentOff, RelativeSource={RelativeSource AncestorType=Button}}"/>
    <Style.Triggers>
    <DataTrigger Binding="{Binding State, RelativeSource={RelativeSource AncestorType=Button}}" Value="True">
    <Setter Property="Text" Value="{Binding ContentOn, RelativeSource={RelativeSource AncestorType=Button}}"/>
    </DataTrigger>
    <DataTrigger Binding="{Binding State, RelativeSource={RelativeSource AncestorType=Button}}" Value="False">
    <Setter Property="Text" Value="{Binding ContentOff, RelativeSource={RelativeSource AncestorType=Button}}"/>
    </DataTrigger>
    </Style.Triggers>
    </Style>
    <Style x:Key="buttonIcon" TargetType="{x:Type ContentControl}">
    <Setter Property="Template" Value="{Binding IconOff, RelativeSource={RelativeSource AncestorType=Button}}"/>
    <Style.Triggers>
    <DataTrigger Binding="{Binding State, RelativeSource={RelativeSource AncestorType=Button}}" Value="True">
    <Setter Property="Template" Value="{Binding IconOn, RelativeSource={RelativeSource AncestorType=Button}}"/>
    </DataTrigger>
    <DataTrigger Binding="{Binding State, RelativeSource={RelativeSource AncestorType=Button}}" Value="False">
    <Setter Property="Template" Value="{Binding IconOff, RelativeSource={RelativeSource AncestorType=Button}}"/>
    </DataTrigger>
    </Style.Triggers>
    </Style>
    </Style.Resources>
    <Setter Property="ContentTemplate">
    <Setter.Value>
    <DataTemplate>
    <StackPanel Orientation="Vertical" Margin="2" >
    <Viewbox Width="36">
    <ContentControl Style="{DynamicResource buttonIcon}"/>
    </Viewbox>
    <TextBlock Style="{DynamicResource buttonText}" VerticalAlignment="Center"/>
    </StackPanel>
    </DataTemplate>
    </Setter.Value>
    </Setter>
    <Setter Property="Width" Value="61"/>
    <Setter Property="Margin" Value="2"/>
    </Style>


    One problem is that because it is based on {StaticResource {x:Type Button}}, it won't inherit the style of the container - e.g. when putting into a Toolbar. For this case, you can just add the toolbar style, Style="{StaticResource {x:Static ToolBar.ButtonStyleKey}}" when laying out the buttons.



    Finally, the use of the control is quite easy:



    <local:VerticalImageButton
    x:Name="TestButton"
    Style="{StaticResource {x:Static ToolBar.ButtonStyleKey}}"
    Command="{Binding TestCommand}"
    IconOff="{StaticResource TestOnIcon}"
    IconOn="{StaticResource TestOffIcon}"
    ContentOff="Connect"
    ContentOn="Disconnect"
    State="{Binding IsTestOn}"/>


    Two icons if someone wants to implement. Note the binding on the fill. This makes the icon change colour with the button, and is needed when binding the button to an ICommand so that the icon also goes gray if the command cannot be executed.



    <ControlTemplate x:Key="TestOffIcon">
    <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>
    <ControlTemplate x:Key="TestOnIcon">
    <Canvas
    Width="76"
    Height="76"
    Clip="F1 M 0,0L 76,0L 76,76L 0,76L 0,0">
    <Path
    Width="35.9625"
    Height="35.9625"
    Canvas.Left="20.0187"
    Canvas.Top="20.0187"
    Stretch="Fill"
    Fill="{Binding RelativeSource={RelativeSource AncestorType=Control}, Path=Foreground}"
    Data="F1 M 27.6073,29.8464L 20.4825,22.7216C 19.8641,22.1033 19.8641,21.1008 20.4825,20.4825C 21.1008,19.8641 22.1033,19.8641 22.7216,20.4825L 29.8464,27.6073C 34.1877,24.3526 40.3735,24.6993 44.3217,28.6475L 28.6475,44.3217C 24.6993,40.3735 24.3526,34.1877 27.6073,29.8464 Z M 47.7483,32.0742C 51.6965,36.0223 52.0433,42.2082 48.7885,46.5494L 55.5175,53.2784C 56.1358,53.8967 56.1358,54.8992 55.5175,55.5175C 54.8992,56.1359 53.8967,56.1359 53.2783,55.5175L 46.5494,48.7886C 42.2081,52.0433 36.0223,51.6965 32.0741,47.7484L 35.4329,44.3896L 32.6339,41.5906C 31.7064,40.6631 31.7064,39.1594 32.6339,38.2319C 33.5614,37.3044 35.0652,37.3044 35.9927,38.2319L 38.7916,41.0308L 41.0308,38.7917L 38.2319,35.9927C 37.3044,35.0652 37.3044,33.5614 38.2319,32.634C 39.1594,31.7065 40.6631,31.7065 41.5906,32.6339L 44.3896,35.4329L 47.7483,32.0742 Z "/>
    </Canvas>
    </ControlTemplate>


    Update



    There were a few mistakes that have been fixed with my edit. Note that in the style one must use RelativeSource={RelativeSource AncestorType=Button} with the binding to make it refer to the button dependency properties.



    Fixed on and off icon references.










    share|improve this question



























      4












      4








      4







      Note: See Verticle WPF icon button that used Segoe MDL2 font for icon for an example using Segoe MDL2 for the icons.



      I need a button that changes appearance based on the state of a Boolean. A previous attempt is here: WPF button with xaml-defined icon that changes with state



      In this implementation, I do this creating a custom control based off Button that does NOT use a ControlTemplate. From what I understand, using ControlTemplate completely replaces the default control template, so you have to create the entire style again.



      Another method is to put a button inside the ControlTemplate and bind the properties to the DependencyProperties of the custom control and the Button it is based on, e.g. https://www.codeproject.com/Tips/773386/WPF-ImageButton. The problem with this is that properties automatically set such as IsEnabled for a command do not get automatically applied to the internal Button



      So I use ContentTemplate and trigger changes via DataTrigger instead of ControlTemplate and ControlTemplate.Triggers



      This is the class with the dependency properties:



      public class VerticalImageButton : Button
      {
      static VerticalImageButton()
      {
      DefaultStyleKeyProperty.OverrideMetadata(typeof(VerticalImageButton), new FrameworkPropertyMetadata(typeof(VerticalImageButton)));
      }

      public bool State
      {
      get { return (bool)GetValue(StateProperty); }
      set { SetValue(StateProperty, value); }
      }

      public static readonly DependencyProperty StateProperty =
      DependencyProperty.Register("State",
      typeof(bool), typeof(VerticalImageButton), new PropertyMetadata(null));

      public ControlTemplate IconOn
      {
      get { return (ControlTemplate)GetValue(IconOnProperty); }
      set { SetValue(IconOnProperty, value); }
      }
      public static readonly DependencyProperty IconOnProperty =
      DependencyProperty.Register("IconOn",
      typeof(ControlTemplate), typeof(VerticalImageButton), new PropertyMetadata(null));

      public ControlTemplate IconOff
      {
      get { return (ControlTemplate)GetValue(IconOffProperty); }
      set { SetValue(IconOffProperty, value); }
      }
      public static readonly DependencyProperty IconOffProperty =
      DependencyProperty.Register("IconOff",
      typeof(ControlTemplate), typeof(VerticalImageButton), new PropertyMetadata(null));

      public string ContentOn
      {
      get { return (string)GetValue(ContentOnProperty); }
      set { SetValue(ContentOnProperty, value); }
      }
      public static readonly DependencyProperty ContentOnProperty =
      DependencyProperty.Register("ContentOn",
      typeof(string), typeof(VerticalImageButton), new PropertyMetadata(null));

      public string ContentOff
      {
      get { return (string)GetValue(ContentOffProperty); }
      set { SetValue(ContentOffProperty, value); }
      }
      public static readonly DependencyProperty ContentOffProperty =
      DependencyProperty.Register("ContentOff",
      typeof(string), typeof(VerticalImageButton), new PropertyMetadata(null));
      }


      Note that the icons are XAML resources wrapped in ControlTemplate, see: WPF button with xaml-defined icon that changes with state



      Now in the Generic.xaml file that is created when you make a custom control in VS:



      <Style TargetType="{x:Type local:VerticalImageButton}" BasedOn="{StaticResource {x:Type Button}}">
      <Style.Resources>
      <Style x:Key="buttonText" TargetType="{x:Type TextBlock}">
      <Setter Property="Text" Value="{Binding ContentOff, RelativeSource={RelativeSource AncestorType=Button}}"/>
      <Style.Triggers>
      <DataTrigger Binding="{Binding State, RelativeSource={RelativeSource AncestorType=Button}}" Value="True">
      <Setter Property="Text" Value="{Binding ContentOn, RelativeSource={RelativeSource AncestorType=Button}}"/>
      </DataTrigger>
      <DataTrigger Binding="{Binding State, RelativeSource={RelativeSource AncestorType=Button}}" Value="False">
      <Setter Property="Text" Value="{Binding ContentOff, RelativeSource={RelativeSource AncestorType=Button}}"/>
      </DataTrigger>
      </Style.Triggers>
      </Style>
      <Style x:Key="buttonIcon" TargetType="{x:Type ContentControl}">
      <Setter Property="Template" Value="{Binding IconOff, RelativeSource={RelativeSource AncestorType=Button}}"/>
      <Style.Triggers>
      <DataTrigger Binding="{Binding State, RelativeSource={RelativeSource AncestorType=Button}}" Value="True">
      <Setter Property="Template" Value="{Binding IconOn, RelativeSource={RelativeSource AncestorType=Button}}"/>
      </DataTrigger>
      <DataTrigger Binding="{Binding State, RelativeSource={RelativeSource AncestorType=Button}}" Value="False">
      <Setter Property="Template" Value="{Binding IconOff, RelativeSource={RelativeSource AncestorType=Button}}"/>
      </DataTrigger>
      </Style.Triggers>
      </Style>
      </Style.Resources>
      <Setter Property="ContentTemplate">
      <Setter.Value>
      <DataTemplate>
      <StackPanel Orientation="Vertical" Margin="2" >
      <Viewbox Width="36">
      <ContentControl Style="{DynamicResource buttonIcon}"/>
      </Viewbox>
      <TextBlock Style="{DynamicResource buttonText}" VerticalAlignment="Center"/>
      </StackPanel>
      </DataTemplate>
      </Setter.Value>
      </Setter>
      <Setter Property="Width" Value="61"/>
      <Setter Property="Margin" Value="2"/>
      </Style>


      One problem is that because it is based on {StaticResource {x:Type Button}}, it won't inherit the style of the container - e.g. when putting into a Toolbar. For this case, you can just add the toolbar style, Style="{StaticResource {x:Static ToolBar.ButtonStyleKey}}" when laying out the buttons.



      Finally, the use of the control is quite easy:



      <local:VerticalImageButton
      x:Name="TestButton"
      Style="{StaticResource {x:Static ToolBar.ButtonStyleKey}}"
      Command="{Binding TestCommand}"
      IconOff="{StaticResource TestOnIcon}"
      IconOn="{StaticResource TestOffIcon}"
      ContentOff="Connect"
      ContentOn="Disconnect"
      State="{Binding IsTestOn}"/>


      Two icons if someone wants to implement. Note the binding on the fill. This makes the icon change colour with the button, and is needed when binding the button to an ICommand so that the icon also goes gray if the command cannot be executed.



      <ControlTemplate x:Key="TestOffIcon">
      <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>
      <ControlTemplate x:Key="TestOnIcon">
      <Canvas
      Width="76"
      Height="76"
      Clip="F1 M 0,0L 76,0L 76,76L 0,76L 0,0">
      <Path
      Width="35.9625"
      Height="35.9625"
      Canvas.Left="20.0187"
      Canvas.Top="20.0187"
      Stretch="Fill"
      Fill="{Binding RelativeSource={RelativeSource AncestorType=Control}, Path=Foreground}"
      Data="F1 M 27.6073,29.8464L 20.4825,22.7216C 19.8641,22.1033 19.8641,21.1008 20.4825,20.4825C 21.1008,19.8641 22.1033,19.8641 22.7216,20.4825L 29.8464,27.6073C 34.1877,24.3526 40.3735,24.6993 44.3217,28.6475L 28.6475,44.3217C 24.6993,40.3735 24.3526,34.1877 27.6073,29.8464 Z M 47.7483,32.0742C 51.6965,36.0223 52.0433,42.2082 48.7885,46.5494L 55.5175,53.2784C 56.1358,53.8967 56.1358,54.8992 55.5175,55.5175C 54.8992,56.1359 53.8967,56.1359 53.2783,55.5175L 46.5494,48.7886C 42.2081,52.0433 36.0223,51.6965 32.0741,47.7484L 35.4329,44.3896L 32.6339,41.5906C 31.7064,40.6631 31.7064,39.1594 32.6339,38.2319C 33.5614,37.3044 35.0652,37.3044 35.9927,38.2319L 38.7916,41.0308L 41.0308,38.7917L 38.2319,35.9927C 37.3044,35.0652 37.3044,33.5614 38.2319,32.634C 39.1594,31.7065 40.6631,31.7065 41.5906,32.6339L 44.3896,35.4329L 47.7483,32.0742 Z "/>
      </Canvas>
      </ControlTemplate>


      Update



      There were a few mistakes that have been fixed with my edit. Note that in the style one must use RelativeSource={RelativeSource AncestorType=Button} with the binding to make it refer to the button dependency properties.



      Fixed on and off icon references.










      share|improve this question















      Note: See Verticle WPF icon button that used Segoe MDL2 font for icon for an example using Segoe MDL2 for the icons.



      I need a button that changes appearance based on the state of a Boolean. A previous attempt is here: WPF button with xaml-defined icon that changes with state



      In this implementation, I do this creating a custom control based off Button that does NOT use a ControlTemplate. From what I understand, using ControlTemplate completely replaces the default control template, so you have to create the entire style again.



      Another method is to put a button inside the ControlTemplate and bind the properties to the DependencyProperties of the custom control and the Button it is based on, e.g. https://www.codeproject.com/Tips/773386/WPF-ImageButton. The problem with this is that properties automatically set such as IsEnabled for a command do not get automatically applied to the internal Button



      So I use ContentTemplate and trigger changes via DataTrigger instead of ControlTemplate and ControlTemplate.Triggers



      This is the class with the dependency properties:



      public class VerticalImageButton : Button
      {
      static VerticalImageButton()
      {
      DefaultStyleKeyProperty.OverrideMetadata(typeof(VerticalImageButton), new FrameworkPropertyMetadata(typeof(VerticalImageButton)));
      }

      public bool State
      {
      get { return (bool)GetValue(StateProperty); }
      set { SetValue(StateProperty, value); }
      }

      public static readonly DependencyProperty StateProperty =
      DependencyProperty.Register("State",
      typeof(bool), typeof(VerticalImageButton), new PropertyMetadata(null));

      public ControlTemplate IconOn
      {
      get { return (ControlTemplate)GetValue(IconOnProperty); }
      set { SetValue(IconOnProperty, value); }
      }
      public static readonly DependencyProperty IconOnProperty =
      DependencyProperty.Register("IconOn",
      typeof(ControlTemplate), typeof(VerticalImageButton), new PropertyMetadata(null));

      public ControlTemplate IconOff
      {
      get { return (ControlTemplate)GetValue(IconOffProperty); }
      set { SetValue(IconOffProperty, value); }
      }
      public static readonly DependencyProperty IconOffProperty =
      DependencyProperty.Register("IconOff",
      typeof(ControlTemplate), typeof(VerticalImageButton), new PropertyMetadata(null));

      public string ContentOn
      {
      get { return (string)GetValue(ContentOnProperty); }
      set { SetValue(ContentOnProperty, value); }
      }
      public static readonly DependencyProperty ContentOnProperty =
      DependencyProperty.Register("ContentOn",
      typeof(string), typeof(VerticalImageButton), new PropertyMetadata(null));

      public string ContentOff
      {
      get { return (string)GetValue(ContentOffProperty); }
      set { SetValue(ContentOffProperty, value); }
      }
      public static readonly DependencyProperty ContentOffProperty =
      DependencyProperty.Register("ContentOff",
      typeof(string), typeof(VerticalImageButton), new PropertyMetadata(null));
      }


      Note that the icons are XAML resources wrapped in ControlTemplate, see: WPF button with xaml-defined icon that changes with state



      Now in the Generic.xaml file that is created when you make a custom control in VS:



      <Style TargetType="{x:Type local:VerticalImageButton}" BasedOn="{StaticResource {x:Type Button}}">
      <Style.Resources>
      <Style x:Key="buttonText" TargetType="{x:Type TextBlock}">
      <Setter Property="Text" Value="{Binding ContentOff, RelativeSource={RelativeSource AncestorType=Button}}"/>
      <Style.Triggers>
      <DataTrigger Binding="{Binding State, RelativeSource={RelativeSource AncestorType=Button}}" Value="True">
      <Setter Property="Text" Value="{Binding ContentOn, RelativeSource={RelativeSource AncestorType=Button}}"/>
      </DataTrigger>
      <DataTrigger Binding="{Binding State, RelativeSource={RelativeSource AncestorType=Button}}" Value="False">
      <Setter Property="Text" Value="{Binding ContentOff, RelativeSource={RelativeSource AncestorType=Button}}"/>
      </DataTrigger>
      </Style.Triggers>
      </Style>
      <Style x:Key="buttonIcon" TargetType="{x:Type ContentControl}">
      <Setter Property="Template" Value="{Binding IconOff, RelativeSource={RelativeSource AncestorType=Button}}"/>
      <Style.Triggers>
      <DataTrigger Binding="{Binding State, RelativeSource={RelativeSource AncestorType=Button}}" Value="True">
      <Setter Property="Template" Value="{Binding IconOn, RelativeSource={RelativeSource AncestorType=Button}}"/>
      </DataTrigger>
      <DataTrigger Binding="{Binding State, RelativeSource={RelativeSource AncestorType=Button}}" Value="False">
      <Setter Property="Template" Value="{Binding IconOff, RelativeSource={RelativeSource AncestorType=Button}}"/>
      </DataTrigger>
      </Style.Triggers>
      </Style>
      </Style.Resources>
      <Setter Property="ContentTemplate">
      <Setter.Value>
      <DataTemplate>
      <StackPanel Orientation="Vertical" Margin="2" >
      <Viewbox Width="36">
      <ContentControl Style="{DynamicResource buttonIcon}"/>
      </Viewbox>
      <TextBlock Style="{DynamicResource buttonText}" VerticalAlignment="Center"/>
      </StackPanel>
      </DataTemplate>
      </Setter.Value>
      </Setter>
      <Setter Property="Width" Value="61"/>
      <Setter Property="Margin" Value="2"/>
      </Style>


      One problem is that because it is based on {StaticResource {x:Type Button}}, it won't inherit the style of the container - e.g. when putting into a Toolbar. For this case, you can just add the toolbar style, Style="{StaticResource {x:Static ToolBar.ButtonStyleKey}}" when laying out the buttons.



      Finally, the use of the control is quite easy:



      <local:VerticalImageButton
      x:Name="TestButton"
      Style="{StaticResource {x:Static ToolBar.ButtonStyleKey}}"
      Command="{Binding TestCommand}"
      IconOff="{StaticResource TestOnIcon}"
      IconOn="{StaticResource TestOffIcon}"
      ContentOff="Connect"
      ContentOn="Disconnect"
      State="{Binding IsTestOn}"/>


      Two icons if someone wants to implement. Note the binding on the fill. This makes the icon change colour with the button, and is needed when binding the button to an ICommand so that the icon also goes gray if the command cannot be executed.



      <ControlTemplate x:Key="TestOffIcon">
      <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>
      <ControlTemplate x:Key="TestOnIcon">
      <Canvas
      Width="76"
      Height="76"
      Clip="F1 M 0,0L 76,0L 76,76L 0,76L 0,0">
      <Path
      Width="35.9625"
      Height="35.9625"
      Canvas.Left="20.0187"
      Canvas.Top="20.0187"
      Stretch="Fill"
      Fill="{Binding RelativeSource={RelativeSource AncestorType=Control}, Path=Foreground}"
      Data="F1 M 27.6073,29.8464L 20.4825,22.7216C 19.8641,22.1033 19.8641,21.1008 20.4825,20.4825C 21.1008,19.8641 22.1033,19.8641 22.7216,20.4825L 29.8464,27.6073C 34.1877,24.3526 40.3735,24.6993 44.3217,28.6475L 28.6475,44.3217C 24.6993,40.3735 24.3526,34.1877 27.6073,29.8464 Z M 47.7483,32.0742C 51.6965,36.0223 52.0433,42.2082 48.7885,46.5494L 55.5175,53.2784C 56.1358,53.8967 56.1358,54.8992 55.5175,55.5175C 54.8992,56.1359 53.8967,56.1359 53.2783,55.5175L 46.5494,48.7886C 42.2081,52.0433 36.0223,51.6965 32.0741,47.7484L 35.4329,44.3896L 32.6339,41.5906C 31.7064,40.6631 31.7064,39.1594 32.6339,38.2319C 33.5614,37.3044 35.0652,37.3044 35.9927,38.2319L 38.7916,41.0308L 41.0308,38.7917L 38.2319,35.9927C 37.3044,35.0652 37.3044,33.5614 38.2319,32.634C 39.1594,31.7065 40.6631,31.7065 41.5906,32.6339L 44.3896,35.4329L 47.7483,32.0742 Z "/>
      </Canvas>
      </ControlTemplate>


      Update



      There were a few mistakes that have been fixed with my edit. Note that in the style one must use RelativeSource={RelativeSource AncestorType=Button} with the binding to make it refer to the button dependency properties.



      Fixed on and off icon references.







      c# wpf






      share|improve this question















      share|improve this question













      share|improve this question




      share|improve this question








      edited Dec 31 '18 at 14:32

























      asked Mar 6 '18 at 10:37









      geometrikal

      205313




      205313






















          0






          active

          oldest

          votes











          Your Answer





          StackExchange.ifUsing("editor", function () {
          return StackExchange.using("mathjaxEditing", function () {
          StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
          StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
          });
          });
          }, "mathjax-editing");

          StackExchange.ifUsing("editor", function () {
          StackExchange.using("externalEditor", function () {
          StackExchange.using("snippets", function () {
          StackExchange.snippets.init();
          });
          });
          }, "code-snippets");

          StackExchange.ready(function() {
          var channelOptions = {
          tags: "".split(" "),
          id: "196"
          };
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function() {
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled) {
          StackExchange.using("snippets", function() {
          createEditor();
          });
          }
          else {
          createEditor();
          }
          });

          function createEditor() {
          StackExchange.prepareEditor({
          heartbeatType: 'answer',
          autoActivateHeartbeat: false,
          convertImagesToLinks: false,
          noModals: true,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: null,
          bindNavPrevention: true,
          postfix: "",
          imageUploader: {
          brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
          contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
          allowUrls: true
          },
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          });


          }
          });














          draft saved

          draft discarded


















          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f188951%2fwpf-customcontrol-image-button-that-changes-with-state-and-does-not-use-controlt%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown

























          0






          active

          oldest

          votes








          0






          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes
















          draft saved

          draft discarded




















































          Thanks for contributing an answer to Code Review Stack Exchange!


          • Please be sure to answer the question. Provide details and share your research!

          But avoid



          • Asking for help, clarification, or responding to other answers.

          • Making statements based on opinion; back them up with references or personal experience.


          Use MathJax to format equations. MathJax reference.


          To learn more, see our tips on writing great answers.





          Some of your past answers have not been well-received, and you're in danger of being blocked from answering.


          Please pay close attention to the following guidance:


          • Please be sure to answer the question. Provide details and share your research!

          But avoid



          • Asking for help, clarification, or responding to other answers.

          • Making statements based on opinion; back them up with references or personal experience.


          To learn more, see our tips on writing great answers.




          draft saved


          draft discarded














          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f188951%2fwpf-customcontrol-image-button-that-changes-with-state-and-does-not-use-controlt%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown





















































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown

































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown







          Popular posts from this blog

          Список кардиналов, возведённых папой римским Каликстом III

          Deduzione

          Mysql.sock missing - “Can't connect to local MySQL server through socket”