Rotate element in steps of 45 degrees, always forward - wpf

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));
}
}

Related

What's the right way to create a XAML Border with repeating elements in Silverlight?

I'm trying to create a border with a blue background and repeating circles. As an example:
For the vertical portion, I'm using a vertical StackPanel in a Grid. A circle (overlapping a blue Rectangle) is declared in a ControlTemplate. To produce the repetition, I've copy-pasted a bunch of ContentControls, each of which points to my ControlTemplate.
For example:
<StackPanel
Grid.Row="0"
Grid.RowSpan="3"
Grid.Column="0"
Orientation="Vertical"
>
<ContentControl
attachedProperties:LightEllipseAttachedProperties.LightState="{Binding ElementName=PhoneApplicationPage, Path=GameController.Instance.Lights}"
Template="{StaticResource LightbulbTemplate}"
/>
**Repeat N times**
<ContentControl
attachedProperties:LightEllipseAttachedProperties.LightState="{Binding ElementName=PhoneApplicationPage, Path=GameController.Instance.Lights}"
Template="{StaticResource LightbulbTemplate}"
/>
</StackPanel>
<ControlTemplate
x:Key="LightbulbTemplate"
>
<Grid>
<VisualStateManager.VisualStateGroups>
</VisualStateManager.VisualStateGroups>
<Rectangle
Fill="#3300CC"
Height="15"
Width="15"
/>
<Ellipse
x:Name="LightEllipse"
Height="8"
Width="8"
>
<Ellipse.Fill>
<SolidColorBrush />
</Ellipse.Fill>
</Ellipse>
</Grid>
</ControlTemplate>
My question: Is there a better way to create a border of repeating elements using Silverlight? Perhaps Border has a Tiling capability so it will repeat the ControlTemplate itself, rather than me adding individual ContentControls?
If you want a simple method for building a shape like that, you could try using a Rectangle with a custom StrokeDashArray:
It was generated by this XAML code:
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0" Height="200">
<Rectangle StrokeThickness="14" StrokeDashCap="Round" Stroke="#FF00B2E6" />
<Rectangle StrokeDashArray="0.1 1.3"
StrokeThickness="10" StrokeDashCap="Round" Margin=".9" >
<Rectangle.Stroke>
<SolidColorBrush Color="#BFFF0606"/>
</Rectangle.Stroke>
</Rectangle>
</Grid>
The XAML uses 2 rectangles. One as the "background" color and the other for the circles. By experimenting with the StrokeDashArray values, you can control the shape of the dash and the distance to the next dash. By using a Round dash cap, and a small dash size (.1), it generates a shape that looks nearly round. You can experiment with the location of the Rectangles, Margins etc., to control the final look.
The nice part about using this technique is that it's extremely an efficient operation on the Phone to draw the shape and it will automatically resize to content as needed.
I've a slightly different proposal (not only in XAML):
Make Background your Border. I've managed to do something like this:
It works quite good and automatically fits to UIElement size, thought may need some time (not much) to load (but can be prepared when you start your App and then reused). I've done it via WritableBitmap - just rendered that many elements (the adventage is that any elements may be used - stars, tringles, even other images) that I need:
private WriteableBitmap CreateBorderBrush(int width, int height)
{
Rectangle firstBrush = new Rectangle();
firstBrush.Width = 15;
firstBrush.Height = 15;
firstBrush.Fill = new SolidColorBrush(Colors.Blue);
Ellipse secondBrush = new Ellipse();
secondBrush.Width = 8;
secondBrush.Height = 8;
secondBrush.Fill = new SolidColorBrush(Colors.Orange);
int dimensionX = width - width % 15;
int dimensionY = height - height % 15;
WriteableBitmap bitmapToBrush = new WriteableBitmap(dimensionX, dimensionY);
for (int i = 0; i < width / 15; i++)
{
bitmapToBrush.Render(firstBrush, new TranslateTransform() { X = i * 15, Y = 0 });
bitmapToBrush.Render(secondBrush, new TranslateTransform() { X = i * 15 + 3, Y = 3 });
}
for (int i = 1; i < height / 15 - 1; i++)
{
bitmapToBrush.Render(firstBrush, new TranslateTransform() { X = 0, Y = i * 15 });
bitmapToBrush.Render(secondBrush, new TranslateTransform() { X = 3, Y = i * 15 + 3 });
bitmapToBrush.Render(firstBrush, new TranslateTransform() { X = dimensionX - 15, Y = i * 15 });
bitmapToBrush.Render(secondBrush, new TranslateTransform() { X = dimensionX - 15 + 3, Y = i * 15 + 3 });
}
for (int i = 0; i < width / 15; i++)
{
bitmapToBrush.Render(firstBrush, new TranslateTransform() { X = i * 15, Y = dimensionY - 15 });
bitmapToBrush.Render(secondBrush, new TranslateTransform() { X = i * 15 + 3, Y = dimensionY - 15 + 3 });
}
bitmapToBrush.Invalidate();
return bitmapToBrush;
}
In MainPage constructor I've used it like this:
this.Loaded += (s, e) =>
{
myGrid.Background = new ImageBrush() { ImageSource = CreateBorderBrush((int)myGrid.ActualWidth, (int)myGrid.ActualHeight) };
};
And the XAML code:
<Grid Name="myGrid" Grid.Row="0" Width="300" Height="200">
<Button x:Name="first" Content="Button" Width="150" Height="100"/>
</Grid>
I would suggest creating a separate UserControl. The number of circles can be a Dependencyproperty of the UserControl. You can then use a ItemsControl to repeat the circles.

