none
Get bounding rectangle of rotated object

    Question

  • I'm trying to get the bounding rectangle of a rotated object on a canvas.  I tried this code:

     

    public Rect CalculateDragElementRect()

    {

    FrameworkElement element = elementSelected as FrameworkElement;

    GeneralTransform transform = element.TransformToAncestor(Helper.getWin());

    Point upperLeft = transform.Transform(new Point(0, 0));

    Point lowerRight = transform.Transform(new Point(element.ActualWidth, element.ActualHeight));

    return new Rect(upperLeft, lowerRight);

    }

     

    but the values are not correct.  I'm not sure if I am providing the correct parameter to TransformToAncestor which comes from this code in App():

     

    Window1 MainWindow = new Window1();

    Helper.setWin(MainWindow);

     

    also tried:

    public Rect CalculateDragElementRect()

    {

    UIElement depObj = elementSelected;

    if (depObj is Visual || depObj is Visual3D)

    return(VisualTreeHelper.GetContentBounds(depObj));

    }

     

    but returns null.

     

    Thanks

    Tom

     

    Friday, December 07, 2007 2:32 AM

Answers

  • You need to transform all 4 corners and construct the bounding box from the resulting min and max x and y values. Two corners arent enough.

     

    Anyway, GeneralTransform.TransformBounds()returns the smallest post-transform (axis-aligned) bounding box for a given pre-transform rectangle. Does that help?

     

    Regards,

    Nick.

     

     

     

     

    Friday, December 07, 2007 2:32 PM
  • You can use a generic function for this kind of transform:

    Code Block

    public Rect GetBounds(FrameworkElement of, FrameworkElement from)
    {

    // Might throw an exception if of and from are not in the same visual tree

    GeneralTransform transform = of.TransformToVisual(from);

    return transform.TransformBounds(new Rect(0, 0, of.ActualWidth, of.ActualHeight));

    }


    Passing  your Canvas as "from" and your element as "of" will do the same what your function does right now.
    Switch them and you will get the Canvas bounds relatively to your element
    Friday, December 07, 2007 11:17 PM

