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;
}
Related
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 need to create a control like this.
Click on this link to see the image
How to do that ?
I got some help to start with. But, still not sure how to do it.
In xaml I have something like this:
<Grid Background="White"
MouseUp="ParentOnMouseUp">
<Canvas x:Name="canvas"
Background="Green"
Width="200"
Height="200"
HorizontalAlignment="Center"
VerticalAlignment="Center"
MouseMove="CanvasOnMouseMove">
<Ellipse x:Name="dot"
Width="20"
Height="20"
Fill="Blue"
Loaded="DotOnLoaded"
MouseDown="DotOnMouseDown"/>
</Canvas>
In the code behind file I have the following code
private void DotOnMouseDown(object sender, MouseButtonEventArgs e)
{
_isDraggingDot = true;
}
private void CanvasOnMouseMove(object sender, MouseEventArgs e)
{
if (_isDraggingDot)
{
var mousePos = e.GetPosition(canvas);
var x = mousePos.X;
if (x < 0)
{
x = 0;
}
if (x > canvas.Width)
{
x = canvas.Width;
}
var y = mousePos.Y;
if (y < 0)
{
y = 0;
}
if (y > canvas.Height)
{
y = canvas.Height;
}
dot.SetValue(Canvas.LeftProperty, x - (dot.Width / 2.0)); // offset ensures dot is centred on mouse pointer
dot.SetValue(Canvas.TopProperty, y - (dot.Height / 2.0));
}
}
private void ParentOnMouseUp(object sender, MouseButtonEventArgs e)
{
_isDraggingDot = false;
//CentreDot();
}/
private void CentreDot()
{
dot.SetValue(Canvas.LeftProperty, (canvas.Width / 2.0) - (dot.Width / 2.0));
dot.SetValue(Canvas.TopProperty, (canvas.Height / 2.0) - (dot.Height / 2.0));
}
private void DotOnLoaded(object sender, RoutedEventArgs e)
{
CentreDot();
}
I am not able to draw the horizontal scale of -180 to +180 and the vertical scale of -30 to +30. Also, once the dot is moved to a specific point. I need the co-ordinates of that point.
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;
}
I have these three functions to trigger the events. I already have a static version of my needs, but I need a dynamically version of it.
bool captured = false;
double x_shape, x_canvas, y_shape, y_canvas;
UIElement source = null;
private void MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
source = (UIElement)sender;
Mouse.Capture(source);
captured = true;
x_shape = Canvas.GetLeft(source);
x_canvas = e.GetPosition(canvasPreview).X;
y_shape = Canvas.GetTop(source);
y_canvas = e.GetPosition(canvasPreview).Y;
}
private void MouseMove(object sender, MouseEventArgs e)
{
//MessageBox.Show("test");
if (captured)
{
double x = e.GetPosition(canvasPreview).X;
double y = e.GetPosition(canvasPreview).Y;
x_shape += x - x_canvas;
Canvas.SetLeft(source, x_shape);
x_canvas = x;
y_shape += y - y_canvas;
Canvas.SetTop(source, y_shape);
y_canvas = y;
}
}
private void MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
Mouse.Capture(null);
captured = false;
}
I have made a canvas in WPF called 'canvasPreview', I want to add the rectangle (currently in the static version I am using an ellipse) to the canvas, it must be draggable with above functions. It is already working, but it have to be dynamically.
I hope you can help me, thank you in advance!
I'm sure this sample code will help you.
XAML:
<Grid Margin="12">
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal">
<Button x:Name="addRectangleButton" Content="Add Rectngle" Click="addRectangleButton_Click"/>
</StackPanel>
<Canvas Grid.Row="1" x:Name="canvas" Margin="0,12,0,0">
<Rectangle x:Name="rectangle" Width="100" Height="50" Fill="RoyalBlue" MouseDown="rectangle_MouseDown" MouseMove="rectangle_MouseMove" MouseUp="rectangle_MouseUp" Canvas.Left="0" Canvas.Top="0"/>
</Canvas>
</Grid>
C#:
bool drag = false;
Point startPoint;
public MainWindow()
{
InitializeComponent();
}
// this creates and adds rectangles dynamically
private void addRectangleButton_Click(object sender, RoutedEventArgs e)
{
// create new Rectangle
Rectangle rectangle = new Rectangle();
// assign properties
rectangle.Width = 100;
rectangle.Height = 50;
rectangle.Fill = new SolidColorBrush(Colors.RoyalBlue);
// assign handlers
rectangle.MouseDown += rectangle_MouseDown;
rectangle.MouseMove += rectangle_MouseMove;
rectangle.MouseUp += rectangle_MouseUp;
// set default position
Canvas.SetLeft(rectangle, 0);
Canvas.SetTop(rectangle, 0);
// add it to canvas
canvas.Children.Add(rectangle);
}
private void rectangle_MouseDown(object sender, MouseButtonEventArgs e)
{
// start dragging
drag = true;
// save start point of dragging
startPoint = Mouse.GetPosition(canvas);
}
private void rectangle_MouseMove(object sender, MouseEventArgs e)
{
// if dragging, then adjust rectangle position based on mouse movement
if (drag)
{
Rectangle draggedRectangle = sender as Rectangle;
Point newPoint = Mouse.GetPosition(canvas);
double left = Canvas.GetLeft(draggedRectangle);
double top = Canvas.GetTop(draggedRectangle);
Canvas.SetLeft(draggedRectangle, left + (newPoint.X - startPoint.X));
Canvas.SetTop(draggedRectangle, top + (newPoint.Y - startPoint.Y));
startPoint = newPoint;
}
}
private void rectangle_MouseUp(object sender, MouseButtonEventArgs e)
{
// stop dragging
drag = false;
}
I'm quite new to WPF so I'm learning most of this the hard (but fun) way. I'm constructing a HSV-like colorpicker usercontrol, and would like to obtain the behaviour were the thumb I use as the "selector" is limited to an elliptic area (circle actually). When moved outside, the selector should stick to the side and not move at all. I believe this is the most common GUI-behaviour, so that is how it should behave. Feel free to suggest a better behaviour!
Is there a common, known and recommended solution to this or is it up to everyone to re-invent the wheel each time?
Any good ideas of how to solve this?
Code-behind:
public partial class HSVColorPicker : UserControl
{
public HSVColorPicker()
{
InitializeComponent();
}
void onDragDelta(object sender, DragDeltaEventArgs e)
{
Canvas.SetLeft(thumb, Canvas.GetLeft(thumb) + e.HorizontalChange);
Canvas.SetTop(thumb, Canvas.GetTop(thumb) + e.VerticalChange);
}
}
XAML:
<Grid>
<Canvas x:Name="canvas">
<Image x:Name="wheel" Source="colorwheel.png" Width="300" Margin="5,5,0,0"/>
<Thumb Name="thumb" DragDelta="onDragDelta" Canvas.Left="104" Canvas.Top="68" Template="{StaticResource thumbTemplate}" />
</Canvas>
</Grid>
While I'm in here, the thumb is always dragging behind the cursor, is there another way to create this? As I said, I'm new to WPF and GUI-making altogether so maybe there are obvious solutions that haven't occurred to me ;)
I did some rethinking and dropped the Thumb completely, using a dummy circle (named thumb) instead. Now im listening to, mousedown, mouseup and mousemove on the canvas and determine what should be possible and not. This has the nice feature that the thumb sticks to the colorwheel edge when the mouse goes outside the area, but the area is a bit larger than the colorwheel, to make it easy to get a point on the border. Not complete but it solved my question so I post it as it is at this moment.
private bool mousePressed { get; set; }
private bool mouseWithinArea { get; set; }
private Point circleMiddlePoint { get; set; }
private int margin;
private double mPX;
private double mPY;
private double localXpos;
private double globalXpos
{
get
{
return localXpos + mPX;
}
set
{
localXpos = value - mPX;
Canvas.SetLeft(thumb, value);
}
}
private double localYpos;
private double globalYpos
{
get
{
return mPY - localYpos;
}
set
{
localYpos = mPY - value;
Canvas.SetTop(thumb, value);
}
}
public HSVColorPicker()
{
InitializeComponent();
wheel.Width = 300;
margin = 15;
mPX = 150+margin;
mPY = 150+margin;
circleMiddlePoint = new Point(mPX, mPY);
}
private void CalcPosition(double X, double Y)
{
double radius = wheel.Width / 2.0;
double vectorX = X - mPX;
double vectorY = Y - mPY;
double distance = Math.Sqrt(vectorX * vectorX + vectorY * vectorY);
if (distance > radius)
{
double factor = radius / distance;
vectorX *= factor;
vectorY *= factor;
}
globalXpos = vectorX + mPX;
globalYpos = vectorY + mPY;
}
private void wheel_MouseDown(object sender, MouseButtonEventArgs e)
{
if (mouseWithinArea)
{
mousePressed = true;
Point mousePoint = e.GetPosition(this);
CalcPosition(mousePoint.X, mousePoint.Y);
}
}
private void wheel_MouseMove(object sender, MouseEventArgs e)
{
Point mousePoint = e.GetPosition(this);
double relX = mousePoint.X - mPX;
double relY = mPY - mousePoint.Y;
if (mouseWithinArea)
{
if (Math.Sqrt(relX * relX + relY * relY) > 150+margin)
{
mouseWithinArea = false;
}
else
{
if (mousePressed)
{
CalcPosition(mousePoint.X, mousePoint.Y);
}
}
}
else
{
if (Math.Sqrt(relX * relX + relY * relY) < 150+margin)
{
mouseWithinArea = true;
if (mousePressed)
{
CalcPosition(mousePoint.X, mousePoint.Y);
}
}
}
}
private void wheel_MouseUp(object sender, MouseButtonEventArgs e)
{
mousePressed = false;
}
}
<Canvas x:Name="canvas" Background="Transparent" MouseDown="wheel_MouseDown" MouseMove="wheel_MouseMove" MouseUp="wheel_MouseUp" Width="330" Height="330">
<Image x:Name="wheel" Source="colorwheel.png" Width="300" Margin="15,15,0,0" />
<Ellipse Margin="0,0,0,0"
x:Name="outerEll"
Stroke="Silver"
StrokeThickness="15"
Width="330"
Height="330"/>
<Ellipse Name="thumb" Stroke="Black" Fill="Silver" Canvas.Left="150" Canvas.Top="150" Width="15" Height="15" Margin="-12" />
</Canvas>
You want the center of your thumb to lay within your color wheel.
So, the distance between the center of your thumb and the center of your color wheel (that is, the center of your canvas) must be less than
or equal to the radius of your color wheel (that is, half the side of your canvas).
Untested c# code:
void onDragDelta(object sender, DragDeltaEventArgs e)
{
double radius = canvas.RenderSize.Width / 2.0;
double thumbCenterX = Canvas.GetLeft(thumb) - thumb.RenderSize.Width + e.HorizontalChange;
double thumbCenterY = Canvas.GetTop(thumb) - thumb.RenderSize.Height + e.VerticalChange;
double colorWheelCenterX = canvas.RenderSize.Width / 2.0;
double colorWheelCenterY = canvas.RenderSize.Height / 2.0;
double vectorX = thumbCenterX - colorWheelCenterX;
double vectorY = thumbCenterY - colorWheelCenterY;
double distance = Math.Sqrt(vectorX * vectorX + vectorY * vectorY);
if(distance > radius) {
double factor = radius / distance;
vectorX *= factor;
vectorY *= factor;
}
Canvas.SetLeft(thumb, colorWheelCenterX + vectorX - thumb.RenderSize.Width / 2.0);
Canvas.SetTop(thumb, colorWheelCenterY + vectorY - thumb.RenderSize.Height / 2.0);
}