RotateTransform shifts my image as well as rotating it

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.

Dynamically adding and moving ellipses using WPF

I'm using WPF and having trouble dynamically/programatically adding ellipses to my grid.
I'm dynamically allocating and placing ellipses inside myGrid. Trouble is the position on the ellipses don't change. I'm using Canvas.SetLeft and SetTop, but the ellipses still seem stuck.
Here is the code for dynamic allocation :
{
...
Ellipse el = new Ellipse();
RadialGradientBrush b = new RadialGradientBrush();
b.RadiusX = r * 10.0f;
b.RadiusY = r * 10.0f;
b.GradientOrigin = new Point(0.5f, 0.5f);
b.GradientOrigin = new Point(0.5f, 0.5f);
b.GradientStops.Add(new GradientStop(Colors.Green, 0.0));
b.GradientStops.Add(new GradientStop(Colors.Blue, 1.0));
el.Width = 5.0f + r * 20.0f;
el.Height = 5.0f + r * 20.0f;
el.Stroke = b;
SetEllipsePosition(el, p);
this.myGrid.Children.Add(el);
...
}
private void SetEllipsePosition(FrameworkElement ellipse, Point j)
{
Canvas.SetLeft(ellipse, j.X);
Canvas.SetTop(ellipse, j.Y);
}
<Grid Height="480" Name="myGrid" Width="640">
<GroupBox Header="Pattern" Height="117" HorizontalAlignment="Left" Margin="10,564,0,0" Name="groupBox1" VerticalAlignment="Top" Width="238"></GroupBox>
<Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="33,30,0,0" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click_1" />
<Grid Margin="6,507,408,-121">
<RadioButton Content="Lines" Height="16" HorizontalAlignment="Left" Margin="15,18,0,0" Name="rbLines" VerticalAlignment="Top" GroupName="RenderStyles" />
<RadioButton Content="Circles" Height="16" HorizontalAlignment="Left" Margin="15,49,0,0" Name="rbCircles" VerticalAlignment="Top" GroupName="RenderStyles" />
</Grid>
</Grid>
The problem is that you are using a Grid but setting Canvas properties, you could add a Canvas into the Grid and draw you ellipses on the Canvas ( add them to the Canvases children), and then it would work.
Or you could use the Margin property of your ellipse to set it's position on the Grid
Canvas.Left and Canvas.Top are attached properties: you set them only if your UI element is going to be contained in a Canvas; and only when it is on a Canvas will those properties be used (by the Canvas layout manager). Same with attached properties from Grid (such as Grid.Column to tell the Grid parent in what column the UI elements "wants" to be), Panel (Panel.ZIndex to tell the Panel parent at what z index the UI element should be put), etc.

How to find the location of control on some upper level control?

How can i find the location of the 'SomeRectangle' ?
This Rectangle is the location and the size that i need to crop from the 'somepicture' that is actually the picture that appear in the background of the main grid.
<Rectangle x:Name="SomeRectangle" Height="50" Width="50" Stroke="Red" HorizontalAlignment="Center" VerticalAlignment="Center" MinWidth="5" MinHeight="5" />
GeneralTransform gt =
SomeRectangle.TransformToVisual(Application.Current.RootVisual as UIElement);
Point offset = gt.Transform(new Point(0, 0));
double controlTop = offset.Y;
double controlLeft = offset.X;
Source: Silverlight Forum

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 ?

Resources