WPF Resize UserControl with aspect ratio - wpf

I have a UserControl and that UserControl has to be resized with aspect ratio.
That means: width:height = 2:1.
Currently I am using this code:
protected override Size ArrangeOverride(Size arrangeBounds)
{
if (ActualWidth == 0 || ActualHeight == 0) return arrangeBounds;
base.ArrangeOverride(arrangeBounds);
double ratio = 2;
if (Parent != null)
{
var size = new Size(arrangeBounds.Height * ratio, arrangeBounds.Height);
double containerWidth = ((FrameworkElement)Parent).ActualWidth;
if (containerWidth < size.Width)
{
double newHeight = arrangeBounds.Height * (containerWidth / size.Width);
canvas.Width = newHeight * ratio;
canvas.Height = newHeight;
}
else
{
canvas.Width = size.Height * ratio;
canvas.Height = size.Height;
}
}
return arrangeBounds;
}
But it is not really working. That means it works but not every time. If I max. the window it sometimes does not get resized, so its a bit "random" if the control gets resized. So if someone would have a better solution if would be very nice.

The most straightforward solution would be to Bind the Height directly to the Width, through a value converter.

It's a bit late, but I recently came across the same problem and since I did not find a good solution I decided to write my own layout control/decorator and wrote a blog post about it here:
http://coding4life.wordpress.com/2012/10/15/wpf-resize-maintain-aspect-ratio/
Basically my solution was to overwrite both MeasureOverride and ArrangeOverride. So far it works very nicely in all of the common containers and I didn't encounter any problems like you described.
I recommend reading the post, where you find a working decorator control, but the most important methods are these:
protected override Size MeasureOverride(Size constraint)
{
if (Child != null)
{
constraint = SizeToRatio(constraint, false);
Child.Measure(constraint);
if(double.IsInfinity(constraint.Width)
|| double.IsInfinity(constraint.Height))
{
return SizeToRatio(Child.DesiredSize, true);
}
return constraint;
}
// we don't have a child, so we don't need any space
return new Size(0, 0);
}
protected override Size ArrangeOverride(Size arrangeSize)
{
if (Child != null)
{
var newSize = SizeToRatio(arrangeSize, false);
double widthDelta = arrangeSize.Width - newSize.Width;
double heightDelta = arrangeSize.Height - newSize.Height;
double top = 0;
double left = 0;
if (!double.IsNaN(widthDelta)
&& !double.IsInfinity(widthDelta))
{
left = widthDelta/2;
}
if (!double.IsNaN(heightDelta)
&& !double.IsInfinity(heightDelta))
{
top = heightDelta/2;
}
var finalRect = new Rect(new Point(left, top), newSize);
Child.Arrange(finalRect);
}
return arrangeSize;
}
public Size SizeToRatio(Size size, bool expand)
{
double ratio = AspectRatio;
double height = size.Width / ratio;
double width = size.Height * ratio;
if (expand)
{
width = Math.Max(width, size.Width);
height = Math.Max(height, size.Height);
}
else
{
width = Math.Min(width, size.Width);
height = Math.Min(height, size.Height);
}
return new Size(width, height);
}
I hope it helps someone!

Wrap your Control with a ViewBox and set the ViewBox.Stretch to Uniform. You can also restrict on MaxWidth and MaxHeight that way.
More Info on the Stretch-Enumeration # MSDN
How to apply Stretch # MSDN

Related

Printing control and setting its position on A4 page

