Get UI element Size from Canvas in WPF Forms - c#

I instantiate new Ui elements onto a canvas like so:
public class MainForm :Canvas
{
List<BannerImage> bannerList;
AddImages()
{
bannerImage = new BannerImage("title", "content");
//accompanied with animation
Children.Add(bannerImage);
bannerList.Add(bannerImage);
}
I need to call the bannerImages to get their current position, the following works:
foreach(bannerItem in bannerList)
{
double rightPosition = Canvas.GetRight(bannerItem);
}
But I can't do the following:
bannerItem.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)
Size s = bannerItem.DesiredSize;
Which always ends up to be
{0,0}
Why is it that I can get the position of the item on the canvas but not the size?

I am just going to take a guess that you didn't override MeasureOverride. I will provide a basic implementation assuming that each element is stacked, but you would need to modify it to take into consideration your child controls and what ever custom layout you may have created (I don't know if they are in a grid, horizontally stacked, in a some kind of scrolled container, etc).
protected override Size MeasureOverride(Size availableSize)
{
var height = 0.0;
var width = 0.0;
foreach (UIElement child in InternalChildren)
{
child.Measure(availableSize);
if (child.DesiredSize.Width > width) width = child.DesiredSize.Width;
height += child.DesiredSize.Height;
}
width = double.IsPositiveInfinity(availableSize.Width) ? width : Math.Min(width, availableSize.Width);
height = double.IsPositiveInfinity(availableSize.Height) ? height : Math.Min(height, availableSize.Height);
return new Size(width, height);
}
Edit
I realized that I explained the issue in a comment, but didn't add it into my answer. The reason you can't get the size is because you have to provide an override in your derived class to compute it. By default, Canvas returns a DesiredSize of 0 since it will adapt to whatever size is assigned to it. In the case of your derived control, you have a Canvas as the base class but you have added additional controls to it. If you don't provide an override of the MeasureOverride method, then the base one (the one implemented by Canvas) is the only one that is called. The base Canvas knows nothing of your controls size requirements. You probably also will need to override ArrangeOverride. This article provides a pretty good explanation about the two methods, what they do and why you need to override them. It also provides and example of both methods.

Related

UIElement.Rendersize does not scale down on changing the window size

