Canvas size as a bounding box of the contained elements - c#

I am implementing a user control that is a view for an arrangement of shapes, which can be moved around and zoomed with the mouse, altogether or individually. The user control contains a single "Root" canvas to which the shapes are added, and the render transform of which determines its position and size in the view.
What I would like to be able is to set the background of the canvas to a rectangle that encloses all its contained shapes. Is this possible ? Of course I can set the Width and Height properties but they seem to count from the origin only. But the shapes can as well be placed at negative coordinates relative to the canvas' origin so the background rectangle of the canvas would have to start at negative coordinates also.
Canvas cnv = new Canvas();
// show the canvas' dimensions
cnv.Background = Brushes.AliceBlue;
for (int i = -5; i < 5; i++)
{
for (int j = -5; j < 5; j++)
{
Rectangle newRectangle = new Rectangle();
cnv.Children.Add(newRectangle);
newRectangle.Width = 7;
newRectangle.Height = 7;
newRectangle.Fill = Brushes.Green;
Canvas.SetTop(newRectangle, j * 10);
Canvas.SetLeft(newRectangle, i * 10);
}
}
// these are the right bounding dimensions, but with the wrong origin
cnv.Width = 107;
cnv.Height = 107;
// nothing available like that?
// cnv.Left = -50;
// cnv.Top= -50;
Of course, if everything fails, I could just add another rectangle for the bounds.

The Canvas is transparent so why not stack it on top of a rectangle in a grid:
<Grid>
<Rectangle Fill="Lime" Stroke="Red" StrokeThickness="5" />
<Canvas />
</Grid>
Or if it realy has to look like a border:
<Border Background="Lime" BorderBrush="Red" BorderThickness="5">
<Canvas />
</Border>

Related

How to get position of drawn rectangle on image in canvas

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;

how to check if an element is going out of its parent in c# wpf

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>

How to set boundary of a TextBlock in a grid?

I have few textblocks in a grid which can be dragged. I want to restrict the user so that user can't drag a textblock outside the grid.
I've tried a few ways like getting the position of the grid so that somehow i can control but it didn't work as expected.
Thanks in advance.
This can be done pretty easily using a Canvas inside the Grid, calculating the coordinates of the TextBlock inside of the Canvas and then continuously checking whether the TextBlock is still within its bounds. When the TextBlock leaves its bounds, the Transform then reverts back to it's last known 'good' coordinates.
XAML
<Grid>
<Grid Name="GridBounds" Width="600" Height="600" Background="Aqua">
<Canvas>
<TextBlock Name="TextBlock1" Text="Drag Me" FontSize="40" ManipulationDelta="TextBlock1_ManipulationDelta" ManipulationMode="TranslateX, TranslateY" HorizontalAlignment="Stretch" Canvas.Left="216" Canvas.Top="234" VerticalAlignment="Stretch"/>
</Canvas>
</Grid>
</Grid>
Code Behind
CompositeTransform TextDrag = new CompositeTransform();
CompositeTransform savedTransform = new CompositeTransform();
public MainPage()
{
this.InitializeComponent();
TextBlock1.RenderTransform = TextDrag;
}
// Copy Transform X and Y if TextBlock is within bounds
private void CopyTransform(CompositeTransform orig, CompositeTransform copy)
{
copy.TranslateX = orig.TranslateX;
copy.TranslateY = orig.TranslateY;
}
private void TextBlock1_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
{
TextDrag.TranslateX += e.Delta.Translation.X;
TextDrag.TranslateY += e.Delta.Translation.Y;
CompositeTransform transform = TextBlock1.RenderTransform as CompositeTransform;
// Get current Top-Left coordinates of TextBlock
var TextTopLeft = TextBlock1.TransformToVisual(GridBounds).TransformPoint(new Point(0, 0));
// Get current Bottom-Right coordinates of TextBlock
var TextBottomRight = TextBlock1.TransformToVisual(GridBounds).TransformPoint(new Point(TextBlock1.ActualWidth, TextBlock1.ActualHeight));
// Get Top-Left grid coordinates
var GridTopLeft = (new Point(0, 0));
// Get Bottom-Right grid coordinates
var GridBottomRight = (new Point(GridBounds.ActualWidth, GridBounds.ActualHeight));
if (TextTopLeft.X < GridTopLeft.X || TextBottomRight.X > GridBottomRight.X)
{
// Out of bounds on X axis - Revert to copied X transform
TextDrag.TranslateX = savedTransform.TranslateX; ;
}
else if (TextTopLeft.Y < GridTopLeft.Y || TextBottomRight.Y > GridBottomRight.Y)
{
// Out of bounds on Y axis - Revert to copied Y transform
TextDrag.TranslateY = savedTransform.TranslateY;
}
else
{
// TextBlock is within bounds - Copy X and Y Transform
CopyTransform(transform, savedTransform);
}
}
Here it is in action.

