RotateTransform shifts my image as well as rotating it - wpf

Ok, So I have an Image object that I want to rotate around its upper left corner. I setup a RotateTransform of 'X' degrees. My Image has the margin set to (400,400,0,0). This centers it on the canvas.
The problem is that when I apply different angles of rotation, the image shifts over to the right and down. Looking at posts, I now understand WHY this is happening. The rotate transform figures out a bounding rectangle so if you rotate the image, it will have a larger bounding rectangle. The Margin is applied to that bounding rectangle which results in the actual image shifting downward and to the right as the angle changes.
What I want is for the upper left corner of the image to always be at precisely (400,400) on my canvas and the image to rotate around that point.
I have done some calculations and can get it to behave by shifting the left and top margin, but can only get the calulations right in quadrant 1 and 4 (0-90 degrees) and (270-360 degrees) using the following equations:
For 0-90 degrees: (just have to alter the TOP part of the margin. Desired margin is (400,400,0,0)
Margin=(400-sin(angle)*Image.Height, 400, 0, 0);
For 270-360 degrees: (just have to alter the TOP part of the margin. Desired margin is (400,400,0,0)
Margin=(400, 400 + sin(angle)*Image.Width, 0, 0);
I have experimented using RotateTransform.Transform(point) and using Image.TranslatPoint(point) but cannot seem to get those to work.
Does anybody have any suggestions for me?
I guess if I could figure out what the binding rectangle of the image was at any given time, I could figure out the difference between it's width/height and my image width/height and use that to shift the margin appropriately.
I have put together a small app that will show my problem. When running, the app shows a red dot where I want the upper left of the rectangle to be. I simplified it to use a grid instead of an image. At the upper left of the screen, there is a textbox where you can specify the angle of rotation. To the right of the text box are two RepeatButtons that will increment and decrement the angle if you just click and hold them down. Notice how when you change the angle, the upper left corner of the grid shifts away from the red dot. I want it to act like you put a pin in the upper left corner, and rotated it about the pin with no shift from the dot that the corner starts on.
Thanks for your help.
Here's the code for the sample app:
xaml:
<Window x:Class="RenderTransformTester.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="900" Width="900">
<Canvas x:Name="_canvas">
<Grid x:Name="_gridImage" Width="400" Height="200" Canvas.Left="400" Canvas.Top="400" Background="Navy">
<Grid.LayoutTransform>
<RotateTransform Angle="45" CenterX="0" CenterY="0"/>
</Grid.LayoutTransform>
</Grid>
<Ellipse Fill="Red" Width="20" Height="20" Margin="390,390,0,0"/>
<TextBox x:Name="_textBoxAngle" Width="70" Height="30" TextChanged="_textBoxAngle_TextChanged" Text="0"/>
<RepeatButton x:Name="_buttonUp" Width="30" Height="15" Margin="70,0,0,0" Click="_buttonUp_Click"/>
<RepeatButton x:Name="_buttonDown" Width="30" Height="15" Margin="70,15,0,0" Click="_buttonDown_Click"/>
</Canvas>
</Window>
CodeBehind:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace RenderTransformTester
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow
{
static double _left = -1;
static double _top = -1;
public MainWindow()
{
InitializeComponent();
}
private void _textBoxAngle_TextChanged(object sender, TextChangedEventArgs e)
{
int angle;
int.TryParse(_textBoxAngle.Text, out angle);
angle += 720;
angle %= 360;
_gridImage.LayoutTransform = new RotateTransform(angle, 0, 0);
}
private void _buttonUp_Click(object sender, RoutedEventArgs e)
{
int angle;
int.TryParse(_textBoxAngle.Text, out angle);
angle++;
angle = angle % 360;
_textBoxAngle.Text = angle.ToString();
}
private void _buttonDown_Click(object sender, RoutedEventArgs e)
{
int angle;
int.TryParse(_textBoxAngle.Text, out angle);
angle--;
angle = angle % 360;
_textBoxAngle.Text = angle.ToString();
}
private static double RadiansToDegrees(double radians)
{
return radians * 180 / Math.PI;
}
private static double DegreesToRadians(double degrees)
{
return degrees * Math.PI / 180;
}
}
}

You want a TranslateTransform within a TransformGroup
<Grid>
<Grid.RenderTransform>
<TransformGroup>
<RotateTransform Angle="45" CenterX="0" CenterY="0" />
<TranslateTransform X="400" Y="400" />
</TransformGroup>
</Grid.RenderTransform>
</Grid>

Don't use Margin for this kind of positioning, use the attached Canvas properties, i.e. Canvas.Left, etc.
You could also use a TransformGroup and apply a TranslateTransform and a RotateTransform, presumably the rotation should be done before the translation.

Related

Why LayoutTransform do not change vertice coordinates (x1,y1) on rotation

I have a canvas which contains shapes (path) where i bind angle with rotatetransform in layout transform.
I generate MouseRightButtonUp on canvas when mouse is over the shape (generated by my path) then right click i rotate by 90 degree using Layout transform.
But it behaves like Render transform because the my binded canvas.left and canvas.top do not updates/change after rotation on right click of mouse over shape
whereas angle gets changed.
my code is here :
<Canvas Name="OuterCanvas" Background="Green" Height="700" Width="1000" >
<ItemsControl AllowDrop="True" x:Name="itemsCtrl" ItemsSource="{Binding ShapesCollection,Mode=TwoWay}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas ClipToBounds="True" IsItemsHost="True" Height="700" Width="1000" Background="Transparent" x:Name="dragCanvas">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseRightButtonUp">
<ie:CallMethodAction MethodName="MouseRightButtonUpEventHandler" TargetObject="{Binding}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Path x:Name="im" Canvas.Left="{Binding Path=Canvas_Left , Mode=TwoWay}"
Canvas.Top="{Binding Path=Canvas_Top , Mode=TwoWay}"
Fill="{Binding Fill}" Stroke="{Binding Stroke}"
StrokeThickness="{Binding StrokeThickness}"
Data="{Binding Path=Data,Mode=TwoWay}">
<Path.LayoutTransform>
<RotateTransform Angle="{Binding Path=Angle , Mode=TwoWay}"/>
</Path.LayoutTransform>
</Path>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding Path=Canvas_Left , Mode=TwoWay}" />
<Setter Property="Canvas.Top" Value="{Binding Path=Canvas_Top , Mode=TwoWay}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</Canvas>
the invoked method for rotation in viewmodel is:
public void MouseRightButtonUpEventHandler(object sender, MouseButtonEventArgs e)
{
e.Handled = true;
ElementBeingDragged = e.Source as UIElement;
if (ElementBeingDragged is System.Windows.Shapes.Path)
if (ElementBeingDragged != null)
{
if (ElementBeingDragged.AllowDrop)
{
var rotateTransform = ((System.Windows.Shapes.Path)ElementBeingDragged).LayoutTransform as RotateTransform;
if (rotateTransform == null)
{
(e.Source as FrameworkElement).LayoutTransform = new RotateTransform();
}
((System.Windows.Shapes.Path)ElementBeingDragged).LayoutTransform = rotateTransform;
((System.Windows.Shapes.Path)ElementBeingDragged).RenderTransformOrigin = new Point(0.5, 0.5);
rotateTransform.Angle += 90;
}
((System.Windows.Shapes.Path)ElementBeingDragged).UpdateLayout();//.LayoutUpdated();
isDragInProgress = false;
Mouse.Capture(null);
}
}
Why Canvas.Left and Canvas.Top never gets updated even after the change in the position of shapeon change of the angle?
(lets say rectangle has A(x1,y1) B(x2,y2) , c(x3,y3) D(x4,y4) then after 90 degree rotation it must be A(x2,y2) B(x3,y3) , c(x4,y4) D(x1,y1)
why the in my Model below Canvas.Left and Canvas.Top never updates ?
public class Shapes :ICloneable
{ private double angle;
public double Angle
{
get { return angle; }//Updates on rotation
set
{
angle = value;
RaisePropertyChanged("Angle");
}
}
public double canvas_Top { get; set; }
public double Canvas_Top
{
get { return canvas_Top; }//Never updates on rotation
set
{
canvas_Top = value;
RaisePropertyChanged("Canvas_Top");
}
}
private double canvas_Left;
public double Canvas_Left
{
get { return canvas_Left; } //Never updates on rotation
set
{
canvas_Left = value;
RaisePropertyChanged("Canvas_Left");
}
}
}
This behavior is expected in Rendertransform but why in this case LayoutTransform behave like Rendertransform because i have used LayOutTranform
A transform does not change the underlying coordinates or Control Properties. Think of it like applying a mask over the base Properties that calculates new positions or values but does not change the original.
For example: If i have a square with a side length of 2. I then apply a Scaling Tranform of 2. The original side length remains =2. The transform will calculate a new side length of 4 and display the square at a size of 4. If i remove the transform, the square would be the original size of 2.
To Compare to your 90 degree rotation example, While you are correct that the rendered values of x1,y1 and x2,y2 change, the underlying values do not. I.e. Visually you see them change, but the transform is what makes that visual change.
The difference between RenderTransform .vs. LayoutTransform primarily happens to WHEN these calculations happen (i.e. when do i multiply my square sides by 2, or rotate my rectangle by 90 degrees). Both will still not adjust the origin of your other properties. Look Here for a good explanation.
(Im sure there a more differences; but this is where i believe the confusion is happening)
If you want to actually adjust the Canvas.Top or Canvas.Left properties from a transform, you would need to apply the transform to those values and then set the Properties with the calculated value from your Transform.

Draw a circle in specified point inside a polygon

I have a polygon with points. I need to draw a circle in specified point inside a polygon. I tried Clip Property of ellipse but unable to get points of polygon.
XAML
<Border x:Name="boardDiagramBorder" BorderThickness="2.5" Margin="5" Background="Gray" >
<Grid x:Name="boardCanvas">
<Polygon x:Name="polyOutLine" ClipToBounds="True" Fill="Black"
Stroke="White"
StrokeThickness="2">
</Polygon>
</Grid>
</Border>
Code to draw Polygon
polyOutLine.Points.Add(new System.Windows.Point() { X = 0, Y = 0 });
polyOutLine.Points.Add(new System.Windows.Point() { X = 118900, Y = 0 });
polyOutLine.Points.Add(new System.Windows.Point() { X = 118900, Y = 62993 });
polyOutLine.Points.Add(new System.Windows.Point() { X = 0, Y = 62993 });
I need to draw a circle in the point 21004,-57874.
You can't draw with precise coordinates within a Grid. You have to use Canvas to draw shapes, including rectangles, circles, and polygons using coordinates.
UPDATE: I extend my answer based on your question in your comment and I put in here.
Depends on the location of your canvas, because the circle will always have the relative coordinates based on your canvas.
For example, if your canvas is located at coordinate of 0,0 from the top left, then your circle will be located in coordinate relative to your Canvas with no further offset. If the Canvas is not positioned at 0,0 then your drawing will be located with the offset from the location of your Canvas.
You can use Canvas.Top and Canvas.Left properties to set the circle center, Or you can use margin property
<Grid>
<Canvas>
<Ellipse x:Name="innerCircle" Width="100"
Height="100"
Fill="#FFF4F4F5"
Stroke="Black"
Canvas.Left="50"
Canvas.Top="0"
/>
<Polygon x:Name="polyOutLine"
Stroke="Purple"
StrokeThickness="2">
<Polygon.Fill>
<SolidColorBrush Opacity="0.4" Color="Blue" />
</Polygon.Fill>
</Polygon>
</Canvas>
</Grid>
To set circle center from code use follwing function
private void SetCircleCenter(int x,int y)
{
double radius = innerCircle.Width / 2;
innerCircle.Margin = new Thickness(x-radius, y-radius, 0, 0);
}

Rotate element in steps of 45 degrees, always forward

Let's say I want to animate rotate an UI element 45 degrees clockwise each time I press a button.
I have defined 8 visual states setting different RotateTransform values between them. So, whenever I press the button I move to the next visual state:
VisualStateManager.GoToElementState(MyElement, "Position2", True)
etc.
The problem is that when moving from the VisualState 8 to VisualState 1, the element rotates backward. Seems logical, since it is moving from 315º to 0º.
The question is, how could achieve to goal of always moving forward when pressing the button?
A basic example with animation could look like this:
<Grid>
<Rectangle Fill="Black" Width="100" Height="100" RenderTransformOrigin=".5,.5">
<Rectangle.RenderTransform>
<RotateTransform x:Name="rotation"/>
</Rectangle.RenderTransform>
</Rectangle>
<Button Content="Rotate" VerticalAlignment="Bottom" Click="Button_Click"/>
</Grid>
with this Button Click handler:
private void Button_Click(object sender, RoutedEventArgs e)
{
const double rotSpeed = 180; // °/s, i.e. 45° in 0.25 seconds
var newAngle = Math.Floor(rotation.Angle / 45 + 1) * 45; // integer multiple of 45°
var duration = TimeSpan.FromSeconds((newAngle - rotation.Angle) / rotSpeed);
rotation.BeginAnimation(
RotateTransform.AngleProperty, new DoubleAnimation(newAngle, duration));
}
I wrote a simple example for you in C#; VB should be very similar.
In your Main Window put a Rectangle and a Button. Then, set the RenderTransformOrigin for the Rectangle like in the Xaml sample.
In your code behind, declare your RotateTransform and attach it to the Rectangle as shown below. Everytime the Button is pressed, the Angle property of the RotateTransform is increased of 45.
#EDIT:
I like #Clemens formula to increase angle (it avoid overflows). I edited my answer.
XAML:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Rectangle x:Name="myRect" RenderTransformOrigin=".5,.5" Grid.Row="0" Width="100" Height="100" Fill="LightBlue" >
</Rectangle>
<Button Grid.Row="1" Width="100" Height="50" Margin="10" Click="Button_Click">Rotate</Button>
</Grid>
Window Code-Behind:
public partial class MainWindow : Window
{
private TransformGroup _transformGroup;
private RotateTransform _rotateTrsf;
public MainWindow()
{
InitializeComponent();
_transformGroup = new TransformGroup();
_rotateTrsf = new RotateTransform();
_transformGroup.Children.Add(_rotateTrsf);
SetupAnimation();
myRect.RenderTransform = _transformGroup;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
_rotateAnimation.To = Math.Floor(_rotateTrsf.Angle / 45 + 1) * 45;
_rotateTrsf.BeginAnimation(RotateTransform.AngleProperty, _rotateAnimation);
_rotateAnimation.From = _rotateAnimation.To;
}
private void SetupAnimation()
{
_rotateAnimation = new DoubleAnimation();
_rotateAnimation.From = 0.0;
_rotateAnimation.To = 45;
_rotateAnimation.Duration = new Duration(TimeSpan.FromSeconds(0.3));
}
}

Silverlight 4 - Am I using TransformToVisual() correctly?

I have just started to play around with Silverlight and I decided to do a small app in Visual Studio 2010. I am trying to find the current position of a usercontrol in a Canvas. Here is the XAML layout:
<Grid x:Name="LayoutRoot" Background="#FF141313">
<Grid.RowDefinitions>
<RowDefinition Height="39"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Button Opacity="0.5" Background="{x:Null}" BorderThickness="1" FontFamily="Courier New" Content="Align Images" Cursor="Hand" Name="buttonAlignImages" Click="buttonAlignImages_Click" Margin="45,8,0,11" HorizontalAlignment="Left" Width="84" />
<Button HorizontalAlignment="Left" Width="33" Opacity="0.5" Background="{x:Null}" BorderThickness="1" FontFamily="Courier New" Content="Home" Cursor="Hand" Margin="8,8,0,11"/>
<Canvas x:Name="ImageContainer" Margin="8" Grid.Row="1" Background="Black"/>
</Grid>
My usercontrol is added to the "ImageContainer" Canvas. One of the buttons in XAML is called "buttonAlignImages". When the user clicks this I basically want the images to be aligned in a specific manner. Anyways to do this I want to first get the position of the usercontrol embedded in the "ImageContainer". So here is the code when the button is clicked:
private void buttonAlignImages_Click(object sender, RoutedEventArgs e)
{
double margin = 5.0;
Point top_left = new Point(margin, margin);
Point top_right = new Point(ActualWidth - margin, margin);
Point bottom_left = new Point(5.0, ActualHeight - margin);
Point bottom_right = new Point(ActualWidth - margin, ActualHeight - margin);
foreach (UIElement element in ImageContainer.Children)
{
Photo singlePhoto = element as Photo;
if (singlePhoto != null)
{
// get the transform for the current photo as applicable to basically this visual
GeneralTransform gt = singlePhoto.TransformToVisual(ImageContainer);
// get the position on the root visual by applying the transform to the singlePhoto
Point singlePhotoTopLeft = gt.Transform(new Point(0, 0));
// now translate the position of the singlePhoto
singlePhoto.Translate(singlePhotoTopLeft.X - top_left.X, singlePhotoTopLeft.Y - top_left.Y);
}
}
}
public void Translate(double deltaX, double deltaY)
{
translateTransform.X += deltaX;
translateTransform.Y += deltaY;
}
The embedded photo usercontrol does move around but when I call gt.Transform(new Point(0,0)) it always gives me (0,0), so the resulting translation is only by 5 pixels. Why does this happen? Am I not using TransformToVisual() correctly?
It s been a while but if u cannot live on without the answer :) will u try
Point singlePhotoTopLeft = gt.Transform(new Point());
instead ?