All replies

  • How do you rotate the object on the canvas?

    Do you use LayoutTransform/RenderTransform property on the object?

     

    I just have tried that and it seems to be working.

     

    Please note that there is LayoutTransform and RenderTransform transform the object differently.

     

    and VisualTreeHelper.GetContentBounds doen not work for me either - Lutz Roeder's Reflector shows that this function ALWAYS recturns Rect.Empty xD

     

    XAML

    <Window x:Class="SctiptParser.Window1"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    Title="Window1">

    <Grid x:Name="grid">

    <Grid.RowDefinitions>

    <RowDefinition Height="200" />

    <RowDefinition Height="Auto" />

    </Grid.RowDefinitions>

    <Canvas>

    <Button Click="OnClick" Content="Click me" x:Name="button"

    Canvas.Left="50" Canvas.Top="50">

    <Button.LayoutTransform>

    <RotateTransform Angle="20" />

    </Button.LayoutTransform>

    </Button>

    </Canvas>

    <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Row="1">

    <TextBlock Text="BoundRect: " />

    <TextBlock x:Name="bound"/>

    </StackPanel>

    </Grid>

    </Window>

     

    Code Behind

    private Rect GetBounds(Visual from) {

    GeneralTransform transform = button.TransformToAncestor(from);

    Point lt = transform.Transform(new Point());

    Point rb = transform.Transform(new Point(button.ActualWidth, button.ActualWidth));

    return new Rect(lt, rb);

    }

    private void OnClick(object sender, RoutedEventArgs e) {

    bound.Text = GetBounds(this).ToString();

    }

     

    Friday, December 07, 2007 5:10 AM
  • Anton,

     

    Thanks for your help.  It got me closer and it seems to be the correct approach, but it's not quite working for me yet, maybe I'm not yet adapting it to my specific case correctly.  It now returns the correct initial value but that value does not change when it's moved (no tranform yet), or rotated (with a RenderTransform).  BTW, I assume there is a typo and the line should read:

     

    Point rb = transform.Transform(new Point(button.ActualWidth, button.ActualHeight));

     

    instead of:

     

    Point rb = transform.Transform(new Point(button.ActualWidth, button.ActualWidth));

     

    I rotate the object using RenderTransform.  I tried changing it to LayoutTransform, but got some strange sizing behavior because I am using an image inside of a Grid and rotating the Grid.

     

    I changed your code to:

     

    private Rect GetBounds(Visual from)

    {

    FrameworkElement element = elementSelected as FrameworkElement;

    GeneralTransform transform = element.TransformToAncestor(from);

    Point lt = transform.Transform(new Point());

    Point rb = transform.Transform(new Point(element.ActualWidth, element.ActualHeight));

    return new Rect(lt, rb);

    }

     

    What seems strange to me is that "element" and "from" are the same object.

     

    I'm still working on this and will let you know if I get it working.

     

    Thanks again

    Tom

     

    Friday, December 07, 2007 1:44 PM
  • You need to transform all 4 corners and construct the bounding box from the resulting min and max x and y values. Two corners arent enough.

     

    Anyway, GeneralTransform.TransformBounds()returns the smallest post-transform (axis-aligned) bounding box for a given pre-transform rectangle. Does that help?

     

    Regards,

    Nick.

     

     

     

     

    Friday, December 07, 2007 2:32 PM
  • Yes that helps.  Thanks to both of you I was able to get this to work:

     

    public Rect CalculateElementRect(UIElement elementSelected )

    {

    FrameworkElement element = elementSelected as FrameworkElement;

    GeneralTransform transform = element.TransformToAncestor(this);

    Point lt = transform.Transform(new Point(0, 0));

    Point rb = transform.Transform(new Point(element.ActualWidth, element.ActualHeight));

    Point rt = transform.Transform(new Point(element.ActualWidth, 0));

    Point lb = transform.Transform(new Point(0, element.ActualHeight));

    double minX = Math.Min(Math.Min(lt.X, rt.X), Math.Min(lb.X, rb.X));

    double minY = Math.Min(Math.Min(lt.Y, rt.Y), Math.Min(lb.Y, rb.Y));

    double maxX = Math.Max(Math.Max(lt.X, rt.X), Math.Max(lb.X, rb.X));

    double maxY = Math.Max(Math.Max(lt.Y, rt.Y), Math.Max(lb.Y, rb.Y));

    return new Rect(minX, minY, (maxX - minX), (maxY - minY));

    }

     

    The "this" is the Canvas in which the object is a child of.

     

    I tried TransformBounds using lt and rb points, but it gave me a different answer for some reason.  I may have made a mistake.  I used:

     

    return transform.TransformBounds(new Rect(lt,rb));

     

    Now my problem is how to convert in the opposite direction. I want to convert cordinates in the bounding rectangle back to coordinates in the transformed object in order to set the Canvas.SetLeft and Canvas.SetRight correctly.  I'm basically dragging a tramsformed object and checking that it does not go outside of the canvas area.

     

    Thanks

    Tom

     

    Friday, December 07, 2007 11:05 PM
  • You can use a generic function for this kind of transform:

    Code Block

    public Rect GetBounds(FrameworkElement of, FrameworkElement from)
    {

    // Might throw an exception if of and from are not in the same visual tree

    GeneralTransform transform = of.TransformToVisual(from);

    return transform.TransformBounds(new Rect(0, 0, of.ActualWidth, of.ActualHeight));

    }


    Passing  your Canvas as "from" and your element as "of" will do the same what your function does right now.
    Switch them and you will get the Canvas bounds relatively to your element
    Friday, December 07, 2007 11:17 PM
  • Thanks,

     

    That's a cleaner implementation then then one I had.

     

    I didn't try to use it in the opposite direction (a little hard to conceptualize).  I came up with a simpler way to keep the dragging within the canvas boundery by taking the dfference between the normal Left returned from Canvas.GetLeft of the object and the bounding rectangle X  of the object like this:

     

    double normalLeft = DragCanvas.GetLeft(this.ElementBeingDragged);

    double normalTop = DragCanvas.GetTop(this.ElementBeingDragged);

    Rect trelem = GetBounds((FrameworkElement)this.ElementBeingDragged,(FrameworkElement)this);

    minX = normalLeft - (trelem.X);

    minY = normalTop - (trelem.Y);

     

    and then I used minX and minY to adjust the Canvas boundery accordingly e.g. 0,0 becomes minX,minY.

     

    Thanks

    Tom

     

     

    Saturday, December 08, 2007 6:19 PM
  • Tom,
    Can i get your mail id?
    I ve the same issue in my code.I tried your code.But i m not getting it working clear.If i send you a sample project, can you please go trough it?
    Wednesday, December 12, 2007 10:01 AM
  • I'm not a WPF expert (only used it part time for 6 weeks i.e. effectively 3 weeks), but I can try to help and if it's something that I've already done, it should'nt be too hard.

     

    tom@giammarresi.com

     

    Tom

     

     

    Wednesday, December 12, 2007 12:48 PM