Following an example from the Book "WPF Control Development Unleashed", i modified a ProgressBar to show a circular timer instead. This contains an Arc which runs counterclockwise around the center of the window (depending on the value of my ProgressBar).
My Arc-class inherits from System.Windows.Shapes.Shape and I use the RenderSize.Width and RenderSize.Height Properties to scale it depending on my Windowsize.
This seemed to work fine at first, but the Rendersize only seems to increase. Thus my arc scales up perfectly fine when i extend the window which contains the ProgressBar, but when i size it down again the size of the arc doesn't decrease.
My ControlTemplate contains a grid in which all the elements (including the arc) are set up, so all other Elements that don't depend directly on the RenderSize (some ellipses and a textblock) scale up and down as desired.
Do you have any ideas why the RenderSize behaves like described and which values i could use instead to calculate the x/y coordinates of start/endpoints of the arc?
Remark: if i set width and height of the grid to a fixed value, the rendersize does not change at all which confuses me even more, since the ellipses and textbox scale accordingly to the grid already.
I also read the book "WPF Control Development Unleashed". And see the scale issue of clock control. The problem is RenderSize was not changed when the window changed its size. My solution is add some logic to ArrangeOverride and MessureOverride method.
private Size _finalSize;
protected override Size MeasureOverride(Size availableSize)
{
Debug.WriteLine(string.Format("Mesure:{0},{1}", availableSize.Width, availableSize.Height));
Size desiredSize = base.MeasureOverride(availableSize);
_finalSize = availableSize;
return desiredSize;
}
protected override Size ArrangeOverride(Size finalSize)
{
Debug.WriteLine(string.Format("Arrange:{0},{1}", _finalSize.Width, _finalSize.Height));
base.ArrangeOverride(_finalSize);
return _finalSize;
}
=========================================================================
I read some materials.They said Messure and Arrange can have same logic but they should not share the data. Luckily I find another solution. First, the control should have the stretch dependency property. And then change the ArcGeometry method.
static Arc()
{
StretchProperty.OverrideMetadata(typeof(Arc), new FrameworkPropertyMetadata(Stretch.Fill));
}
private Geometry GetArcGeometry2()
{
Point startPoint = PointAtAngle(RenderSize, Math.Min(StartAngle, EndAngle));
Point endPoint = PointAtAngle(RenderSize, Math.Max(StartAngle, EndAngle));
Point stopPoint = PointAtAngle(RenderSize, Math.Max(EndAngle, 359.99));//Add this
Size arcSize = new Size(Math.Max(0, (RenderSize.Width - StrokeThickness)/2),
Math.Max(0, (RenderSize.Height - StrokeThickness)/2));
bool isLargeArc = Math.Abs(EndAngle - StartAngle) > 180;
StreamGeometry geom = new StreamGeometry();
using (StreamGeometryContext context = geom.Open())
{
context.BeginFigure(startPoint, false, false);
context.ArcTo(endPoint, arcSize, 0, isLargeArc, SweepDirection.Counterclockwise, true, false);
context.ArcTo(stopPoint, arcSize, 0, !isLargeArc, SweepDirection.Counterclockwise, false, false);//add blank arc
}
geom.Transform = new TranslateTransform(StrokeThickness/2, StrokeThickness/2);
return geom;
}
I had a similar issue, and (after looking at wwg2222's answer) solved it by adding the following:
protected override Size MeasureOverride(Size constraint) =>
new Size(constraint.Width, constraint.Height);
After this point, the shape sized properly and the DefiningGeometry override had the proper RenderSize to work with. I didn't need to override the ArrangeOverride function.
I made a round progressbar myself, but i didn't use the RenderSize at all.
A better approach is to define an InnerRadius and OuterRadius property on your Arc and let their values be between 0 and 1.
Then, do the calculations in the Arc's DefiningGeometry override.
Since the Arc is a Shape, you can use its Stretch property to scale it up inside the controltemplate.
One issue with scaling is that the arc's bounding box varies with its value/angle. This is why you need to define an EllipseGeometry inside DefiningGeometry to fixate the boundaries of the Arc. And because of this extra Geometry I even added a second EllipseGeometry to get the filling right (because of the FillRule.EvenOdd value).
Good luck!

Best way to create a resizable Grid of Button control with constrained proportions (Silverlight. XAML, WinRT)

Writing a WinRT app in XAML/C# where I'd like a simple grid of square shaped buttons. The number of buttons is fixed currently, however in future there will be more added as I create more content.
Having to handle all UI resizes (snapped, filled, portrait, etc) and resolutions I ran into problems with the UIContainer (I was using a Grid then switched the WrapGrid) simply resizing the buttons automatically because I do not know of any way to constrain the aspect ratio and having square buttons is important to my UI.
Is there a way to constrain the aspect ratio / proportions of the Width and Height of a button control? If so, I'm assuming it would be to create a custom control, but other than creating styles and data templates I'm really just out of my depth.
Any suggestions on the best way to attack this problem?
You can create a simple decorator control that would override ArrangeOverride and always arrange itself into a square, like this:
public class SquareDecorator : ContentControl
{
public SquareDecorator()
{
VerticalAlignment = VerticalAlignment.Stretch;
HorizontalAlignment = HorizontalAlignment.Stretch;
VerticalContentAlignment = VerticalAlignment.Stretch;
HorizontalContentAlignment = HorizontalAlignment.Stretch;
}
protected override Size MeasureOverride(Size availableSize)
{
var baseSize = base.MeasureOverride(availableSize);
double sideLength = Math.Max(baseSize.Width, baseSize.Height);
return new Size(sideLength, sideLength);
}
protected override Size ArrangeOverride(Size finalSize)
{
double sideLength = Math.Min(finalSize.Width, finalSize.Height);
var result = base.ArrangeOverride(new Size(sideLength, sideLength));
return result;
}
}
Now you can wrap your buttons with this decorator:
<z:SquareDecorator>
<Button Content="I'm Square"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch" />
</z:SquareDecorator>
I'm assuming you can't just set height and width to a fixed size (on the button itself or in a style for the button).
I tried doing this in Silverlight:
<Button Height={Binding ActualWidth, RelativeSource={RelativeSource Self}}/>
But it doesn't want to work. Don't know why. It might work in WinRT.
Alternatively, you can create a custom Panel to arrange and size your buttons. Should not be difficult given your simple requirements. It involves implementing just two functions and knowledge of basic arithmetic. Here is an example that creates a UniformGrid.
I don't think a UserControl or deriving from Button would be better choices.
Here's my version of Pavlo's answer. It is more efficient and elegant than deriving from ContentControl (which must use a ControlTemplate, adds other elements to the visual tree, and has tons of other unneeded functionality). I also believe it is more correct, because MeasureOverride returns the correct desired size.
public class SquareDecorator : Panel
{
protected override Size MeasureOverride(Size availableSize)
{
if( Children.Count == 0 ) return base.MeasureOverride(availableSize);
if( Children.Count > 1 ) throw new ArgumentOutOfRangeException("SquareDecorator should have one child");
Children[0].Measure(availableSize);
var sideLength = Math.Max(Children[0].DesiredSize.Width, Children[0].DesiredSize.Height);
return new Size(sideLength, sideLength);
}
protected override Size ArrangeOverride(Size finalSize)
{
if( Children.Count == 0 ) return base.ArrangeOverride(finalSize);
if( Children.Count > 1 ) throw new ArgumentOutOfRangeException("SquareDecorator should have one child");
double sideLength = Math.Min(finalSize.Width, finalSize.Height);
Children[0].Arrange(new Rect(0, 0, sideLength, sideLength));
return new Size(sideLength, sideLength);
}
}
Use it the same way (Button's Horizontal/VerticalAlignment must be stretch, but this is the default. Also note you can get useful effects if you set SquareDecorator's Horizontal/VerticalAlignment to non-stretch.):
<z:SquareDecorator>
<Button Content="I'm Square"/>
</z:SquareDecorator>
I would have derived from FrameworkElement, but it looks like neither Silverlight nor WinRT allow you to do that. (FrameworkElement is not sealed, but has no AddVisualChild method which makes it useless to derive from. Sigh, I hate .NET and/or Microsoft)

How to Override Children.Add method of Canvas class in WPF

I already have a Custom Canvas class in which i have override the 'OnMoseDown' method.
But now i also want to override 'Children.Add' method.
But i didnt find any solution.
I want to get Child.ActualWidth when a child gets added to canvas.
Thanks.
I just found my answer by myself. Hope this may help someone on some day.
Just added a Add method in my Custom class
public void Add(UserControl element)
{
bool alreadyExist = false;
element.Uid = element.Name;
foreach (UIElement child in this.Children)
{
if (child.Uid == element.Uid)
{
alreadyExist = true;
this.BringToFront(child);
}
}
if (!alreadyExist)
{
this.Children.Add(element);
this.UpdateLayout();
double top = (this.ActualHeight - element.ActualHeight) / 2;
double left = (this.ActualWidth - element.ActualWidth) / 2;
Canvas.SetLeft(element, left);
Canvas.SetTop(element, top);
}
}
This will likely not work correctly. A child will get added before it's ActualWidth is set, as that property gets set during the layout phase.
Depending on your goals here, you may want to consider overriding MeasureOverride and ArrangeOverride instead, and making a custom layout panel instead of subclassing Canvas.

Label Size is always NaN?

I have a Panel which I want to extend and override MeassureOverride and Arrange to have my custom layout.
Basically, the panel will contain some labels. As the label has some text content, it should have a specific size. However when I use label.ActualHeight or actualwidth, desiredSize ... in the MeassureOverride or ArrangeOverride, all result to NaN. Is there any way I can get the desired Size of the label so that the text content is fit?
Do you call base.MeasureOverride(abailableSize) and base.ArrangeOverride(finalSize) at the end of each method?
Here is an example of creating a custom panel
A custom implementation of MeasureOverride might look like this (from the post):
protected override Size MeasureOverride(Size availableSize)
{
Size sizeSoFar = new Size(0, 0);
double maxWidth = 0.0;
foreach (UIElement child in Children)
{
child.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
if (sizeSoFar.Width + child.DesiredSize.Width > availableSize.Width)
{
sizeSoFar.Height += child.DesiredSize.Height;
sizeSoFar.Width = 0;
}
else
{
sizeSoFar.Width += child.DesiredSize.Width;
maxWidth = Math.Max(sizeSoFar.Width, maxWidth);
}
}
return new Size(maxWidth, sizeSoFar.Height);
}
A custom implementation of ArrangeOverride might look like this (from the post):
protected override Size ArrangeOverride(Size finalSize)
{
Size sizeSoFar = new Size(0, 0);
foreach (UIElement child in Children)
{
child.Arrange(new Rect(sizeSoFar.Width, sizeSoFar.Height,
child.DesiredSize.Width, child.DesiredSize.Height));
if (sizeSoFar.Width + child.DesiredSize.Width >= finalSize.Width)
{
sizeSoFar.Height += child.DesiredSize.Height;
sizeSoFar.Width = 0;
}
else
{
sizeSoFar.Width += child.DesiredSize.Width;
}
}
return finalSize;
}
If you want to force the panel rendering (call the MeasureOverride function), use the InvalidateMeasure function
You could also check out Custom Panel Elements on msdn.
The DesiredSize for each child is only set after you have measured it. In your MeasureOverride you must call child.Measure() for every of your panel's children. The same goes with child.Arrange() in ArrangeOverride.
See http://msdn.microsoft.com/en-us/library/ms745058.aspx#LayoutSystem_Measure_Arrange
Edit in response to your comment: just pass the maximum size your label could have (the available size), or a constrained size if you need to. The label once measured will use its minimum size as the DesiredSize if the alignments are different from stretch.

Getting initial dimensions from inherited UserControl

I am trying to create a series of UserControls that all inherit from a custom UserControl object. One of the key behaviors I want to implement is the ability to dynamically resize all of the controls to fit the control size.
In order to do this, I need to get the initial width and height of the control to compare it to the resized dimensions. In my inherited controls, I can put code in the constructor after the InitializeComponent() call to grab the dimensions. Is there any way I can do this from the base object code?
Also, if there is a better approach to doing this, I am open to suggestions.
I ended up using my base control's dimensions as the fixed size for all inherited controls. This was fairly easy to do by overriding the SetBoundsCore() method:
public partial class BaseControl : UserControl
{
private int _defaultWidth;
private int _defaultHeight;
public BaseControl()
{
InitializeComponent();
_defaultWidth = this.Width;
_defaultHeight = this.Height;
}
protected override void SetBoundsCore(int x, int y, int width, int height, BoundsSpecified specified)
{
if (this.DesignMode)
{
width = _defaultWidth;
height = _defaultHeight;
}
base.SetBoundsCore(x, y, width, height, specified);
}
}
Any controls inherited BaseControl automatically default to its fixed dimensions.
At runtime, my resize code calculates a resize ratio based on the new Width and Height vs. the _defaultWidth and _defaultHeight members.
In the user control, take advantage of the docking and anchoring properties of every control in the container. When the user control is sized or resized, the contents should adjust themselves automatically. Then in code, all you need to do is set the size of the user control.

Categories