locked
TransformBounds and RenderTransformOrigin RRS feed

  • Question

  • I have a problem using TransformBounds and RenderTransformOrigin. The Left en Top attributes of the returned Rect are not correct. Especially when resizing or rotating the object.  I am working with .Net 4.0 Beta 2.

    This is the code (I use the dummyRectangle to visualize the returned Rect object):

    <Window x:Class="TouchTransformIssue.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="Touch Transform Issue" Height="600" Width="800"
            >
        <Canvas x:Name="LayoutRoot" ManipulationStarting="LayoutRoot_ManipulationStarting" ManipulationDelta="LayoutRoot_ManipulationDelta">
            <Image Source="/TouchTransformIssue;component/images/Koala.jpg" Width="200" x:Name="imageKoala" IsManipulationEnabled="True" RenderTransformOrigin="0.5,0.5">
                <Image.RenderTransform>
                    <MatrixTransform Matrix="1,0,0,1,200,100"/>
                </Image.RenderTransform>
            </Image>      
            <Rectangle x:Name="dummyRectangle" Width="100" Height="100" Stroke="Black" StrokeThickness="3" Canvas.Left="500"></Rectangle>
        </Canvas>
    </Window>
    The C#-code of the ManipulationDelta event handler (since this is a multitouch application):
            private void LayoutRoot_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
            {
                var element = e.OriginalSource as UIElement;
                var transform = element.RenderTransform as MatrixTransform;
                var matrix = (null == transform) ? Matrix.Identity : transform.Matrix;
    
                matrix.ScaleAt(e.DeltaManipulation.Scale.X, e.DeltaManipulation.Scale.Y, e.ManipulationOrigin.X, e.ManipulationOrigin.Y);
                matrix.RotateAt(e.DeltaManipulation.Rotation, e.ManipulationOrigin.X, e.ManipulationOrigin.Y);
                matrix.Translate(e.DeltaManipulation.Translation.X, e.DeltaManipulation.Translation.Y);
    
                element.RenderTransform = new MatrixTransform(matrix);
    
                Rect check = element.RenderTransform.TransformBounds(new Rect(element.RenderSize));
    
                dummyRectangle.Width = check.Width;
                dummyRectangle.Height = check.Height;
                Canvas.SetLeft(dummyRectangle, check.Left);
                Canvas.SetTop(dummyRectangle, check.Top);
                
                e.Handled = true;
            }

    If I omit the RenderTransformOrigin it seems to work, but this is a dummy project; in my real project I need to specify the RenderTransformOrigin. And even then, when I remove the RenderTransformOrigin it seems it doesn't work always.

    Is there anybody who can help?


    Jim
    Thursday, February 18, 2010 2:29 PM

Answers

  • The problem is that TransformBounds is a method on the Transform - it just applies the transform to a rectangle.  RenderTransformOrigin is set on the UIElement itself, not on the Transform.  There are a couple options you can try:

    1) You can just handle the RenderTransformOrigin explicitly.  All it does, effectively, is add a TranslateTransform to your element before and after its current RenderTransform.  You can calculate the amount to translate by multiplying the RenderTransformOrigin x and y by the element's RenderSize width and height.  Then, when calculating your bounding rectangle, transform the rect through the first translate, the actual render transform, then the translate back.

    2) The better solution is to use VisualTreeHelper, since it will account for all the other properties on your element, not just the transform (like Clip, Effect, etc).  You'd want to call VisualTreeHelper.GetDescendentBounds(yourElement) to get its "local space" bounds, which included the bounds of all its content and children before any transform has been applied.  Then you want to call yourElement.TransformToAncestor(yourElement's parent), which will give you a transform from your local space to your parent's local space, including all the transforms, etc. on your element.  Apply that transform to your descendent bounds and I believe you'll have the bounds you're looking for.
    • Proposed as answer by Brendan Clark - MSFT Thursday, February 18, 2010 10:17 PM
    • Marked as answer by zephyr2b Friday, February 19, 2010 9:12 AM
    Thursday, February 18, 2010 10:17 PM

All replies

  • The problem is that TransformBounds is a method on the Transform - it just applies the transform to a rectangle.  RenderTransformOrigin is set on the UIElement itself, not on the Transform.  There are a couple options you can try:

    1) You can just handle the RenderTransformOrigin explicitly.  All it does, effectively, is add a TranslateTransform to your element before and after its current RenderTransform.  You can calculate the amount to translate by multiplying the RenderTransformOrigin x and y by the element's RenderSize width and height.  Then, when calculating your bounding rectangle, transform the rect through the first translate, the actual render transform, then the translate back.

    2) The better solution is to use VisualTreeHelper, since it will account for all the other properties on your element, not just the transform (like Clip, Effect, etc).  You'd want to call VisualTreeHelper.GetDescendentBounds(yourElement) to get its "local space" bounds, which included the bounds of all its content and children before any transform has been applied.  Then you want to call yourElement.TransformToAncestor(yourElement's parent), which will give you a transform from your local space to your parent's local space, including all the transforms, etc. on your element.  Apply that transform to your descendent bounds and I believe you'll have the bounds you're looking for.
    • Proposed as answer by Brendan Clark - MSFT Thursday, February 18, 2010 10:17 PM
    • Marked as answer by zephyr2b Friday, February 19, 2010 9:12 AM
    Thursday, February 18, 2010 10:17 PM
  • Yes, yes, yes! This is exactly what I was looking for. Thank you very much for your help and your very quick response.

    I will spread the word ;-)
    Friday, February 19, 2010 9:26 AM
  • option 1) would also be the solution to my problem, but I'm having some difficulty working out the TranslateTransform required:

    private Rect GetBoundingBox(FrameworkElement f)
    {
    Point location = new Point((double)f.GetValue(Canvas.LeftProperty), (double)f.GetValue(Canvas.TopProperty));
    Rect r = new Rect(location, f.DesiredSize);
    TranslateTransform t1 = new TranslateTransform(f.RenderTransformOrigin.X * f.DesiredSize.Width, f.RenderTransformOrigin.Y * f.DesiredSize.Height);
    r.Transform(t1.Value);
    Matrix m = f.RenderTransform.Value;
    r.Transform(m);
    r.Transform(t1.Value);
    return r;
    }

    Do you use the same TranslateTransform before as after? (my gut instinct is NO - but I don't know what it should be!)

    Tuesday, September 13, 2011 8:29 AM