Home > Blockchain >  Controlling text formatting, linebreaks, and colors of custom button with template
Controlling text formatting, linebreaks, and colors of custom button with template

Time:04-09

I've been asked to build a relatively simple application launcher program (to replace an old DOS-based menu system). I've built the application in WPF and created a custom class derived from Button called "ApplicationLauncherButton".

I wanted all of the buttons to have the same "look-and-feel", so I decided they would all be styled the same. As such, I decided to replace the control template with one that I would create. To keep my main window XAML as clean as possible, I created a separate resource dictionary file to hold the template and referenced it in the app.xaml.

This worked reasonably well. However, some of the text on the buttons is very long and I wanted to control where linebreaks would occur. This was accomplished relatively easily by wrapping the button text in a TextBlock and inserting tags where I wanted them. Unfortunately, this had a side effect: the text color change I wanted to occur on a mouse-over event was no longer being applied. When the button text is NOT wrapped in a TextBlock, the mouse-over formatting works as desired, but I lose the ability to control where the linebreaks occur.

I've spent a couple of hours trying to figure out how to get it to work, but haven't had any luck. I've only been working with WPF for a couple of weeks, so there may be a much easier way to accomplish what I'm trying to do. Any suggestions would be welcome.

App.xaml

<Application x:Class="WPF1.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:WPF1"
             StartupUri="MainWindow.xaml">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="Themes\Default\Button.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>

MainWindow.xaml

<Window x:Class="WPF1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WPF1"
        mc:Ignorable="d"
        Title="Application Launcher" Height="787" Width="728" 
        Background="{StaticResource Default.DarkGreenBrush}" SizeChanged="Window_SizeChanged" WindowStartupLocation="CenterScreen">
    <StackPanel Margin="5">
        <GroupBox Header="Design Programs" Margin="5" Foreground="White" FontSize="11" FontWeight="Bold">
            <WrapPanel>
                <local:ApplicationLauncherButton 
                    ApplicationUncPath="\\server\applications\DesignerApp1.exe"
                    Click="ApplicationLauncherButton_Click">
                        Designer Application 1
                    </TextBlock>
                </local:ApplicationLauncherButton>
                <local:ApplicationLauncherButton
                    ApplicationUncPath="\\server\applications\DesignerApp2.exe"
                    Click="ApplicationLauncherButton_Click">
                    <TextBlock>
                        Designer<LineBreak/>
                        Application 2
                    </TextBlock>
                </local:ApplicationLauncherButton>
                <local:ApplicationLauncherButton
                    ApplicationUncPath="\\server\applications\DesignerApp3.exe"
                    Click="ApplicationLauncherButton_Click">
                    <TextBlock>
                        Designer<LineBreak/>
                        Application 3
                    </TextBlock>
                </local:ApplicationLauncherButton>
                <local:ApplicationLauncherButton
                    ApplicationUncPath="\\server\applications\DesignerApp4.exe                    
                    Click="ApplicationLauncherButton_Click">
                    <TextBlock>
                        Designer<LineBreak/>
                        Application 4
                    </TextBlock>
                </local:ApplicationLauncherButton>
                <local:ApplicationLauncherButton
                    ApplicationUncPath="\\server\applications\DesignerApp5.exe                    
                    Click="ApplicationLauncherButton_Click">
                    <TextBlock>
                        Designer<LineBreak/>
                        Application 5
                    </TextBlock>
                </local:ApplicationLauncherButton>
            </WrapPanel>
        </GroupBox>
        <GroupBox Header="Utility Programs" Margin="5" Foreground="White" FontSize="11" FontWeight="Bold">
            <WrapPanel>
                <local:ApplicationLauncherButton
                    ApplicationUncPath="\\server\applications\UtilityApp1.exe                    
                    Click="ApplicationLauncherButton_Click">
                    <TextBlock>
                        Utility<LineBreak/>
                        Application 1</TextBlock>
                </local:ApplicationLauncherButton>
                <local:ApplicationLauncherButton
                    ApplicationUncPath="\\server\applications\UtilityApp2.exe                    
                    Click="ApplicationLauncherButton_Click">
                    <TextBlock>
                        Utility<LineBreak/>
                        Application 2</TextBlock>
                </local:ApplicationLauncherButton>
                <local:ApplicationLauncherButton
                    ApplicationUncPath="\\server\applications\UtilityApp3.exe                    
                    Click="ApplicationLauncherButton_Click">
                    <TextBlock>
                        Utility<LineBreak/>
                        Application 3</TextBlock>
                </local:ApplicationLauncherButton>
                <local:ApplicationLauncherButton
                    ApplicationUncPath="\\server\applications\UtilityApp4.exe                    
                    Click="ApplicationLauncherButton_Click">
                    <TextBlock>
                        Utility<LineBreak/>
                        Application 4</TextBlock>
                </local:ApplicationLauncherButton>
                <local:ApplicationLauncherButton
                    ApplicationUncPath="\\server\applications\UtilityApp5.exe                    
                    Click="ApplicationLauncherButton_Click">
                    <TextBlock>
                        Utility<LineBreak/>
                        Application 5</TextBlock>
                </local:ApplicationLauncherButton>
            </WrapPanel>
        </GroupBox>
    </StackPanel>
