WPF 拿到控件的最初样式,修改获得焦点样式

背景:

我定义了按钮的圆角。但是我发现当点击按钮后,此时按钮获得焦点,按钮周围会有一圈包围,这样就完全看不出来我定义的圆角了。

我最开始尝试通过屏蔽焦点样式和禁止焦点:

( <Setter Property="FocusVisualStyle" Value="{x:Null}" />

<Setter Property="Focusable" Value="False" />)

去除这个包围。

但是我发现这种方法过于简单粗暴了,最好还是更改一下包围的样式,比如也让他带圆角。

那接下来就是研究如何替换这个包围了。

最开始有两个尝试:VisualStateManager 与 Adorner

但二者都存在问题:

VisualStateManager,这种方式要重写控件模板:

XML 复制代码
<Style x:Key="FuncButtonStyle" BasedOn="{StaticResource RadButtonStyle}" TargetType="telerik:RadButton">
    <Setter Property="Background" Value="Transparent" />
    <Setter Property="Margin" Value="5,0" />
    <Setter Property="Width" Value="75" />
    <Setter Property="Height" Value="75" />
    <Setter Property="CornerRadius" Value="8" />
    
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="telerik:RadButton">
                <Grid>
                    <VisualStateManager.VisualStateGroups>
                        <!-- CommonStates -->
                        <VisualStateGroup x:Name="CommonStates">
                            <VisualState x:Name="Normal" />
                            <VisualState x:Name="MouseOver">
                                <Storyboard>
                                    <ColorAnimation 
                                        Storyboard.TargetName="BackgroundBorder" 
                                        Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)"
                                        To="#E0E0E0" Duration="0"/>
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="Pressed">
                                <Storyboard>
                                    <ColorAnimation 
                                        Storyboard.TargetName="BackgroundBorder" 
                                        Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)"
                                        To="#CCCCCC" Duration="0"/>
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="Disabled">
                                <Storyboard>
                                    <DoubleAnimation 
                                        Storyboard.TargetName="ContentPanel" 
                                        Storyboard.TargetProperty="Opacity" 
                                        To="0.4" Duration="0"/>
                                </Storyboard>
                            </VisualState>
                        </VisualStateGroup>
                        
                        <!-- FocusStates:用你的四角圆弧替代 Telerik 的直角框 -->
                        <VisualStateGroup x:Name="FocusStates">
                            <VisualState x:Name="Unfocused" />
                            <VisualState x:Name="Focused">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames 
                                        Storyboard.TargetName="FocusCorner" 
                                        Storyboard.TargetProperty="Visibility">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="FocusWithin" />
                        </VisualStateGroup>
                    </VisualStateManager.VisualStateGroups>
                    
                    <Border x:Name="BackgroundBorder"
                            Background="{TemplateBinding Background}"
                            CornerRadius="{TemplateBinding CornerRadius}"/>
                    
                    <ContentPresenter x:Name="ContentPanel"
                                      Margin="{TemplateBinding Padding}"
                                      HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                                      VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                                      Content="{TemplateBinding Content}"
                                      ContentTemplate="{TemplateBinding ContentTemplate}"/>
                    
                    <!-- ✅ 四角圆弧焦点装饰 -->
                    <Rectangle x:Name="FocusCorner"
                               Margin="-5"
                               Stroke="#007ACC"
                               StrokeThickness="2"
                               StrokeDashArray="2, 30"
                               StrokeDashCap="Round"
                               RadiusX="8"
                               RadiusY="8"
                               IsHitTestVisible="False"
                               Visibility="Collapsed"
                               SnapsToDevicePixels="True"/>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

要完全覆盖掉之前的按压( <VisualState x:Name="Pressed">),悬浮(<VisualState x:Name="MouseOver">)这些状态定义,我不想有这么大改动。

使用Adorner需要后台编写C#代码,略显麻烦,而我只想在xaml中实现。

OK,上面的两种方法只是改造作用,我并没有发现那个包围圈到底是如何出现的。

我先查看了RadButton的代码:

从上面代码推断出:

  1. UpdateVisualStates 方法控制焦点状态
cs 复制代码
internal virtual void UpdateVisualStates(bool useTransitions)
{
    // ... 其他状态 ...
    
    // ✅ 这里:通过 VisualStateManager 切换到 Focused/Unfocused 状态
    if (this.IsFocused && this.IsEnabled)
        this.GoToState("Focused", useTransitions);   // ← 触发 Focused 状态
    else
        this.GoToState("Unfocused", useTransitions);
}
  1. 焦点状态只控制显示/隐藏

当按钮获得焦点时,UpdateVisualStates 会调用 GoToState("Focused", useTransitions),这会触发模板中定义的 Focused VisualState,通常会显示一个模板内预先定义好的矩形框 (比如名为 FocusVisualFocusBorder 的元素)。

  1. 没有使用 FocusVisualStyle

