I want to rotate and afterwards move an image in c#. The image is in a Canvas . My problem is if you rotate the image with the following
private void Schiff_OnMouseWheel(object sender, MouseWheelEventArgs e)
{
Image _schiff = (Image)sender;
if (!_schiff.IsMouseCaptured) return;
Matrix _mat = _schiff.RenderTransform.Value;
Point _mouse = e.GetPosition(_schiff);
if (e.Delta > 0)
{
_mat.RotateAtPrepend(22.5, _mouse.X, _mouse.Y);
}
else
{
_mat.RotateAtPrepend(-22.5, _mouse.X, _mouse.Y);
}
MatrixTransform _mtf = new MatrixTransform(_mat);
_schiff.RenderTransform = _mtf;
}
or RotateTransform
double _angle = 0.0;
_angle += 22.5;
if (_angle == 360.0) _angle = 0.0;
RotateTransform _rotate = new RotateTransform(_angle, _schiff.Width / 2, _schiff.Height / 2);
_schiff.RenderTransform = _rotate;A
you just rotate the "picture", but not it's base. So if you want to move the image with Canvas.GetLeft/GetTop, it behaves like it is still not rotated. So if you set the Top/Left-corner, the actual corner of the rotated image isn't placed where I wanted it to be.
At https://wpf.2000things.com/2013/03/08/772-use-rendertransformorigin-to-change-center-point-for-rotation-transforms/, in the picture you can see what I mean. How can I possibly rotate the "base" with the actual image? I saw it is possible in WinForms, but (how) does it work in WPF?
Thanks in advance, if anything is unclear/wrong I will edit my question.
edit:
https://i.stack.imgur.com/tqhKw.png
You can see the two arrows. They are my images. I rotated them at the center with my above MouseWheelEvent. On the right site there is my movement tab. You can change the speed (the checkbox after "Geschwindigkeit") and then you can either turn left in one section (where - is 0°, | is 22.5° and || is 45° and a section is 69 points in the Canvas) or right and end up in a new location.
You should use coordinates relative to the Canvas for transforming the Image element.
With this Image in a Canvas
<Canvas x:Name="canvas">
<Image Width="100"
Source="C:\Users\Public\Pictures\Sample Pictures\Koala.jpg"
MouseLeftButtonDown="OnMouseLeftButtonDown"
MouseLeftButtonUp="OnMouseLeftButtonUp"
MouseMove="OnMouseMove"
MouseWheel="OnMouseWheel">
<Image.RenderTransform>
<MatrixTransform />
</Image.RenderTransform>
</Image>
</Canvas>
the code would look like shown below.
The important part is to use e.GetPosition(canvas) and not to set the child element's Canvas.Left, Canvas.Top and RenderTransformOrigin properties. All transformations are done with a single Matrix in its RenderTransform.
private Point? mousePos;
private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
((IInputElement)sender).CaptureMouse();
mousePos = e.GetPosition(canvas);
}
private void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
((IInputElement)sender).ReleaseMouseCapture();
mousePos = null;
}
private void OnMouseMove(object sender, MouseEventArgs e)
{
if (mousePos.HasValue)
{
var element = (UIElement)sender;
var transform = (MatrixTransform)element.RenderTransform;
var matrix = transform.Matrix;
var pos = e.GetPosition(canvas);
matrix.Translate(pos.X - mousePos.Value.X, pos.Y - mousePos.Value.Y);
transform.Matrix = matrix;
mousePos = pos;
}
}
private void OnMouseWheel(object sender, MouseWheelEventArgs e)
{
var element = (UIElement)sender;
var transform = (MatrixTransform)element.RenderTransform;
var matrix = transform.Matrix;
var pos = e.GetPosition(canvas);
matrix.RotateAt(e.Delta > 0 ? 22.5 : -22.5, pos.X, pos.Y);
transform.Matrix = matrix;
}
Related
I have a WPF application that suppresses all the mouse inputs so I want to create my own inputs using xaml/behavior for MapSui 3.02. I've added the following method into my ViewModel to allow the user to zoom.
public void MapControlOnMouseWheel(object sender, MouseWheelEventArgs e)
{
Easing easing = default;
if (e.Delta > 0)
{
MapControl.Navigator.ZoomIn(mousePosition, 200, easing);
}
if (e.Delta < 0)
{
MapControl.Navigator.ZoomOut(mousePosition, 200, easing);
}
}
This Zoom method works ok, but it's not as good as the out of the box MapSui Zoom. It's not as smooth, and if you zoom a little too fast it ignores inputs until it has finished the last zoom event.
The out of the box MapSui zoom will zoom faster if you scroll the mouse button faster.
How is this achieved?
For completeness, here is the panning Method which explains where mousePosition comes from in the above zooming Method.
public void MapControlOnMouseMove(object sender, MouseEventArgs e)
{
var screenPosition = e.GetPosition(MapControl);
mousePosition = new Point(screenPosition.X, screenPosition.Y);
Point worldPosition = MapControl.Viewport.ScreenToWorld(screenPosition.X, screenPosition.Y);
if (mouseMiddledown)
{
var panX = lastPosition.X - worldPosition.X;
var panY = lastPosition.Y - worldPosition.Y;
Point newCenter = new Point(lastCentre.X + panX, lastCentre.Y + panY);
MapControl.Navigator?.NavigateTo(newCenter, MapControl.Viewport.Resolution, 0);
lastPosition = new Point(worldPosition.X + panX, worldPosition.Y + panY);
lastCentre = newCenter;
}
else
{
lastPosition = worldPosition;
lastCentre = MapControl.Viewport.Center;
}
CoordsText = $"{worldPosition.X:F0}, {worldPosition.Y:F0}";
}
I have a canvas (OuterCanvas) within a canvas (InnerCanvas) and am handling zooming in an out as follows:
private ScaleTransform ScaleTransform = new ScaleTransform();
private void OuterCanvas_MouseWheel(Object sender, MouseWheelEventArgs e)
{
if (e.Delta >= 1)
{
ScaleTransform.ScaleX += 0.03;
ScaleTransform.ScaleY += 0.03;
}
else
{
ScaleTransform.ScaleX -= 0.03;
ScaleTransform.ScaleY -= 0.03;
}
ScaleTransform.CenterX = ActualWidth / 2;
ScaleTransform.CenterY = ActualHeight / 2;
InnerCanvas.RenderTransform = TransformGroup;
}
This zooms in and out quite okay at the centre of the canvas. I'd like to zoom in and out around the mouse pointer, so change the CenterX and CenterY lines as follows:
ScaleTransform.CenterX = e.GetPosition(this).X;
ScaleTransform.CenterY = e.GetPosition(this).Y;
Again this works satisfactory, however if I attempt to move the mouse during two zoom events (mouse wheel events), the whole canvas jumps rather drastically as the scale factor increases or decreases.
Demonstration of problem
This is the same issue as when the CenterX and CenterY properties are set to the ActualWidth/2 and ActualHeight/2 and the window size is changed.
What should the correct handling be of the CenterX and CenterY properties be to avoid this issue?
Here is a more or less complete solution, including the possibility to pan the inner Canvas:
<Canvas x:Name="outerCanvas"
MouseWheel="OnMouseWheel"
MouseMove="OnMouseMove"
MouseLeftButtonDown="OnMouseLeftButtonDown"
MouseLeftButtonUp="OnMouseLeftButtonUp">
<Canvas x:Name="innerCanvas"
Width="400" Height="400" Background="AliceBlue">
<Canvas.RenderTransform>
<MatrixTransform />
</Canvas.RenderTransform>
</Canvas>
</Canvas>
with these event handlers:
private double zoomScaleFactor = 1.1;
private Point? mousePos;
private void OnMouseWheel(object sender, MouseWheelEventArgs e)
{
var pos = e.GetPosition(outerCanvas);
var scale = e.Delta > 0 ? zoomScaleFactor : 1 / zoomScaleFactor;
var transform = (MatrixTransform)innerCanvas.RenderTransform;
var matrix = transform.Matrix;
matrix.ScaleAt(scale, scale, pos.X, pos.Y);
transform.Matrix = matrix;
}
private void OnMouseMove(object sender, MouseEventArgs e)
{
if (mousePos.HasValue)
{
var pos = e.GetPosition(outerCanvas);
var delta = pos - mousePos.Value;
var transform = (MatrixTransform)innerCanvas.RenderTransform;
var matrix = transform.Matrix;
matrix.Translate(delta.X, delta.Y);
transform.Matrix = matrix;
mousePos = pos;
}
}
private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (outerCanvas.CaptureMouse())
{
mousePos = e.GetPosition(outerCanvas);
}
}
private void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
outerCanvas.ReleaseMouseCapture();
mousePos = null;
}
I would like to rotate the border with inertia. Where do I leave something out?
MainPage.xaml:
<Grid>
<Border x:Name="ManipulationBorder" Width="200" Height="200" HorizontalAlignment="Center" VerticalAlignment="Center" Background="Red"/>
</Grid>
MainPage.xaml.cs:
private TransformGroup transforms;
private MatrixTransform previousTransform;
private CompositeTransform deltaTransform;
public MainPage()
{
this.InitializeComponent();
InitManipulationTransforms();
ManipulationBorder.ManipulationDelta += new ManipulationDeltaEventHandler(ManipulateMe_ManipulationDelta);
ManipulationBorder.ManipulationMode =
ManipulationModes.TranslateX |
ManipulationModes.TranslateY |
ManipulationModes.Rotate |
ManipulationModes.TranslateInertia |
ManipulationModes.RotateInertia;
}
private void InitManipulationTransforms()
{
transforms = new TransformGroup();
previousTransform = new MatrixTransform() { Matrix = Matrix.Identity };
deltaTransform = new CompositeTransform();
transforms.Children.Add(previousTransform);
transforms.Children.Add(deltaTransform);
ManipulationBorder.RenderTransform = transforms;
}
private void ManipulateMe_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
{
previousTransform.Matrix = transforms.Value;
// Center point for rotation
Point center = previousTransform.TransformPoint(new Point(e.Position.X, e.Position.Y));
deltaTransform.CenterX = center.X;
deltaTransform.CenterY = center.Y;
// Rotation
deltaTransform.Rotation = e.Delta.Rotation;
}
When I actually go to apply delta rotation in ManipulationDeltaRoutedEventArgs it does not work. Where am I wrong?
Thanks in advance.
When I actually go to apply delta rotation in ManipulationDeltaRoutedEventArgs it does not work. Where am I wrong?
The standard touch-based rotation requires two touch points or more:
For single touch rotation you need fix the center of deltaTransform firstly.
You need recalculate the Angle has been changed with single touch.
you will know the value of Angle = a1 - a2.
private void Right_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
{
var x = this.RightRotateTransform.CenterX - e.Position.X;
var y = this.RightRotateTransform.CenterY - e.Position.Y;
double a1 = Math.Atan(y / x);
double a2 = Math.Atan((e.Delta.Translation.Y - y) / (x - e.Delta.Translation.X));
this.RightRotateTransform.Angle += a1 - a2;
}
I'm trying to rotate a rectangle when scrolling slider
there are 2 steps: Click on a rectangle - Scroll the slider.
If I draw only one rectangle, everything is ok.
But when I draw 2 rectangles or more and start rotating, all of my rectangle are rotated together with same angle.
I have no idea about this.
Can anyone help me?
Thanks in advance!
Here is my code: (I found the code for rotating at another post in this page)
Shape _shape;
RotateTransform rt = new RotateTransform();
private void MyCanvas_MouseRightButtonDown(object sender, MouseButtonEventArgs)
{
/////////////////////////////////////////////////////
//for know which rectangle has been clicked
if (MyTransform_type == TRANSFORM_TYPE.ROTATE)
{
_shape = e.OriginalSource as Shape;
if (_shape != null)
{
_shape = (Shape)e.OriginalSource;
}
}
}
private void MyCanvas_MouseRightButtonUp(object sender, MouseButtonEventArgs e)
{
if (MyTransform_type != TRANSFORM_TYPE.NONE && MyTransform_type != TRANSFORM_TYPE.ROTATE)
{
if (_shape == null)
return;
//_shape.ReleaseMouseCapture();
Cursor = Cursors.Arrow;
}
}
private void sldRotate_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
if (_shape != null)
{
_shape.RenderTransform = rt;
_shape.RenderTransformOrigin = new Point(0.5, 0.5);
var da = new DoubleAnimation(rt.Angle, sldRotate.Value, new Duration(TimeSpan.FromSeconds(0.001)));
rt.BeginAnimation(RotateTransform.AngleProperty, da);
rt.Angle = sldRotate.Value;
}
}
Your mistake is that you create a single RotateTransform object and assign it to the different shapes. So after clicking on several rectangles, every rectangle has the same rotatetransform instance. If you now change the value of the rotatetransform, every rectangle will rotate...
In order to fix that you should change your sldRotate_ValueChanged method. Check if the current shape has already a rotatetransform. If not then create one, if yes adjust the rotatetransform...
Additionally, if you have such a small animation time, you can just leave it out:
private void sldRotate_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
if (_shape != null)
{
var rt = new RotateTransform();
rt.Angle = sldRotate.Value;
_shape.RenderTransform = rt;
_shape.RenderTransformOrigin = new Point(0.5, 0.5);
}
}
It looks like you are using the same RotateTransform named "rt" for all of your rectangles. The simpliest solution will be:
private void MyCanvas_MouseRightButtonDown(object sender, MouseButtonEventArgs)
{
if (MyTransform_type == TRANSFORM_TYPE.ROTATE)
{
_shape = e.OriginalSource as Shape;
//creating new RotateTransform
rt=new RotateTransform();
if (_shape != null)
{
_shape = (Shape)e.OriginalSource;
}
}
}
also you could do
_shape.RenderTransform = rt;
in MyCanvas_MouseRightButtonDown after
_shape = e.OriginalSource as Shape;
instead of doing it each time, when sldRotate_ValueChanged executes to avoid unneeded assignments.
I am trying to make it so that when the user holds down the left mouse button the slider control appears where the mouse is positioned and the user can move the slider control left/right. Once the left mouse button is removed it will pass the value into a method. I am completely stuck on this. I have the slider control built, with a default position (the position can vary).
I have tried the following code to get the slider control to move to the mouses position but it does not do anything:
private void Button_Click(object sender, RoutedEventArgs e)
{
TranslateTransform currentTransform = new TranslateTransform();
currentTransform = slider.RenderTransform as TranslateTransform;
currentTransform.X = Mouse.GetPosition(Deck_Door).X;
currentTransform.Y = Mouse.GetPosition(Deck_Door).Y;
slider.Visibility = Visibility.Visible;
}
Thanks in advance!
First you should capture mouse
then you should calculate mouse move distance
protected override void OnPreviewMouseLeftButtonDown(MouseButtonEventArgs e)
{
this.CaptureMouse();
//Show slider here
}
protected override void OnPreviewMouseLeftButtonUp(MouseButtonEventArgs e)
{
this.ReleaseMouseCapture();
//Hide slider here and get it's value
}
Point previousMousePosition;
protected override void OnPreviewMouseMove(MouseEventArgs e)
{
if (this.IsMouseCaptured)
{
Point point = e.GetPosition(this);
double d = point.X - previousMousePosition.X;
//you can use this value to change the slider's value
this.previousMousePosition = point;
}
}
I was able to do this fairly easily. I modified the slider code to include a name for the translateTransform:
<Slider x:Name="slider"
TickFrequency="1"
Value="1"
IsSnapToTickEnabled="True"
IsMoveToPointEnabled="True"
Minimum="0"
Maximum="10"
ValueChanged="slider_ValueChanged"
AutoToolTipPlacement="BottomRight"
Grid.Column="0" VerticalAlignment="Top" Margin="0,-3,51.5,0"
Thumb.DragCompleted="slider_DragCompleted" >
<Slider.RenderTransform>
<TranslateTransform x:Name="mySliderTransform" />
</Slider.RenderTransform>
</Slider>
Then I hooked a click event to a button and used the following code:
private void Button_Click(object sender, RoutedEventArgs e)
{
slider.Visibility = Visibility.Visible;
slider.Value = 1;
// get the mouse positions
string x = Mouse.GetPosition(this).X.ToString();
string y = Mouse.GetPosition(this).Y.ToString();
// convert the mouse position to a double
var X = Convert.ToDouble(x);
var Y = Convert.ToDouble(y);
// reset the slider transform and apply the coordinates of the mouse position.
mySliderTransform.X = 0;
mySliderTransform.Y = 0;
mySliderTransform.X = X - 20;
mySliderTransform.Y = Y;
}