</Window>

Button.xaml (in the \Themes\Default folder of the project)

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

    <!-- Colors -->
    <Color x:Key="Default.LightGreen">#509248</Color>
    <Color x:Key="Default.DarkGreen">#014419</Color>
    <Color x:Key="Button.Static.Background.Color">#FFDDDDDD</Color>
    <Color x:Key="Button.Static.Border.Color">#FF707070</Color>
    <Color x:Key="Button.Static.Foreground.Color">#FFFFFF</Color>
    <Color x:Key="Button.MouseOver.Background.Color">#90D288</Color>
    <Color x:Key="Button.MouseOver.Border.Color">#FF707070</Color>
    <Color x:Key="Button.MouseOver.Foreground.Color">#EEEE00</Color>
    <Color x:Key="Button.Pressed.Background.Color">#307228</Color>
    <Color x:Key="Button.Pressed.Border.Color">#FF505050</Color>
    <Color x:Key="Button.Disabled.Background.Color">#FFF4F4F4</Color>
    <Color x:Key="Button.Disabled.Border.Color">#FFADB2B5</Color>
    <Color x:Key="Button.Disabled.Foreground.Color">#FF838383</Color>

    <!-- Brushes -->
    <SolidColorBrush x:Key="Default.LightGreenBrush" Color="{StaticResource Default.LightGreen}"/>
    <SolidColorBrush x:Key="Default.DarkGreenBrush" Color="{StaticResource Default.DarkGreen}"/>
    <SolidColorBrush x:Key="Button.Static.Background.Brush" Color="{StaticResource Default.LightGreen}" />
    <SolidColorBrush x:Key="Button.Static.Border.Brush" Color="{StaticResource Button.Static.Border.Color}"/>
    <SolidColorBrush x:Key="Button.Static.Foreground.Brush" Color="{StaticResource Button.Static.Foreground.Color}"/>
    <SolidColorBrush x:Key="Button.MouseOver.Background.Brush" Color="{StaticResource Button.MouseOver.Background.Color}"/>
    <SolidColorBrush x:Key="Button.MouseOver.Border.Brush" Color="{StaticResource Button.MouseOver.Border.Color}"/>
    <SolidColorBrush x:Key="Button.MouseOver.Foreground.Brush" Color="{StaticResource Button.MouseOver.Foreground.Color}"/>
    <SolidColorBrush x:Key="Button.Pressed.Background.Brush" Color="{StaticResource Button.Pressed.Background.Color}"/>
    <SolidColorBrush x:Key="Button.Pressed.Border.Brush" Color="{StaticResource Button.Pressed.Border.Color}"/>
    <SolidColorBrush x:Key="Button.Disabled.Background.Brush" Color="{StaticResource Button.Disabled.Background.Color}"/>
    <SolidColorBrush x:Key="Button.Disabled.Border.Brush" Color="{StaticResource Button.Disabled.Border.Color}"/>
    <SolidColorBrush x:Key="Button.Disabled.Foreground.Brush" Color="{StaticResource Button.Disabled.Foreground.Color}"/>

    <Style x:Key="FocusVisual">
        <Setter Property="Control.Template">
            <Setter.Value>
                <ControlTemplate>
                    <Rectangle Margin="2" StrokeDashArray="1 2" SnapsToDevicePixels="true" StrokeThickness="1" 
                                   Stroke="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

    <Style TargetType="{x:Type local:ApplicationLauncherButton}">
        <Setter Property="FocusVisualStyle" Value="{StaticResource FocusVisual}"/>
        <Setter Property="Background" Value="{StaticResource Button.Static.Background.Brush}"/>
        <Setter Property="BorderBrush" Value="{StaticResource Button.Static.Border.Brush}"/>
        <Setter Property="Foreground" Value="{StaticResource Button.Static.Foreground.Brush}"/>
        <Setter Property="BorderThickness" Value="1"/>
        <Setter Property="HorizontalContentAlignment" Value="Center"/>
        <Setter Property="VerticalContentAlignment" Value="Center"/>
        <Setter Property="Padding" Value="5"/>
        <Setter Property="Width" Value="125"/>
        <Setter Property="Height" Value="125"/>
        <Setter Property="Margin" Value="5"/>
        <Setter Property="FontSize" Value="14"/>
        <Setter Property="FontWeight" Value="Bold"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate x:Name="control" TargetType="{x:Type local:ApplicationLauncherButton}">
                    <Border x:Name="border" Background="{TemplateBinding Background}" BorderThickness="{TemplateBinding BorderThickness}" 
                                BorderBrush="{TemplateBinding BorderBrush}" SnapsToDevicePixels="true">
                        <TextBlock x:Name="textBlock" TextWrapping="Wrap" VerticalAlignment="Center" HorizontalAlignment="Center">
                                <ContentPresenter x:Name="contentPresenter" Focusable="False" RecognizesAccessKey="True" Visibility="Visible"/>
                        </TextBlock>
                    </Border>

                    <ControlTemplate.Triggers>
                        <Trigger Property="IsDefaulted" Value="true">
                            <Setter Property="BorderBrush" TargetName="border" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
                        </Trigger>
                        <Trigger Property="IsMouseOver" Value="true">
                            <Setter Property="Background" TargetName="border" Value="{StaticResource Button.MouseOver.Background.Brush}"/>
                            <Setter Property="BorderBrush" TargetName="border" Value="{StaticResource Button.MouseOver.Border.Brush}"/>
                            <Setter Property="TextElement.Foreground"  TargetName="contentPresenter" Value="{StaticResource Button.MouseOver.Foreground.Brush}"/>
                        </Trigger>
                        <Trigger Property="IsPressed" Value="true">
                            <Setter Property="Background" TargetName="border" Value="{StaticResource Button.Pressed.Background.Brush}"/>
                            <Setter Property="BorderBrush" TargetName="border" Value="{StaticResource Button.Pressed.Border.Brush}"/>
                            <Setter Property="TextElement.Foreground"  TargetName="contentPresenter" Value="{StaticResource Button.MouseOver.Foreground.Brush}"/>
                        </Trigger>
                        <Trigger Property="IsEnabled" Value="false">
                            <Setter Property="Background" TargetName="border" Value="{StaticResource Button.Disabled.Background.Brush}"/>
                            <Setter Property="BorderBrush" TargetName="border" Value="{StaticResource Button.Disabled.Border.Brush}"/>
                            <Setter Property="TextElement.Foreground" TargetName="contentPresenter" Value="{StaticResource Button.Disabled.Foreground.Brush}"/>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