UWP Canvas drawing out of bounds

I have a canvas (not InkCanvas!) and I am able to draw Polylines on it. This is working just fine but there is a huge problem with drawing out of bounds like shown in the GIF below.
My canvas is inside a ScrollViewer and the ScrollViewer is inside a GridView.
I tried to catch the pointer leaving the canvas with the following event handlers:
canvas.PointerExited += Canvas_PointerExited;
canvas.PointerCaptureLost += Canvas_PointerCaptureLost;
But it seems those events are fired way too slow.
I tried to use the Clip property of my canvas but there is no change in behaviour. And there is no "ClipToBound" property for the UWP canvas.
My whole view is generated in Code-Behind because I have to generate multiple canvases on one view.
Is there a way to stop this behaviour?
EDIT1:
As requested: more insight of my code.
The XAML Page looks like this:
<Grid x:Name="BoundingGrid">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="15*"/>
</Grid.RowDefinitions>
<Grid x:Name="InkGrid" VerticalAlignment="Top" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" />
<Grid x:Name="CanvasGrid" Grid.Row="1" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" VerticalAlignment="Top"/>
</Grid>
It's all inside a Page.
My code behind looks like this:
My constructors:
public ImprovedCanvasManager(Grid boundingGrid, Grid overviewGrid, string filepath, double height)
{
drawCanvas = new Canvas();
overviewGrid.Loaded += OverviewGrid_Loaded;
overviewGrid.SizeChanged += OverviewGrid_SizeChanged;
RowDefinition rd = new RowDefinition();
rd.Height = new GridLength(height);
overviewGrid.RowDefinitions.Add(rd);
InitializeScrollViewer();
Grid.SetRow(scroll, overviewGrid.RowDefinitions.Count);
Grid.SetColumn(scroll, 0);
scroll.Content = drawCanvas;
overviewGrid.Children.Add(scroll);
LoadImage(filepath);
}
public ImprovedCanvasManager(Grid boundingGrid, Grid overviewGrid, Grid inkToolGrid, string filepath, double height = 1000) : this(boundingGrid, overviewGrid, filepath, height)
{
AddDrawingToolsToCanvas(inkToolGrid, overviewGrid);
EnableDrawingOnCanvas(drawCanvas);
}
I only got two contructors to make it simple for me to instantiate canvases with the ability to draw and without the ability to draw.
This is how i initialise my ScrollViewer:
private void InitializeScrollViewer()
{
scroll = new ScrollViewer();
scroll.VerticalAlignment = VerticalAlignment.Top;
scroll.VerticalScrollMode = ScrollMode.Auto;
scroll.HorizontalScrollMode = ScrollMode.Auto;
scroll.VerticalScrollBarVisibility = ScrollBarVisibility.Visible;
scroll.HorizontalScrollBarVisibility = ScrollBarVisibility.Visible;
scroll.ZoomMode = ZoomMode.Enabled;
scroll.ManipulationMode = ManipulationModes.All;
scroll.MinZoomFactor = 1;
scroll.MaxZoomFactor = 3;
}
Those are the only lines of code that affect any viewbuilding.
Edit 2:
My canvas doesn't fill the surrounding Grid on the left, but on the bottom.
The code in your PointerMoved handler should be relative to the canvas.
private void OnPointerMoved(object sender, PointerRoutedEventArgs e)
{
var point = e.GetCurrentPoint(canvas); // <-- relative to canvas.
var x = point.Position.X;
var y = point.Position.Y;
x = Math.Max(x, 0);
y = Math.Max(y, 0);
x = Math.Min(canvas.ActualWidth, x);
y = Math.Min(canvas.ActualHeight, y);
// add point to polyline...
}
If x/y are negative or greater than the size of the canvas, then they are out of bounds. You can either limit the point to the border of the canvas as the code above does, or you can discard the point completely.

best way for clickable image map in wpf

