Search code examples
wpfrotationvisualbrush

RenderTransform in WPF causing unexpected layout changes


I am trying to animate a number of shapes within a visualbrush but when I perform a rotation the shapes 'pulse'. I am assuming that as the shapes rotate the bounding boxes are forcing a layout pass. However since I am using a RenderTransform I wasn't expecting this to trigger layout changes.

This code illustrates the problem:

<Window x:Class="WpfApplication1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Height="200" Width="200">
<StackPanel>
    <Border BorderBrush="Red" BorderThickness="1"
            Height="100" Width="100">
        <Border.Triggers>
            <EventTrigger RoutedEvent="FrameworkElement.Loaded">
                <BeginStoryboard>
                    <Storyboard RepeatBehavior="Forever" >
                        <DoubleAnimationUsingKeyFrames Storyboard.TargetName="inner_Ellipse"
                            Storyboard.TargetProperty="(UIElement.RenderTransform).(RotateTransform.Angle)">
                            <LinearDoubleKeyFrame KeyTime="0:0:3" Value="-360"/>
                        </DoubleAnimationUsingKeyFrames>
                    </Storyboard>
                </BeginStoryboard>
                <BeginStoryboard>
                    <Storyboard  RepeatBehavior="Forever" >
                        <DoubleAnimationUsingKeyFrames Storyboard.TargetName="outer_Ellipse"
                            Storyboard.TargetProperty="(UIElement.RenderTransform).(RotateTransform.Angle)">                                             
                            <LinearDoubleKeyFrame KeyTime="0:0:3" Value="360"/>
                        </DoubleAnimationUsingKeyFrames>
                    </Storyboard>
                </BeginStoryboard>
            </EventTrigger>
        </Border.Triggers>
        <Border.Background>
            <VisualBrush Stretch="Uniform">
                <VisualBrush.Visual>                      
                    <Canvas Width="20" Height="20">
                        <Ellipse x:Name="outer_Ellipse" 
                                 Stroke="Blue" StrokeThickness="1"   
                                 Width="20" Height="20" 
                                 RenderTransformOrigin="0.5,0.5">
                            <Ellipse.RenderTransform>
                                    <RotateTransform/>
                            </Ellipse.RenderTransform>
                        </Ellipse>
                        <Ellipse  x:Name="inner_Ellipse" 
                                  Stroke="Red" StrokeThickness="1"
                                  Width="18" Height="18" 
                                  Margin="1,1,0,0"
                                  RenderTransformOrigin="0.5,0.5">
                            <Ellipse.RenderTransform>
                                    <RotateTransform/>
                            </Ellipse.RenderTransform>
                        </Ellipse>
                    </Canvas>
                </VisualBrush.Visual>
            </VisualBrush>
        </Border.Background>
    </Border>
</StackPanel>

This is a simple sample of a much more complicated application where I am using the Visual Brushes to decorate 2d planes being manipulated in 3d. It all works well until I try and animate the brushes. I have tried several different approaches but always seem to run into this layout issue.

Any suggestions appreciated.

Thanks

Rob


Solution

  • Well after a good deal of trial and error I have a working solution. The contents of the VisualBrush correctly scale and don't cause the bounding box problem:

    <Window x:Class="WpfApplication1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
        SizeToContent="WidthAndHeight" >
    <StackPanel>
        <Border BorderBrush="Black" BorderThickness="1"
            Height="400" Width="400">
            <Border.Triggers>
                <EventTrigger RoutedEvent="FrameworkElement.Loaded">
                    <BeginStoryboard>
                        <Storyboard RepeatBehavior="Forever" >
                            <DoubleAnimationUsingKeyFrames Storyboard.TargetName="inner_Ellipse"
                            Storyboard.TargetProperty="(UIElement.RenderTransform).(RotateTransform.Angle)">
                                <LinearDoubleKeyFrame KeyTime="0:0:3" Value="-360"/>
                            </DoubleAnimationUsingKeyFrames>
                        </Storyboard>
                    </BeginStoryboard>
                    <BeginStoryboard>
                        <Storyboard  RepeatBehavior="Forever" >
                            <DoubleAnimationUsingKeyFrames Storyboard.TargetName="outer_Ellipse"
                            Storyboard.TargetProperty="(UIElement.RenderTransform).(RotateTransform.Angle)">
                                <LinearDoubleKeyFrame KeyTime="0:0:3" Value="360"/>
                            </DoubleAnimationUsingKeyFrames>
                        </Storyboard>
                    </BeginStoryboard>
                </EventTrigger>
            </Border.Triggers>
    
            <Border.Background>
                <VisualBrush Stretch="UniformToFill">
                    <VisualBrush.Visual>
                        <Border BorderBrush="Transparent" BorderThickness="1"
                                Width="200" Height="200">
                            <StackPanel Width="200" Height="200">
                                <Canvas>
                                    <Rectangle x:Name="outer_Ellipse" 
                                            Stroke="Blue" StrokeThickness="1"   
                                            Width="20" Height="200" 
                                               Canvas.Left="40"
                                            RenderTransformOrigin="0.5,0.5">
                                        <Rectangle.RenderTransform>
                                            <RotateTransform/>
                                        </Rectangle.RenderTransform>
                                    </Rectangle>
                                    <Ellipse x:Name="inner_Ellipse" 
                                             Stroke="Red" StrokeThickness="1"
                                             StrokeDashArray="2"
                                             Canvas.Top="30"
                                             Canvas.Left="20"
                                            Width="200" Height="200" 
                                            RenderTransformOrigin="0.5,0.5">
                                        <Ellipse.RenderTransform>
                                            <RotateTransform/>
                                        </Ellipse.RenderTransform>
                                    </Ellipse>
                                </Canvas>
                            </StackPanel>
                        </Border>
                    </VisualBrush.Visual>
                </VisualBrush>
            </Border.Background>
        </Border>
    </StackPanel>
    </Window>
    

    The 'secret' appears to be wrapping the canvas containing the brush contents in a StackPanel and wrapping this in a Border. (a Grid, DockPanel and WrapPanel will also work in place of the StackPanel).

                            <Border BorderBrush="Transparent" BorderThickness="1"
                                Width="200" Height="200">
                            <StackPanel Width="200" Height="200">
    

    The Border must have a BorderBrush set and a BorderThickness. Also they must both have an explicit width and height. Here I have set values appropriate to the content so it scales correctly.

    Without all 3 of these components the bounding box issue re occurs. Clearly something about the layout policies of the containers makes a difference but I have no idea what or why.

    Not at all a satisfying solution and I would appreciate it if anyone can shine a light on what's going on here.

    At least it works, both in the above demo and my main application, so far anyway!

    Rob