I am trying to print selected control to PDF/printer yet I have problem with positioning it on printed page.
When I changed location in Rect it doesn't affect the outcome. I want the control to be in the center of the page or at least the left top corner of the control should be in left top corner of the page, but all the time I have the same picture which starts at the middle of page.
This is my code:
private async Task PrintObjectVisual(bool canUserSelectPrinter, FrameworkElement frameworkElement)
{
if (_CreatePrinterDialog(canUserSelectPrinter, out var prnt)) return;
var offset = VisualTreeHelper.GetOffset(frameworkElement);
Transform originalScale = frameworkElement.LayoutTransform.Clone();
try
{
bool allowForceLandscape = true;
bool forceLandscape =
allowForceLandscape &&
(_starMainPage.ActPage.PresPage.PrintLandscape || frameworkElement.ActualWidth > prnt.PrintableAreaWidth)
||
(frameworkElement is IGraphViewControl || frameworkElement is IMapViewControl);
double scale = 0.0;
PageMediaSize pageSize = null;
pageSize = new PageMediaSize(PageMediaSizeName.ISOA4, CmToPx(29.7), CmToPx(21));
prnt.PrintTicket.PageMediaSize = pageSize;
if (forceLandscape)
{
prnt.PrintTicket.PageOrientation = PageOrientation.Landscape;
if (pageSize.Width != null && pageSize.Height != null)
scale = Math.Min(
(pageSize.Height.Value / frameworkElement.ActualWidth), (pageSize.Width.Value / frameworkElement.ActualHeight));
}
else
{
prnt.PrintTicket.PageOrientation = PageOrientation.Portrait;
if (pageSize.Width != null && pageSize.Height != null)
scale = Math.Min(
(pageSize.Width.Value / frameworkElement.ActualWidth), (pageSize.Height.Value / frameworkElement.ActualHeight));
}
//if (scale > 1.0)
// scale = 1.0;
frameworkElement.LayoutTransform = new ScaleTransform(scale, scale);
// after a thought i know that i might not need it
//var size = _GetPrintAreaSize(forceLandscape, pageSize);
//frameworkElement.Measure(size);
//frameworkElement.Arrange(
// new Rect(new Point(0, 0), size));
prnt.PrintVisual(frameworkElement, $"Printing Object {frameworkElement.Name}");
}
catch (Exception e)
{
PresHelper.WriteToDebug(_presBaseObject, e);
}
finally
{
frameworkElement.LayoutTransform = originalScale;
// after a thought i know that i might not need it
//Size size2 = new Size(_starMainPage.Canvas.ActualWidth, _starMainPage.Canvas.ActualHeight);
//frameworkElement.Measure(size2);
//var x = offset.X - (offset.X * 2);// offset.X
//var y = offset.Y - (offset.Y * 2);// offset.X
//frameworkElement.Arrange(new Rect(new Point(x, y), size2));
}
}
private static bool _CreatePrinterDialog(bool canUserSelectPrinter, out PrintDialog prnt)
{
prnt = new PrintDialog();
if (canUserSelectPrinter)
{
var result = prnt.ShowDialog();
if (result == false)
return true;
}
return false;
}
private Size _GetPrintAreaSize(bool forceLandscape, PageMediaSize pageSize)
{
if (pageSize.Height == null || pageSize.Width == null) throw new NullReferenceException($"Page size values are null! Can't print without it.");
Size size;
if (forceLandscape)
size = new Size(pageSize.Height.Value, pageSize.Width.Value);
else
size = new Size(pageSize.Width.Value, pageSize.Height.Value);
return size;
}
Ok, so after some time on it I have solved the problem.
I assign original Canvas.LeftProperty and TopProperty to some private class members and then assign my own (double) values.
This code is just before the printing.
Later i'm assigning old values:
frameworkElement.LayoutTransform = new ScaleTransform(scale, scale);
left = (double)frameworkElement.GetValue(Canvas.LeftProperty);
top = (double)frameworkElement.GetValue(Canvas.TopProperty);
frameworkElement.SetValue(Canvas.LeftProperty, _marginSize);
frameworkElement.SetValue(Canvas.TopProperty, _marginSize);
prnt.PrintVisual(frameworkElement, $"Printing MikObject {frameworkElement.Name}");
}
catch (Exception e)
{
PresHelper.WriteToDebug(_presBaseObject, e);
}
finally
{
frameworkElement.SetValue(Canvas.LeftProperty, left);
frameworkElement.SetValue(Canvas.TopProperty, top);
frameworkElement.LayoutTransform = originalScale;
}

2D over 3D anti-aliasing artifacts

