I am trying to make an image rotate to follow the mouse position just like the ship from asteroids but to be controled with the mouse position instead of the arrow keys... help would be apreciated!
First you need to know the position of your image. You can then find the cursor position by the MouseMove event. This event invokes every time your mouse moves. You can use the GetPosition method to find a Point to obtain your X and Y coordinates.
private void Window_MouseMove(object sender, MouseEventArgs e)
{
Point point = e.GetPosition(null);
}
Now that you've obtained the X and Y coordinate you can use Pythagorean Theorem to find the distance between your image and where your cursor is. Now to find the angle you can AngleOfImage = sin^-1(Y/total distance between your image and cursor) to find the angle.
If you want the image to move after it has already rendered you need to use the RenderTransform property. Since you need the image to rotate you can make use of the RotateTransform class to accomplish this. Since you have calculated the angle and set it equal to a property, AngleOfImage, you can bind that property to the Angle dependency property of RotateTransform So your xaml would look something like this.
<Image>
<Image.RenderTransform>
<RotateTransform Angle="{Binding AngleOfImage}"/>
</Image.RenderTransform>
</Image>
Actual working code for MouseMove event to calculate the angle
private void Window_MouseMove(object sender, MouseEventArgs e)
{
var somePoint = e.GetPosition(mainWindow);
X = somePoint.X;
Y = somePoint.Y;
var newX = Abs(X - RectangleOriginX);
var newY = Abs(Y - RectangleOriginY);
var powX = Pow(newX, 2);
var powY = Pow(newY, 2);
var distance = Sqrt(powX + powY);
var result = newX / distance;
Angle = Asin(result).ToDegrees();
}
Where ToDegrees() is just an extension method to convert the value to degrees. RectangleOriginX and RectangleOriginY are the points of my control I am moving which I got from this
private void mainWindow_Loaded(object sender, RoutedEventArgs e)
{
var origialPoint = rect.TransformToAncestor(mainWindow).Transform(new Point(0, 0));
RectangleOriginX = origialPoint.X;
RectangleOriginY = origialPoint.Y;
}
Also my working xaml
<Rectangle x:Name="rect" Width="100" Height="100" RenderTransformOrigin="0.5,0.5" Fill="Red">
<Rectangle.RenderTransform>
<RotateTransform Angle="{Binding Angle}"/>
</Rectangle.RenderTransform>
</Rectangle>
Sorry for my bad english
Try to use System.Windows.Forms.Timer, and in the Timer_tick event call some Move(MouseEventArgs e).
Something like this
private void Move(MouseEventArgs e)
{
if(mainwindow has focus)
{
your_image.Position = NextPosition(e);
}
}
instance of MouseEventArgs is field of your class, which changes in MouseMove event.
In NextPosition you can use any pathfind algorithm
If I can understand you right
you can make it by some maths
Get the center point first
Get the mouse position
Use Math.ASin() to get the angle
It will be like that Math.ASin(x / d);
x will be mouse location - image center location
d will be the distance between the center of the image and mouse location you can get it by Pythagorean theorem
then use a timer to make that happen.
I hope that my answer will help you.
Related
I'd like to create a control in my WPF app that allows the user to drag a dot inside a box/circle. This will be used to drive the pan and tilt values for a camera.
I am not sure how to create a control like that. The picture below is an example of the type of control that I want to develop.
Here's a very quick solution to get you started.
For the XAML, I've used an Ellipse control for the "dot". The Ellipse is placed inside a Canvas control (which allows the dot to be moved around):-
<Grid Background="White"
MouseUp="ParentOnMouseUp">
<Canvas x:Name="canvas"
Background="Green"
Width="200"
Height="200"
HorizontalAlignment="Center"
VerticalAlignment="Center"
MouseMove="CanvasOnMouseMove">
<!-- Implement your blue circle b/g as an Image control here ... />
<Ellipse x:Name="dot"
Width="20"
Height="20"
Fill="Blue"
Loaded="DotOnLoaded"
MouseDown="DotOnMouseDown"/>
</Canvas>
</Grid>
First I handle the Ellipse's MouseDown event:
private void DotOnMouseDown(object sender, MouseButtonEventArgs e)
{
_isDraggingDot = true;
}
All I do here is set a flag to indicate that I'm starting to drag the dot.
Next, I handle the Canvas MouseMove event, which is where I move the dot around. It includes logic to ensure the dot doesn't stray outside the canvas:
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));
}
}
This is also where you would calculate the dot's vertical and horizontal offset from centre, and use these values to update the pan and tilt.
Finally, I implement the MouseUp event on the outer control (the Grid in my example):
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));
}
The reason for handling the event on the outer control is to ensure that the dot is returned to the centre if the user releases the mouse button outside the Canvas.
(Note that I also set a b/g colour on the Grid, otherwise it defaults to transparent and won't detect mouse events!)
Lastly I wire up the Ellipse's Loaded event to initially centre the dot when the UI loads:
private void DotOnLoaded(object sender, RoutedEventArgs e)
{
CentreDot();
}
As I mentioned, this is just a quick solution where the dot simply follows the mouse. One If you don't like this, you could calculate how far the mouse is (vertically and horizontally) from the canvas centre, then use a small percentage of these values to position the dot away from the centre, effectively requiring more mouse movement to move the dot, which might feel more "natural".
Another idea may be to "snap" the dot's position to the nearest of the four arrow buttons (N,S,E,W), or even include the points in between (NE,SE,SW,NW).
I am trying to implement a zoom-functionality for a canvas using the mouse wheel.
Currently I am just Zooming to the center position of the canvas using CenterX="0.5" and CenterY="0.5".
I would like to change the behavior so that the zooming happens at the mouse position and I would like to know if this is possible with a ScaleTransform.
Currently I use the following code:
<Canvas Width="500" Height="500">
<Canvas.LayoutTransform>
<ScaleTransform CenterX="0.5" CenterY="0.5"
ScaleX="{Binding Zoom}"
ScaleY="{Binding Zoom}" />
</Canvas.LayoutTransform>
</Canvas>
A very basic approach to zoom a Canvas (or any other UIElement) at a specific position would be to use a MatrixTransform for the RenderTransform property
<Canvas Width="500" Height="500" MouseWheel="Canvas_MouseWheel">
<Canvas.RenderTransform>
<MatrixTransform/>
</Canvas.RenderTransform>
</Canvas>
and update the Matrix property of the transform like in this MouseWheel handler:
private void Canvas_MouseWheel(object sender, MouseWheelEventArgs e)
{
var element = (UIElement)sender;
var position = e.GetPosition(element);
var transform = (MatrixTransform)element.RenderTransform;
var matrix = transform.Matrix;
var scale = e.Delta >= 0 ? 1.1 : (1.0 / 1.1); // choose appropriate scaling factor
matrix.ScaleAtPrepend(scale, scale, position.X, position.Y);
transform.Matrix = matrix;
}
I spent the past two days agonizing over this issue and I figured it out. This will get you smooth zooming in toward the mouse and smooth zooming out. I'm posting my solution here for anyone who might search and stumble back here.
// Class constructor
public YourClass(Canvas theCanvas) //You may not need the Canvas as an argument depending on your scope
{
panTransform = new TranslateTransform();
zoomTransform = new ScaleTransform();
bothTransforms = new TransformGroup();
bothTransforms.Children.Add(panTransform);
bothTransforms.Children.Add(zoomTransform);
theCanvas.RenderTransform = bothTransforms;
//Handler
theCanvas.MouseWheel += wheelEvent;
//You also need your own handlers for panning, which I'm not showing here.
}
private void returnCalculatedScale()
{
double d;
//Do some math to get a new scale. I keep track of an integer, and run it through the formula y^(x/3) where X is the integer.
return d;
}
// Mouse wheel handler, where the magic happens
private void wheelEvent(object sender, MouseWheelEventArgs e)
{
Point position = e.GetPosition(mainCanvas);
zoomTransform.CenterX = position.X;
zoomTransform.CenterY = position.Y;
zoomTransform.ScaleX = returnCalculatedScale();
zoomTransform.ScaleY = returnCalculatedScale();
Point cursorpos = Mouse.GetPosition(mainCanvas); //This was the secret, as the mouse position gets out of whack when the transform occurs, but Mouse.GetPosition lets us get the point accurate to the transformed canvas.
double discrepancyX = cursorpos.X - position.X;
double discrepancyY = cursorpos.Y - position.Y;
//If your canvas is already panned an arbitrary amount, this aggregates the discrepancy to the TranslateTransform.
panTransform.X += discrepancyX;
panTransform.Y += discrepancyY;
I am writing a silverlight application, where there is a requirement, that I need to draw rectangle over image and move it along with the mouse move. I can move the rectangle by holding left mouse click, but now I need to move without clicking or holding the mouse left click.
I have seen many examples but they all implements the moving shapes and rectangle on left mouse click, which definitely is not my requirement.
I tried many ways but couldn't get it right. Below is the code what I do currently. Any suggestions would be welcomed.
XAML
<Canvas x:Name="draw" Grid.Column="0" Background="Transparent"
Margin="0,0,0,150" Grid.RowSpan="2">
<Rectangle x:Name="SquareBlue" Width="100" Height="100" Canvas.Top="155" Canvas.Left="268" Fill="Transparent" Stroke="Black" StrokeThickness="2" />
</Canvas>
<Image x:Name="myImage" Height="100"/>
<TextBox x:Name="X" Margin="0,0,110,0"></TextBox>
<TextBox x:Name="Y" Margin="0,0,110,0"/>
<Image x:Name="pictureBox1" Height="100"/>
Code Behind
Boolean isMouseCaptured;
Double mouseX;
Double mouseY;
Int32 zIndex = 0;
private void Shape_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
Shape s = sender as Shape;
isMouseCaptured = false;
s.ReleaseMouseCapture();
mouseY = -1;
mouseX = -1;
}
private void Shape_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Shape s = sender as Shape;
mouseY = e.GetPosition(null).Y;
mouseX = e.GetPosition(null).X;
isMouseCaptured = true;
s.CaptureMouse();
s.SetValue(Canvas.ZIndexProperty, zIndex);
zIndex++;
}
private void Shape_MouseMove(object sender, MouseEventArgs e)
{
if (isMouseCaptured)
{
Shape s = sender as Shape;
double deltaY = e.GetPosition(null).Y - mouseY;
double deltaX = e.GetPosition(null).X - mouseX;
double newTop = deltaY + (double)s.GetValue(Canvas.TopProperty);
double newLeft = deltaX + (double)s.GetValue(Canvas.LeftProperty);
s.SetValue(Canvas.TopProperty, newTop);
s.SetValue(Canvas.LeftProperty, newLeft);
mouseY = e.GetPosition(null).Y;
mouseX = e.GetPosition(null).X;
X.Text = mouseX.ToString();
Y.Text = mouseY.ToString();
}
You probably want to consider the shape captured whenever the Shape_MouseMove is fired.
And then, every mouse move on the canvas/window moves the shape to be centerd around the mouse cursor.
You then have to decide when to release it maybe:
by detecting when the mouse leaves the canvas/window.
using the buttons for releasing explicitly.
checking the time between mouseMove events. if 1 second passed, then be in a logical state where the shape is not and cannot be captured in mouseMove. only when mouse is out of the shape, return to normal state. so user will lose the move and has to cursor out and in again to capture again.
additionaly, when entering the state described in 3, launch a timer and then if after a second the cursor is on the shape, re-capture.
I'm trying to learn WPF, so here's a simple question, I hope:
I have a window that contains an Image element bound to a separate data object with user-configurable Stretch property
<Image Name="imageCtrl" Source="{Binding MyImage}" Stretch="{Binding ImageStretch}" />
When the user moves the mouse over the image, I would like to determine the coordinates of the mouse with respect to the original image (before stretching/cropping that occurs when it is displayed in the control), and then do something with those coordinates (update the image).
I know I can add an event-handler to the MouseMove event over the Image control, but I'm not sure how best to transform the coordinates:
void imageCtrl_MouseMove(object sender, MouseEventArgs e)
{
Point locationInControl = e.GetPosition(imageCtrl);
Point locationInImage = ???
updateImage(locationInImage);
}
Now I know I could compare the size of Source to the ActualSize of the control, and then switch on imageCtrl.Stretch to compute the scalars and offsets on X and Y, and do the transform myself. But WPF has all the information already, and this seems like functionality that might be built-in to the WPF libraries somewhere. So I'm wondering: is there a short and sweet solution? Or do I need to write this myself?
EDIT I'm appending my current, not-so-short-and-sweet solution. Its not that bad, but I'd be somewhat suprised if WPF didn't provide this functionality automatically:
Point ImgControlCoordsToPixelCoords(Point locInCtrl,
double imgCtrlActualWidth, double imgCtrlActualHeight)
{
if (ImageStretch == Stretch.None)
return locInCtrl;
Size renderSize = new Size(imgCtrlActualWidth, imgCtrlActualHeight);
Size sourceSize = bitmap.Size;
double xZoom = renderSize.Width / sourceSize.Width;
double yZoom = renderSize.Height / sourceSize.Height;
if (ImageStretch == Stretch.Fill)
return new Point(locInCtrl.X / xZoom, locInCtrl.Y / yZoom);
double zoom;
if (ImageStretch == Stretch.Uniform)
zoom = Math.Min(xZoom, yZoom);
else // (imageCtrl.Stretch == Stretch.UniformToFill)
zoom = Math.Max(xZoom, yZoom);
return new Point(locInCtrl.X / zoom, locInCtrl.Y / zoom);
}
It would probably be easier if you used a ViewBox. For example:
<Viewbox Stretch="{Binding ImageStretch}">
<Image Name="imageCtrl" Source="{Binding MyImage}" Stretch="None"/>
</Viewbox>
Then when you go and call GetPosition(..) WPF will automatically account for the scaling.
void imageCtrl_MouseMove(object sender, MouseEventArgs e)
{
Point locationInControl = e.GetPosition(imageCtrl);
}
In WPF/C# how would I rotate a "graphic" to face the current mouse position?
Basically what I want is a "wheel" UI Control (like an analog volume dial). I want to be able to click and drag the dial and it will rotate to follow the mouse. Then when I release the mouse it will stop following (obviously!).
How would I create one of these? does one already exist somewhere?
I haven't seen any controls like this around (though it's been a while since I looked at all of the controls that WPF control vendors were offering), but it's relatively straightforward to create one.
All you'd have to do is create a custom control containing an Image (or XAML drawing) that you can rotate to follow the mouse. Then, bind a RotateTransform to an 'Angle' DependencyProperty on your custom control so that when 'angle' is updated, the image/drawing rotates to match:
<UserControl x:Class="VolumeControlLibrary.VolumeControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:VolumeControlLibrary"
Height="60" Width="60">
<Image Source="/VolumeControl;component/knob.png" RenderTransformOrigin="0.5,0.5" >
<Image.RenderTransform>
<RotateTransform Angle="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:VolumeControl}}, Path=Angle}"/>
</Image.RenderTransform>
</Image>
</UserControl>
Setting RenderTransformOrigin to "0.5, 0.5" ensures that the control rotates around its center, rather than rotating around the top left corner; we'll have to compensate for this in the angle calculation too.
In the code behind file for your control, add handlers for the mouse and the Angle DependencyProperty:
public partial class VolumeControl : UserControl
{
// Using a DependencyProperty backing store for Angle.
public static readonly DependencyProperty AngleProperty =
DependencyProperty.Register("Angle", typeof(double), typeof(VolumeControl), new UIPropertyMetadata(0.0));
public double Angle
{
get { return (double)GetValue(AngleProperty); }
set { SetValue(AngleProperty, value); }
}
public VolumeControl()
{
InitializeComponent();
this.MouseLeftButtonDown += new MouseButtonEventHandler(OnMouseLeftButtonDown);
this.MouseUp += new MouseButtonEventHandler(OnMouseUp);
this.MouseMove += new MouseEventHandler(OnMouseMove);
}
private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Mouse.Capture(this);
}
private void OnMouseUp(object sender, MouseButtonEventArgs e)
{
Mouse.Capture(null);
}
private void OnMouseMove(object sender, MouseEventArgs e)
{
if (Mouse.Captured == this)
{
// Get the current mouse position relative to the volume control
Point currentLocation = Mouse.GetPosition(this);
// We want to rotate around the center of the knob, not the top corner
Point knobCenter = new Point(this.ActualHeight / 2, this.ActualWidth / 2);
// Calculate an angle
double radians = Math.Atan((currentLocation.Y - knobCenter.Y) /
(currentLocation.X - knobCenter.X));
this.Angle = radians * 180 / Math.PI;
// Apply a 180 degree shift when X is negative so that we can rotate
// all of the way around
if (currentLocation.X - knobCenter.X < 0)
{
this.Angle += 180;
}
}
}
}
Capturing the mouse ensures that your control will continue to get mouse updates even when the user mouses off of the control (until they let go of the click), and by getting the position of the mouse relative to the current element (the control), your calculation should always be the same regardless of where the control actually renders on screen.
In this example, when the mouse moves we calculate the angle between it and the center of the control, and then set this angle to the Angle DependencyProperty we created. Since the image we're displaying is bound to this angle property, WPF automatically applies the new value, which results in the knob rotating in combination with the mouse moving.
Using the control in your solution is easy; just add:
<local:VolumeControl />
You would bind to the Angle property on VolumeControl if you wanted to bind the value of the knob to something in your application; that value is currently in degrees, but could add an additional property to convert between an angle in degrees and a value that makes sense to you (say, a value from 0 - 10).
To add to that post, the angle between the mouse point and the object point is calculated like:
dot = currentLocation.X * objectPosition.X + currentLocation.Y * objectPosition.Y;
angle = Math.Acos(dot);
In my case i have dynamically created shapes which shall rotated toward mouse direction. To solve this I used a lightweight function. All I need is following:
The centerpoint of the current selected shape
The point from the last mouseover step
And the point from the current mouseover step
It is not necessary to use methods from the Math library. I calculate the angle which depends on the difference of the current mouse over point and the previous mouse over point and the position in relation o the center point. Finally I add the angle on the exisitng angle of the current object.
private void HandleLeftMouseDown(MouseButtonEventArgs eventargs)
{
//Calculate the center point of selected object
//...
//assuming Point1 is the top left point
var xCenter = (_selectedObject.Point2.X - _selectedObject.Point1.X) / 2 + _selectedObject.Point1.X
var yCenter = (_selectedObject.Point2.Y - _selectedObject.Point1.Y) / 2 + _selectedObject.Point1.Y
_selectedObjectCenterPoint = new Point((double) xCenter, (double) yCenter);
//init set of last mouse over step with the mouse click point
var clickPoint = eventargs.GetPosition(source);
_lastMouseOverPoint = new Point(clickPoint.X,clickPoint.Y);
}
private void HandleMouseMove(MouseEventArgs eventArgs)
{
Point pointMouseOver = eventArgs.GetPosition(_source);
//Get the difference to the last mouse over point
var xd = pointMouseOver.X - _lastMouseOverPoint.X;
var yd = pointMouseOver.Y - _lastMouseOverPoint.Y;
// the direction depends on the current mouse over position in relation to the center point of the shape
if (pointMouseOver.X < _selectedObjectCenterPoint.X)
yd *= -1;
if (pointMouseOver.Y > _selectedObjectCenterPoint.Y)
xd *= -1;
//add to the existing Angle
//not necessary to calculate the degree measure
_selectedObject.Angle += (xd + yd);
//save mouse over point
_lastMouseOverPoint = new Point(pointMouseOver.X, pointMouseOver.Y);
}