[WPF Bug清单]之(11)——错位的RenderTransform动画

WPF中制作位移类动画大致有3种方式,MarginRenderTransformLayoutTransform。虽然3者的效果略有不同,但是不少情况下3种方式可以通用。这时RenderTransform就以其优秀的平均性能成了3者的首选。因为RenderTransform不涉及Layout的调整,不会触发界面的重新布局(关于RenderTransformLayoutTransform之间的不同可以参考这篇文章,由于某个众所周知的原因,要看这篇文章需要FQ,所以直接点是打不开的)。但是当你了解到RenderTransform所存在的Bug时,可能就需要考虑一番了。

我们都知道很多控件都有FocusVisualStyle,一般就是一个虚线框。RenderTransform的问题就在于,控件的FocusVisualStyle中的元素,不会随着控件本身一起被Transform

Bug的重现过程如下图所示。

1. 程序运行图

 

一个简单得不能再简单的程序。在空窗体上放一个Button,建立一个动画,当MouseEnter这个Button的时候,用RenderTransform把这个Button向右向下移动100个像素。代码如下。

Demo Code
 1 <Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 2     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 3     x:Class="AnimationConflict.MainWindow"
 4     x:Name="Window" Title="MainWindow"
 5     Width="200" Height="200">
 6     <Window.Resources>
 7         <Storyboard x:Key="OnMouseEnter">
 8             <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="button"
 9                 Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.X)">
10                 <SplineDoubleKeyFrame KeyTime="00:00:01" Value="100"/>
11             </DoubleAnimationUsingKeyFrames>
12             <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="button"
13                 Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.Y)">
14                 <SplineDoubleKeyFrame KeyTime="00:00:01" Value="100"/>
15             </DoubleAnimationUsingKeyFrames>
16         </Storyboard>
17     </Window.Resources>
18     <Window.Triggers>
19         <EventTrigger RoutedEvent="Mouse.MouseEnter" SourceName="button">
20             <BeginStoryboard Storyboard="{StaticResource OnMouseEnter}"/>
21         </EventTrigger>
22     </Window.Triggers>
23     <Grid x:Name="LayoutRoot">
24         <Button x:Name="button" RenderTransformOrigin="0.5,0.5"
25                 HorizontalAlignment="Left" VerticalAlignment="Top"
26                 Width="75" Content="Button">
27             <Button.RenderTransform>
28                 <TranslateTransform/>
29             </Button.RenderTransform>
30         </Button>
31     </Grid>
32 </Window>

  

    程序刚运行起来的时候Button是没有获得焦点的,我们按几下Tab键,让这个Button获得焦点。如图2所示。

2. Button得到了焦点

 

然后不要再动任何键了,把鼠标移到Button上面以触发动画。发现什么了?看图3

 

3. FocusButton落下了

 

当鼠标再次MouseEnterButton时,虚框会被自动放回正确的位置。整个示例程序可以从这里下载。运行环境是.NET Framework 3.5 SP1

 

初步判断,这个BugWPFFocusVisualStyle的设计方式有关。这个虚框与Adorner Layer类似,并不属于这个Button,所以渲染时没有被当被Button的一部分。而RenderTransform是一种纯渲染时行为,所以虚框就被无视了。从逻辑上来讲似乎这个Bug很合理,不然会让Render的设计很复杂,本来Render一个控件只要关心控件自身就可以了,现在还要关心相关的其它控件。但是我相信以微软的水平,应该是有能力解决这个问题的。就像文字渲染模糊的问题,WPF刚出来就被人诟病,微软还振振有词地解释说这就是WPF的像素无关和理想渲染模型设计原则的体现。这不到了.NET 4.0最终还是提供了选项关闭WPF的理想渲染模型。

 

其实在WPF中类似这样的问题有很多,比如Validation Error TemplatePopup Window都有类似的问题。没有为PopupWindow写一个专题是因为产生Bug的条件可以避免出现。但是对于这类,动画与Focus的组合,是很常见的情况。

 

而且这个问题从WPF的使用者而言是如此底层。到目前为止,我也只想到两个不是办法的办法。

1.       不显示FocusVisualStyle

2.       不用RenderTransform

其实这只是回避问题式的方案。不知大家有什么好的解决方案?

 

更新解决方案:

根据Curry的回复。目前已经的最佳解决方案是。在Storyboard的CurrentTimeInvalidated事件处理函数中加入下面的代码。

 

AdornerLayer.GetAdornerLayer(button).Update();

 

这样Button的行为就正确了。但是大量使用可能导致一些性能问题。当然如果只有几个控件应该是没有问题的。

 

原文地址:https://www.cnblogs.com/nankezhishi/p/RenderTransformAndFocus.html