We have 3D models in WPF and use a cursor made from a Canvas and some internal parts. We turn the HW cursor off and move the Canvas through MouseEvent. The issue is that there are terrible artifacts on the screen as you move from left to right (not nearly as bad right to left). I have played with snaptodevicepixels, Edge mode and nvidea AA settings but the only thing that "fixes" is setting edge mode to aliased for the 3dviewport - and we don't want that. I have even made the cursor completely transparent and it still leaves artifacts.
I broke out some of the code for demonstration. You can especially see the artifacts moving upper-left to lower-right.
Anyone think they can help me out here? I'm head banging and not in a good way. It's looking more like a bug in the AA code.
Thanks
T
FYI: I used some Petzold code here for the beach ball.
Bad AA BeachBall
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Media3D;
using System.Windows.Shapes;
namespace Cursoraa2
{
class Program : Window
{
[STAThread]
public static void Main() => new Application().Run(new Program());
public Program()
{
Width = 500;
Height = 500;
var grid = new Grid();
var viewport = new Viewport3D();
grid.Children.Add(viewport);
Content = grid;
//RenderOptions.SetEdgeMode(viewport,EdgeMode.Aliased);
DynamicCurosr.Start(grid, grid, 40, Color.FromArgb(40, 0x33, 0x33, 0xff), Colors.Blue);
MouseMove += MainWindow_MouseMove;
MakeBeachBallSphere(viewport);
}
private void MainWindow_MouseMove(object sender, MouseEventArgs e) => DynamicCurosr.Move(e.GetPosition(this));
public void MakeBeachBallSphere(Viewport3D viewport)
{
// Get the MeshGeometry3D from the GenerateSphere method.
var mesh = GenerateSphere(new Point3D(0, 0, 0), 1, 36, 18);
mesh.Freeze();
// Define a brush for the sphere.
var brushes = new Brush[6] { Brushes.Red, Brushes.Blue,
Brushes.Yellow, Brushes.Orange,
Brushes.White, Brushes.Green };
var drawgrp = new DrawingGroup();
for (var i = 0; i < brushes.Length; i++)
{
var rectgeo = new RectangleGeometry(new Rect(10 * i, 0, 10, 60));
var geodraw = new GeometryDrawing(brushes[i], null, rectgeo);
drawgrp.Children.Add(geodraw);
}
var drawbrsh = new DrawingBrush(drawgrp);
drawbrsh.Freeze();
// Define the GeometryModel3D.
var geomod = new GeometryModel3D
{
Geometry = mesh,
Material = new DiffuseMaterial(drawbrsh)
};
// Create a ModelVisual3D for the GeometryModel3D.
var modvis = new ModelVisual3D { Content = geomod };
viewport.Children.Add(modvis);
// Create another ModelVisual3D for light.
var modgrp = new Model3DGroup();
modgrp.Children.Add(new AmbientLight(Color.FromRgb(128, 128, 128)));
modgrp.Children.Add(new DirectionalLight(Color.FromRgb(128, 128, 128), new Vector3D(2, -3, -1)));
modvis = new ModelVisual3D {Content = modgrp};
viewport.Children.Add(modvis);
// Create the camera.
var cam = new PerspectiveCamera(new Point3D(0, 0, 8), new Vector3D(0, 0, -1), new Vector3D(0, 1, 0), 45);
viewport.Camera = cam;
// Create a transform for the GeometryModel3D.
var axisangle = new AxisAngleRotation3D(new Vector3D(1, 1, 0), 180);
var rotate = new RotateTransform3D(axisangle);
geomod.Transform = rotate;
// Animate the RotateTransform3D.
//DoubleAnimation anima = new DoubleAnimation(360, new Duration(TimeSpan.FromSeconds(5)));
//anima.RepeatBehavior = RepeatBehavior.Forever;
//axisangle.BeginAnimation(AxisAngleRotation3D.AngleProperty, anima);
}
MeshGeometry3D GenerateSphere(Point3D center, double radius, int slices, int stacks)
{
// Create the MeshGeometry3D.
var mesh = new MeshGeometry3D();
// Fill the Position, Normals, and TextureCoordinates collections.
for (var stack = 0; stack <= stacks; stack++)
{
var phi = Math.PI / 2 - stack * Math.PI / stacks;
var y = radius * Math.Sin(phi);
var scale = -radius * Math.Cos(phi);
for (var slice = 0; slice <= slices; slice++)
{
var theta = slice * 2 * Math.PI / slices;
var x = scale * Math.Sin(theta);
var z = scale * Math.Cos(theta);
var normal = new Vector3D(x, y, z);
mesh.Normals.Add(normal);
mesh.Positions.Add(normal + center);
mesh.TextureCoordinates.Add(
new Point((double)slice / slices,
(double)stack / stacks));
}
}
// Fill the TriangleIndices collection.
for (var stack = 0; stack < stacks; stack++)
for (var slice = 0; slice < slices; slice++)
{
var n = slices + 1; // Keep the line length down.
if (stack != 0)
{
mesh.TriangleIndices.Add((stack + 0) * n + slice);
mesh.TriangleIndices.Add((stack + 1) * n + slice);
mesh.TriangleIndices.Add((stack + 0) * n + slice + 1);
}
if (stack != stacks - 1)
{
mesh.TriangleIndices.Add((stack + 0) * n + slice + 1);
mesh.TriangleIndices.Add((stack + 1) * n + slice);
mesh.TriangleIndices.Add((stack + 1) * n + slice + 1);
}
}
return mesh;
}
}
public static class DynamicCurosr
{
static public bool InSession { get; private set; }
private static Panel theCursor;
private static readonly ScaleTransform ScaleTransform = new ScaleTransform(1, 1);
private static readonly MatrixTransform MatrixTransform = new MatrixTransform(1, 0, 0, 1, 0, 0);
private static Color defaultFill = Color.FromArgb(20, 255, 255, 255);
private static Color fillFromUser;
private static double strokeFromUser = 0;
private static Color strokeColorFromUser = Colors.Black;
private static int centerDotSizeFromUser = 10; // need to get from user
private static double initialDiameter = double.NaN;
private static Panel cursorPanel;
private static Panel mousePanel;
public static bool Start(Panel cursorPanelIn, Panel mousePanelIn, double radius)
{
return Start(cursorPanelIn, mousePanelIn, radius, defaultFill);
}
public static bool Start(Panel cursorPanelIn, Panel mousePanelIn, double radius, Color fill, Color strokeColor = default(Color), double strokeSize = .16)
{
strokeColor = strokeColor == default(Color) ? Colors.Black : strokeColor;
strokeColorFromUser = strokeColor;
fillFromUser = fill;
strokeFromUser = strokeColor == default(Color) ? 0 : strokeSize;
initialDiameter = double.IsNaN(initialDiameter) ? radius * 2 : initialDiameter;
return Start(cursorPanelIn, mousePanelIn);
}
private static bool Start(Panel cursorPanelIn, Panel mousePanelIn)
{
if (InSession) return false;
cursorPanel = cursorPanelIn;
mousePanel = mousePanelIn;
var point = Mouse.GetPosition(cursorPanel);
theCursor = MakeACursor(theCursor, initialDiameter / 2);
InSession = true;
cursorPanel.Cursor = Cursors.None;
theCursor.Visibility = Visibility.Visible;
Move(point);
if (cursorPanel.Children.Contains(theCursor))
return false;
cursorPanel.Children.Add(theCursor);
Mouse.OverrideCursor = Cursors.None;
return true;
}
public static void Stop()
{
if (InSession)
{
Mouse.OverrideCursor = null;
theCursor.Visibility = Visibility.Collapsed;
cursorPanel.Children.Remove(theCursor);
InSession = false;
}
}
public static void Move(Point point)
{
if (InSession && theCursor.Visibility == Visibility.Visible)
{
var m = MatrixTransform.Matrix;
m.OffsetX = point.X - theCursor.Width / 2;
m.OffsetY = point.Y - theCursor.Height / 2;
MatrixTransform.Matrix = m;
theCursor.RenderTransform = MatrixTransform;
}
}
public static Panel MakeACursor(Panel theCursor, double radius, Color fillColorIn = default(Color), Color strokeColorIn = default(Color))
{
var strokeColor = new SolidColorBrush(strokeColorIn == default(Color) ? strokeColorFromUser : strokeColorIn);
if (theCursor == null)
{
theCursor = new Grid()
{
Width = radius * 2,
Height = radius * 2,
Background = null,
VerticalAlignment = VerticalAlignment.Top,
HorizontalAlignment = HorizontalAlignment.Left,
RenderTransform = ScaleTransform,
RenderTransformOrigin = new Point(.5, .5),
};
var cursorElement = new Ellipse
{
Width = radius * 2,
Height = radius * 2,
Fill = new SolidColorBrush(fillColorIn == default(Color) ? fillFromUser : fillColorIn),
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Top,
StrokeThickness = strokeFromUser,
Stroke = strokeColor,
RenderTransformOrigin = new Point(.5, .5)
};
theCursor.Children.Add(cursorElement);
}
MakeCursorOverlay(theCursor, radius, strokeColor);
return theCursor;
}
public static void MakeCursorOverlay(Panel theCursor, double radius, SolidColorBrush strokeColor)
{
var save = theCursor.Children[0];
theCursor.Children.Clear();
theCursor.Children.Add(save);
var circle = new Ellipse
{
Width = centerDotSizeFromUser,
Height = centerDotSizeFromUser,
Fill = null,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
StrokeThickness = strokeFromUser,
Stroke = strokeColor,
RenderTransformOrigin = new Point(.5, .5)
};
theCursor.Children.Add(circle);
}
}
}