WPF: how to make the (0,0) in center inside a Canvas

The WPF Canvas has a coordinate system starting at (0,0) at the top-left of the control.
For example, setting the following will make my control appear on the top-left:
<Control Canvas.Left="0" Canvas.Top="0">
How can I change it to the standard cartesian coordinates?
Basically:
(0,0) at center
flip Y
I noticed this post is similar, but it does not talk about translating the coordinate system. I tried adding a TranslateTransform, but I can't make it work.
There is no need to create a custom Panel. Canvas will do just fine. Simply wrap it inside another control (such as a border), center it, give it zero size, and flip it with a RenderTransform:
<Border>
<Canvas HorizontalAlignment="Center" VerticalAlignment="Center"
Width="0" Height="0"
RenderTransform="1 0 0 -1 0 0">
...
</Canvas>
</Border>
You can do this and everything in the canvas will still appear, except (0,0) will be at the center of the containing control (in this case, the center of the Border) and +Y will be up instead of down.
Again, there is no need to create a custom panel for this.
It was very easy to do. I looked at the original Canvas's code using .NET Reflector, and noticed the implementation is actually very simple. The only thing required was to override the function ArrangeOverride(...)
public class CartesianCanvas : Canvas
{
public CartesianCanvas()
{
LayoutTransform = new ScaleTransform() { ScaleX = 1, ScaleY = -1 };
}
protected override Size ArrangeOverride( Size arrangeSize )
{
Point middle = new Point( arrangeSize.Width / 2, arrangeSize.Height / 2 );
foreach( UIElement element in base.InternalChildren )
{
if( element == null )
{
continue;
}
double x = 0.0;
double y = 0.0;
double left = GetLeft( element );
if( !double.IsNaN( left ) )
{
x = left;
}
double top = GetTop( element );
if( !double.IsNaN( top ) )
{
y = top;
}
element.Arrange( new Rect( new Point( middle.X + x, middle.Y + y ), element.DesiredSize ) );
}
return arrangeSize;
}
}
You can simply change the Origin with RenderTransformOrigin.
<Canvas Width="Auto" Height="Auto"
HorizontalAlignment="Center"
VerticalAlignment="Center"
RenderTransformOrigin="0.5,0.5">
<Canvas.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleY="-1" ScaleX="1" />
</TransformGroup>
</Canvas.RenderTransform>
</Canvas>
The best thing is to write a custom Canvas, in which you can write ArrangeOverride in such a way that it takes 0,0 as the center.
Update : I had given another comment in the below answer (#decasteljau) I wont recommend deriving from Canvas, You can derive from Panel and add two Attached Dependancy properties Top and Left and put the same code you pasted above. Also doesnt need a constructor with LayoutTransform in it, And dont use any transform on the panel code use proper measure and arrange based on the DesiredSize of the panel So that you can get nice content resize behavior too. Canvas doesn't dynamically position items when the Canvas size changes.

Resources