How do get a grid to rotate on mouse drag as it shows in the gif in WPF c#?
I've tried this code but I'm getting a very bad result and I do not have any idea how should I do the sizing.
double angle = 30;
Point oldPoint;
Point newPoint;
int d;
RotateTransform transe = new RotateTransform();
private void Border_MouseMove(object sender, MouseEventArgs e)
{
Grid b = sender as Grid;
if (Mouse.Captured == b)
{
newPoint = Mouse.GetPosition(mu);
if (oldPoint.Y != newPoint.Y)
{
if (oldPoint.Y > newPoint.Y)
transe.Angle = (oldPoint.Y - newPoint.Y);
else
transe.Angle += (newPoint.Y - oldPoint.Y);
gt.RenderTransform = transe;
}
return;
}
}
private void Border_MouseLeftButtonDown_2(object sender, MouseButtonEventArgs e)
{
Grid b = sender as Grid;
if (Mouse.Captured != b)
{
oldPoint = Mouse.GetPosition(mu);
Mouse.Capture(b);
}
}
private void Border_MouseLeftButtonUp_1(object sender, MouseButtonEventArgs e)
{
oldPoint = new Point(0, 0);
newPoint = new Point(0, 0);
Mouse.Capture(null);
}
The contol in xaml:
<Grid x:Name="mu" Height="90" Width="128" MouseUp="mu_MouseUp" MouseMove="Drase_MouseMove"
Canvas.Left="30" Canvas.Top="45" Cursor="Arrow" Background="White">
<Grid x:Name="gt" HorizontalAlignment="Left" Height="6" VerticalAlignment="Center"
Background="Black" Width="{Binding ActualWidth, ElementName=mu, Mode=OneWay}"
MouseMove="Border_MouseMove" MouseLeftButtonDown="Border_MouseLeftButtonDown_2"
MouseLeftButtonUp="Border_MouseLeftButtonUp_1" RenderTransformOrigin="0.5,0.5"/>
</Grid>
This is what I have managed to get so far. As you can see it's rotating uncontrollably.
Your approach isn't quite right. Here's my working version of the code:
private void Border_MouseMove(object sender, MouseEventArgs e)
{
Grid b = sender as Grid;
if (Mouse.Captured == b)
{
Point origin = new Point(mu.ActualWidth / 2, mu.ActualHeight / 2);
var rawPoint = Mouse.GetPosition(mu);
var transPoint = new Point(rawPoint.X - origin.X, rawPoint.Y - origin.Y);
var radians = Math.Atan2(transPoint.Y, transPoint.X);
var angle = radians * (180 / Math.PI);
transe.Angle = angle;
gt.RenderTransform = transe;
}
}
I'm not the best when it comes to the math, but I'll try to what I'm doing. The idea behind my code is that the angle of the rotate transform should always match the angle of the mouse relative to the vertical center-line of the mu.
Point origin = new Point(mu.ActualWidth / 2, mu.ActualHeight / 2); gives me the center of the mu, which is the mathematical origin point for the calculations.
var transPoint = new Point(rawPoint.X - origin.X, rawPoint.Y - origin.Y); translates the location of the mouse cursor so that it's relative to the center of mu (i.e. origin), as opposed to the top-left corner.
Then, stealing a bit of code I don't quite understand from this answer, I use Atan2 to calculate the angle.
Related
Issue: Attempting to zoom (scale) an Image from (or at the) mouse location using transforms in the Paint event to translate bitmap origin to mouse location, then scale the Image and translate its origin back.
The Image jumps and fails to scale from the relocated origin when translating the mouse location.
Rotate, scale, and pan function correctly without translating to the the mouse location.
Running on .Net 4.7.2, using Visual Studio in Windows 10 1909
v18363.778
The relevant code blocks:
private void trackBar1_Scroll(object sender, EventArgs e)
{
// Get rotation angle
ang = trackBar1.Value;
pnl1.Invalidate();
}
private void pnl1_MouseWheel(object sender, MouseEventArgs e)
{
// Get mouse location
mouse = e.location;
// Get new scale (zoom) factor
zoom = (float)(e.Delta > 0 ? zoom * 1.05 : zoom / 1.05);
pnl1.Invalidate();
}
private void pnl1_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button != MouseButtons.Left) return;
pan = true;
mouX = e.X;
mouY = e.Y;
oldX = imgX;
oldY = imgY;
}
private void pnl1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button != MouseButtons.Left || !pan) return;
// Coordinates of panned image
imgX = oldX + e.X - mouX;
imgY = oldY + e.Y - mouY;
pnl1.Invalidate();
}
private void pnl1_MouseUp(object sender, MouseEventArgs e)
{
pan = false;
}
private void pnl1_Paint(object sender, PaintEventArgs e)
{
// Apply rotation angle # center of bitmap
e.Graphics.TranslateTransform(img.Width / 2, img.Height / 2);
e.Graphics.RotateTransform(ang);
e.Graphics.TranslateTransform(-img.Width / 2, -img.Height / 2);
// Apply scaling factor - focused # mouse location
e.Graphics.TranslateTransform(mouse.X, mouse.Y, MatrixOrder.Append);
e.Graphics.ScaleTransform(zoom, zoom, MatrixOrder.Append);
e.Graphics.TranslateTransform(-mouse.X, -mouse.Y, MatrixOrder.Append);
// Apply drag (pan) location
e.Graphics.TranslateTransform(imgX, imgY, MatrixOrder.Append);
// Draw "bmp" # location
e.Graphics.DrawImage(img, 0, 0);
}
A few suggestions and a couple of tricks.
Not exactly tricks, just some methods to speed up the calculations when more than one graphic transformation is in place.
Divide and conquer: split the different graphics effects and transformations in different, specialized, methods that do one thing. Then design in a way that makes it possible for these methods to work together when needed.
Keep it simple: when Graphics objects need to accumulate more than a couple of transformations, the order in which Matrices are stacked can cause misunderstandings. It's simpler (and less prone to generate weird outcomes) to calculate some generic transformations (translate and scale, mostly) beforehand, then let GDI+ render already pre-cooked objects and shapes.
Here, only Matrix.RotateAt and Matrix.Multiply are used.
Some notes about Matrix transformations here: Flip the GraphicsPath
Use the right tools: for example, a Panel used as canvas is not exactly the best choice. This Control is not double-buffered; this feature can be enabled, but the Panel class is not meant for drawing, while a PictureBox (or a non-System flat Label) supports it on its own.
Some more notes here: How to apply a fade transition effect to Images
The sample code shows 4 zoom methods, plus generates rotation transformations (which work side-by-side, don't accumulate).
The Zoom modes are selected using an enumerator (private enum ZoomMode):
Zoom modes:
ImageLocation: Image scaling is performed in-place, keeping the current Location on the canvas in a fixed position.
CenterCanvas: while the Image is scaled, it remains centered on the Canvas.
CenterMouse: the Image is scaled and translated to center itself on the current Mouse location on the Canvas.
MouseOffset: the Image is scaled and translated to maintain a relative position determined by the initial location of the Mouse pointer on the Image itself.
You can notice that the code simplifies all the calculations, applying translations exclusively relative to the Rectangle that defines the current Image bounds and only in relation to the Location of this shape.
The Rectangle is only scaled when the calculation needs to preemptively determine what the Image size will be after the Mouse Wheel has generated the next Zoom factor.
Visual sample of the implemented functionalities:
Sample code:
canvas is the Custom Control, derived from PictureBox (you can find its definition at the bottom). This control is added to the Form in code, here. Modify as needed.
trkRotationAngle is the TrackBar used to define the current rotation of the Image. Add this control to the Form in the designer.
radZoom_CheckedChanged is the event handler of all the RadioButtons used to set the current Zoom Mode. The value these Controls set is assigned in their Tag property. Add these controls to the Form in the designer.
using System.Drawing;
using System.Drawing.Drawing2D;
using System.IO;
using System.Windows.Forms;
public partial class frmZoomPaint : Form
{
private float rotationAngle = 0.0f;
private float zoomFactor = 1.0f;
private float zoomStep = .05f;
private RectangleF imageRect = RectangleF.Empty;
private PointF imageLocation = PointF.Empty;
private PointF mouseLocation = PointF.Empty;
private Bitmap drawingImage = null;
private PictureBoxEx canvas = null;
private ZoomMode zoomMode = ZoomMode.ImageLocation;
private enum ZoomMode
{
ImageLocation,
CenterCanvas,
CenterMouse,
MouseOffset
}
public frmZoomPaint()
{
InitializeComponent();
string imagePath = [Path of the Image];
drawingImage = (Bitmap)Image.FromStream(new MemoryStream(File.ReadAllBytes(imagePath)));
imageRect = new RectangleF(Point.Empty, drawingImage.Size);
canvas = new PictureBoxEx(new Size(555, 300));
canvas.Location = new Point(10, 10);
canvas.MouseWheel += canvas_MouseWheel;
canvas.MouseMove += canvas_MouseMove;
canvas.MouseDown += canvas_MouseDown;
canvas.MouseUp += canvas_MouseUp;
canvas.Paint += canvas_Paint;
Controls.Add(canvas);
}
private void canvas_MouseWheel(object sender, MouseEventArgs e)
{
mouseLocation = e.Location;
float zoomCurrent = zoomFactor;
zoomFactor += e.Delta > 0 ? zoomStep : -zoomStep;
if (zoomFactor < .10f) zoomStep = .01f;
if (zoomFactor >= .10f) zoomStep = .05f;
if (zoomFactor < .0f) zoomFactor = zoomStep;
switch (zoomMode) {
case ZoomMode.CenterCanvas:
imageRect = CenterScaledRectangleOnCanvas(imageRect, canvas.ClientRectangle);
break;
case ZoomMode.CenterMouse:
imageRect = CenterScaledRectangleOnMousePosition(imageRect, e.Location);
break;
case ZoomMode.MouseOffset:
imageRect = OffsetScaledRectangleOnMousePosition(imageRect, zoomCurrent, e.Location);
break;
default:
break;
}
canvas.Invalidate();
}
private void canvas_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button != MouseButtons.Left) return;
mouseLocation = e.Location;
imageLocation = imageRect.Location;
canvas.Cursor = Cursors.NoMove2D;
}
private void canvas_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button != MouseButtons.Left) return;
imageRect.Location =
new PointF(imageLocation.X + (e.Location.X - mouseLocation.X),
imageLocation.Y + (e.Location.Y - mouseLocation.Y));
canvas.Invalidate();
}
private void canvas_MouseUp(object sender, MouseEventArgs e) =>
canvas.Cursor = Cursors.Default;
private void canvas_Paint(object sender, PaintEventArgs e)
{
var drawingRect = GetDrawingImageRect(imageRect);
using (var mxRotation = new Matrix())
using (var mxTransform = new Matrix()) {
e.Graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
e.Graphics.PixelOffsetMode = PixelOffsetMode.Half;
mxRotation.RotateAt(rotationAngle, GetDrawingImageCenterPoint(drawingRect));
mxTransform.Multiply(mxRotation);
e.Graphics.Transform = mxTransform;
e.Graphics.DrawImage(drawingImage, drawingRect);
}
}
private void trkRotationAngle_ValueChanged(object sender, EventArgs e)
{
rotationAngle = trkAngle.Value;
canvas.Invalidate();
canvas.Focus();
}
private void radZoom_CheckedChanged(object sender, EventArgs e)
{
var rad = sender as RadioButton;
if (rad.Checked) {
zoomMode = (ZoomMode)int.Parse(rad.Tag.ToString());
}
canvas.Focus();
}
#region Drawing Methods
public RectangleF GetScaledRect(RectangleF rect, float scaleFactor) =>
new RectangleF(rect.Location,
new SizeF(rect.Width * scaleFactor, rect.Height * scaleFactor));
public RectangleF GetDrawingImageRect(RectangleF rect) =>
GetScaledRect(rect, zoomFactor);
public PointF GetDrawingImageCenterPoint(RectangleF rect) =>
new PointF(rect.X + rect.Width / 2, rect.Y + rect.Height / 2);
public RectangleF CenterScaledRectangleOnCanvas(RectangleF rect, RectangleF canvas)
{
var scaled = GetScaledRect(rect, zoomFactor);
rect.Location = new PointF((canvas.Width - scaled.Width) / 2,
(canvas.Height - scaled.Height) / 2);
return rect;
}
public RectangleF CenterScaledRectangleOnMousePosition(RectangleF rect, PointF mousePosition)
{
var scaled = GetScaledRect(rect, zoomFactor);
rect.Location = new PointF(mousePosition.X - (scaled.Width / 2),
mousePosition.Y - (scaled.Height / 2));
return rect;
}
public RectangleF OffsetScaledRectangleOnMousePosition(RectangleF rect, float currentZoom, PointF mousePosition)
{
var currentRect = GetScaledRect(imageRect, currentZoom);
if (!currentRect.Contains(mousePosition)) return rect;
float scaleRatio = currentRect.Width / GetScaledRect(rect, zoomFactor).Width;
PointF mouseOffset = new PointF(mousePosition.X - rect.X, mousePosition.Y - rect.Y);
PointF scaledOffset = new PointF(mouseOffset.X / scaleRatio, mouseOffset.Y / scaleRatio);
PointF position = new PointF(rect.X - (scaledOffset.X - mouseOffset.X),
rect.Y - (scaledOffset.Y - mouseOffset.Y));
rect.Location = position;
return rect;
}
#endregion
}
The simple PictureBoxEx custom control (modify and extend as needed):
This PictureBox is selectable, so it can be focused, with a Mouse click
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
[DesignerCategory("Code")]
public class PictureBoxEx : PictureBox
{
public PictureBoxEx() : this (new Size(200, 200)){ }
public PictureBoxEx(Size size) {
SetStyle(ControlStyles.Selectable | ControlStyles.UserMouse, true);
BorderStyle = BorderStyle.FixedSingle;
Size = size;
}
}
#Jimi: Thank you for the detailed information - very useful for visualizing the concepts involved in the graphics manipulations. I had arrived at a functioning solution (see code below) however, your code utilizes steps with greater efficiency. Admittedly, my code is developed with more of an intent to learn the mechanics of image manipulation - as I am still at the early part of the learning curve. Nonetheless, your illustration of the mechanics and techniques is extremely helpful.
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
namespace ZoomImage
{
public partial class Form1 : Form
{
Image img;
Bitmap bmp;
float ang = 0;
float zoom = 1;
bool pan;
bool? ctr = false;
Point mcurrent;
PointF mouse;
PointF image;
PointF _image;
PointF rotate;
public Form1()
{
InitializeComponent();
MouseWheel += mouseWheel;
img = Image.FromFile(#"C:\testimage.jpg");
bmp = new Bitmap(img);
// Set initial scale to fit canvas window
float wRatio = (float)pbx.Width / (float)img.Width;
float hRatio = (float)pbx.Height / (float)img.Height;
zoom = Math.Min(wRatio, hRatio);
image.X = (pbx.Width - zoom * img.Width) / 2;
image.Y = (pbx.Height - zoom * img.Height) / 2;
}
private void label()
{
string _imgX = string.Format("{0:000}", image.X);
string _imgY = string.Format("{0:000}", image.Y);
lbl1.Text = "Location: " + _imgX + ", " + _imgY + "\r\nRotation: " + ang + "\r\nZoom: " + zoom + "\r\nMouse: " + mcurrent.X + ", " + mcurrent.Y;
}
private void btnRotate_Click(object sender, EventArgs e)
{
if (ModifierKeys == Keys.Control)
{
string msg = "Set center of rotation point:\r\n\nMove mouse to desired center ";
msg += "of rotation then hold \"Alt\" and left-click.\r\n\n";
msg += "To restore center of rotation to center of image:\r\n\nHold \"Shift\" and";
msg += " click \"Rotate\".";
MessageBox.Show(msg,"Change center of rotation");
ctr = null;
pbx.Focus();
return;
}
else if (ModifierKeys == Keys.Shift)
{
ctr = false;
return;
}
ang = ang == 270 ? 0 : ang += 90;
if (ang > 360) ang -= 360;
trackBar1.Value = (int)ang;
ctr = ctr == null ? false : ctr;
if (ctr == false) rotate = new PointF(img.Width / 2, img.Height / 2);
pbx.Invalidate();
}
private void trackBar1_Scroll(object sender, EventArgs e)
{
ang = trackBar1.Value;
if (ctr == false) rotate = new PointF(img.Width / 2, img.Height / 2);
pbx.Invalidate();
}
private void mouseWheel(object sender, MouseEventArgs e)
{
mouse = new PointF(e.X - image.X, e.Y - image.Y);
float zinc = 0.05f;
float zfac = 1 + zinc;
zoom = (float)(e.Delta > 0 ? zoom * (zfac) : zoom / (zfac));
// Adjust "img" (bitmap) orgin to maintain fixed focus # mouse location
if (e.Delta > 0)
{
image.X -= zinc * mouse.X;
image.Y -= zinc * mouse.Y;
}
else
{
image.X += (1 - 1 / (zfac)) * mouse.X;
image.Y += (1 - 1 / (zfac)) * mouse.Y;
}
image = new PointF(image.X, image.Y);
pbx.Invalidate();
}
private void mouseDown(object sender, MouseEventArgs e)
{
if (e.Button != MouseButtons.Left) return;
if (ModifierKeys == Keys.Alt && ctr == null)
{
ctr = true;
rotate = new PointF((e.X - image.X) / zoom, (e.Y - image.Y) / zoom);
return;
}
pan = true;
mouse = e.Location;
_image = image;
}
private void mouseMove(object sender, MouseEventArgs e)
{
mcurrent = e.Location;
label();
if (e.Button != MouseButtons.Left || !pan) return;
image.X = _image.X + e.X - mouse.X;
image.Y = _image.Y + e.Y - mouse.Y;
image = new PointF(image.X, image.Y);
pbx.Invalidate();
}
private void mouseUp(object sender, MouseEventArgs e)
{
pan = false;
}
private void pbx_Paint(object sender, PaintEventArgs e)
{
label();
// Generate bitmap "bmp" - this can be saved as drawn...if deisred
bmp = new Bitmap(img.Width, img.Height);
using (Graphics g = Graphics.FromImage(bmp))
{
Matrix transform = new Matrix();
transform.Scale(zoom, zoom, MatrixOrder.Append);
transform.RotateAt(ang, rotate);
transform.Translate(image.X, image.Y, MatrixOrder.Append);
g.Transform = transform;
g.DrawImage(img, 0, 0);
}
e.Graphics.DrawImage(bmp, 0, 0);
}
}
}
I am rotating the a panel (Grid) and Flip (Transform) the panel. When i do this separate, both are working fine. Rotate and then Flip (OR) Flip and Rotate is not working fine.
I am rotating the panel to 90 degree every time. When i rotate to 90 degree, the panel height and width is changed. In that case, if i Flip the panel, issue occur. For 180 degree, no issue.
Issue Steps
Step-1: Rotate image to 90 degree by clicking the rotate button in the sample. You will get the below output.
Step-2: Now Flip the image by Transform button and the image moved to left side. Expected output is, It should be center and Flip.
You can see the difference of Transformation of image, by directly clicking Transform button, without click on Rotate.
This is my code.
[XAML]:
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<Grid x:Name="grid1" Grid.Row="0">
<Image x:Name="image1" Source="Images/Buldingimage.jpeg"/>
</Grid>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Button Click="Button_Click_1" Grid.Column="0">Rotate</Button>
<Button Click="Button_Click_2" Grid.Column="1">Transform</Button>
</Grid>
</Grid>
[C#]:
double rotationAngle = 0;
private void Button_Click_1(object sender, RoutedEventArgs e)
{
//Rotate
this.rotationAngle += 90;
this.grid1.RenderTransformOrigin = new Point(0.5, 0.5);
this.grid1.LayoutTransform = new RotateTransform() { Angle = this.rotationAngle };
}
bool mIsHorizontalImageflipped = false;
private void Button_Click_2(object sender, RoutedEventArgs e)
{
//Transform
int[] value ;
float[] origin = new float[] { 0.5f, 0.5f };
string path = "(UIElement.RenderTransform).(ScaleTransform.ScaleX)";
if (!this.mIsHorizontalImageflipped)
{
value = new int[] { 1, -1 };
this.mIsHorizontalImageflipped = true;
}
else
{
this.mIsHorizontalImageflipped = false;
value = new int[] { -1, 1 };
}
this.Animate(value, origin, path);
}
internal void Animate(int[] value, float[] origin, string path)
{
this.grid1.RenderTransform = new ScaleTransform();
Storyboard sb = new Storyboard();
this.grid1.RenderTransformOrigin = new Point(origin[0], origin[1]);
DoubleAnimationUsingKeyFrames keyFrames = new DoubleAnimationUsingKeyFrames();
SplineDoubleKeyFrame keyFrame = new SplineDoubleKeyFrame();
keyFrame.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(0));
keyFrame.Value = value[0];
keyFrames.KeyFrames.Add(keyFrame);
keyFrame = new SplineDoubleKeyFrame();
keyFrame.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(1));
KeySpline keySpline = new KeySpline();
keySpline.ControlPoint1 = new Point(0.64, 0.84);
keySpline.ControlPoint2 = new Point(0, 1);
keyFrame.KeySpline = keySpline;
keyFrames.KeyFrames.Add(keyFrame);
keyFrame.Value = value[1];
Storyboard.SetTargetProperty(keyFrames, new PropertyPath(path));
Storyboard.SetTarget(keyFrames, this.grid1);
sb.Children.Add(keyFrames);
sb.Begin();
}
Click here to download the sample.
Please provide suggestion or what i did wrong on this.
Regards,
Bharathi.
If you set the RenderTransform property to a ScaleTransform, you are effectively removing the RotateTransform and vice versa.
To be able to apply both transforms simultaneously, you could use a TransformGroup:
<Grid x:Name="grid1" Grid.Row="0">
<Image x:Name="image1" Source="Images/Buldingimage.jpeg"/>
<Grid.RenderTransform>
<TransformGroup>
<RotateTransform x:Name="rt" />
<ScaleTransform x:Name="st" />
</TransformGroup>
</Grid.RenderTransform>
</Grid>
You would then simply change the Angle of the RotateTransform and animate the ScaleTransform as before:
double rotationAngle = 0;
private void Button_Click_1(object sender, RoutedEventArgs e)
{
//Rotate
this.rotationAngle += 90;
this.grid1.RenderTransformOrigin = new Point(0.5, 0.5);
rt.Angle = this.rotationAngle;
}
bool mIsHorizontalImageflipped = false;
private void Button_Click_2(object sender, RoutedEventArgs e)
{
//Transform
int[] value;
float[] origin = new float[] { 0.5f, 0.5f };
string path = "RenderTransform.Children[1].ScaleX";
if (!this.mIsHorizontalImageflipped)
{
value = new int[] { 1, -1 };
this.mIsHorizontalImageflipped = true;
}
else
{
this.mIsHorizontalImageflipped = false;
value = new int[] { -1, 1 };
}
this.Animate(value, origin, path);
}
internal void Animate(int[] value, float[] origin, string path)
{
Storyboard sb = new Storyboard();
this.grid1.RenderTransformOrigin = new Point(origin[0], origin[1]);
DoubleAnimationUsingKeyFrames keyFrames = new DoubleAnimationUsingKeyFrames();
SplineDoubleKeyFrame keyFrame = new SplineDoubleKeyFrame();
keyFrame.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(0));
keyFrame.Value = value[0];
keyFrames.KeyFrames.Add(keyFrame);
keyFrame = new SplineDoubleKeyFrame();
keyFrame.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(1));
KeySpline keySpline = new KeySpline();
keySpline.ControlPoint1 = new Point(0.64, 0.84);
keySpline.ControlPoint2 = new Point(0, 1);
keyFrame.KeySpline = keySpline;
keyFrames.KeyFrames.Add(keyFrame);
keyFrame.Value = value[1];
Storyboard.SetTargetProperty(keyFrames, new PropertyPath(path));
Storyboard.SetTarget(keyFrames, this.grid1);
sb.Children.Add(keyFrames);
sb.Begin();
}
I need help to bound the path or canvas in a boundary. What I want is, when I click the mouse-left-button, holding it and move for panning. It should not move when mouse pointer reach some boundary as BORDER. I'll add code here please help me out
XAML code:
<Border x:Name="OutMoastBorder" Height="820" Width="820" ClipToBounds="True" BorderThickness="2" BorderBrush="Black" Block.IsHyphenationEnabled="True">
<Border x:Name="clipBorder" Height="810" Width="810" BorderThickness="2" BorderBrush="Black" ClipToBounds="True">
<Canvas x:Name="CanvasPanel" Height="800" Width="800" Background="Beige">
</Canvas>
</Border>
</Border>
<Grid>
<Button Content="Original Size" Height="23" Name="btn_Original" Width="75" Click="btn_Original_Click" Margin="4,4,921,973" />
<TextBox Height="23" HorizontalAlignment="Left" Margin="4,59,0,0" Name="txtNoOfZones" VerticalAlignment="Top" Width="120" MaxLength="2" PreviewTextInput="txtNoOfZones_PreviewTextInput" />
<Label Content="Enter a number below for No. of Zones" Height="28" HorizontalAlignment="Left" Margin="4,33,0,0" Name="label1" VerticalAlignment="Top" Width="220" FontFamily="Vijaya" FontSize="15" FontWeight="Bold" FontStyle="Normal" />
<Button Content="Zones" Height="23" HorizontalAlignment="Left" Margin="130,58,0,0" Name="btnNoOfZones" VerticalAlignment="Top" Width="41" Click="btnNoOfZones_Click" />
</Grid>
</Grid>
Code behind for zooming and panning:
void Zoom_MouseWheel(object sender, MouseWheelEventArgs e)
{
Point p = e.MouseDevice.GetPosition(((Path)sender));
Matrix m = CanvasPanel.RenderTransform.Value;
if (e.Delta > 0)
m.ScaleAtPrepend(1.1, 1.1, p.X, p.Y);
else
m.ScaleAtPrepend(1 / 1.1, 1 / 1.1, p.X, p.Y);
CanvasPanel.RenderTransform = new MatrixTransform(m);
// CanvasPanel.RenderTransformOrigin = new Point(0.5, 0.5);
}
private Point origin;
private Point start;
void Pan_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (((Path)sender).IsMouseCaptured) return;
((Path)sender).CaptureMouse();
start = e.GetPosition(clipBorder);
origin.X = CanvasPanel.RenderTransform.Value.OffsetX;
origin.Y = CanvasPanel.RenderTransform.Value.OffsetY;
}
void Pan_MouseLeftBtnUp(object sender, MouseButtonEventArgs e)
{
((Path)sender).ReleaseMouseCapture();
}
void Pan_MouseMove(object sender, MouseEventArgs e)
{
if (!((Path)sender).IsMouseCaptured) return;
Point p = e.MouseDevice.GetPosition(clipBorder);
Matrix m = CanvasPanel.RenderTransform.Value;
m.OffsetX = origin.X + (p.X - start.X);
m.OffsetY = origin.Y + (p.Y - start.Y);
CanvasPanel.RenderTransform = new MatrixTransform(m);
}
private const int NoOfSectors = 180;
private const int NoOfZones = 5;
void OnLoaded(object sender, RoutedEventArgs args)
{
const double PIES = 2 * Math.PI / NoOfSectors;
Point center = new Point(CanvasPanel.ActualWidth / 2, CanvasPanel.ActualHeight / 2); // center (x,y) Co-ordinates to get center of canvas
double radius = 0.1 * Math.Min(CanvasPanel.ActualWidth, CanvasPanel.ActualHeight);
for (int s = 0; s <= NoOfSectors; s++)
{
for (int z = 1; z <= NoOfZones; z++)
{
Path path = new Path();
PathGeometry pathGeo = new PathGeometry();
PathFigure pathFig = new PathFigure();
double radians = 2 * Math.PI * s / NoOfSectors;
double outerRadius = radius * z;
double innerRadius = radius * ((z - 1));
Size outerArcSize = new Size(outerRadius, outerRadius);
Size innerArcSize = new Size(innerRadius, innerRadius);
Point p1_1st_LineSegment; //------------------------------> Points variable, to store each iterate point in these.
Point p2_1st_ArcSegment;
Point p3_2nd_LineSegment;
Point p4_2nd_ArcSegment;
p1_1st_LineSegment = new Point(center.X + innerRadius * Math.Cos(radians - PIES), center.Y - innerRadius * Math.Sin(radians - PIES)); // point for LINE from Center
p2_1st_ArcSegment = new Point(center.X + innerRadius * Math.Cos(radians), center.Y - innerRadius * Math.Sin(radians)); // point for ARC after the 1st LINE formation
p3_2nd_LineSegment = new Point(center.X + outerRadius * Math.Cos(radians), center.Y - outerRadius * Math.Sin(radians)); // Point for 2nd LINE after forming both LINE abd ARC
p4_2nd_ArcSegment = new Point(center.X + outerRadius * Math.Cos(radians - PIES), center.Y - outerRadius * Math.Sin(radians - PIES)); // Point for 2nd ARC which is Counter-CLockwise that closes a path
pathFig.StartPoint = center;
pathFig.Segments.Add(new LineSegment(p1_1st_LineSegment, true));
pathFig.Segments.Add(new ArcSegment(p2_1st_ArcSegment, innerArcSize, 1, false, SweepDirection.Clockwise, true));
pathFig.Segments.Add(new LineSegment(p3_2nd_LineSegment, true));
pathFig.Segments.Add(new ArcSegment(p4_2nd_ArcSegment, outerArcSize, 1, false, SweepDirection.Counterclockwise, true));
pathFig.IsClosed = false; //false because, path has to be close with ARC, not with LINE
pathFig.IsFilled = true;
pathGeo.Figures.Add(pathFig); // binding data to a Geometry
pathGeo.FillRule = FillRule.Nonzero;
path.Data = pathGeo; // binding whole geometry data to a path
path.Stroke = Brushes.Black;
path.Fill = Brushes.Silver;
path.StrokeThickness = 0.1;
// CanvasPanel.RenderTransformOrigin = new Point(0.5, 0.5); //--------------------> this makes "Canvas" to be Zoom from center
CanvasPanel.Children.Add(path); // binding to a CanvasPanel as a children
path.MouseLeftButtonDown += MouseLeftButtonClick; // calling Mouse-click-event
path.MouseWheel += Zoom_MouseWheel;
path.MouseLeftButtonDown += Pan_MouseLeftButtonDown;
path.MouseLeftButtonUp += Pan_MouseLeftBtnUp;
path.MouseMove += Pan_MouseMove;
}
}
Please help me out.
Regards,
Viswanath.
PLz replace the MouseMove event with this following code in code behind.
void Pan_MouseMove(object sender, MouseEventArgs e)
{
if (!((Path)sender).IsMouseCaptured) return;
Point p = e.MouseDevice.GetPosition(clipBorder);
if (p.X > 0 && p.Y > 0 && p.X < clipBorder.ActualWidth && p.Y < clipBorder.ActualHeight)
{
Matrix m = CanvasPanel.RenderTransform.Value;
m.OffsetX = origin.X + (p.X - start.X);
m.OffsetY = origin.Y + (p.Y - start.Y);
CanvasPanel.RenderTransform = new MatrixTransform(m);
}
}
I have tested, this will surely solve this.
regards,
Viswa
I'm trying to create a simple Window that allows viewing/zooming/moving an image. It is starting to behave similar to what it should with the image resizing to adjust to the window as we resize.
The weird thing is that as you reduce the width of the window, at a certain point, the height of the picture starts reducing when it shouldn't, and then it bounces into a different position. In the code, however, the height of the image remains the same so "something" else is altering the layout.
The other weird thing is that as you increase the width, the image almost remains centered except that it gradually tilts towards the right.
So my question is: what is screwing up my layout like this?
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="NaturalGroundingPlayer.ImageViewerWindow"
x:Name="Window" Title="Image Viewer" Width="640" Height="480" SizeChanged="Window_SizeChanged">
<Grid x:Name="LayoutRoot">
<Canvas x:Name="ImgCanvas" ClipToBounds="True">
<ContentControl x:Name="ImgContentCtrl" Height="300" Width="400">
<Grid x:Name="ImgGrid">
<Image x:Name="ImgObject"/>
<Thumb x:Name="ImgThumb" Opacity="0" DragDelta="ImgThumb_DragDelta" MouseWheel="ImgThumb_MouseWheel"/>
</Grid>
</ContentControl>
</Canvas>
</Grid>
</Window>
public partial class ImageViewerWindow : Window {
public static ImageViewerWindow Instance(string fileName) {
ImageViewerWindow NewForm = new ImageViewerWindow();
NewForm.LoadImage(fileName);
NewForm.Show();
return NewForm;
}
private double scale;
public ImageViewerWindow() {
InitializeComponent();
}
public void LoadImage(string fileName) {
BitmapImage NewImage = new BitmapImage();
NewImage.BeginInit();
NewImage.UriSource = new Uri(fileName);
NewImage.EndInit();
ImgObject.Source = NewImage;
//ImgContentCtrl.Width = NewImage.Width;
//ImgContentCtrl.Height = NewImage.Height;
BestFit();
}
private void ImgThumb_DragDelta(object sender, DragDeltaEventArgs e) {
double ImgLeft = Canvas.GetLeft(ImgContentCtrl);
double ImgTop = Canvas.GetTop(ImgContentCtrl);
Canvas.SetLeft(ImgContentCtrl, (ImgLeft + e.HorizontalChange));
Canvas.SetTop(ImgContentCtrl, (ImgTop + e.VerticalChange));
}
private void ImgThumb_MouseWheel(object sender, MouseWheelEventArgs e) {
// Zoom in when the user scrolls the mouse wheel up and vice versa.
if (e.Delta > 0) {
// Limit zoom-in to 500%
if (scale < 5)
scale += 0.1;
} else {
// When mouse wheel is scrolled down...
// Limit zoom-out to 80%
if (scale > 0.8)
scale -= 0.1;
}
DisplayImage();
}
private void BestFit() {
// Set the scale of the ContentControl to 100%.
scale = 1;
// Set the position of the ContentControl so that the image is centered.
Canvas.SetLeft(ImgContentCtrl, 0);
Canvas.SetTop(ImgContentCtrl, 0);
}
private void Window_SizeChanged(object sender, SizeChangedEventArgs e) {
DisplayImage();
}
private void DisplayImage() {
double RatioWidth = LayoutRoot.ActualWidth / ImgObject.Source.Width;
double RatioHeight = LayoutRoot.ActualHeight / ImgObject.Source.Height;
if (RatioHeight > RatioWidth) {
ImgContentCtrl.Width = LayoutRoot.ActualWidth * scale;
ImgContentCtrl.Height = LayoutRoot.ActualHeight * RatioHeight * scale;
} else {
ImgContentCtrl.Height = LayoutRoot.ActualHeight * scale;
ImgContentCtrl.Width = LayoutRoot.ActualWidth * RatioWidth * scale;
}
}
}
Sjips, you first need to put any picture in it to display.
dkozl, that works indeed, I wasn't sure the Image control would respect proportions automatically as the demo I found wasn't respecting proportions while resizing. Simply putting this code in DisplayImage works perfect, even for repositioning properly when zooming. I guess I was much closer to the answer than I thought!
private void DisplayImage() {
ImgContentCtrl.Width = LayoutRoot.ActualWidth * scale;
Canvas.SetLeft(ImgContentCtrl, LayoutRoot.ActualWidth * (1 - scale) / 2);
ImgContentCtrl.Height = LayoutRoot.ActualHeight * scale;
Canvas.SetTop(ImgContentCtrl, LayoutRoot.ActualHeight * (1 - scale) / 2);
}
That being said, I still don't undestand the weird behaviors that happened before...
I’m new to windows phone development and trying to use the Microsoft.Phone.Controls.Toolkit for zooming and panning on an image. My issue is that zooming and panning only work on the first try. If I zoom/pan on the image, go elsewhere within the app and then go back to the image it doesn’t do anything. The OnPinchStarted method never runs when it is supposed to be envoked. I’ve used several different ways that I’ve found from searching here and elsewhere for the gesturelistener. Code is posted below. Before I go another route and toss out this way I wanted to see if there is something I’m missing with what I have because it works just fine the first time.
First try:
XAML:
<Grid x:Name="LayoutRoot" Background="Transparent">
<Image x:Name="Map"
Source="Images/park.png"
HorizontalAlignment="Center" VerticalAlignment="Center"
Stretch="Uniform" >
<toolkit:GestureService.GestureListener>
<toolkit:GestureListener
PinchStarted="OnPinchStarted"
PinchDelta="OnPinchDelta"
DragDelta="OnDragDelta"/>
</toolkit:GestureService.GestureListener>
<Image.RenderTransform>
<CompositeTransform
ScaleX="1" ScaleY="1"
TranslateX="0" TranslateY="0"/>
</Image.RenderTransform>
</Image>
</Grid>
CS:
// these two fields fully define the zoom state:
private double TotalImageScale = 1d;
private Point ImagePosition = new Point(0, 0);
private const double MAX_IMAGE_ZOOM = 5;
private Point _oldFinger1;
private Point _oldFinger2;
private double _oldScaleFactor;
// Initializes the zooming operation
private void OnPinchStarted(object sender, PinchStartedGestureEventArgs e)
{
_oldFinger1 = e.GetPosition(Map, 0);
_oldFinger2 = e.GetPosition(Map, 1);
_oldScaleFactor = 1;
}
//Computes the scaling and translation to correctly zoom around your fingers.
private void OnPinchDelta(object sender, PinchGestureEventArgs e)
{
var scaleFactor = e.DistanceRatio / _oldScaleFactor;
if (!IsScaleValid(scaleFactor))
return;
var currentFinger1 = e.GetPosition(Map, 0);
var currentFinger2 = e.GetPosition(Map, 1);
var translationDelta = GetTranslationDelta(
currentFinger1,
currentFinger2,
_oldFinger1,
_oldFinger2,
ImagePosition,
scaleFactor);
_oldFinger1 = currentFinger1;
_oldFinger2 = currentFinger2;
_oldScaleFactor = e.DistanceRatio;
UpdateImageScale(scaleFactor);
UpdateImagePosition(translationDelta);
}
//Moves the image around following your finger.
private void OnDragDelta(object sender, DragDeltaGestureEventArgs e)
{
var translationDelta = new Point(e.HorizontalChange, e.VerticalChange);
if (IsDragValid(1, translationDelta))
UpdateImagePosition(translationDelta);
}
//Computes the translation needed to keep the image centered between your fingers.
private Point GetTranslationDelta(
Point currentFinger1, Point currentFinger2,
Point oldFinger1, Point oldFinger2,
Point currentPosition, double scaleFactor)
{
var newPos1 = new Point(
currentFinger1.X + (currentPosition.X - oldFinger1.X) * scaleFactor,
currentFinger1.Y + (currentPosition.Y - oldFinger1.Y) * scaleFactor);
var newPos2 = new Point(
currentFinger2.X + (currentPosition.X - oldFinger2.X) * scaleFactor,
currentFinger2.Y + (currentPosition.Y - oldFinger2.Y) * scaleFactor);
var newPos = new Point(
(newPos1.X + newPos2.X) / 2,
(newPos1.Y + newPos2.Y) / 2);
return new Point(
newPos.X - currentPosition.X,
newPos.Y - currentPosition.Y);
}
//Updates the scaling factor by multiplying the delta.
private void UpdateImageScale(double scaleFactor)
{
TotalImageScale *= scaleFactor;
ApplyScale();
}
//Applies the computed scale to the image control.
private void ApplyScale()
{
((CompositeTransform)Map.RenderTransform).ScaleX = TotalImageScale;
((CompositeTransform)Map.RenderTransform).ScaleY = TotalImageScale;
}
//Updates the image position by applying the delta.
//Checks that the image does not leave empty space around its edges.
private void UpdateImagePosition(Point delta)
{
var newPosition = new Point(ImagePosition.X + delta.X, ImagePosition.Y + delta.Y);
if (newPosition.X > 0) newPosition.X = 0;
if (newPosition.Y > 0) newPosition.Y = 0;
if ((Map.ActualWidth * TotalImageScale) + newPosition.X < Map.ActualWidth)
newPosition.X = Map.ActualWidth - (Map.ActualWidth * TotalImageScale);
if ((Map.ActualHeight * TotalImageScale) + newPosition.Y < Map.ActualHeight)
newPosition.Y = Map.ActualHeight - (Map.ActualHeight * TotalImageScale);
ImagePosition = newPosition;
ApplyPosition();
}
//Applies the computed position to the image control.
private void ApplyPosition()
{
((CompositeTransform)Map.RenderTransform).TranslateX = ImagePosition.X;
((CompositeTransform)Map.RenderTransform).TranslateY = ImagePosition.Y;
}
//Resets the zoom to its original scale and position
private void ResetImagePosition()
{
TotalImageScale = 1;
ImagePosition = new Point(0, 0);
ApplyScale();
ApplyPosition();
}
//Checks that dragging by the given amount won't result in empty space around the image
private bool IsDragValid(double scaleDelta, Point translateDelta)
{
if (ImagePosition.X + translateDelta.X > 0 || ImagePosition.Y + translateDelta.Y > 0)
return false;
if ((Map.ActualWidth * TotalImageScale * scaleDelta) + (ImagePosition.X + translateDelta.X) < Map.ActualWidth)
return false;
if ((Map.ActualHeight * TotalImageScale * scaleDelta) + (ImagePosition.Y + translateDelta.Y) < Map.ActualHeight)
return false;
return true;
}
//Tells if the scaling is inside the desired range
private bool IsScaleValid(double scaleDelta)
{
return (TotalImageScale * scaleDelta >= 1) && (TotalImageScale * scaleDelta <= MAX_IMAGE_ZOOM);
}
Second Try(from http://www.wintellect.com/blogs/jprosise/building-touch-interfaces-for-windows-phones-part-4)
XAML:
<Grid x:Name="LayoutRoot" Background="Transparent">
<Grid x:Name="ContentPanel" Grid.Row="1" RenderTransformOrigin="0.5,0.5" >
<toolkit:GestureService.GestureListener>
<toolkit:GestureListener PinchDelta="OnPinchDelta"
PinchStarted="OnPinchStarted" DragDelta="OnDragDelta" />
</toolkit:GestureService.GestureListener>
<Grid.RenderTransform>
<CompositeTransform x:Name="HambyTransform" />
</Grid.RenderTransform>
<Image Source="Images/trails.png"
HorizontalAlignment="Center" VerticalAlignment="Center"
Stretch="Uniform" />
</Grid>
</Grid>
CS:
private double _cx, _cy;
private void OnPinchStarted(object sender, PinchStartedGestureEventArgs e)
{
_cx = HambyTransform.ScaleX;
_cy = HambyTransform.ScaleY;
}
//scale the map
private void OnPinchDelta(object sender, PinchGestureEventArgs e)
{
//compute the new scaling factors
double cx = _cx * e.DistanceRatio;
double cy = _cy * e.DistanceRatio;
// If they're between 1.0 and 4.0, inclusive, apply them
if (cx >= 1.0 && cx <= 4.0 && cy >= 1.0 && cy <= 4.0)
{
HambyTransform.ScaleX = cx;
HambyTransform.ScaleY = cy;
}
}
private void OnDragDelta(object sender, DragDeltaGestureEventArgs e)
{
HambyTransform.TranslateX += e.HorizontalChange;
HambyTransform.TranslateY += e.VerticalChange;
}
After many attempts to find a fix, which I never did, I started all over with a new project and the issue went away. Zoom/pan works fine. I don't know what happened or if something internal in my project was corrupt, but its working now. I hope this helps anybody who runs into the same issue. I had to do alot of copying/pasting code, but it worked.