FinalSize in ArrangeOverride is coming wrong

I'm doing a custom panel, based(heriting) the WrapPanel.
I'm doing this because some UserControls I'm using in the WrapPanel don't wrap because they are able to use an "infinite" width, so a WrapPanel with a vertical orientation will never wrap.
I wanted to do one WrapPanel that ignore the width of the elements when having orientation = Vertical, and ignore the height of the elements when having an orientation = Horizontal.
Currently I've the following code:
protected override Size ArrangeOverride(Size finalSize)
{
int numberOfColumns, numberOfRows;
UIElement uiElement = InternalChildren.OfType<UIElement>().First();
if (Orientation == Orientation.Horizontal)
{
double desiredWidth = uiElement.DesiredSize.Width;
numberOfColumns = (int)Math.Round(finalSize.Width / desiredWidth);
numberOfRows = (int)Math.Ceiling(InternalChildren.Count / (double)numberOfColumns);
}
else
{
double desiredHeight = uiElement.DesiredSize.Height;
numberOfRows = (int)Math.Round(finalSize.Height / desiredHeight);
numberOfColumns = (int)Math.Ceiling(InternalChildren.Count / (double)numberOfRows);
}
int row = 0, column = 0;
Size elementSize = new Size(Math.Floor(finalSize.Width / numberOfColumns), Math.Floor(finalSize.Height / numberOfRows));
foreach (UIElement child in InternalChildren)
{
child.Arrange(new Rect(new Point(row * elementSize.Width, column * elementSize.Height), elementSize));
if (Orientation == Orientation.Horizontal)
{
column++;
if (column >= numberOfColumns)
{
column = 0;
row++;
}
}
else
{
row++;
if (row >= numberOfRows)
{
row = 0;
column++;
}
}
}
return finalSize;
}
The issue is that the finalSize that I receives is bigger than the "ActualSize" of the component(Width 2x the actualSize in fact).
What did I miss?

