I am trying to create a 3..2..1 countdown in the form of a user control. Something like this. My Idea was to create two rectangles on top of each other, one light and one dark and have a radial circle as the clipper for the dark rectangle. The radial circle would have Angle property animated so it would turn around.
I found an implementation of the radial circle and bound the Clip property of the rectangle on the RenderedGeometry property of my circle. Here is the result :
The red stroke is the shape of my clipper. This seems to be an odd behavior of the clipping but I sort of understand it but I would like to know if there was a way of going around the
fact that my clipped object seems to use the RenderedGeometry in a weird way.
Edit 1 : The effect I am looking for http://www.youtube.com/watch?v=9FPHTo5V2BQ
The simple derived Shape control shown below draws the countdown rectangle. You have to set its Fill (and perhaps Stroke), Width, Height and Angle properties, and you can animate Angle from 0 to 360.
public class CountdownRect : Shape
{
static CountdownRect()
{
WidthProperty.OverrideMetadata(typeof(CountdownRect),
new FrameworkPropertyMetadata((o, e) => ((CountdownRect)o).UpdateGeometry()));
HeightProperty.OverrideMetadata(typeof(CountdownRect),
new FrameworkPropertyMetadata((o, e) => ((CountdownRect)o).UpdateGeometry()));
StrokeLineJoinProperty.OverrideMetadata(typeof(CountdownRect),
new FrameworkPropertyMetadata(PenLineJoin.Round));
}
public static readonly DependencyProperty AngleProperty =
DependencyProperty.Register("Angle", typeof(double), typeof(CountdownRect),
new FrameworkPropertyMetadata((o, e) => ((CountdownRect)o).UpdateGeometry()));
public double Angle
{
get { return (double)GetValue(AngleProperty); }
set { SetValue(AngleProperty, value); }
}
private readonly StreamGeometry geometry = new StreamGeometry();
protected override Geometry DefiningGeometry
{
get { return geometry; }
}
private void UpdateGeometry()
{
if (!double.IsNaN(Width) && !double.IsNaN(Height))
{
var angle = ((Angle % 360d) + 360d) % 360d;
var margin = StrokeThickness / 2d;
var p0 = new Point(margin, margin);
var p1 = new Point(Width - margin, margin);
var p2 = new Point(Width - margin, Height - margin);
var p3 = new Point(margin, Height - margin);
using (var context = geometry.Open())
{
if (angle == 0d)
{
context.BeginFigure(p0, true, true);
context.LineTo(p1, true, false);
context.LineTo(p2, true, false);
context.LineTo(p3, true, false);
}
else
{
var x = p2.X / 2d;
var y = p2.Y / 2d;
var a = Math.Atan2(x, y) / Math.PI * 180d;
var t = Math.Tan(angle * Math.PI / 180d);
context.BeginFigure(new Point(x, y), true, true);
if (angle < a)
{
context.LineTo(new Point(x + y * t, p0.Y), true, false);
context.LineTo(p1, true, false);
context.LineTo(p2, true, false);
context.LineTo(p3, true, false);
context.LineTo(p0, true, false);
}
else if (angle < 180d - a)
{
context.LineTo(new Point(p2.X, y - x / t), true, false);
context.LineTo(p2, true, false);
context.LineTo(p3, true, false);
context.LineTo(p0, true, false);
}
else if (angle < 180d + a)
{
context.LineTo(new Point(x - y * t, p2.Y), true, false);
context.LineTo(p3, true, false);
context.LineTo(p0, true, false);
}
else if (angle < 360d - a)
{
context.LineTo(new Point(p0.X, y + x / t), true, false);
context.LineTo(p0, true, false);
}
else
{
context.LineTo(new Point(x + y * t, p0.Y), true, false);
}
context.LineTo(new Point(x, p0.Y), true, false);
}
}
}
}
}
You can clip your rectangle by using an ArcSegment in the clipping PathGeometry, and animate that ArcSegment's endpoint (Point).
The endpoint can be animated with a PointAnimationUsingPath animation, using an identical ArcSegment as its path. Below is a suggestion based on Charlez Petzold's excellent answer here: Drawing pie slices
<UserControl ... >
<UserControl.Resources>
<Point x:Key="SweepCenter" X="100" Y="100" />
<Size x:Key="SweepRadius" Width="130" Height="130" />
<!-- Start sweeping at twelve o'clock.. -->
<Point x:Key="SweepStart" X="100" Y="-30" />
<!-- ..and keep sweeping clockwise until we're (almost) back at the start point: -->
<Point x:Key="SweepEnd" X="99.99" Y="-30" />
<Storyboard x:Key="Sweeper" RepeatBehavior="Forever" AutoReverse="False" >
<PointAnimationUsingPath Storyboard.TargetName="arc"
Storyboard.TargetProperty="Point"
Duration="0:0:5">
<PointAnimationUsingPath.PathGeometry>
<PathGeometry>
<PathFigure StartPoint="{StaticResource SweepStart}">
<ArcSegment Size="{StaticResource SweepRadius}"
Point="{StaticResource SweepEnd}"
SweepDirection="Clockwise"
IsLargeArc="True" />
</PathFigure>
</PathGeometry>
</PointAnimationUsingPath.PathGeometry>
</PointAnimationUsingPath>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="arc"
Storyboard.TargetProperty="IsLargeArc" >
<DiscreteBooleanKeyFrame KeyTime="0:0:2.5" Value="True" />
<DiscreteBooleanKeyFrame KeyTime="0:0:5" Value="False" />
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</UserControl.Resources>
<Grid Width="200" Height="200" >
<Rectangle Fill="Black" />
<Rectangle Fill="Gray" >
<Rectangle.Triggers>
<EventTrigger RoutedEvent="Loaded">
<BeginStoryboard Storyboard="{StaticResource Sweeper}" />
</EventTrigger>
</Rectangle.Triggers>
<Rectangle.Clip>
<PathGeometry>
<PathFigure StartPoint="{StaticResource SweepCenter}"
IsClosed="True" >
<LineSegment Point="{StaticResource SweepStart}" />
<ArcSegment x:Name="arc"
Size="{StaticResource SweepRadius}"
Point="{StaticResource SweepStart}"
SweepDirection="Clockwise" />
</PathFigure>
</PathGeometry>
</Rectangle.Clip>
</Rectangle>
</Grid>
</UserControl>
Related
so i have this Path and i need to place it rotated by its center in a coordinate. so i have this static Path in .xaml
<Path Stroke="Black" RenderTransformOrigin="0.379,0.494" Canvas.Left="30" Canvas.Top="0">
<Path.RenderTransform>
<TransformGroup>
<RotateTransform Angle="-38.28"/>
<TranslateTransform X="-30" Y="-30"/>
</TransformGroup>
</Path.RenderTransform>
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigureCollection>
<PathFigure StartPoint="75,30">
<PathFigure.Segments>
<PathSegmentCollection>
<LineSegment Point="0,0"/>
<LineSegment Point="12,30"/>
<LineSegment Point="0, 60"/>
<LineSegment Point="75, 30"/>
</PathSegmentCollection>
</PathFigure.Segments>
</PathFigure>
</PathFigureCollection>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>
but then i create it from code the same way and it just doesn't wanna center, it just starts rotating around the point
{
PathGeometry pathGeom = new PathGeometry();
Sprite = new Path
{
Data = pathGeom,
RenderTransformOrigin = new Point(0.379, 0.5),
Stroke = new SolidColorBrush(Color.FromArgb(120, 0, 0, 0)),
Fill = new SolidColorBrush(Color.FromArgb(120, 30, 200, 7))
};
var pf = new PathFigure { StartPoint = new Point(75, 30) };
pf.Segments.Add(new LineSegment { Point = new Point(0, 0) });
pf.Segments.Add(new LineSegment { Point = new Point(12, 30) });
pf.Segments.Add(new LineSegment { Point = new Point(0, 60) });
pf.Segments.Add(new LineSegment { Point = new Point(75, 30) });
pathGeom.Figures.Add(pf);
UpdateRotation();
canvas.Children.Add(Sprite);
}
private void UpdateRotation()
{
Sprite.RenderTransform = new TransformGroup
{
Children = new TransformCollection {
new RotateTransform(Road.Angle + (Direction == -1 ? 90 : 0), -30, -30) , <-- i tried doing this
new TranslateTransform(-30, -30), <-- and this separately, but they both didn't work
}
};
Canvas.SetLeft(Sprite, Loc.X);
Canvas.SetTop(Sprite, Loc.Y);
}
here's what the static path looks like:what it looks like
You may simplify your drawing by defining the pivot point as origin - with coordinates (0,0). There is no need for a TranslateTransform or a centered RotateTransform. You would also not have to create new transforms on each direction update. Just set the Angle property of the existing RotateTransform.
private Path Sprite { get; }
public MainWindow()
{
InitializeComponent();
Sprite = new Path
{
Data = Geometry.Parse("M0,0 L-12,-30 63,0 -12,30Z"),
// or Data = Geometry.Parse("M-13,0 L-25,-30 50,0 -25,30Z"),
// or whatever corresponds to the original RenderTransformOrigin
RenderTransform = new RotateTransform(),
Stroke = new SolidColorBrush(Color.FromArgb(120, 0, 0, 0)),
Fill = new SolidColorBrush(Color.FromArgb(120, 30, 200, 7))
};
canvas.Children.Add(Sprite);
UpdatePosition(100, 100, 45); // for example
}
private void UpdatePosition(double x, double y, double direction)
{
((RotateTransform)Sprite.RenderTransform).Angle = direction;
Canvas.SetLeft(Sprite, x);
Canvas.SetTop(Sprite, y);
}
I have a method that animates the position of a marker on a boardgame. If players rolls the dice and the dice return 4, then marker is moved to a specific position on the board. The animation works fine.
However, the marker moves in a straight line - I would like the marker to move through all the position until it reach the end position.
Here is my int[,]
this.Path = new int[,] { { 0, 0 },{ 40, 50 }, { 95, 45 }, {130,0 }, { 110,-60 }, { 60, -100 }, { 0,-140 }, { -40, -200 }, { -30,-280 }};
It holds the X and Y position relative to startposition(margin).
And the animation method - how can I change it so the player rolls eg. 2 it should animate position 0 and 1 on the array.
private void AnimatePlayerMovement(Player player, int eyes)
{
//This will hold hour animation
Piece.RenderTransform = new CompositeTransform();
//New storyboard
Storyboard storyboard = new Storyboard();
//New DoubleAnimation - Y
DoubleAnimation translateYAnimation = new DoubleAnimation();
translateYAnimation.From = this.Path[player.position - eyes , 1];
translateYAnimation.To = this.Path[player.position , 1];
translateYAnimation.EasingFunction = new ExponentialEase();
translateYAnimation.EasingFunction.EasingMode = EasingMode.EaseOut;
translateYAnimation.Duration = new Duration(TimeSpan.FromMilliseconds(500));
Storyboard.SetTarget(translateYAnimation, Piece);
Storyboard.SetTargetProperty(translateYAnimation, "(UIElement.RenderTransform).(CompositeTransform.TranslateY)");
storyboard.Children.Add(translateYAnimation);
//New DoubleAnimation - X
DoubleAnimation translateXAnimation = new DoubleAnimation();
translateXAnimation.From = this.Path[player.position - eyes, 0];
translateXAnimation.To = this.Path[player.position, 0];
//translateXAnimation.From = this.Path[player.position - eyes, 0];
//translateXAnimation.To = this.Path[player.position, 0];
translateXAnimation.Duration = new Duration(TimeSpan.FromMilliseconds(500));
Storyboard.SetTarget(translateXAnimation, Piece);
Storyboard.SetTargetProperty(translateXAnimation, "(UIElement.RenderTransform).(CompositeTransform.TranslateX)");
storyboard.Children.Add(translateXAnimation);
//executing the storyboard
storyboard.Begin();
}
Well, here's an ugly solution. It will work playing animation sequentially, one after one.
Let's assume the Marker is a Rectangle, so, we add some animation on it's TranslateX and TranslateY:
<Page.Resources>
<Storyboard x:Name="PathAnimationStory"
Completed="Story_Completed">
<DoubleAnimation x:Name="anim_x"
Duration="0:0:0.5"
Storyboard.TargetName="TargetTranform"
Storyboard.TargetProperty="X"/>
<DoubleAnimation x:Name="anim_y"
Duration="0:0:0.5"
Storyboard.TargetName="TargetTranform"
Storyboard.TargetProperty="Y"/>
</Storyboard>
</Page.Resources>
<Rectangle
Width="50"
Height="50"
Fill="Red"
HorizontalAlignment="Left"
VerticalAlignment="Top">
<Rectangle.RenderTransform>
<TranslateTransform x:Name="TargetTranform" X="0" Y="0"/>
</Rectangle.RenderTransform>
</Rectangle>
Notice the Completed event handled in the Storyboard. We need to chain the animation. To do that, handle the Completed event when the animation stops, it will check if any other points are to be gone through and start the animation again if any:
private void MoveMarker(params double[] co_ordinates)
{
PathAnimationStory.Stop();
if (co_ordinates.Length == 0)
return;
/// the number of co_ordinates must be even
if (co_ordinates.Length % 2 == 1)
return;
Co_ordinates = co_ordinates;
remaining_nodes = co_ordinates.Length / 2;
Story_Completed(default, default);
}
private void Story_Completed(object sender, object e)
{
if(remaining_nodes > 0)
{
int next_node = (int)(Co_ordinates.Length / 2 - remaining_nodes);
anim_x.To = Co_ordinates[2 * next_node];
anim_y.To = Co_ordinates[2 * next_node + 1];
remaining_nodes--;
PathAnimationStory.Begin();
}
}
All done, now let's check our code:
MoveMarker(100, 0, 100, 100, 0, 100, 100, 0);
gives this output:
Which I think somewhat meets your requirement.
Hope that helps.
I have 1 shape on canvas.
Case 1 : I am using a TransformGroup to new place with rotation
Case 2 : I use hand to move and rotate shape from original place to last place in case 1.
I want to check if the shape is laid correctly or not.
So I want to compare 2 TransformGroup value. Do you have any idea about this?
I also want to handle the shapes which is symmetrical too.
So I want to compare 2 TransformGroup value.
I'm not sure how you implement your features, but for comparing the TransformGroup value, you could just name the transform objects inside the group and get the properties of each transform for comparing. For example, the following code snippet get the rotate angle and translate value:
XAML
<StackPanel Margin="15">
<StackPanel.Resources>
<Storyboard x:Name="myStoryboard">
<DoubleAnimation
RepeatBehavior="Forever"
Storyboard.TargetName="rotateTransform"
Storyboard.TargetProperty="Angle"
From="0"
To="360"
Duration="0:0:8" />
<DoubleAnimation
RepeatBehavior="Forever"
Storyboard.TargetName="translateTransform"
Storyboard.TargetProperty="X"
To="200"
Duration="0:0:8" />
</Storyboard>
</StackPanel.Resources>
<Rectangle
Width="50"
Height="50"
Fill="RoyalBlue"
PointerPressed="StartAnimation" >
<Rectangle.RenderTransform>
<TransformGroup x:Name="transformgroup">
<RotateTransform x:Name="rotateTransform" Angle="45" CenterX="25" CenterY="25" />
<TranslateTransform x:Name="translateTransform" X="0" />
</TransformGroup>
</Rectangle.RenderTransform>
</Rectangle>
<TextBox x:Name="txtAngle" Header="CurrentAngle" />
<TextBox x:Name="txtX" Header="CurrentX" />
<Button
x:Name="btnget"
Click="btnget_Click"
Content="gettransform" />
</StackPanel>
Code behind
private void StartAnimation(object sender, PointerRoutedEventArgs e)
{
myStoryboard.Begin();
}
private void btnget_Click(object sender, RoutedEventArgs e)
{
txtAngle.Text= rotateTransform.Angle.ToString();
txtX.Text = translateTransform.X.ToString();
//RotateTransform rotatetransget= transformgroup.Children[0] as RotateTransform;
}
This is my solution only for polygon and it correct if the shape is symmetrical.
Just compare transform by compare it points after transform
Boolean CompareTransform(TransformGroup transformGroupA, TransformGroup transformGroupB)
{
if (element is Polygon)
{
Polygon p = element as Polygon;
List<Point> listPointA = new List<Point>();
List<Point> listPointB = new List<Point>();
for (int i = 0; i < p.Points.Count; i++)
{
Point pointA = transformGroupA.Value.Transform(p.Points[i]);
Point pointB = transformGroupB.Value.Transform(p.Points[i]);
listPointA.Add(pointA);
listPointB.Add(pointB);
}
for (int i = 0; i < listPointB.Count; i++)
{
if (GetDistance(listPointA[0].X, listPointA[0].Y, listPointB[i].X, listPointB[i].Y) < 10)
{
List<Point> newList = new List<Point>();
newList.AddRange(listPointB.GetRange(i, listPointB.Count - i));
newList.AddRange(listPointB.GetRange(0, i));
return ComparePointsList(listPointA, newList);
}
}
return false;
}
return false;
}
bool ComparePointsList(List<Point> listA, List<Point> listB)
{
for (int i = 0; i < listA.Count; i++)
{
if (GetDistance(listA[i].X, listA[i].Y, listB[i].X, listB[i].Y) > 10)
{
return false;
}
}
return true;
}
double GetDistance(double x1, double y1, double x2, double y2)
{
return Math.Sqrt(Math.Pow(x1 - x2, 2) + Math.Pow(y1 - y2, 2));
}
Are you have any idea for eclipse?
I'm experimenting with the concept of drawing grid lines over a control and was wondering what adjustments I might need to make to make this actually work. I found some code on another post that enables grid lines to be drawn OnRender over a canvas. Here's what that looks like:
public class MyCanvas : Canvas
{
public bool IsGridVisible = true;
protected override void OnRender(System.Windows.Media.DrawingContext dc)
{
base.OnRender(dc);
if (IsGridVisible)
{
// Draw GridLines
Pen pen = new Pen(Brushes.Black, 1);
pen.DashStyle = DashStyles.Solid;
for (double x = 0; x < this.ActualWidth; x += 2)
{
dc.DrawLine(pen, new Point(x, 0), new Point(x, this.ActualHeight));
}
for (double y = 0; y < this.ActualHeight; y += 2)
{
dc.DrawLine(pen, new Point(0, y), new Point(this.ActualWidth, y));
}
}
}
public MyCanvas()
{
DefaultStyleKey = typeof(MyCanvas);
}
}
This part: y += 2 indicates how many other pixels/points to wait before drawing next line, though I am uncertain of it's correctness.
Here's the xaml:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<ScrollViewer>
<local:MyCanvas>
<local:MyCanvas.LayoutTransform>
<ScaleTransform ScaleX="{Binding ElementName=Slider, Path=Value}" ScaleY="{Binding ElementName=Slider, Path=Value}"/>
</local:MyCanvas.LayoutTransform>
<Image Canvas.Top="2" Canvas.Left="2" Source="C:\Users\Me\Pictures\nyan-wallpaper2.jpg" Width="325" RenderOptions.BitmapScalingMode="NearestNeighbor"/>
</local:MyCanvas>
</ScrollViewer>
<Slider x:Name="Slider" Maximum="500" Grid.Row="1" Value="1"/>
</Grid>
Here are screenshots of what the above results in.
As you can see, the grid lines change in size as you zoom and the lines themselves do not snap around each individual pixel. I highlighted an example pixel in red to show how small the lines should be versus how they actually are.
I read that the thickness of the pen should be divided by the scale value, however, I tested this by replacing Pen pen = new Pen(Brushes.Black, 1); with Pen pen = new Pen(Brushes.Black, 1 / 3); and set the ScaleX and ScaleY of MyCanvas to 3. At that point, no lines showed at all.
Any help at all is immensely valued!
Got it working like this for anyone curious:
MainWindow.xaml.cs
namespace Test
{
public class MyCanvas : Canvas
{
public bool IsGridVisible = false;
#region Dependency Properties
public static DependencyProperty ZoomValueProperty = DependencyProperty.Register("ZoomValue", typeof(double), typeof(MyCanvas), new FrameworkPropertyMetadata(1d, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnZoomValueChanged));
public double ZoomValue
{
get
{
return (double)GetValue(ZoomValueProperty);
}
set
{
SetValue(ZoomValueProperty, value);
}
}
private static void OnZoomValueChanged(DependencyObject Object, DependencyPropertyChangedEventArgs e)
{
}
#endregion
protected override void OnRender(System.Windows.Media.DrawingContext dc)
{
base.OnRender(dc);
IsGridVisible = ZoomValue > 4.75 ? true : false;
if (IsGridVisible)
{
// Draw GridLines
Pen pen = new Pen(Brushes.Black, 1 / ZoomValue);
pen.DashStyle = DashStyles.Solid;
for (double x = 0; x < this.ActualWidth; x += 1)
{
dc.DrawLine(pen, new Point(x, 0), new Point(x, this.ActualHeight));
}
for (double y = 0; y < this.ActualHeight; y += 1)
{
dc.DrawLine(pen, new Point(0, y), new Point(this.ActualWidth, y));
}
}
}
public MyCanvas()
{
DefaultStyleKey = typeof(MyCanvas);
}
}
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private WriteableBitmap bitmap = new WriteableBitmap(500, 500, 96d, 96d, PixelFormats.Bgr24, null);
private void Button_Click(object sender, RoutedEventArgs e)
{
int size = 1;
Random rnd = new Random(DateTime.Now.Millisecond);
bitmap.Lock(); // Lock() and Unlock() could be moved to the DrawRectangle() method. Just do some performance tests.
for (int y = 0; y < 500; y++)
{
for (int x = 0; x < 500; x++)
{
byte colR = (byte)rnd.Next(256);
byte colG = (byte)rnd.Next(256);
byte colB = (byte)rnd.Next(256);
DrawRectangle(bitmap, size * x, size * y, size, size, Color.FromRgb(colR, colG, colB));
}
}
bitmap.Unlock(); // Lock() and Unlock() could be moved to the DrawRectangle() method. Just do some performance tests.
Image.Source = bitmap; // This should be done only once
}
public void DrawRectangle(WriteableBitmap writeableBitmap, int left, int top, int width, int height, Color color)
{
// Compute the pixel's color
int colorData = color.R << 16; // R
colorData |= color.G << 8; // G
colorData |= color.B << 0; // B
int bpp = writeableBitmap.Format.BitsPerPixel / 8;
unsafe
{
for (int y = 0; y < height; y++)
{
// Get a pointer to the back buffer
int pBackBuffer = (int)writeableBitmap.BackBuffer;
// Find the address of the pixel to draw
pBackBuffer += (top + y) * writeableBitmap.BackBufferStride;
pBackBuffer += left * bpp;
for (int x = 0; x < width; x++)
{
// Assign the color data to the pixel
*((int*)pBackBuffer) = colorData;
// Increment the address of the pixel to draw
pBackBuffer += bpp;
}
}
}
writeableBitmap.AddDirtyRect(new Int32Rect(left, top, width, height));
}
}
}
MainWindow.xaml
<Window x:Class="Test.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Test"
mc:Ignorable="d"
Title="MainWindow"
Height="Auto"
Width="Auto"
WindowStartupLocation="CenterScreen"
WindowState="Maximized">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<ScrollViewer>
<local:MyCanvas ZoomValue="{Binding ElementName=ScaleTransform, Path=ScaleX}">
<local:MyCanvas.LayoutTransform>
<ScaleTransform x:Name="ScaleTransform" ScaleX="{Binding ElementName=Slider, Path=Value}" ScaleY="{Binding ElementName=Slider, Path=Value}"/>
</local:MyCanvas.LayoutTransform>
<Image Canvas.Top="1" Canvas.Left="1" x:Name="Image" RenderOptions.BitmapScalingMode="NearestNeighbor"/>
</local:MyCanvas>
</ScrollViewer>
<StackPanel Grid.Row="1" Orientation="Horizontal">
<Slider x:Name="Slider" Maximum="100" Minimum="0.5" Value="1" Width="200"/>
<Button Click="Button_Click" Content="Click Me!"/>
</StackPanel>
</Grid>
</Window>
We generate a bitmap with random colored pixels and then render the grid lines only if zoomed up close. Performance-wise, this is actually better than expected. I should note, though, that if you attempt to zoom below 50%, the app crashes. Not sure if it's an issue with the grid lines being drawn at a minute size (IsGridVisible = true where ZoomValue < 0.5) or with the bitmap being generated. Either way, cheers!
Update
Didn't realize the grid lines are still behind the contents of the canvas. Haven't worked out a solution for that yet...
Update 2
Replace:
<local:MyCanvas ZoomValue="{Binding ElementName=ScaleTransform, Path=ScaleX}">
<local:MyCanvas.LayoutTransform>
<ScaleTransform x:Name="ScaleTransform" ScaleX="{Binding ElementName=Slider, Path=Value}" ScaleY="{Binding ElementName=Slider, Path=Value}"/>
</local:MyCanvas.LayoutTransform>
<Image Canvas.Top="1" Canvas.Left="1" x:Name="Image" RenderOptions.BitmapScalingMode="NearestNeighbor"/>
</local:MyCanvas>
With:
<Grid>
<Canvas>
<Canvas.LayoutTransform>
<ScaleTransform ScaleX="{Binding ElementName=Slider, Path=Value}" ScaleY="{Binding ElementName=Slider, Path=Value}"/>
</Canvas.LayoutTransform>
<Image Canvas.Top="5" Canvas.Left="5" x:Name="Image" RenderOptions.BitmapScalingMode="NearestNeighbor"/>
</Canvas>
<local:MyGrid ZoomValue="{Binding ElementName=ScaleTransform, Path=ScaleX}">
<local:MyGrid.LayoutTransform>
<ScaleTransform x:Name="ScaleTransform" ScaleX="{Binding ElementName=Slider, Path=Value}" ScaleY="{Binding ElementName=Slider, Path=Value}"/>
</local:MyGrid.LayoutTransform>
</local:MyGrid>
</Grid>
I believe another boost in performance as we are utilizing a simpler control to display the grid lines, plus, the grid lines can be placed either below or above desired controls.
Update 3
I have decided to post my latest solution, which is significantly more efficient and can all be done in XAML:
<Grid>
<Grid.Background>
<DrawingBrush Viewport="0,0,5,5" ViewportUnits="Absolute" TileMode="Tile">
<DrawingBrush.Drawing>
<DrawingGroup>
<DrawingGroup.Children>
<GeometryDrawing Geometry="M-.5,0 L50,0 M0,10 L50,10 M0,20 L50,20 M0,30 L50,30 M0,40 L50,40 M0,0 L0,50 M10,0 L10,50 M20,0 L20,50 M30,0 L30,50 M40,0 L40,50">
<GeometryDrawing.Pen>
<Pen Thickness="1" Brush="Black" />
</GeometryDrawing.Pen>
</GeometryDrawing>
</DrawingGroup.Children>
</DrawingGroup>
</DrawingBrush.Drawing>
</DrawingBrush>
</Grid.Background>
</Grid>
I have been struggling for two weeks for this problem . I am applying dragging and scaling to an image inside canvas.Dragging works fine and is limiting inside canvas IsBoundary functions but when I am applying scaling its drag area changes . If increases scaling with mouse drag area increases also and whem I make it shrink in size drag area also shrinks.Help me to solve this problem of limiting scaling
Thanks.
Here is my code link
sample
I think I understand your question. When you scale an item in a canvas the translation needs to account for the change in scale. Is that right?
Assuming this XAML:
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Border Width="500"
Height="500"
BorderBrush="White"
BorderThickness="1">
<Canvas x:Name="MyCanvas">
<Rectangle x:Name="MyRectangle"
Width="50"
Height="50"
Fill="CornflowerBlue">
<Rectangle.RenderTransform>
<CompositeTransform TranslateX="225" TranslateY="225" />
</Rectangle.RenderTransform>
</Rectangle>
</Canvas>
</Border>
</Grid>
Try this code-behind:
void MainPage_Loaded(object sender, RoutedEventArgs args)
{
MyRectangle.ManipulationMode =
ManipulationModes.TranslateX
| ManipulationModes.TranslateY;
var transform = MyRectangle.RenderTransform as CompositeTransform;
var reposition = new Action<double, double>((x, y) =>
{
var size = new Size(MyRectangle.ActualWidth * transform.ScaleX, MyRectangle.ActualHeight * transform.ScaleY);
var location = MyRectangle.TransformToVisual(MyRectangle).TransformPoint(new Point(0, 0));
var minX = -location.X;
var maxX = MyCanvas.ActualWidth - size.Width;
var newX = Within(x, minX, maxX);
transform.TranslateX = Within(newX, minX, maxX);
var minY = -location.Y;
var maxY = MyCanvas.ActualHeight - size.Height;
var newY = Within(y, minY, maxX);
transform.TranslateY = Within(newY, minY, maxY);
});
MyRectangle.ManipulationDelta += (s, e) =>
{
var newX = transform.TranslateX + e.Delta.Translation.X;
var newY = transform.TranslateY + e.Delta.Translation.Y;
reposition(newX, newY);
};
MyRectangle.PointerWheelChanged += (s, e) =>
{
// require control
if (Window.Current.CoreWindow.GetKeyState(VirtualKey.Control)
== Windows.UI.Core.CoreVirtualKeyStates.None)
return;
// ignore horizontal
var props = e.GetCurrentPoint(MyRectangle).Properties;
if (props.IsHorizontalMouseWheel)
return;
// apply scale
var newScale = transform.ScaleX + (double)props.MouseWheelDelta * .001;
transform.ScaleX = transform.ScaleY = newScale;
// reposition
reposition(transform.TranslateX, transform.TranslateY);
};
}
public double Within(double value, double min, double max)
{
if (value <= min)
return min;
else if (value >= max)
return max;
else
return value;
}
I hope this helps.
Note: Since I am not on a touch machine right now, I implemented the mouse wheel to scale. But you can modify the code as you want. The logic would be identical.
Best of luck!