整个源代码中没有出现任何关于 FocusVisualStyle 的引用 。所以你在样式中设置 FocusVisualStyle="{x:Null}" 无效,必须要禁用Focus:Focusable = false,因为 Telerik 根本没打算用这个FocusVisualStyle属性。

既然控件的源代码都看了。让我们继续看一下按钮的基本样式,可以通过以下方法获取到原样式:

在控件上右键-编辑模板-编辑副本 ;

XML 复制代码
  <Style x:Key="RadButtonStyle1" TargetType="{x:Type telerik1:RadButton}">
      <Setter Property="helpers:ThemeHelper.MouseOverBrush" Value="{telerik1:VisualStudio2019Resource ResourceKey=MouseOverBrush}"/>
      <Setter Property="helpers:ThemeHelper.FocusBrush" Value="{telerik1:VisualStudio2019Resource ResourceKey=AccentFocusedBrush}"/>
      <Setter Property="helpers:ThemeHelper.PressedBrush" Value="{telerik1:VisualStudio2019Resource ResourceKey=AccentDarkBrush}"/>
      <Setter Property="BorderThickness" Value="1"/>
      <Setter Property="FontFamily" Value="{telerik1:VisualStudio2019Resource ResourceKey=FontFamily}"/>
      <Setter Property="FontSize" Value="{telerik1:VisualStudio2019Resource ResourceKey=FontSize}"/>
      <Setter Property="Foreground" Value="{telerik1:VisualStudio2019Resource ResourceKey=MarkerBrush}"/>
      <Setter Property="Background" Value="{telerik1:VisualStudio2019Resource ResourceKey=PrimaryBrush}"/>
      <Setter Property="BorderBrush" Value="{telerik1:VisualStudio2019Resource ResourceKey=BasicBrush}"/>
      <Setter Property="Padding" Value="5 3"/>
      <Setter Property="FocusVisualStyle" Value="{x:Null}"/>
      <Setter Property="HorizontalContentAlignment" Value="Center"/>
      <Setter Property="VerticalContentAlignment" Value="Center"/>
      <Setter Property="Template">
          <Setter.Value>
              <ControlTemplate TargetType="{x:Type telerik1:RadButton}">
                  <Grid SnapsToDevicePixels="true">
                      <Border x:Name="BorderVisual" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="{TemplateBinding CornerRadius}"/>
                      <ContentPresenter x:Name="Content" ContentStringFormat="{TemplateBinding ContentStringFormat}" ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" ContentTemplateSelector="{TemplateBinding ContentTemplateSelector}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="true" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
                      <Border x:Name="FocusVisual" BorderBrush="{TemplateBinding}" BorderThickness="{TemplateBinding BorderThickness}" Opacity="0"/>
                  </Grid>
                  <ControlTemplate.Triggers>
                      <MultiTrigger>
                          <MultiTrigger.Conditions>
                              <Condition Property="IsBackgroundVisible" Value="false"/>
                              <Condition Property="IsEnabled" Value="true"/>
                              <Condition Property="IsMouseOver" Value="false"/>
                          </MultiTrigger.Conditions>
                          <Setter Property="Background" TargetName="BorderVisual" Value="Transparent"/>
                          <Setter Property="BorderBrush" TargetName="BorderVisual" Value="Transparent"/>
                      </MultiTrigger>
                      <MultiTrigger>
                          <MultiTrigger.Conditions>
                              <Condition Property="IsFocused" Value="true"/>
                              <Condition Property="IsEnabled" Value="true"/>
                              <Condition Property="IsMouseOver" Value="false"/>
                              <Condition Property="IsPressed" Value="false"/>
                          </MultiTrigger.Conditions>
                          <Setter Property="Opacity" TargetName="FocusVisual" Value="1"/>
                      </MultiTrigger>
                      <Trigger Property="IsMouseOver" Value="true">
                          <Setter Property="Background" TargetName="BorderVisual" Value="{Binding (helpers:ThemeHelper.MouseOverBrush), RelativeSource={RelativeSource Mode=TemplatedParent}}"/>
                          <Setter Property="BorderBrush" TargetName="BorderVisual" Value="{telerik1:VisualStudio2019Resource ResourceKey=AccentBrush}"/>
                      </Trigger>
                      <Trigger Property="IsPressed" Value="true">
                          <Setter Property="Foreground" Value="{telerik1:VisualStudio2019Resource ResourceKey=MarkerInvertedBrush}"/>
                          <Setter Property="Background" TargetName="BorderVisual" Value="{Binding (helpers:ThemeHelper.PressedBrush), RelativeSource={RelativeSource Mode=TemplatedParent}}"/>
                          <Setter Property="BorderBrush" TargetName="BorderVisual" Value="{Binding (helpers:ThemeHelper.PressedBrush), RelativeSource={RelativeSource Mode=TemplatedParent}}"/>
                      </Trigger>
                      <Trigger Property="IsEnabled" Value="false">
                          <Setter Property="Opacity" TargetName="Content" Value="{telerik1:VisualStudio2019Resource ResourceKey=DisabledOpacity}"/>
                          <Setter Property="Background" TargetName="BorderVisual" Value="{telerik1:VisualStudio2019Resource ResourceKey=PrimaryBrush}"/>
                          <Setter Property="BorderBrush" TargetName="BorderVisual" Value="{telerik1:VisualStudio2019Resource ResourceKey=ComplementaryBrush}"/>
                      </Trigger>
                      <MultiTrigger>
                          <MultiTrigger.Conditions>
                              <Condition Property="IsEnabled" Value="false"/>
                              <Condition Property="IsBackgroundVisible" Value="false"/>
                          </MultiTrigger.Conditions>
                          <Setter Property="Background" TargetName="BorderVisual" Value="Transparent"/>
                          <Setter Property="BorderBrush" TargetName="BorderVisual" Value="Transparent"/>
                      </MultiTrigger>
                  </ControlTemplate.Triggers>
              </ControlTemplate>
          </Setter.Value>
      </Setter>
  </Style>

