If a parent control asks its children "How big do you want to be?", then what use is the availableSize parameter that's passed along? I've taken a peek via Reflector into the StackPanel's source and I still can't figure it out.
If the child wants to be 150x30, then it still reports 150x30 even if availableSize is 100x20, doesn't it? And if the child is expected to constrain itself to the availableSize, then that might as well be done on the size that's returned from calling MeasureOverride on the child - no point in passing that parameter.
Is there something that I'm not taking into account?
If the child wants to be 150x30, then it still reports 150x30 even if availableSize is 100x20, doesn't it?
It depends on the control, but generally the answer is no. In any case, the point is to give it the opportunity to fit itself to the container, but it is not required to do so.
Think about the difference between a Grid and a StackPanel. The Grid will typically size itself precisely to the available size. The StackPanel, by contrast, will size itself infinitely in one direction only (depending on its orientation), regardless of the available size. In the other direction, it will extend itself only as far as the space needed for its children, unless its "HorizontalAlignment" / "VerticalAlignment" is set to "Stretch", in which case it will stretch itself out to the available size in that direction.
The ViewBox is a more complex example that makes good use of "availableSize". It generally sizes itself to the available space, and scales/stretches its children depending on the values of "Stretch" and "StretchDirection".
The point is to give the element the opportunity to size itself correctly. After all the parent control might clip it if it doesn't respect the available size.
One has to differentiate between the following possibilities:
Container
Container has unlimited size (ScrollViewer) or wants to know how much space the child needs. In this case the availableSize in MeasureOverride() is infinite.
Container has limited size. In this case the availableSize in MeasureOverride() is the limited size.
Child
Child can adjust to Container size. In this case it returns availableSize if availableSize is not infinite. If it is infinite and Child cannot calculate size on its own, it can return 0.
Child has a fixed size. In this case it does not care about the availableSize but returns the fixed size.
The same thing happens again in ArrangeOverride(Size arrangeBounds), although with possibly different sizes. Therefore, do not use values calculated in MeasureOverride(), but recalculate them in ArrangeOverride().
arrangeBounds cannot be infinite. Instead of infinite, the container passes the available size calculated in its Arrange() method. The child can still use a different size. If it is too big, the container will clip it. If it is too small, the Container needs to align it somehow (ContentAlignment).
If a control (Container, Child) is fixed size or not depends also on properties like Width, MinWidth, MaxWidth, HorizontalAlignment, etc. Depending on these settings, a control demands in some parameter combinations a fixed size and in others it can adjust to the available size.
Related
I'm trying to make a game which uses a pixel art style. I've created my assets and used them as resources, which are added into the window as System.Controls.Image objects.
In the designer, they always turn out too small. As a result, when I resize them, they become somewhat blurred due to some sort of bicubic interpolation being applied onto them.
I have managed to avoid this by avoiding resizing; I resize in the designer to find out what size is appropriate, and then use nearest neighbour resizing on the original image (using an external program) to get the source file to that size. I then update the image in the project and remove any resizing, therefore leaving it at original size, i.e. interpolation-free.
As you can imagine, this is a rather tedious process. I looked into interpolation choices during resizing, but most answers I can find relate to System.Drawing.Image, not System.Controls.Image. I feel like any such solution (if adapted) would be horribly messy and involve multiple (and perhaps unecessary) conversions/casts.
Is there any way to get nearest neighbour resizing on System.Controls.Image?
To set the resize mode, you need to set the RenderOptions.BitmapScalingMode="NearestNeighbor" option for the visual tree. You can set this at the window level.
To address the larger issue, it seems that something is causing your images to be scaled in the first place:
Ensure you are setting the Stretch="None" option on the Image control,
Ensure that you are using SnapsToDeveicePixels or Layout rounding
Lastly if all else fails, explicitly set the width and height of the image.
I have also run into instances where the image file's DPI not being set to (I believe) 90, causes the renderer to apply scaling.
I am writing MeasureOverride implementation and there is one point I am kind of stuck.
The return value of the function.
This is the code.
protected override Size MeasureOverride(Size availableSize)
{
Double cHeight = 0.0;
Double cWidth = 0.0;
Size size = new Size(0, 0);
foreach (UIElement child in InternalChildren)
{
child.Measure(new Size(availableSize.Width, availableSize.Height));
if (child.DesiredSize.Width > cWidth)
{
cWidth = child.DesiredSize.Width;
}
cHeight += child.DesiredSize.Height;
}
size.Width = double.IsPositiveInfinity(availableSize.Width) ? size.Width : cWidth;
size.Height = double.IsPositiveInfinity(availableSize.Height) ? size.Height : cHeight;
return size;
}
My understanding is that returning an empty Size object is an indication that the element will use all the space available. However in this case when the space available is infinite positive, then it is returning zero.
Should it not be other way around. When infinite space is available then use only the space needed by the child elements? Otherwise constrain itself to use whatever space is available?
size.Width = double.IsPositiveInfinity(availableSize.Width) ? cWidth : availableSize.Width;
size.Height = double.IsPositiveInfinity(availableSize.Height) ? cHeight : availableSize.Height;
This one is kind of difficult to answer. It depends on what you want to achieve.
Probably you know that the measure phase determins the desired sizes only. The arrange phase fiddles with final values then.
I can imagine a panel with a behavior as in the code above. It could avoid a parent ScrollViewer to reserve too much space in case a child of our panel desires very much space (for example because it is an ItemsControl with many items itself). By returning zero the surrounding ScrollViewer would not reserve this space and in the arrange phase our panel could occupy space as needed although we returned zero.
It would be a very special case, but I was dicussing this very problem with a collegue today when he had a DataGrid along other elements within a ScrollViewer.
Without the code in "Arrange" this is all speculative but it's a possible usage of such code.
With MeasureOverride, you are returning the size that your control wants to be. If MeasureOverride is given infinite bounds then you can make it the size that holds all of the children or not; it is up to you. On the other hand, if you return a size that is larger than you are given, your control parent might accept that and just cut off your control later. For instance, if your control is in a grid and the grid width is set to 500, your MeasureOverride function will get passed an available width of 500. You can return 600 for the width and you will get that 600 width in the ArangeOverride function, but the parent grid will still only give your control 500 width and your content will just get cut off.
I tried this with my own PriorityStackPanel control written for WinRT (Windows 8.1 Store App) and it worked as I described. I think that WPF is just about identical in this case.
In WinRT, there is no "empty" size object. The size object will have some value set for it. NAN and PositiveInfinity both cause exceptions so you really should be returning the size that you want for your control. How you pick that size is up to you but it can't be a "you can decide for me: value like an empty size object (in WinRT). Even if you can return that in WPF, I advise against it since it makes the code less-than-clear. Just return the available size that you were given if that is what you need. Return a larger size if you need more room, but don't expect the container to show all of your content if you ask for more room than it is giving you.
I have a window that has a menu, a toolbar at the top, and various other controls. I then have my own control that derives from ContentControl that I want to have use up all remaining space. I can't leave it to its own devices unfortunately, because the control is a Win32 control that's sort of... put inside this WPF control, and I need to use SetWindowPos.
At the moment what I am doing is using ArrangeOverride, getting the MainWindow.Content control and looking at the Height and Width. I then use Size I get in as a parameter and call the SetWindowPos function. It's written in C++/CLI, and here's the code:
Size WebView::ArrangeOverride(Size finalSize)
{
Application::Current->MainWindow->Measure(finalSize);
UIElement^ obj = dynamic_cast<UIElement^>(Application::Current->MainWindow->Content);
double objHei = obj->RenderSize.Height;
double objWid = obj->RenderSize.Width;
SetWindowPos(hWnd, NULL, objWid-finalSize.Width, objHei-finalSize.Height, finalSize.Width, finalSize.Height, NULL);
So in my head I thought this would then set the position of the control to within the remaining available space. And it does sort of work, but it seems as if the MainWindow.Content control is not being measured until afterwards? What am I doing wrong here?
edit: most of the problems seem to be when full-screening the window and then un-fullscreening it.
I have managed to fix this by using the answer to this question here
I simply put my control into a Frame, so it'd be the parent.
Then using the Point I set the window position to that, along with the size that is passed through as a parameter to the ArrangeOverride method.
I'm drawing a lot of lines on a long canvas (think stripchart) and have it tuned fairly well for performance, using the low-level geometry classes and freezing them, etc. This improved performance dramatically, but it still takes a few seconds to load a few thousand items into the canvas. I ran a performance analysis on the application, and it looks like a big percentage of the time is taken by each call to canvas.children.add(). I've read that this should be a lightweight call, and since I'm calling it numerous times in one method, it shouldn't be trying to do anything heavy inbetween... Could there possibly be any other reason this might be taking so much time? And any way I might speed it up?
The performance is not terrible, but I fear it could become more of a problem later when I need to deal with larger sets of data.
Just for reference, it looks like it is called 1400 times in this sample, and it taking almost 3 seconds of CPU time on a modern/fast laptop.
The canvas is contained in a hierachy of other controls though, so I'm curious if they might be contributing to this.
Extra note: I'm also not setting a specific height on the canvas, as it is set to fill the grid parent container. Could this be a source of the problems?
Main problem is that Children.Add is always a slow operation, even if you use StreamGeometry objects. I faced the same problem recently and concluded the following:
If you put a bunch of objects into a new canvas and nest it into the main canvas, the performance of the addition operation will be increased dramatically.
So, instead of adding 1400 elements, put 200 elements in 7 canvases and add those 7 canvases to the main canvas.
Since all objects now belong to different canvases, you will need to adjust your app a bit, but this would be a less drastical solution than moving to an alternative solution like DrawingVisual
Just to add about the hierarchy of controls the canvas is within, and the height of the canvas:
the Canvas always takes as much space as its given, and no matter what children u add to it - it NEVER triggers a new Measuer/Arrange passes on its parents. therefor whatever u do inside a canvas could never affect the visual tree it is contained in.
To sum it up - the problem cannot come from there, and the suggestion about the StreamGeomatry is exactly right- this is what causing u the performance issues, and switching to streamgeormatry would solve it.
I would suggest that you draw your shapes directly into an image instead of adding them as children.
Rendering children has a HUGE overhead (as you can see).
There's a similar question with a reference to some helpful articles:
How to draw line of ten thousands of points with WPF within 0.5 second?
I'm implementing a custom control with some labels on it and I need to measure the size of those labels to have an optimal layout. In this way I can properly show the control for each font and font size.
Could you tell me how can I do that, please?
Thank you.
The correct way to size and arrange your custom control according to the size of its sub-elements is to override MeasureOverride and ArrangeOverride.
See the links for details, but in a nutshell, your control is supposed to (in MeasureOverride):
call UIElement.Measure on all children (which will include your labels), which will return the size that each of your children would like to have,
calculate your own desired size and
return this size to the framework (by using it as the return value of MeasureOverride).
Afterwards, in ArrangeOverride, you get the size allocated to your control by the framework as a parameter and you
determine how much of the space you want to allocate to each of your child elements and
call UIElement.Arrange on each child element.
you might want to look up ev.Graphics.MeasureString(str,font)