1

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

DocGigawatts
  • 285
  • 6
  • 15

2 Answers2

2

I was able to track down the cause of your problem. It has to do with the Stretch="Uniform" Property setting on your VisualBrush. It appears the framework is computing a bounding rectangle on your VisuaBrush.Visual, and then stretching it to fit Border.Background. The following code should illustrate the behavior. I took out your inner_Ellipse and added an outer_Rectangle that should simulate the bounding rectangle being stretched:

<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="outer_Rectangle"
                            Storyboard.TargetProperty="(UIElement.RenderTransform).(RotateTransform.Angle)">
                                <LinearDoubleKeyFrame KeyTime="0:0:6" Value="360"/>
                            </DoubleAnimationUsingKeyFrames>
                        </Storyboard>
                    </BeginStoryboard>
                    <BeginStoryboard>
                        <Storyboard  RepeatBehavior="Forever" >
                            <DoubleAnimationUsingKeyFrames Storyboard.TargetName="outer_Ellipse"
                            Storyboard.TargetProperty="(UIElement.RenderTransform).(RotateTransform.Angle)">
                                <LinearDoubleKeyFrame KeyTime="0:0:6" 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>
                            <Rectangle x:Name="outer_Rectangle" 
                                 Stroke="Blue" StrokeThickness="1"   
                                 Width="20" Height="20" 
                                 RenderTransformOrigin="0.5,0.5">
                                <Rectangle.RenderTransform>
                                    <RotateTransform/>
                                </Rectangle.RenderTransform>
                            </Rectangle>
                        </Canvas>
                    </VisualBrush.Visual>
                </VisualBrush>
            </Border.Background>
        </Border>
    </StackPanel>
</Window>

As to solving the problem, I am not sure. One way would be to use Stretch="None" on your VisualBrush, but that doesn't seem ideal because it then falls on you to deal with the size of your VisualBrush.Visual contents.

Bojin Li
  • 5,769
  • 2
  • 24
  • 37
  • Yes, I was aware that switching off the stretch fixed it, assuming the inner contents aren't bigger than the object the visual is being applied to. Your example probably illustrates the problem better. Trouble is my 'content' for the brush is coming from datatemplates and stretch is the correct behavior depending on the size of the control being decorated. Basically I want the visual to 'clip' behind if it exceeds the bounding box of the border in this case. Thanks – DocGigawatts Mar 15 '12 at 20:44
0

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

DocGigawatts
  • 285
  • 6
  • 15