背景:

我定义了按钮的圆角。但是我发现当点击按钮后,此时按钮获得焦点,按钮周围会有一圈包围,这样就完全看不出来我定义的圆角了。
我最开始尝试通过屏蔽焦点样式和禁止焦点:
( <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的代码:


从上面代码推断出:
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);
}
- 焦点状态只控制显示/隐藏
当按钮获得焦点时,UpdateVisualStates 会调用 GoToState("Focused", useTransitions),这会触发模板中定义的 Focused VisualState,通常会显示一个模板内预先定义好的矩形框 (比如名为 FocusVisual 或 FocusBorder 的元素)。
- 没有使用 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>
可以看出,关于包围的显示逻辑:
FocusVisual元素
XML
<Border x:Name="FocusVisual" BorderBrush="{TemplateBinding}" BorderThickness="{TemplateBinding BorderThickness}" Opacity="0"/>
这就是那个没有弧度的包围框 !它默认 Opacity="0"(隐藏),当按钮获得焦点时会显示。
- 控制焦点显示的触发器
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>
当按钮获得焦点且没有悬停/按下时,FocusVisual 的 Opacity 变为 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" />
