WPF Canvas Only Extends to the Right? - c#

I'm making a WPF application using C# and I am in the stage of developing more UI for it. I have all my components such as TextBlocks and other Canvases inside of a main Canvas, but if I drag the left side of it to make more room, it expands on the right side, meaning I have to move everything over. The same thing happens when I try and extend the top; The bottom gets bigger. I have the RenderTransformOrigin of the Canvas set to 0,0 so I'm not quite sure what is going on. Any help is appreciated.
Thanks!

By default, a Canvas doesn't have a Width or Height, its actual size depends if you set those properties explicitly, or if you set HorizontalAlignment or VerticalAlignment to Stretch.
In either case, elements inside of a Canvas are aligned relative to its top-left corner, except if you explicitly set their Canvas.Top, Canvas.Left, Canvas.Right and/or Canvas.Bottom properties.
You could also position elements containing a Geometry (like derivatives of Shape, Path being a common example) by their absolute Geometry coordinates. Actually this already is the way they are rendered.
Regarding your comment in the question, usually you don't change where things align relative to the Canvas, since it is much easier (I think even recommended) to reposition the canvas itself, so that everything it contains would be repositioned too.
Common ways to do that would be adding elements to a Canvas, and then position that Canvas inside another "parent" Canvas, similar to "grouping" in vector design programs like CorelDraw/Inkscape/Illustrator.
Another way would be to use RenderTransform property of Canvas containing the elements you want to reposition.
After you get used to see Canvas as a "coordinate system", you can and should think of Canvas-inside-Canvas-inside-Canvas-... as a way of hierarchically grouping things that share the same cartesian space, as opposed to Panels and ContentControls, which are nested inside one another and cannot typically share space.

Like I said in my comment, all the controls inside the canvas are positioned relative to the canvas' top left point. If you want them to retain their "position" when the canvas is resized to the left or top, you will need to offset them yourself. You could do this with a utility method that you call whenever you want to resize the canvas:
public static void StretchWidthLeft(Canvas canvas, double newWidth)
{
double diff = newWidth - canvas.Width;
canvas.Width = newWidth;
foreach (UIElement child in canvas.Children)
{
Canvas.SetLeft(child, Canvas.GetLeft(child) + diff);
}
}
public static void StretchHeightTop(Canvas canvas, double newHeight)
{
double diff = newHeight - canvas.Height;
canvas.Height = newHeight;
foreach (UIElement child in canvas.Children)
{
Canvas.SetTop(child, Canvas.GetTop(child) + diff);
}
}
Note that these methods do not reposition the canvases themselves. How you would do that depends on how the canvas' parent is maintaining their position.

Related

C# UWP Universal Windows App - Shape Resizing

I'm working on an application that has a graphic editor. Aside from some other graphic components the main feature are some shapes (rectangles, triangles, stars etc.). The thing I would like to do is to have the ability to resize them after a double click, so there should be a container shown with points that can be dragged to resize the shape. There are many great tutorials out in the internet but mainly for rectangles or for other shapes that are "packed" into rectangle viewboxes.
The issue is that I need to have my resize points exactly on the vertexes, so for example I can't have a triangle inside a rectangle viewbox, but I need exactly three points - one on every vertex. The same applies for the other shapes like stars and arrows which are much more complicated.
Here's what I mean:
So I think I have two ways. Either pass a dynamic list of points after a double click and display them as separate shapes or binding them with my shape inside my shape class, but I cannot figure out how to add multiple shapes to a viewbox so that they keep their abilities. Here is a simplified snippet of how my shape classes look like:
public class RectangleObject : ShapeObject
{
private Rectangle _rectangle;
private Viewbox _viewbox;
public RectangleObject(Color fillColor)
{
_rectangle = new Rectangle()
{
Fill = new SolidColorBrush(fillColor),
Stretch = Stretch.Fill
};
_viewbox.child = _rectangle;
}
public void SetDimensions(){}
//... and some other methods
}
A nice solution would be if the viewbox could contain my shape and the resize points but I will appreciate any advice. I know that a canvas would be a better solution but all other components are packed in viewboxes so it would be problematic because of inheritance and would need many changes. Thanks in advance!
Yes I know exactly what you mean. I used CorelDraw back in the day and then Adobe Illustrator and both had the concept of shape transformation mode (your image on the right) as well as vertex edit mode (your image on the left). UWP is similar to WPF and WPF has a concept of adorners which you can customise to do whatever you want including what I just described.
So when you enter one of these modes, rather than modify your existing shape to show selectors; instead you create adorners that are superimposed over the shape. That way one doesn't clobber the other and you may have custom behavior for each as you indicated.
See also
Adorners - MSDN, https://msdn.microsoft.com/en-us/library/ms743737(v=vs.110).aspx, retrieved 2017-1-12

