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;
}
Related
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);
}
}
}
This is the same question asked before and the answer is also verified but in My Project I am using WebApi 2 and want return image from Ihttpactionresult
I have searched and lot and done some code Which I don't know if I am doing right..
I also Looked at ImageReizer but it saves the resized Images in the folder. Idon't want to save the Image.
Here is My Controller Action
[HttpGet]
[Route("GetFile")]
public IHttpActionResult GetFile(string filename, int w = 0, int h = 0)
{
//string filePath = "fdsafsa";
//int width = 0;
//var fileStream = File.Open("/ProjectFiles", FileMode.Open);
//var content = new StreamContent(fileStream);
//content.Headers.ContentType = new MediaTypeHeaderValue("png");
var imageFile = HttpContext.Current.Server.MapPath(#filename);
if(File.Exists(imageFile))
{
var srcImage = Image.FromFile(imageFile);
var newImage = new Bitmap(w, h);
var graphics = Graphics.FromImage(newImage);
var stream = new MemoryStream();
graphics.SmoothingMode = SmoothingMode.AntiAlias;
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
graphics.DrawImage(srcImage, new Rectangle(0, 0, w, h));
newImage.Save(stream, ImageFormat.Png);
//var content = new StreamContent(stream);
HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);
result.Content = new ByteArrayContent(stream.ToArray());
result.Content.Headers.ContentType = new MediaTypeHeaderValue("image/png");
return Ok(result);
}
This is the html where I want to render the Image
<img src="/api/Public/GetFile?filename=/ProjectFiles/Project_2087/art-therapy-career2_73937984-7e03-4067-91bf-2dd4fc10b328.jpg&w=100&h=100" alt="image" />
How can I show the resized Image dynamically without storing the resize image on the server. I don't want to save the resize Images.
I need help soon.
Thanks for your help.
Another approach ......
[HttpGet]
[Route("GetFileStream")]
public IHttpActionResult GetFileStream(string filename, int w = 0, int h = 0)
{
var imagePath = HttpContext.Current.Server.MapPath(#filename);
var result = getResizedImage(imagePath, w, h);
return Ok(result);
}
byte[] getResizedImage(String path, int width, int height)
{
Bitmap imgIn = new Bitmap(path);
double y = imgIn.Height;
double x = imgIn.Width;
double factor = 1;
if (width > 0)
{
factor = width / x;
}
else if (height > 0)
{
factor = height / y;
}
System.IO.MemoryStream outStream = new System.IO.MemoryStream();
Bitmap imgOut = new Bitmap((int)(x * factor), (int)(y * factor));
Graphics g = Graphics.FromImage(imgOut);
g.Clear(Color.White);
g.DrawImage(imgIn, new Rectangle(0, 0, (int)(factor * x), (int)(factor * y)), new Rectangle(0, 0, (int)x, (int)y), GraphicsUnit.Pixel);
imgOut.Save(outStream, getImageFormat(path));
return outStream.ToArray();
}
**
This is the Working Code with Mvc
**
This is the code I found working for Simple Mvc Application
An Action
public ActionResult Thumb(string filename = "Azalea.jpg", int w = 0, int h = 0)
{
string path = Path.Combine(Server.MapPath("~/images2"), filename);
//Bitmap bitmap = new Bitmap(filepath);
//if (bitmap == null)
//{
// return new EmptyResult();
//}
//MemoryStream ms = new MemoryStream();
//bitmap.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
Bitmap imgIn = new Bitmap(path);
double y = imgIn.Height;
double x = imgIn.Width;
double factor = 1;
if (w > 0)
{
factor = w / x;
}
else if (h > 0)
{
factor = h / y;
}
System.IO.MemoryStream outStream = new System.IO.MemoryStream();
Bitmap imgOut = new Bitmap((int)(x * factor), (int)(y * factor));
Graphics g = Graphics.FromImage(imgOut);
g.Clear(Color.White);
g.DrawImage(imgIn, new Rectangle(0, 0, (int)(factor * x), (int)(factor * y)), new Rectangle(0, 0, (int)x, (int)y), GraphicsUnit.Pixel);
imgOut.Save(outStream, getImageFormat(path));
outStream.Seek(0, SeekOrigin.Begin);
FileStreamResult fileStreamResult = new FileStreamResult(outStream, "image/png");
return fileStreamResult;
}
In cshtml page
<img src="~/Home/Thumb/#Model.ImageName?w=700&h=300" alt="image" />
How to use this With web Api2 ?
Answer of My Question after long search.Please Improve this
[HttpGet]
[Route("GetFileStream")]
public IHttpActionResult GetFileStream(string filename, int w = 0, int h = 0)
{
var imagePath = HttpContext.Current.Server.MapPath(#filename);
return new FileResult(imagePath, w, h, "image/jpeg");
}
public class FileResult : IHttpActionResult
{
private readonly string filePath;
private readonly string contentType;
private readonly int width;
private readonly int height;
public FileResult(string filePath, int width, int height, string contentType = null)
{
this.filePath = filePath;
this.contentType = contentType;
this.width = width;
this.height = height;
}
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
return Task.Run(() =>
{
var result = getResizedImage(filePath, width, height);
var response = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new ByteArrayContent(result)
//Content = new StreamContent(File.OpenRead(filePath))
};
var contentType = this.contentType ?? MimeMapping.GetMimeMapping(Path.GetExtension(filePath));
response.Content.Headers.ContentType = new MediaTypeHeaderValue(contentType);
return response;
}, cancellationToken);
}
}
static byte[] getResizedImage(String path, int width, int height)
{
Bitmap imgIn = new Bitmap(path);
double y = imgIn.Height;
double x = imgIn.Width;
double factor = 1;
if (width > 0)
{
factor = width / x;
}
else if (height > 0)
{
factor = height / y;
}
System.IO.MemoryStream outStream = new System.IO.MemoryStream();
Bitmap imgOut = new Bitmap((int)(x * factor), (int)(y * factor));
Graphics g = Graphics.FromImage(imgOut);
g.Clear(Color.White);
g.DrawImage(imgIn, new Rectangle(0, 0, (int)(factor * x), (int)(factor * y)), new Rectangle(0, 0, (int)x, (int)y), GraphicsUnit.Pixel);
imgOut.Save(outStream, getImageFormat(path));
//outStream.Seek(0, SeekOrigin.Begin);
//System.Web.Mvc.FileStreamResult fileStreamResult = new System.Web.Mvc.FileStreamResult(outStream, "image/png");
//return fileStreamResult;
return outStream.ToArray();
}
string getContentType(String path)
{
switch (Path.GetExtension(path))
{
case ".bmp": return "Image/bmp";
case ".gif": return "Image/gif";
case ".jpg": return "Image/jpeg";
case ".png": return "Image/png";
default: break;
}
return "";
}
static ImageFormat getImageFormat(String path)
{
switch (Path.GetExtension(path))
{
case ".bmp": return ImageFormat.Bmp;
case ".gif": return ImageFormat.Gif;
case ".jpg": return ImageFormat.Jpeg;
case ".png": return ImageFormat.Png;
default: break;
}
return ImageFormat.Jpeg;
}
Please Help in improving this answer,
Thanks,
Sanuj
You can pass your byteArray to View, then use something like this:
Create your image model:
public class ImageData
{
public string Name { get; set; }
public byte[] Content { get; set; }
. . .
}
Then in controller assign and return your model to View():
#{
string imageBase = Convert.ToBase64String(Model.ImageContent);
string imageSource = string.Format("data:image/gif;base64,{0}", imageBase);
}
<img src="#imageSource" alt="#Model.ImageName" width="100" height="100" />
I want a textbox where you can input text from keyboard but be shown as an arc. Is it possible ?
I have found this solution in codeproject. Author created TextOnAPath control which can display curved text.
Source code:
[ContentProperty("Text")]
public class TextOnAPath : Control
{
static TextOnAPath()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(TextOnAPath), new FrameworkPropertyMetadata(typeof(TextOnAPath)));
Control.FontSizeProperty.OverrideMetadata(typeof(TextOnAPath),
new FrameworkPropertyMetadata(
new PropertyChangedCallback(OnFontPropertyChanged)));
Control.FontFamilyProperty.OverrideMetadata(typeof(TextOnAPath),
new FrameworkPropertyMetadata(
new PropertyChangedCallback(OnFontPropertyChanged)));
Control.FontStretchProperty.OverrideMetadata(typeof(TextOnAPath),
new FrameworkPropertyMetadata(
new PropertyChangedCallback(OnFontPropertyChanged)));
Control.FontStyleProperty.OverrideMetadata(typeof(TextOnAPath),
new FrameworkPropertyMetadata(
new PropertyChangedCallback(OnFontPropertyChanged)));
Control.FontWeightProperty.OverrideMetadata(typeof(TextOnAPath),
new FrameworkPropertyMetadata(
new PropertyChangedCallback(OnFontPropertyChanged)));
}
static void OnFontPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TextOnAPath textOnAPath = d as TextOnAPath;
if (textOnAPath == null)
return;
if (e.NewValue == null || e.NewValue == e.OldValue)
return;
textOnAPath.UpdateText();
textOnAPath.Update();
}
double[] _segmentLengths;
TextBlock[] _textBlocks;
Panel _layoutPanel;
bool _layoutHasValidSize = false;
#region Text DP
public String Text
{
get { return (String)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
// Using a DependencyProperty as the backing store for Text. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(String), typeof(TextOnAPath),
new PropertyMetadata(null, new PropertyChangedCallback(OnStringPropertyChanged),
new CoerceValueCallback(CoerceTextValue)));
static void OnStringPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TextOnAPath textOnAPath = d as TextOnAPath;
if (textOnAPath == null)
return;
if (e.NewValue == e.OldValue || e.NewValue == null)
{
if (textOnAPath._layoutPanel != null)
textOnAPath._layoutPanel.Children.Clear();
return;
}
textOnAPath.UpdateText();
textOnAPath.Update();
}
static object CoerceTextValue(DependencyObject d, object baseValue)
{
if ((String)baseValue == "")
return null;
return baseValue;
}
#endregion
#region TextPath DP
public Geometry TextPath
{
get { return (Geometry)GetValue(TextPathProperty); }
set { SetValue(TextPathProperty, value); }
}
public static readonly DependencyProperty TextPathProperty =
DependencyProperty.Register("TextPath", typeof(Geometry), typeof(TextOnAPath),
new FrameworkPropertyMetadata(null,
new PropertyChangedCallback(OnTextPathPropertyChanged)));
static void OnTextPathPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TextOnAPath textOnAPath = d as TextOnAPath;
if (textOnAPath == null)
return;
if (e.NewValue == e.OldValue || e.NewValue == null)
return;
textOnAPath.TextPath.Transform = null;
textOnAPath.UpdateSize();
textOnAPath.Update();
}
#endregion
#region DrawPath DP
/// <summary>
/// Set this property to True to display the TextPath geometry in the control
/// </summary>
public bool DrawPath
{
get { return (bool)GetValue(DrawPathProperty); }
set { SetValue(DrawPathProperty, value); }
}
public static readonly DependencyProperty DrawPathProperty =
DependencyProperty.Register("DrawPath", typeof(bool), typeof(TextOnAPath),
new PropertyMetadata(false, new PropertyChangedCallback(OnDrawPathPropertyChanged)));
static void OnDrawPathPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TextOnAPath textOnAPath = d as TextOnAPath;
if (textOnAPath == null)
return;
if (e.NewValue == e.OldValue || e.NewValue == null)
return;
textOnAPath.Update();
}
#endregion
#region DrawLinePath DP
/// <summary>
/// Set this property to True to display the line segments under the text (flattened path)
/// </summary>
public bool DrawLinePath
{
get { return (bool)GetValue(DrawLinePathProperty); }
set { SetValue(DrawLinePathProperty, value); }
}
// Using a DependencyProperty as the backing store for DrawFlattendPath. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DrawLinePathProperty =
DependencyProperty.Register("DrawLinePath", typeof(bool), typeof(TextOnAPath),
new PropertyMetadata(false, new PropertyChangedCallback(OnDrawLinePathPropertyChanged)));
static void OnDrawLinePathPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TextOnAPath textOnAPath = d as TextOnAPath;
if (textOnAPath == null)
return;
if (e.NewValue == e.OldValue || e.NewValue == null)
return;
textOnAPath.Update();
}
#endregion
#region ScaleTextPath DP
/// <summary>
/// If set to True (default) then the geometry defined by TextPath automatically gets scaled to fit the width/height of the control
/// </summary>
public bool ScaleTextPath
{
get { return (bool)GetValue(ScaleTextPathProperty); }
set { SetValue(ScaleTextPathProperty, value); }
}
public static readonly DependencyProperty ScaleTextPathProperty =
DependencyProperty.Register("ScaleTextPath", typeof(bool), typeof(TextOnAPath),
new PropertyMetadata(false, new PropertyChangedCallback(OnScaleTextPathPropertyChanged)));
static void OnScaleTextPathPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TextOnAPath textOnAPath = d as TextOnAPath;
if (textOnAPath == null)
return;
if (e.NewValue == e.OldValue)
return;
bool value = (Boolean)e.NewValue;
if (value == false && textOnAPath.TextPath != null)
textOnAPath.TextPath.Transform = null;
textOnAPath.UpdateSize();
textOnAPath.Update();
}
#endregion
void UpdateText()
{
if (Text == null || FontFamily == null || FontWeight == null || FontStyle == null)
return;
_textBlocks = new TextBlock[Text.Length];
_segmentLengths = new double[Text.Length];
for (int i = 0; i < Text.Length; i++)
{
TextBlock t = new TextBlock();
t.FontSize = this.FontSize;
t.FontFamily = this.FontFamily;
t.FontStretch = this.FontStretch;
t.FontWeight = this.FontWeight;
t.FontStyle = this.FontStyle;
t.Text = new String(Text[i], 1);
t.RenderTransformOrigin = new Point(0.0, 1.0);
t.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
_textBlocks[i] = t;
_segmentLengths[i] = t.DesiredSize.Width;
}
}
void Update()
{
if (Text == null || TextPath == null || _layoutPanel == null || !_layoutHasValidSize)
return;
List<Point> intersectionPoints;
intersectionPoints = GeometryHelper.GetIntersectionPoints(TextPath.GetFlattenedPathGeometry(), _segmentLengths);
_layoutPanel.Children.Clear();
_layoutPanel.Margin = new Thickness(FontSize);
for (int i = 0; i < intersectionPoints.Count - 1; i++)
{
double oppositeLen = Math.Sqrt(Math.Pow(intersectionPoints[i].X + _segmentLengths[i] - intersectionPoints[i + 1].X, 2.0) + Math.Pow(intersectionPoints[i].Y - intersectionPoints[i + 1].Y, 2.0)) / 2.0;
double hypLen = Math.Sqrt(Math.Pow(intersectionPoints[i].X - intersectionPoints[i + 1].X, 2.0) + Math.Pow(intersectionPoints[i].Y - intersectionPoints[i + 1].Y, 2.0));
double ratio = oppositeLen / hypLen;
if (ratio > 1.0)
ratio = 1.0;
else if (ratio < -1.0)
ratio = -1.0;
//double angle = 0.0;
double angle = 2.0 * Math.Asin(ratio) * 180.0 / Math.PI;
// adjust sign on angle
if ((intersectionPoints[i].X + _segmentLengths[i]) > intersectionPoints[i].X)
{
if (intersectionPoints[i + 1].Y < intersectionPoints[i].Y)
angle = -angle;
}
else
{
if (intersectionPoints[i + 1].Y > intersectionPoints[i].Y)
angle = -angle;
}
TextBlock currTextBlock = _textBlocks[i];
RotateTransform rotate = new RotateTransform(angle);
TranslateTransform translate = new TranslateTransform(intersectionPoints[i].X, intersectionPoints[i].Y - currTextBlock.DesiredSize.Height);
TransformGroup transformGrp = new TransformGroup();
transformGrp.Children.Add(rotate);
transformGrp.Children.Add(translate);
currTextBlock.RenderTransform = transformGrp;
_layoutPanel.Children.Add(currTextBlock);
if (DrawLinePath == true)
{
Line line = new Line();
line.X1 = intersectionPoints[i].X;
line.Y1 = intersectionPoints[i].Y;
line.X2 = intersectionPoints[i + 1].X;
line.Y2 = intersectionPoints[i + 1].Y;
line.Stroke = Brushes.Black;
_layoutPanel.Children.Add(line);
}
}
// don't draw path if already drawing line path
if (DrawPath == true && DrawLinePath == false)
{
Path path = new Path();
path.Data = TextPath;
path.Stroke = Brushes.Black;
_layoutPanel.Children.Add(path);
}
}
public TextOnAPath()
{
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
_layoutPanel = GetTemplateChild("LayoutPanel") as Panel;
if (_layoutPanel == null)
throw new Exception("Could not find template part: LayoutPanel");
_layoutPanel.SizeChanged += new SizeChangedEventHandler(_layoutPanel_SizeChanged);
}
Size _newSize;
void _layoutPanel_SizeChanged(object sender, SizeChangedEventArgs e)
{
_newSize = e.NewSize;
UpdateSize();
Update();
}
void UpdateSize()
{
if (_newSize == null || TextPath == null)
return;
_layoutHasValidSize = true;
double xScale = _newSize.Width / TextPath.Bounds.Width;
double yScale = _newSize.Height / TextPath.Bounds.Height;
if (TextPath.Bounds.Width <= 0)
xScale = 1.0;
if (TextPath.Bounds.Height <= 0)
xScale = 1.0;
if (xScale <= 0 || yScale <= 0)
return;
if (TextPath.Transform is TransformGroup)
{
TransformGroup grp = TextPath.Transform as TransformGroup;
if (grp.Children[0] is ScaleTransform && grp.Children[1] is TranslateTransform)
{
if (ScaleTextPath)
{
ScaleTransform scale = grp.Children[0] as ScaleTransform;
scale.ScaleX *= xScale;
scale.ScaleY *= yScale;
}
TranslateTransform translate = grp.Children[1] as TranslateTransform;
translate.X += -TextPath.Bounds.X;
translate.Y += -TextPath.Bounds.Y;
}
}
else
{
ScaleTransform scale;
TranslateTransform translate;
if (ScaleTextPath)
{
scale = new ScaleTransform(xScale, yScale);
translate = new TranslateTransform(-TextPath.Bounds.X * xScale, -TextPath.Bounds.Y * yScale);
}
else
{
scale = new ScaleTransform(1.0, 1.0);
translate = new TranslateTransform(-TextPath.Bounds.X, -TextPath.Bounds.Y );
}
TransformGroup grp = new TransformGroup();
grp.Children.Add(scale);
grp.Children.Add(translate);
TextPath.Transform = grp;
}
}
}
public static class GeometryHelper
{
public static List<Point> GetIntersectionPoints(PathGeometry FlattenedPath, double[] SegmentLengths)
{
List<Point> intersectionPoints = new List<Point>();
List<Point> pointsOnFlattenedPath = GetPointsOnFlattenedPath(FlattenedPath);
if (pointsOnFlattenedPath == null || pointsOnFlattenedPath.Count < 2)
return intersectionPoints;
Point currPoint = pointsOnFlattenedPath[0];
intersectionPoints.Add(currPoint);
// find point on flattened path that is segment length away from current point
int flattedPathIndex = 0;
int segmentIndex = 1;
while (flattedPathIndex < pointsOnFlattenedPath.Count - 1 &&
segmentIndex < SegmentLengths.Length + 1)
{
Point? intersectionPoint = GetIntersectionOfSegmentAndCircle(
pointsOnFlattenedPath[flattedPathIndex],
pointsOnFlattenedPath[flattedPathIndex + 1], currPoint, SegmentLengths[segmentIndex - 1]);
if (intersectionPoint == null)
flattedPathIndex++;
else
{
intersectionPoints.Add((Point)intersectionPoint);
currPoint = (Point)intersectionPoint;
pointsOnFlattenedPath[flattedPathIndex] = currPoint;
segmentIndex++;
}
}
return intersectionPoints;
}
static List<Point> GetPointsOnFlattenedPath(PathGeometry FlattenedPath)
{
List<Point> flattenedPathPoints = new List<Point>();
// for flattened geometry there should be just one PathFigure in the Figures
if (FlattenedPath.Figures.Count != 1)
return null;
PathFigure pathFigure = FlattenedPath.Figures[0];
flattenedPathPoints.Add(pathFigure.StartPoint);
// SegmentsCollection should contain PolyLineSegment and LineSegment
foreach (PathSegment pathSegment in pathFigure.Segments)
{
if (pathSegment is PolyLineSegment)
{
PolyLineSegment seg = pathSegment as PolyLineSegment;
foreach (Point point in seg.Points)
flattenedPathPoints.Add(point);
}
else if (pathSegment is LineSegment)
{
LineSegment seg = pathSegment as LineSegment;
flattenedPathPoints.Add(seg.Point);
}
else
throw new Exception("GetIntersectionPoint - unexpected path segment type: " + pathSegment.ToString());
}
return (flattenedPathPoints);
}
static Point? GetIntersectionOfSegmentAndCircle(Point SegmentPoint1, Point SegmentPoint2,
Point CircleCenter, double CircleRadius)
{
// linear equation for segment: y = mx + b
double slope = (SegmentPoint2.Y - SegmentPoint1.Y) / (SegmentPoint2.X - SegmentPoint1.X);
double intercept = SegmentPoint1.Y - (slope * SegmentPoint1.X);
// special case when segment is vertically oriented
if (double.IsInfinity(slope))
{
double root = Math.Pow(CircleRadius, 2.0) - Math.Pow(SegmentPoint1.X - CircleCenter.X, 2.0);
if (root < 0)
return null;
// soln 1
double SolnX1 = SegmentPoint1.X;
double SolnY1 = CircleCenter.Y - Math.Sqrt(root);
Point Soln1 = new Point(SolnX1, SolnY1);
// have valid result if point is between two segment points
if (IsBetween(SolnX1, SegmentPoint1.X, SegmentPoint2.X) &&
IsBetween(SolnY1, SegmentPoint1.Y, SegmentPoint2.Y))
//if (ValidSoln(Soln1, SegmentPoint1, SegmentPoint2, CircleCenter))
{
// found solution
return (Soln1);
}
// soln 2
double SolnX2 = SegmentPoint1.X;
double SolnY2 = CircleCenter.Y + Math.Sqrt(root);
Point Soln2 = new Point(SolnX2, SolnY2);
// have valid result if point is between two segment points
if (IsBetween(SolnX2, SegmentPoint1.X, SegmentPoint2.X) &&
IsBetween(SolnY2, SegmentPoint1.Y, SegmentPoint2.Y))
//if (ValidSoln(Soln2, SegmentPoint1, SegmentPoint2, CircleCenter))
{
// found solution
return (Soln2);
}
}
else
{
// use soln to quadradratic equation to solve intersection of segment and circle:
// x = (-b +/ sqrt(b^2-4ac))/(2a)
double a = 1 + Math.Pow(slope, 2.0);
double b = (-2 * CircleCenter.X) + (2 * (intercept - CircleCenter.Y) * slope);
double c = Math.Pow(CircleCenter.X, 2.0) + Math.Pow(intercept - CircleCenter.Y, 2.0) - Math.Pow(CircleRadius, 2.0);
// check for no solutions, is sqrt negative?
double root = Math.Pow(b, 2.0) - (4 * a * c);
if (root < 0)
return null;
// we might have two solns...
// soln 1
double SolnX1 = (-b + Math.Sqrt(root)) / (2 * a);
double SolnY1 = slope * SolnX1 + intercept;
Point Soln1 = new Point(SolnX1, SolnY1);
// have valid result if point is between two segment points
if (IsBetween(SolnX1, SegmentPoint1.X, SegmentPoint2.X) &&
IsBetween(SolnY1, SegmentPoint1.Y, SegmentPoint2.Y))
//if (ValidSoln(Soln1, SegmentPoint1, SegmentPoint2, CircleCenter))
{
// found solution
return (Soln1);
}
// soln 2
double SolnX2 = (-b - Math.Sqrt(root)) / (2 * a);
double SolnY2 = slope * SolnX2 + intercept;
Point Soln2 = new Point(SolnX2, SolnY2);
// have valid result if point is between two segment points
if (IsBetween(SolnX2, SegmentPoint1.X, SegmentPoint2.X) &&
IsBetween(SolnY2, SegmentPoint1.Y, SegmentPoint2.Y))
//if (ValidSoln(Soln2, SegmentPoint1, SegmentPoint2, CircleCenter))
{
// found solution
return (Soln2);
}
}
// shouldn't get here...but in case
return null;
}
static bool IsBetween(double X, double X1, double X2)
{
if (X1 >= X2 && X <= X1 && X >= X2)
return true;
if (X1 <= X2 && X >= X1 && X <= X2)
return true;
return false;
}
}
Usage:
<TextOnAPath:TextOnAPath FontSize="30" DrawPath="True"
Text="The quick brown fox jumped over the lazy hen.">
<TextOnAPath:TextOnAPath.TextPath>
<PathGeometry Figures="M0,0 C120,361 230.5,276.5 230.5,276.5
L308.5,237.50001 C308.5,237.50001 419.5,179.5002 367.5,265.49993
315.5,351.49966 238.50028,399.49924 238.50028,399.49924 L61.500017,
420.49911"/>
</TextOnAPath:TextOnAPath.TextPath>
</TextOnAPath:TextOnAPath>
</Grid>
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();
}
}
}
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;
}
}