How can I animate a ScrollIntoView action in Silverlight 4?

Um... that's pretty much it.
How can I animate a ScrollIntoView action in Silverlight 4?
Edits:
I know how to animated the scrolling. I just create my own DependencyProperty and scroll the ScrollViewer when it changes. What I need to do, is set up how much it should be changed? How do I calculate that? What does ScrollIntoView actually do?
From Reflector:
public static void ScrollIntoView(this ScrollViewer viewer, FrameworkElement element, double horizontalMargin, double verticalMargin, Duration duration)
{
if (viewer == null)
{
throw new ArgumentNullException("viewer");
}
if (element == null)
{
throw new ArgumentNullException("element");
}
Rect? itemRect = element.GetBoundsRelativeTo(viewer);
if (itemRect.HasValue)
{
double verticalOffset = viewer.VerticalOffset;
double verticalDelta = 0.0;
double hostBottom = viewer.ViewportHeight;
double itemBottom = itemRect.Value.Bottom + verticalMargin;
if (hostBottom < itemBottom)
{
verticalDelta = itemBottom - hostBottom;
verticalOffset += verticalDelta;
}
double itemTop = itemRect.Value.Top - verticalMargin;
if ((itemTop - verticalDelta) < 0.0)
{
verticalOffset -= verticalDelta - itemTop;
}
double horizontalOffset = viewer.HorizontalOffset;
double horizontalDelta = 0.0;
double hostRight = viewer.ViewportWidth;
double itemRight = itemRect.Value.Right + horizontalMargin;
if (hostRight < itemRight)
{
horizontalDelta = itemRight - hostRight;
horizontalOffset += horizontalDelta;
}
double itemLeft = itemRect.Value.Left - horizontalMargin;
if ((itemLeft - horizontalDelta) < 0.0)
{
horizontalOffset -= horizontalDelta - itemLeft;
}
if (duration == TimeSpan.Zero)
{
viewer.ScrollToVerticalOffset(verticalOffset);
viewer.ScrollToHorizontalOffset(horizontalOffset);
}
else
{
Storyboard storyboard = new Storyboard();
SetVerticalOffset(viewer, viewer.VerticalOffset);
SetHorizontalOffset(viewer, viewer.HorizontalOffset);
DoubleAnimation verticalOffsetAnimation = new DoubleAnimation {
To = new double?(verticalOffset),
Duration = duration
};
DoubleAnimation horizontalOffsetAnimation = new DoubleAnimation {
To = new double?(verticalOffset),
Duration = duration
};
Storyboard.SetTarget(verticalOffsetAnimation, viewer);
Storyboard.SetTarget(horizontalOffsetAnimation, viewer);
Storyboard.SetTargetProperty(horizontalOffsetAnimation, new PropertyPath(HorizontalOffsetProperty));
Storyboard.SetTargetProperty(verticalOffsetAnimation, new PropertyPath(VerticalOffsetProperty));
storyboard.Children.Add(verticalOffsetAnimation);
storyboard.Children.Add(horizontalOffsetAnimation);
storyboard.Begin();
}
}
}

