使用模板控制自定义按钮的文本格式、换行符和颜色

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

有人要求我构建一个相对简单的应用程序启动器程序(以替换旧的基于 DOS 的菜单系统)。我在 WPF 中构建了应用程序,并创建了一个自定义的 class 派生自名为“ApplicationLauncherButton”的按钮。

我希望所有按钮都具有相同的“外观和感觉”,因此我决定它们的样式都相同。因此,我决定用我要创建的模板替换控件模板。为了让我的主要 window XAML 尽可能干净,我创建了一个单独的资源字典文件来保存模板并在 app.xaml.

中引用它

这相当有效。但是,按钮上的某些文本很长,我想控制换行符出现的位置。通过将按钮文本包装在 TextBlock 中并在我需要的位置插入标签,可以相对轻松地完成此操作。不幸的是,这有一个副作用:不再应用我希望在鼠标悬停事件上发生的文本颜色更改。当按钮文本未包含在 TextBlock 中时,鼠标悬停格式会按预期工作,但我无法控制换行符出现的位置。

我花了几个小时试图弄清楚如何让它工作,但没有任何运气。我只使用 WPF 几个星期,所以可能有更简单的方法来完成我想做的事情。欢迎任何建议。

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(在项目的\Themes\Default文件夹中)

<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>

附带说明一下,我按原样构建 window 的部分原因是我打算将应用程序信息移动到数据源,根据数据动态创建按钮和组.这将允许我添加/删除应用程序而无需修改应用程序启动器代码。

不得将纯文本和 Inline 元素混合作为 TextBlock 的内容。
使用 Run 显示文本。然后将 Run.Foreground 属性 绑定到 Button(或父 TextBlock)。请参阅下面的示例。

你的按钮模板也是错误的。您不应将 ContentPresenter 包装到 TextBlock 中,尤其是当您添加另一个 TexBlock 作为 Button.Content 的值时。这会给你一个 TextBlock 嵌套到 TextBlock:

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

还将 ContentPresenter.HorizontalAlignment 绑定到模板化父项的 HorizontalContentAlignmentContentPresenter.VerticalAlignment 也是如此)。不要明确设置它,因为这会断开 Button.HorizontalContentAlignment 与模板的连接,使其无用。

您可以使用针对 RunStyle,并将其添加到 ApplicationLauncherButtonStyleResourceDictionary 中,以避免重复代码:

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>

例子

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