I have an image with many parts in c# WPF
I want make each part click make think
I have tried to split the image to parts and make event on each
image but the problem is the nested part of images
what is the best way to make image map ?
You can do it easily with Expression Design which is included in Microsoft Expression Studio. This is steps you gonna do:
Add image to Expression Design.
Then you can use PaintBrush tool for split image into parts as you want.
Then you must export this to xaml. In export window you can choose Xaml Silverlight 3
Canvas as format and Paths as Text.
As you understand, it automatically converts objects you draw on your image to path object with all coordinates on it.
Then you can copy exported xaml and paste it to your application.
You can download Expression Studio from Dreamspark for free.
I have just made sample and exported it to xaml:
<Canvas xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Name="Untitled1" Width="62" Height="62" Clip="F1 M 0,0L 62,0L 62,62L 0,62L 0,0" UseLayoutRounding="False">
<Canvas x:Name="Layer_1" Width="62" Height="62" Canvas.Left="0" Canvas.Top="0">
<Image x:Name="Image" Source="Untitled1_files/image0.png" Width="1920" Height="1080" Canvas.Left="0" Canvas.Top="0">
<Image.RenderTransform>
<TransformGroup>
<MatrixTransform Matrix="1,0,0,1,-929.667,-510.667"/>
</TransformGroup>
</Image.RenderTransform>
</Image>
<Path x:Name="Path" Width="159.722" Height="161.743" Canvas.Left="82.757" Canvas.Top="-0.415951" Stretch="Fill" Fill="#FFE7DEDE" Data="F1 M 82.8307,30.8333C 81.8859,46.01 90.3304,60.6249 90.3304,75.831C 90.3304,88.8304 91.9427,101.93 90.3304,114.829C 89.0281,125.247 87.0101,136.367 90.3304,146.327C 95.3301,161.327 119.518,161.327 135.328,161.327C 157.018,161.327 175.778,144.86 193.825,132.828C 209.523,122.363 235.198,120.495 241.823,102.83C 243.994,97.0391 240.326,90.2367 237.323,84.8306C 230.656,72.8294 223.759,60.756 214.824,50.3323C 205.057,38.9377 205.748,18.0458 192.325,11.3342C 183.723,7.03329 173.332,8.29683 163.827,6.83447C 144.945,3.92956 125.479,-3.30947 106.83,0.834766C 94.3289,3.61269 83.6265,18.0524 82.8307,30.8333 Z "/>
</Canvas>
</Canvas>
The exported part is Path object. You can do whatever you want on it. For example you can handle MouseClick event for this path and change background of path....
You could overlay the image with a set of transparent shapes:
<Canvas>
<Image Source="..."/>
<Ellipse Canvas.Left="50" Canvas.Top="50" Width="100" Height="50"
Fill="Transparent" MouseDown="Ellipse_MouseDown"/>
<Path Data="..." MouseDown="Path_MouseDown"/>
</Canvas>
These shapes could be simple Rectangles or Ellipses, or more or less complex Polygons or Paths.
Its very simple. rather than go to any complicated way follow this,It's simplest
1)First declare your image in XAML
<Grid Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center">
<Image Name="imagePath" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
2)Find mouse position on image itself to get x and y co-ordinate
String[] arr = new String[2];
var mousePos = Mouse.GetPosition(imagePath);
arr = mousePos.ToString().Split(',');
double x = Double.Parse(arr[0].ToString());
double y = Double.Parse(arr[1].ToString());
3)Declare area where you wants to get clickable area or mousehover
if (x >= 10 && y >= 10 && x <= 20 && y <= 20//this declares square area with x1,y1,x2,y2
{
//do whatever you want to do
//don't forget to add left and top each time
left = left +x;//x=10 i.e x1
top = top + y;//y=20 i.e y1
}
4)Add this x1 and y1 each time you move on image
int left = 0;
int top = 0;
Entire code;ll look like this
InitializeComponent();
imagePath.MouseMove += new MouseEventHandler(myMouseMove);
private void myMouseMove(object sender, MouseEventArgs e)
{
String[] arr = new String[2];
var mousePos = Mouse.GetPosition(imagePath);
arr = mousePos.ToString().Split(',');
double x = Double.Parse(arr[0].ToString());
double y = Double.Parse(arr[1].ToString());
int x = (int)xx;
int y = (int)yy;
int left = 0;
int top = 0;
Console.WriteLine("Screen Cordinate-------------------------" + x + ", " + y);
for (int i = 0; i < n; i++)
{
if (x >= x1 && y >= y1 && x <= x2 && y <= y2
{
Mouse.OverrideCursor = Cursors.Hand;
left = left + x1;
top = top + y1;
break;
}
else
{
Mouse.OverrideCursor = Cursors.Arrow;
}
}
where x1,y1,x2,y2 are the cordinates on image
Thats it!!!
I think use Adorner to implement the funtions is best way.
Here is smaple for add control on image (image annotating) and Adorner overview in MSDN

Categories