How to calculate translate while zooming in WPF/Surface

I'm doing a surface app where I need to scale my scene (zoom) while the user scales with their fingers (i.e pinching)
Currently I have it working ok, but the issue is that I need to zoom in on the center point between the users fingers.
I have the point, but the maths behind the translation is hard to grasp.
When I apply a ScaleTransform to my Canvas scene, it zooms in on the top left of the canvas, i need it to zoom in on the center point of my pinch gesture (which, again, I do have).
How would the maths for the translation work to keep the zoom to appear to zoom in on the center point of the gesture?
Edit:
This is basically what I have:
void Affine2DManipulationDelta(object sender, Affine2DOperationDeltaEventArgs e)
{
//apply zoom and translate
if (e.ScaleDelta != 1)
ApplyZoom(e);
else if (e.Delta.Length != 0)
ApplyTranslation(e);
}
private void ApplyZoom(Affine2DOperationDeltaEventArgs e)
{
//scale
var newScale = _zoomTransform.ScaleX * e.ScaleDelta;
newScale = GetCappedZoomLevel(newScale);
_zoomTransform.ScaleX = newScale;
_zoomTransform.ScaleY = newScale;
}
private void ApplyTranslation(Affine2DOperationDeltaEventArgs e)
{
var xDiff = e.ManipulationOrigin.X - _screenStartPoint.X;
var yDiff = e.ManipulationOrigin.Y - _screenStartPoint.Y;
var translateX = xDiff + _startOffset.X;
var translateY = yDiff + _startOffset.Y;
//bounds testing to limit translation
var rect = new Rect(0.0, 0.0, ZoomCanvas.RenderSize.Width, ZoomCanvas.RenderSize.Height);
Rect bounds = ZoomCanvas.TransformToAncestor(MainViewportCanvas).TransformBounds(rect);
if (CanTranslateX(translateX, bounds))
_translateTransform.X = translateX;
if (CanTranslateY(translateY, bounds))
_translateTransform.Y = translateY;
}
Pretty basic really, but it works to a point...
_zoomTransform is a ScaleTransform, and _translateTransform is a TranslateTransform
MainViewport is a canvas that contains ZoomCanvas which is the canvas that I apply the transforms to.
Here is my implementation that ended up working just fine
private void ApplyZoom(Affine2DOperationDeltaEventArgs e)
{
//scale
var newScale = _zoomTransform.ScaleX * e.ScaleDelta;
newScale = GetCappedZoomLevel(newScale);
var pinchPoint = e.ManipulationOrigin;
DoZoom(newScale, _transformGroup.Inverse.Transform(pinchPoint), pinchPoint);
}
private void DoZoom(double newScale, Point pinchPosition, Point physicalPosition)
{
_translateTransform.X = -1 * (pinchPosition.X * newScale - physicalPosition.X);
_translateTransform.Y = -1 * (pinchPosition.Y * newScale - physicalPosition.Y);
_zoomTransform.ScaleX = newScale;
_zoomTransform.ScaleY = newScale;
}
I had to do exactly this myself. Basically, a Surface control that can host arbitrary content and allow the user to pan and zoom (using gestures). The handler for my maniplation processor's Affine2DManipulationDelta is shown below. Hopefully it's fairly self-explanatory and gets you where you need to be.
private void OnManipulationDelta(object sender, Affine2DOperationDeltaEventArgs e)
{
Debug.Assert(_scrollViewer != null);
if (CanScale && _scrollViewer != null && e.ScaleDelta != 1)
{
var scrollViewerContent = _scrollViewer.Content as UIElement;
//can't do anything if the content isn't present and isn't a UIElement
if (scrollViewerContent != null)
{
var newScale = Scale * e.ScaleDelta;
newScale = Math.Max(MinScale, newScale);
newScale = Math.Min(MaxScale, newScale);
var origin = e.ManipulationOrigin;
var pointInContent = _scrollViewer.TranslatePoint(origin, scrollViewerContent);
var deltaScale = newScale - Scale;
//width and height changes across the whole image
var deltaWidth = deltaScale * _scrollViewer.ExtentWidth;
var deltaHeight = deltaScale * _scrollViewer.ExtentHeight;
//width and height changes relative to the point in the scroll viewer's content
deltaWidth = (pointInContent.X / _scrollViewer.ExtentWidth) * deltaWidth;
deltaHeight = (pointInContent.Y / _scrollViewer.ExtentHeight) * deltaHeight;
_offset = Vector.Add(_offset, new Vector(deltaWidth, deltaHeight));
var centerPoint = new Point(origin.X + deltaWidth, origin.Y + deltaHeight);
centerPoint.Offset(_offset.X, _offset.Y);
Scale = newScale;
HorizontalOffset = _scrollViewer.HorizontalOffset + deltaWidth;
VerticalOffset = _scrollViewer.VerticalOffset + deltaHeight;
}
}
else if (CanPan && e.Delta.Length != 0)
{
var newHorizontalOffset = _scrollViewer.HorizontalOffset + (e.Delta.X * -1);
var newVerticalOffset = _scrollViewer.VerticalOffset + (e.Delta.Y * -1);
newHorizontalOffset = Math.Max(0, newHorizontalOffset);
newVerticalOffset = Math.Max(0, newVerticalOffset);
HorizontalOffset = newHorizontalOffset;
VerticalOffset = newVerticalOffset;
}
}

Resources