Search code examples
wpfxaml

How can I bind the RadioButton Content property value to a TextBlock Text in a Style?


I have this style for a RadioButton on WPF

<Style x:Key="UserRadioButton" TargetType="RadioButton">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="RadioButton">
                <Grid>
                    <Border
                        x:Name="border"
                        Background="Transparent"
                        BorderBrush="Black"
                        BorderThickness="2"
                        CornerRadius="5">

                        <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center">
                            <StackPanel>
                                <fa:IconImage MaxHeight="50" Icon="User" />
                                <TextBlock
                                    Width="Auto"
                                    Height="Auto"
                                    FontSize="16"
                                    Text="{TemplateBinding Content}" />
                            </StackPanel>
                        </ContentPresenter>
                    </Border>
                </Grid>
                <ControlTemplate.Triggers>
                    <Trigger Property="IsChecked" Value="True">
                        <Setter TargetName="border" Property="Background" Value="LightBlue" />
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

And the TextBlock Text it's not Binding to the content

I tried to use different properties to bind but it's not working


Solution

  • ContentPresenter does not allow content to be defined using XAML content syntax. This is because the ContentPresenter does not derive from ContentControl. The ConTentControl type is decorated with the ContentPropertyAttribute attribute which is inherited to all inheritors. And ContentPresenter does not apply the ContentPropertyAttribute itself. Therefore, ContentPresenter does not support XAML content syntax.

    The ContentPresenter is used inside the template of a ContentControl to actually render the value of the ContentControl.Content property. Because it supports templating, it can render any content.
    If you override the Controltemplate of a ContentControl (e.g. RadioButton) you usually you would embed a ContentPresenter into the new layout.
    In your case you are trying to replace the ContentPresenter by adding a TextBlock to render the content. This limits the reusability significantly as now the content can only be a string. For this reason, the recommended way is to embed the ContentPresenter into you extended layout. You always want to show the ContentControl.Content value so you always would embed the ContentPresenter.

    Also don't set Border attributes like Background property locally as this eliminates customization by assignig local values on the templated parent (for example RadioButton Background="Red" /> won't work anymore).
    Instead use TemplateBinding to bind those properties to the owner of the ControlTemplate and set the default values using Style.Setters.

    Recommended solution
    Embed the ContentPresenter into the custom layout:

    <Style x:Key="UserRadioButton"
           TargetType="RadioButton">
      <Setter Property="Background"
              Value="Transparent" />
      <Setter Property="BorderBrush"
              Value="Black" />
      <Setter Property="BorderThickness"
              Value="2" />
      <Setter Property="Template">
        <Setter.Value>
          <ControlTemplate TargetType="RadioButton">
            <Border x:Name="border"
                    Background="{TemplateBinding Background}"
                    BorderBrush="{TemplateBinding BorderBrush}"
                    BorderThickness="{TemplateBinding BorderThickness}"
                    Padding="{TemplateBinding Padding}"
                    CornerRadius="5">
              <StackPanel HorizontalAlignment="Center"
                          VerticalAlignment="Center">
                <fa:IconImage MaxHeight="50" 
                              Icon="User" />
    
                <!-- 
                     Automatically shows the value of the Content property .
                     and also inherits properties like FontSize, FontWeight etc.
                     from the templated parent control.
                -->
                <ContentPresenter />
              </StackPanel>
            </Border>
          </ControlTemplate>
        </Setter.Value>
      </Setter>
    </Style>
    

    For the sake of completeness, below are different variants you can use if you want to replace the ContentPresenter with your TextBlock.
    Note that non of those examples below are recommended solutions.

    Example 1 (not recommended)
    Set ContentPrersenter.Content explicitly:

    <ControlTemplate TargetType="RadioButton">
      <Border x:Name="border"
              Background="{TemplateBinding Background}"
              BorderBrush="{TemplateBinding BorderBrush}"
              BorderThickness="{TemplateBinding BorderThickness}"
              Padding="{TemplateBinding Padding}"
              CornerRadius="5">
        <ContentPresenter>
    
          <!-- 
               The XAML content syntax is not supported by the ContentPresenter.
               However, we can use the XAML property element syntax to set the Content:
          -->
          <ContentPresenter.Content>
            <StackPanel HorizontalAlignment="Center"
                        VerticalAlignment="Center">
              <fa:IconImage MaxHeight="50" 
                            Icon="User" />
              <TextBlock Width="Auto"
                         Height="Auto"
                         FontSize="16"
                         Text="{TemplateBinding Content}" />
            </StackPanel>
          <ContentPresenter.Content>
        </ContentPresenter>
      </Border>
    </ControlTemplate>
    

    Example 2 (not recommended)
    Simply drop the ContentPrersenter from the layout as its content was already explicitly defined in example 1 what effectively makes the ContentPresenter pointless:

    <ControlTemplate TargetType="RadioButton">
      <Border x:Name="border"
              Background="{TemplateBinding Background}"
              BorderBrush="{TemplateBinding BorderBrush}"
              BorderThickness="{TemplateBinding BorderThickness}"
              Padding="{TemplateBinding Padding}"
              CornerRadius="5">
    
        <!-- 
             Because the Content is presented using the TextBlock 
             the ContentPresenter has no more use and can be dropped from the layout.
        -->
        <StackPanel HorizontalAlignment="Center"
                    VerticalAlignment="Center">
          <fa:IconImage MaxHeight="50" 
                        Icon="User" />
          <TextBlock Width="Auto"
                     Height="Auto"
                     FontSize="16"
                     Text="{TemplateBinding Content}" />
        </StackPanel>      
      </Border>
    </ControlTemplate>
    

    Example 3 (recommended for some scenarios, for example when the original layout and interaction behavior, like mouse over state effects, should be preserved)
    A more useful alternative is to define the value for ContentControl.Content inline either directly on the element for example

    <!-- 
         This solution will preserve the original layout, 
         like the circle that highlights the toggle state, 
         and interaction behavior
    -->
    <RadioButton>
      <RadioButton.Content> 
        <TextBlock Text="Some text" /> 
      </RadioButton.Content> 
    </RadioButton>
    

    or with the help of a Style :

    <!-- 
         This solution will preserve the original layout, 
         like the circle that highlights the toggle state, 
         and interaction behavior
    -->
    <Style x:Key="UserRadioButton"
           TargetType="RadioButton">
      <Setter Property="Content">
        <Setter.Value>
          <StackPanel HorizontalAlignment="Center"
                      VerticalAlignment="Center">
            <fa:IconImage MaxHeight="50" 
                          Icon="User" />
    
            <!-- 
                 This example binds the the TextBlock.Text property 
                 to the current DataContext of the styled control (RadioButton).
            -->
            <TextBlock Text="{Binding textPropertyOfDataContext}" />
          </StackPanel>
        </Setter.Value>
      </Setter>
    </Style>
    

    Example 4 (recommended for some scenarios, for example when the original layout and interaction behavior, like mouse over state effects, should be preserved)
    Another useful alternative is to define the value for ContentControl.Content by defining a DataTemplate for the ContentControl.ContentTemplate property:

    <!-- 
         This solution will preserve the original layout, 
         like the circle that highlights the toggle state, 
         and interaction behavior
    -->
    <Style x:Key="UserRadioButton"
           TargetType="RadioButton">
      <Setter Property="ContentTemplate">
        <Setter.Value>
          <DataTemplate>
            <StackPanel HorizontalAlignment="Center"
                        VerticalAlignment="Center">
              <fa:IconImage MaxHeight="50" 
                            Icon="User" />
    
              <!-- 
                   This example binds then TextBlock.Text property 
                   to the current DataContext of the DataTemplate 
                   which is the ContentControl.Content value.
              -->
              <TextBlock Text="{Binding}" />
            </StackPanel>
          </DataTemplate>
        </Setter.Value>
      </Setter>
    </Style>