I have a canvas (ZoomableCanvas) which implements zooming and has child items. I'm trying to get the relative position of a child item when zooming in, in order to display extended information over it.
<Canvas x:Name="CanvasContainer" Width="1500" Height="780">
<ZoomableCanvas ApplyTransform="false" Loaded="ZoomableCanvas_Loaded" x:Name="ShareCanvas" Width="1500" Height="780" MinWidth="1500" MinHeight="780" Grid.Row="0" Grid.Column="0" />
</Canvas>
Code behind:
Canvas main = touched.FindVisualParent<Canvas>(); //Finds "CanvasContainer"
Point relativePoint = touched.TransformToAncestor(main).Transform(new Point(0, 0));
double canvasTop = Canvas.GetTop(touched);
But here canvasTop != relativePoint.Y before zooming?
Am I using this incorrectly. Doesn't it map to the parent visual and give a relative point?
The values you're comparing will only be equal if
There is no transform (RenderTranform or LayoutTransform) set on
touched, since these are taken into account in TransformToAncestor but
not in the Canvas.Left or Canvas.Top properties.
The ZoomableCanvas has no offset relative to the outer canvas. This
is because Canvas.GetTop(touched) returns the top offset relative
to the ZoomableCanvas (assumed that touched is a child of the
ZoomableCanvas), whereas relativePoint is relative to the outer
Canvas.
To illustrate the first issue, just put an element with a RenderTransform into a Canvas:
<Canvas x:Name="canvas">
<Label x:Name="child" Content="Hello" Canvas.Left="50" Canvas.Top="100">
<Label.RenderTransform>
<TranslateTransform X="50" Y="100"/>
</Label.RenderTransform>
</Label>
</Canvas>
If you'd call
var point = child.TransformToAncestor(canvas).Transform(new Point());
var left = Canvas.GetLeft(child);
var top = Canvas.GetTop(child);
it would return point as (100, 200), whereas left is 50 and top is 100.
Related
I'm creating OCR app and my idea is to draw rectangle on a image to create bounding box from where I want to extract text from image so not to take all ocr recognized text.
I have canvas and inside it image and rectangle. The image is put by default to top=0 and left=0 and then stretched uniform to fit on the canvas so top and left still is the same. How can I get position of rectangle mapped to position on image.
With my code bellow and on result image, because I draw rectangle on canvas top of the rectangle is 100 and left 300 and. But I want to get position of rectangle on the image.
<Grid Grid.Row="1">
<Canvas
Background="Aquamarine"
x:Name="CanvasImagePreview"
SizeChanged="Canvas_SizeChanged"
PointerPressed="Canvas_PointerPressed"
PointerReleased="Canvas_PointerReleased"
PointerMoved="Canvas_PointerMoved"
>
<Image x:Name="ImagePreview"
Width="{Binding Path=ActualWidth, ElementName=CanvasImagePreview}"
Height="{Binding Path=ActualHeight, ElementName=CanvasImagePreview}"
Stretch="Uniform"/>
<Rectangle
x:Name="BoudingBox_Rect"
RadiusX="10"
RadiusY="10"
StrokeThickness="3"
Stroke="Red"
Visibility="Collapsed"
/>
</Canvas>
<Grid/>
result image
By testing, we cannot get the actual size of image when the image is stretched to fill the Canvas panel by using the {Binding} extension to set the Width and Height property. The value of Width and Heigtht property of Image is actual the value of Canvas.
You need to calculate the actual size of the image based on the original size of the image which can be get from the corresponding BitmapImage. Then, get the position of rectangle on the image by calculating.
Please check the following code:
var CanvasActualHeight = CanvasImagePreview.ActualHeight;
var CanvasActualWidth = CanvasImagePreview.ActualWidth;
double visualHeight=0.0;
double visualWeight=0.0;
BitmapImage bitmapImage = (BitmapImage)ImagePreview.Source;
if(bitmapImage!=null)
{
var originalHeight = bitmapImage.PixelHeight;
var originalWidth = bitmapImage.PixelWidth;
if((CanvasActualHeight / originalHeight) >(CanvasActualWidth / originalWidth))
{
visualHeight = originalHeight* CanvasActualWidth / originalWidth;
visualWeight = CanvasActualWidth;
}
else
{
visualHeight = CanvasActualHeight;
visualWeight = originalWidth * CanvasActualHeight / originalHeight;
}
}
var relativeToCanvasTop = BoudingBox_Rect.Margin.Top;
var relativeToCanvasLeft = BoudingBox_Rect.Margin.Left;
var relativeToImageTop = BoudingBox_Rect.Margin.Top - (CanvasActualHeight - visualHeight)/2;
var relativeToImageLeft = BoudingBox_Rect.Margin.Left-(CanvasActualWidth - visualWeight)/2;
I am creating shapes at run time on canvas in my app and except all the shapes, ellipse is going out of canvas. How do I restrict it to canvas? All other shapes are contained in canvas because of the control points at their vertices. How do I keep a check as to not let ellipse go out of canvas without clipping. I have used ClipToBounds and it doesn't meet my needs.
Also, an alternate solution is if I can add a controlpoint at the left side of ellipse of radiusX property. I can't add a controlpoint to left side of radiusX on ellipse. If you could help me with either of that?
radiusXcp = new ControlPoint(this, EllipseGeom, EllipseGeometry.RadiusYProperty, 1, true, false);
radiusXcp.RelativeTo = EllipseGeometry.CenterProperty;
shape_ControlPoints.Add(radiusXcp);
radiusXcp = new ControlPoint(this, EllipseGeom, EllipseGeometry.RadiusXProperty, 0, true, false);
radiusXcp.RelativeTo = EllipseGeometry.CenterProperty;
shape_ControlPoints.Add(radiusXcp);
//EllipseGeom.RadiusX = -EllipseGeom.RadiusX;
//radiusXcp = new ControlPoint(this, EllipseGeom, EllipseGeometry.RadiusXProperty, 0, true, false);
//radiusXcp.RelativeTo = EllipseGeometry.CenterProperty;
//shape_ControlPoints.Add(radiusXcp);
//EllipseGeom.RadiusX = -EllipseGeom.RadiusX;
Here is a quick example of what i would do. It could be improved on and the code is mainly written to be easy to read and follow. It also does not handle the possibility to if the shape's size is bigger than the Canvas (not sure if that is a use case in your project).
For the example I used the "Loaded" event on the Canvas, to reset the position before drawing. You would want this check before you draw the Ellipse object.
private void TestCanvas_Loaded(object sender, RoutedEventArgs e)
{
//canvas = 450 x 800
Ellipse test_ellipse = new Ellipse();
test_ellipse.Width = 100;
test_ellipse.Height = 100;
test_ellipse.Fill = Brushes.Red;
Canvas.SetLeft(test_ellipse, 700);
Canvas.SetTop(test_ellipse, -500);
Reset_Ellipse_Bounds(TestCanvas, ref test_ellipse);
TestCanvas.Children.Add(test_ellipse);
}
private void Reset_Ellipse_Bounds(Canvas myCanvas, ref Ellipse myEllipse)
{
var left = Canvas.GetLeft(myEllipse);
var top = Canvas.GetTop(myEllipse);
//handle too far right
if (left + myEllipse.Width > myCanvas.ActualWidth)
Canvas.SetLeft(myEllipse, myCanvas.ActualWidth - myEllipse.Width);
//handle too far left
if(left < 0)
Canvas.SetLeft(myEllipse, 0);
//handle too far up
if (top < 0)
Canvas.SetTop(myEllipse, 0);
//handle too far down
if (top + myEllipse.Height > myCanvas.ActualHeight)
Canvas.SetTop(myEllipse, myCanvas.ActualHeight - myEllipse.Height);
}
For Completeness the XAML:
<Grid>
<Canvas x:Name="TestCanvas" Loaded="TestCanvas_Loaded" />
</Grid>
The idea is to check the bounding box against the Canvas edges. There are ways to improve this, but i figured the simplest solution is easier to follow.
Within each if statement you could add more logic or a method to do further processing, but this should answer the general question of knowing if it is outside the parent.
Just set ClipToBounds="true" to its father control, it avoids the canvas to be drawn outside of it.
In my case I set it to Grid as followed :
<Grid x:Name="MainGrid" Background="WhiteSmoke" ClipToBounds="true">
<Canvas Margin="10" Background="Transparent"
SizeChanged="ViewportSizeChanged"
MouseLeftButtonDown="ViewportMouseLeftButtonDown"
MouseLeftButtonUp="ViewportMouseLeftButtonUp"
MouseMove="ViewportMouseMove"
MouseWheel="ViewportMouseWheel">
<Canvas x:Name="canvas" Width="1000" Height="600"
HorizontalAlignment="Left" VerticalAlignment="Top">
<Canvas.RenderTransform>
<MatrixTransform x:Name="transform"/>
</Canvas.RenderTransform>
</Canvas>
<Canvas x:Name="canvas2" Width="1000" Height="600"
HorizontalAlignment="Left" VerticalAlignment="Top">
<Canvas.RenderTransform>
<MatrixTransform x:Name="transform2"/>
</Canvas.RenderTransform>
</Canvas>
</Canvas>
</Grid>
I have this Xaml:
<Grid x:Name="DrawingGrid" Visibility="Collapsed">
<AppBarButton x:Name="ExitDrawingButton" Icon="Cancel" Click="ExitDrawingButton_Click"></AppBarButton>
<ScrollViewer x:Name="DScrollViewer" ManipulationMode="All" MaxZoomFactor="2.0" MinZoomFactor="1.0" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden" DoubleTapped="DScrollViewer_DoubleTapped" Width="1140" Height="770">
<Canvas x:Name="inkCanvas" Background="Transparent" Width="1140" Height="770">
<StackPanel x:Name="DStackPanel" Orientation="Horizontal" Margin="0,0,0,0">
<Image x:Name="DImage0" HorizontalAlignment="Left" Source="{Binding nextImage}" Width="570" Canvas.ZIndex="0"/>
<Image x:Name="DImage1" HorizontalAlignment="Left" Source="{Binding nextImage}" Width="570" Canvas.ZIndex="0"/>
</StackPanel>
</Canvas>
</ScrollViewer>
</Grid>
I am drawing on Canvas using the CanvasManager.cs class and it is working fine.
Now I need to zoom on the Canvas: Zoom the Canvas (the ink) and Zoom what it contains (the StackPanel + the Images) together.
On doubleTapping the ScrollViewer containing the Canvas I have this method:
private async void DScrollViewer_DoubleTapped(object sender, DoubleTappedRoutedEventArgs e)
{
Point point = e.GetPosition(DScrollViewer);
if (DScrollViewer.ZoomFactor == 1)
{
await Task.Delay(300);
DScrollViewer.ChangeView(point.X, point.Y, 2.0F, false);
}
else
{
await Task.Delay(300);
DScrollViewer.ChangeView(point.X, point.Y, 1.0F, false);
}
}
The result is: only the Canvas (its Ink) is Zooming and the StackPanel and its Images are left at the place, same scale, intact!
What Am I doing wrong?
Your mistake is you are assign a constant value for the width of each image (Width="570") so that it will never zoomed in or out unless you have change it's width programmatically.
The best way to change the image's width is bind a variable value for it, you can create a converter that divide the width of canvas over two (canvas.width / 2) and bind the width of each image to this converter..
Then your zoom will work perfectly.
I have a grid (which also could be a rectangle) which I want to draw from it's bottom to the top. It's a long lineair bar.
<Grid Grid.Row="2" Background="AliceBlue" x:Name="SkillBar11">
<TextBlock Foreground="Black" HorizontalAlignment="Center" VerticalAlignment="Bottom" Margin="0,0,0,40" FontSize="16">IV</TextBlock>
</Grid>
and I can make it draw from the middle to top and bottom with this code in C#:
Storyboard s = new Storyboard();
DoubleAnimation doubleAni = new DoubleAnimation();
doubleAni.To = SkillBar11.ActualHeight;
doubleAni.From = 0;
doubleAni.Duration = new Duration(TimeSpan.FromSeconds(1));
Storyboard.SetTarget(doubleAni, SkillBar11);
doubleAni.EnableDependentAnimation = true;
s.Children.Add(doubleAni);
Storyboard.SetTargetProperty(doubleAni, "Height");
s.Begin();
But I can't get it to be drawn from the bottom to the top, instead of the middle to bottom and top. Can someone help me out? :)
I have found the anwser. You can orientate how the bar grows by setting a Horizontal or Verticalalignment in the grid properties. This way it will start at the position where you aligned it!
Ask a question
Quick access
Search related threads
I have the following code for xaml:
<Grid Name="navigationGrid" Grid.RowSpan="2">
<ScrollViewer Name="scrollViewer" Grid.Row="0"
HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden"
k:KinectRegion.IsHorizontalRailEnabled="true"
k:KinectRegion.IsVerticalRailEnabled="true"
k:KinectRegion.ZoomMode="Enabled" LayoutUpdated="scrollViewer_LayoutUpdated" >
<Image Name="navigationImage" RenderTransformOrigin="0.5, 0.5"/>
</ScrollViewer>
</Grid>
Now I would like to change the zoom of the image manually i.e. suppose to a zoom value Z , so I do the following:
System.Windows.Media.Matrix m = navigationImage.LayoutTransform.Value;
m.ScaleAtPrepend(Z / m.M11), Z / m.M11, m.M21, m.M22); //Z is the zoom value
navigationImage.LayoutTransform = new System.Windows.Media.MatrixTransform(m);
But after performing layoutTransform on the image, I am now not being able to use the kinect zoom gesture. I guess it is due to assigning new LayoutTransform.
So instead of
new System.Windows.Media.MatrixTransform(m);
I would like something like
navigationImage.LayoutTransform.SetValue(ScaleTransform.ScaleXProperty, Z / m.M11);
but this gives me an error "Cannot set a property on object 'Identity' because it is in a read-only state." So my question is how to set new value to layoutTransform.
You set Scale Transform to Layout Transform and then update the Scale Transform.
var scale = new ScaleTransform();
navigationImage.LayoutTransform = scale;
// Elsewhere...
scale.ScaleX = newValue;