How to determine if control is outside of Form?

I am trying to determine if a dynamically added control is outside of the form.
At first, I thought it might be possible to calculate it by getting the height of the form, and location of the dynamically added control.
But I noticed that the Control.Location and Form.Height have "nothing" in common.
I don't think I really understand what the correlation is between Height and Location.
For example:
I thought that if your form has a height of 500, and I put the control at the bottom of the form, it should give the Location: X, 500 (X is width, not relevant here). But this is not correct, it shows me for example: X, 465. Am I missing something?
So I need to be able to recognize if the control is outside of the form, even if it's just one pixel.
I've found several similar questions here on SO, yet this does not really give me the answer that I need, unfortunately.
So, is there any way to do this? Is it possible to calculate it?
The Height of the form also includes the height of the title bar and borders.
You can use the ClientSize of the form:
From the documentation on MSDN:
The size of the client area of the form is the size of the form excluding the borders and the title bar. The client area of a form is the area within a form where controls can be placed. You can use this property to get the proper dimensions when performing graphics operations or when sizing and positioning controls on the form. To get the size of the entire form, use the Size property or use the individual properties Height and Width.
The position of the control is relative to its container, so (0,0) is the left upper corner inside the form.
I know this is an older thread, but you can try using this method:
public static bool IsOutofBounds(Form form, Control control)
{
int controlEnd_X = control.Location.X + control.ClientSize.Width;
int controlEnd_Y = control.Location.Y + control.ClientSize.Height;
if (form.ClientSize.Width < controlEnd_X || form.ClientSize.Height < controlEnd_Y)
{
return true;
}
else
{
return false;
}
}
It works for checking whether a control is out of bounds of its parent form.
You could use this code to check if controls is inside form:
var Inside = frm.ClientRectange.Intersect(ctrl.Bounds) == ctrl.Bounds;
the top left corner of a form is (0,0) lower right corner is (formHeight, fromWidth).
to check this place two text boxes on a form and write this code in the mouse move event to see how x and y change.
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
textBox1.Text = e.X.ToString();
textBox2.Text = e.Y.ToString();
}
Note that there is an difference between the number returned from the edge of the form and the size chosen by you. In my 500*500 form it is actually 460*483. the difference is always the same for any border style and any resolution.
To place a control on your form use the location structure in the form or use the top and left properties for the control; top = x, left = y.
Remember your offset from the actual height and width you measured and the dimension of the control.
To add a button with the following dimensions 80*30 in the bottom right corner I would right something like this:
button1.Location = new System.Drawing.Point(402, 430);
bottom left corner:
button1.Location = new System.Drawing.Point(0, 430);

Confused about drawing a rectangle in WPF

I want to draw a rectangle on a canvas in WPF. For drawing a line I can do this:
line.X1 = ls.P0.X;
line.Y1 = ls.P0.Y;
line.X2 = ls.P1.X;
line.Y2 = ls.P1.Y;
MyCanvas.Children.Add(line);
...in other words the location is a property of the line itself. I want to draw a rectangle the same way, i.e., assign its coordinates and add it to my canvas. But the examples I've seen online so far seem to look like this:
rect = new Rectangle
{
Stroke = Brushes.LightBlue,
StrokeThickness = 2
};
Canvas.SetLeft(rect,startPoint.X);
Canvas.SetTop(rect,startPoint.X);
canvas.Children.Add(rect);
...in other words it doesn't look like the rectangle has an inherent location, but instead its location is set by calling a method of Canvas. Is this true - Lines have inherent coordinates but Rectangles do not? Is there any way to have a rectangle in WPF with an inherent location, like a line, or do I have to roll my own (using lines)?
You could use a Path control with a RectangleGeometry like this:
var rect = new Path
{
Data = new RectangleGeometry(new Rect(x, y, width, height)),
Stroke = Brushes.LightBlue,
StrokeThickness = 2
};
canvas.Children.Add(rect);
...in other words it doesn't look like the rectangle has an inherent location, but instead its location is set by calling a method of Canvas. Is this true - Lines have inherent coordinates but Rectangles do not?
Locations in WPF are relative, which begs the question: coordinates relative to what?
Line, Rectangle, and Path all inherit from Shape, and in the case of any Shape object, the coordinates of the defining geometry are relative to the top-left corner of the Shape itself. Thus, when you create a Line object from (100, 300) to (300, 100), the resulting element is 300x300 points in size, even though the visible line has bounds of 200x200:
In this case, it is unnecessary to place the Line within a Canvas, as you are not using the coordinate system of the Canvas.
Some shapes like Line and Path allow you to place geometry at any "internal" coordinates you like. Others, like Rectangle and Ellipse, always position their defining geometry at (0, 0) internally, forcing you to use other layout properties to position the shapes within the greater scene (e.g., Canvas.Top/Left, Margin, etc.).
In your example, if you were to define a Rectangle of 200x200 points, and use the Canvas attached properties to position the rectangle at (100, 100), the resulting Rectangle element would measure 200x200, while the parent Canvas would measure itself to be at least 300x300, which is arguably more intuitive:
(shading added for clarity)
You are correct in that this is rather inconsistent. You may find it useful to always use layout properties (e.g., Canvas.Left/Top) to position shapes within a scene such that all elements are using the same coordinate system.