可以看出,关于包围的显示逻辑:

  1. FocusVisual 元素
XML 复制代码
<Border x:Name="FocusVisual" BorderBrush="{TemplateBinding}" BorderThickness="{TemplateBinding BorderThickness}" Opacity="0"/>

这就是那个没有弧度的包围框 !它默认 Opacity="0"(隐藏),当按钮获得焦点时会显示。

  1. 控制焦点显示的触发器
XML 复制代码
<MultiTrigger>
    <MultiTrigger.Conditions>
        <Condition Property="IsFocused" Value="true"/>
        <Condition Property="IsEnabled" Value="true"/>
        <Condition Property="IsMouseOver" Value="false"/>
        <Condition Property="IsPressed" Value="false"/>
    </MultiTrigger.Conditions>
    <Setter Property="Opacity" TargetName="FocusVisual" Value="1"/>
</MultiTrigger>

当按钮获得焦点且没有悬停/按下时,FocusVisualOpacity 变为 1,显示那个直角边框。

OK,知道了这个包围显示的原理,那就可以从样式上替换掉它:

FocusVisual 替换成想要的自定义装饰:

XML 复制代码
<!-- 把原来的 FocusVisual 替换成这个 -->
<Rectangle x:Name="FocusCorner"
           Margin="-5"
           Stroke="#007ACC"
           StrokeThickness="2"
           StrokeDashArray="2, 2"
           StrokeDashCap="Round"
           RadiusX="8"
           RadiusY="8"
           IsHitTestVisible="False"
           SnapsToDevicePixels="True"
           Visibility="Collapsed"/>

然后修改触发器:

XML 复制代码
<MultiTrigger>
    <MultiTrigger.Conditions>
        <Condition Property="IsFocused" Value="true"/>
        <Condition Property="IsEnabled" Value="true"/>
        <Condition Property="IsMouseOver" Value="false"/>
        <Condition Property="IsPressed" Value="false"/>
    </MultiTrigger.Conditions>
    <Setter Property="Visibility" TargetName="FocusCorner" Value="Visible"/>
</MultiTrigger>
复制代码
在按钮上应用这个新样式:

左边按钮是新样式,右边按钮是老样式。

或者在外层显示一个带弧度的Border:

cs 复制代码
 <!-- 把原来的 FocusVisual 替换成这个 -->
<Border
     x:Name="FocusBorder"
     Margin="-3"
     BorderBrush="#007ACC"
     BorderThickness="2"
     CornerRadius="{TemplateBinding CornerRadius}"
     IsHitTestVisible="False"
     SnapsToDevicePixels="True"
     Visibility="Collapsed" />
相关推荐
主宰者23 分钟前
【WPF】MainWindow前添加加载窗口,发现加载过程中有白色无内容窗口闪烁一下的解决方案
wpf
Vae_Mars1 天前
WPF中的ControlTemplate(控件模板)
wpf
SamChan901 天前
Python+ReportLab自动生成PDF翻译质量审计报告:从数据到可视化的完整方案
开发语言·python·ai·pdf·wpf
circuitsosk1 天前
多智能体协同在能源数据分析中的实践:分配-执行-校验闭环架构设计
python·数据分析·wpf·能源·并行计算·多智能体系统·agent协作
weixin199701080162 天前
《大促护航:电商API限流与降级,双11峰值500万调用的架构复盘》(附Python源码)
python·架构·wpf
hh9503 天前
gent Plan x DeepSeek Harness:多 Agent 协作场景下的中央编排实践
人工智能·wpf·adg·火山引擎·agent plan·adg成都社区
Vae_Mars3 天前
WPF中的Lazy<T>使用方法
wpf
zQ.ii3 天前
WPF 触发器学习笔记
笔记·学习·wpf
SamChan903 天前
用PostgreSQL+pgvector构建PDF翻译记忆库:向量相似度检索+增量更新实战
数据库·python·ai·postgresql·pdf·wpf