[WPF Bug清单]之(11)——错位的RenderTransform动画
在WPF中制作位移类动画大致有3种方式,Margin、RenderTransform和LayoutTransform。虽然3者的效果略有不同,但是不少情况下3种方式可以通用。这时RenderTransform就以其优秀的平均性能成了3者的首选。因为RenderTransform不涉及Layout的调整,不会触发界面的重新布局(关于RenderTransform与LayoutTransform之间的不同可以参考这篇文章,由于某个众所周知的原因,要看这篇文章需要FQ,所以直接点是打不开的)。但是当你了解到RenderTransform所存在的Bug时,可能就需要考虑一番了。
我们都知道很多控件都有FocusVisualStyle,一般就是一个虚线框。RenderTransform的问题就在于,控件的FocusVisualStyle中的元素,不会随着控件本身一起被Transform。
Bug的重现过程如下图所示。
图1. 程序运行图
一个简单得不能再简单的程序。在空窗体上放一个Button,建立一个动画,当MouseEnter这个Button的时候,用RenderTransform把这个Button向右向下移动100个像素。代码如下。
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所示。
然后不要再动任何键了,把鼠标移到Button上面以触发动画。发现什么了?看图3。
图3. Focus被Button落下了
当鼠标再次MouseEnterButton时,虚框会被自动放回正确的位置。整个示例程序可以从这里下载。运行环境是.NET Framework 3.5 SP1。
初步判断,这个Bug与WPF对FocusVisualStyle的设计方式有关。这个虚框与Adorner Layer类似,并不属于这个Button,所以渲染时没有被当被Button的一部分。而RenderTransform是一种纯渲染时行为,所以虚框就被无视了。从逻辑上来讲似乎这个Bug很合理,不然会让Render的设计很复杂,本来Render一个控件只要关心控件自身就可以了,现在还要关心相关的其它控件。但是我相信以微软的水平,应该是有能力解决这个问题的。就像文字渲染模糊的问题,WPF刚出来就被人诟病,微软还振振有词地解释说这就是WPF的像素无关和理想渲染模型设计原则的体现。这不到了.NET 4.0最终还是提供了选项关闭WPF的理想渲染模型。
其实在WPF中类似这样的问题有很多,比如Validation Error Template、Popup Window都有类似的问题。没有为PopupWindow写一个专题是因为产生Bug的条件可以避免出现。但是对于这类,动画与Focus的组合,是很常见的情况。
而且这个问题从WPF的使用者而言是如此底层。到目前为止,我也只想到两个不是办法的办法。
1. 不显示FocusVisualStyle。
2. 不用RenderTransform。
其实这只是回避问题式的方案。不知大家有什么好的解决方案?
更新解决方案:
根据Curry的回复。目前已经的最佳解决方案是。在Storyboard的CurrentTimeInvalidated事件处理函数中加入下面的代码。
AdornerLayer.GetAdornerLayer(button).Update();
这样Button的行为就正确了。但是大量使用可能导致一些性能问题。当然如果只有几个控件应该是没有问题的。