Canvas has Width of 0 after adding children. How to cause canvas to recalculate width?

I'm creating a custom canvas which has dynamically added shapes (lines) which are added as children. Unfortunately, after adding the children, the canvas has a width of 0, which is not expected.
Ideally, the canvas should update its width by using its children's dimensions.
How can I modify my canvas to account for the dimensions of its children, and then update its width?
As I said, I changed the shapes on the canvas dynamically. I do this every time one of its dependency properties is changed using a callback function.
Generally, this is what that callback function looks like:
This is not my algorithm, but it should demonstrate the point.
public void UpdateLines()
{
for(int i = 0; i < NUM_BARS; i++)
{
Line bar = new Line();
bar.Stroke = Brushes.Black;
bar.StrokeThickness = 4;
bar.X1 = 0;
bar.X2 = ActualWidth;
bar.Y1 = i * (bar.StrokeThickness + 2);
bar.Y1 = bar.Y2;
this.Children.Add(bar);
}
//[Inserted here]
}
The next thing I noticed is that I could add a line with a large length, and that line would appear, but the canvas width would still remain 0.
At the "Inserted Here" mark, I put this line of code to test:
Children.Add(new Line() { Stroke=Brushes.Black,
StrokeThickness = 4, X1 = 0, X2 = 200, Y1 = 10, Y2 = 10 });
So, why the canvas width is not updating properly? I need to use it for calculating the size of my lines.
Your help is greatly appreciated. Thanks.
Please refer to the Panels Overview page on MSDN for further help with WPF Panels, but in short, the Canvas Panel does not resize itself when child items are added (or at any time). From the linked page:
Canvas is not affected by the size of its children. As a result, it is possible for a child element to overdraw other elements outside the bounding rectangle of the parent Canvas.
Therefore, you will either need to manually update the size of the Canvas, or use a different type of layout Panel that does resize itself when child items are added, although the Canvas is the only Panel that enables users to place child controls at absolute positions within it.
Again, from the linked page:
Canvas provides the most flexible layout support of any Panel. Height and Width properties are used to define the area of the canvas, and elements inside are assigned absolute coordinates relative to the area of the parent Canvas. Four attached properties, Canvas.Left, Canvas.Top, Canvas.Right and Canvas.Bottom, allow fine control of object placement within a Canvas, allowing the developer to position and arrange elements precisely on the screen.

Restricting usercontrol from moving beyond the canvas xaml

I am developing Windows 8.1 Store Apps using XAML.
The scenario is I am having a Canvas in which I am placing more than one user controls. The user controls can be moved on click of a button. I should restrict the user controls from moving beyond the Canvas. What is the way to do it.? I am having Manipulation events within the usercontrol for movement.
I expect that your controls that you move are children from the canvas?
If so i think you can do a check to see if the bounds of you control moving on your canvas hits the border of you canvas. If this is happening then make it stop.
Basic gameprogramming stuff. I will look for a example
Here it is.
This part of code will get you the place of your control inside your container
public static class BoundsHelper
{
public static Rect GetBoundsInParentContainer(Control control)
{
Vector offset = VisualTreeHelper.GetOffset(control);
Rect rect = new Rect(offset.X, offset.Y, control.ActualHeight, control.ActualWidth);
return rect;
}
}
So if you have a button in the top left corner of the canvas and you ask that button the control where it is it will give you a rect where the X and Y are 0 and the Width and Height of your control. So if you move your control inside your canvas. Call this method and check if your control is still inside the Height and width of your canvas.
Here the code to move to the right but first check if you are allowed to move
private void MoveRightAndCheck(Rect boundsControl, Control container)
{
int stepSize = 10;
if (container.ActualWidth > (boundsControl.X + boundsControl.Width + stepSize))
{
//Your code to move your control inside the canvas
}
}

Categories

Resources