</ResourceDictionary>

On a side note, part of the reason I've structured my window as I have is that I intend to move the application information to a data source, dynamically creating buttons and groups based upon the data. This will allow me to add / remove applications without having to modify the Application Launcher code.

CodePudding user response:

You must not mix plain text and Inline elements as content of the TextBlock.
Use a Run to display the text. Then bind the Run.Foreground property to the Button (or the parent TextBlock). See example below.

Also your button's template is wrong. You should not wrap the ContentPresenter into a TextBlock, especially when you add another TexBlock as value for the Button.Content. This will give you a TextBlock nested into a TextBlock:

<TextBlock> x:Name="TextBlockFromTemplate">
  <TextBlock x:Name="TextBlockToReplaceContentPresenter" />
</TextBlock>

Also bind the ContentPresenter.HorizontalAlignment to the templated parent's HorizontalContentAlignment (same for the ContentPresenter.VerticalAlignment). Don't set it explicitly as this would disconnect the Button.HorizontalContentAlignment from the template, making it useless.

You can use a Style that targets Run, and which you add to ResourceDictionary of the Style for the ApplicationLauncherButton, to avoid repeating code:

Generic.xaml

<Style TargetType="{x:Type local:ApplicationLauncherButton}">
  <Style.Resources>
    <Style TargetType="Run">
      <Setter Property="Foreground"
              Value="{Binding RelativeSource={RelativeSource AncestorType=Button}, Path=Foreground}" />
    </Style>
  </Style.Resources>

  <Setter Property="BorderThickness"
          Value="1" />
  <Setter Property="HorizontalContentAlignment"
          Value="Center" />
  <Setter Property="VerticalContentAlignment"
          Value="Center" />
  <Setter Property="Template">
    <Setter.Value>
      <ControlTemplate x:Name="control"
                       TargetType="{x:Type local:ApplicationLauncherButton}">
        <Border x:Name="border"
                Background="{TemplateBinding Background}"
                BorderThickness="{TemplateBinding BorderThickness}"
                BorderBrush="{TemplateBinding BorderBrush}"
                SnapsToDevicePixels="true">
          <ContentPresenter x:Name="contentPresenter"
                            HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                            VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
        </Border>

        <ControlTemplate.Triggers>
          <Trigger Property="IsMouseOver"
                    Value="True">
            <Setter Property="Background"
                    TargetName="border"
                    Value="Orange" />
            <Setter Property="BorderBrush"
                    TargetName="border"
                    Value="Red" />
            <Setter Property="Foreground"
                    Value="White" />
          </Trigger>
        </ControlTemplate.Triggers>
      </ControlTemplate>
    </Setter.Value>
  </Setter>
</Style>

Example

<local:ApplicationLauncherButton>
  <TextBlock>
    <Run>Designer</Run>
    <LineBreak />
    <Run>Application 4</Run>
  </TextBlock>
</local:ApplicationLauncherButton>
  • Related