phibel
phibel

Reputation: 171

Inherited WPF custom control does not inherit parent Command

I've created a IconButton for use in WPF/XMAML. It should be able to display an Icon MDL2 Assets font on top and an text on bottom. It should have the appearance of an default WPF toolbar button. I decided to create a custom control which inherits from default WPF button.

So I created the custom control and added Dependency Properties for Text and the somehow cryptic MDL2IconCode:

public class IconButton : Button
{
  public static readonly DependencyProperty TextProperty;
  public static readonly DependencyProperty MDL2IconCodeProperty;

  public string Text
  {
    get { return (string)GetValue(TextProperty); }
    set { SetValue(TextProperty, value); }
  }


  public string MDL2IconCode
  {
    get { return (string)GetValue(MDL2IconCodeProperty); }
    set { SetValue(MDL2IconCodeProperty, value); }
  }


  static IconButton()
  {
    DefaultStyleKeyProperty.OverrideMetadata(typeof(IconButton),
                                             new FrameworkPropertyMetadata(typeof(IconButton)));

    TextProperty = DependencyProperty.Register("Text",
                                               typeof(string),
                                               typeof(IconButton),
                                               new PropertyMetadata("Button text", OnTextChanged));

    MDL2IconCodeProperty = DependencyProperty.Register("MDL2IconCode",
                                                       typeof(string),
                                                       typeof(IconButton),
                                                       new PropertyMetadata("\uf13e", OnIconTextChanged));
  }

  static void OnTextChanged(DependencyObject o,
                            DependencyPropertyChangedEventArgs e)
  {
    var iconButton = o as IconButton;
    if (iconButton == null)
    {
      return;
    }
    string newText = e.NewValue as string;
    iconButton.Text = newText;
  }

  static void OnIconTextChanged(DependencyObject o,
                                DependencyPropertyChangedEventArgs e)
  {
    var iconButton = o as IconButton;
    if (iconButton == null)
    {
      return;
    }

    string newText = e.NewValue as string;
    iconButton.MDL2IconCode = newText;
  }
}

The ResourceDictionary of Generic.xaml looks like this:

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:UI.CustomControls">


  <Style TargetType="{x:Type local:IconButton}" 
         BasedOn="{StaticResource {x:Type Button}}">
    <Setter Property="Template">
      <Setter.Value>
        <ControlTemplate TargetType="{x:Type local:IconButton}">
          <Button Style="{StaticResource {x:Static ToolBar.ButtonStyleKey}}">
            <StackPanel>
              <TextBlock HorizontalAlignment="Center"
                         Text="{TemplateBinding MDL2IconCode}"
                         FontFamily="Segoe MDL2 Assets"
                         FontSize="16"
                         x:Name="iconTextBlock"/>
              <TextBlock HorizontalAlignment="Center" 
                         Text="{TemplateBinding Text}"
                         x:Name="textTextBlock"/>
            </StackPanel>

          </Button>

        </ControlTemplate>
      </Setter.Value>
    </Setter>
  </Style>
</ResourceDictionary>

The button looks as it should.

But the command binding in XAML isn't working anymore. But it should work, as per inheritance it still is a button..

Maybe anyone has an idea what to add to make the command binding work?

Upvotes: 0

Views: 640

Answers (2)

mm8
mm8

Reputation: 169400

The ControlTemplate of a Button shouldn't include another Button.

You should template your control to look like a Button if that's what you want:

<Style TargetType="{x:Type local:IconButton}" BasedOn="{StaticResource {x:Static ToolBar.ButtonStyleKey}}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type local:IconButton}">
                <Border Name="Bd" Background="{TemplateBinding Control.Background}"
                            BorderBrush="{TemplateBinding Control.BorderBrush}"
                            BorderThickness="{TemplateBinding Control.BorderThickness}"
                            Padding="{TemplateBinding Control.Padding}" SnapsToDevicePixels="true">
                    <ContentControl HorizontalAlignment="{TemplateBinding Control.HorizontalContentAlignment}"
                              VerticalAlignment="{TemplateBinding Control.VerticalContentAlignment}"
                              SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}">
                        <StackPanel>
                            <TextBlock HorizontalAlignment="Center"
                                           Text="{TemplateBinding MDL2IconCode}"
                                           FontFamily="Segoe MDL2 Assets"
                                           FontSize="16"
                                           x:Name="iconTextBlock"/>
                            <TextBlock HorizontalAlignment="Center" 
                                           Text="{TemplateBinding Text}"
                                           x:Name="textTextBlock"/>
                        </StackPanel>
                    </ContentControl>
                </Border>
                <ControlTemplate.Triggers>
                    <Trigger Property="UIElement.IsMouseOver" Value="true">
                        <Setter TargetName="Bd" Value="#80DADADA" Property="BorderBrush"/>
                        <Setter TargetName="Bd" Value="#FFB6BDC5" Property="Background"/>
                    </Trigger>
                    <Trigger Property="UIElement.IsKeyboardFocused" Value="true">
                        <Setter TargetName="Bd" Value="#80DADADA" Property="BorderBrush"/>
                        <Setter TargetName="Bd" Value="#FFB6BDC5" Property="Background"/>
                    </Trigger>
                    <Trigger Property="ButtonBase.IsPressed" Value="true">
                        <Setter TargetName="Bd" Value="#90006CD9" Property="BorderBrush"/>
                        <Setter TargetName="Bd" Value="#400080FF" Property="Background"/>
                    </Trigger>
                    <Trigger Property="UIElement.IsEnabled" Value="false">
                        <Setter Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}" Property="Foreground"/>
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

It will then behave like any other Button which means that you can bind its Command property as usual:

<local:IconButton Command="{Binding YourCommand}" />

Upvotes: 1

thatguy
thatguy

Reputation: 22119

Bind the command of the Button inside of your control template to the templated parent.

<ControlTemplate TargetType="{x:Type local:IconButton}">
   <Button Style="{StaticResource {x:Static ToolBar.ButtonStyleKey}}"
           Command="{TemplateBinding Command}"
           CommandParameter="{TemplateBinding CommandParameter}"
           CommandTarget="{TemplateBinding CommandTarget}">
      <!-- ...other code. -->
   </Button>
</ControlTemplate>

But it should work, as per inheritance it still is a button..

No. The Button inside of your control template does not magically bind to the corresponding properties of its templated parent, regardless if it is derived from Button or any other control. You will have to do so for other dependency properties like the CommandParameter as well.

Please also note that TemplateBinding is an optimized binding that does not have all capabilities of the more powerful Binding markup extension. Consequently, when TemplateBinding does not work, e.g. in two-way binding scenarios, you can use TemplatedParent like this:

{Binding RelativeSource={RelativeSource TemplatedParent}, Path=MyDependencyProperty}

For more information, you can refer to the TemplateBinding documentation.

Upvotes: 1

Related Questions