How to get controls in WPF to fill available space? - wpf

Some WPF controls (like the Button) seem to happily consume all the available space in its' container if you don't specify the height it is to have.
And some, like the ones I need to use right now, the (multiline) TextBox and the ListBox seem more worried about just taking the space necessary to fit their contents, and no more.
If you put these guys in a cell in a UniformGrid, they will expand to fit the available space. However, UniformGrid instances are not right for all situations. What if you have a grid with some rows set to a * height to divide the height between itself and other * rows? What if you have a StackPanel and you have a Label, a List and a Button, how can you get the list to take up all the space not eaten by the label and the button?
I would think this would really be a basic layout requirement, but I can't figure out how to get them to fill the space that they could (putting them in a DockPanel and setting it to fill also doesn't work, it seems, since the DockPanel only takes up the space needed by its' subcontrols).
A resizable GUI would be quite horrible if you had to play with Height, Width, MinHeight, MinWidth etc.
Can you bind your Height and Width properties to the grid cell you occupy? Or is there another way to do this?

There are also some properties you can set to force a control to fill its available space when it would otherwise not do so. For example, you can say:
HorizontalContentAlignment="Stretch"
... to force the contents of a control to stretch horizontally. Or you can say:
HorizontalAlignment="Stretch"
... to force the control itself to stretch horizontally to fill its parent.

Each control deriving from Panel implements distinct layout logic performed in Measure() and Arrange():
Measure() determines the size of the panel and each of its children
Arrange() determines the rectangle where each control renders
The last child of the DockPanel fills the remaining space. You can disable this behavior by setting the LastChild property to false.
The StackPanel asks each child for its desired size and then stacks them. The stack panel calls Measure() on each child, with an available size of Infinity and then uses the child's desired size.
A Grid occupies all available space, however, it will set each child to their desired size and then center them in the cell.
You can implement your own layout logic by deriving from Panel and then overriding MeasureOverride() and ArrangeOverride().
See this article for a simple example.

Well, I figured it out myself, right after posting, which is the most embarassing way. :)
It seems every member of a StackPanel will simply fill its minimum requested size.
In the DockPanel, I had docked things in the wrong order. If the TextBox or ListBox is the only docked item without an alignment, or if they are the last added, they WILL fill the remaining space as wanted.
I would love to see a more elegant method of handling this, but it will do.

Use the HorizontalAlignment and VerticalAlignment layout properties. They control how an element uses the space it has inside its parent when more room is available than it required by the element.
The width of a StackPanel, for example, will be as wide as the widest element it contains. So, all narrower elements have a bit of excess space. The alignment properties control what the child element does with the extra space.
The default value for both properties is Stretch, so the child element is stretched to fill all available space. Additional options include Left, Center and Right for HorizontalAlignment and Top, Center and Bottom for VerticalAlignment.

Use SizeChanged="OnSizeChanged" in your xaml and the set the sizes you want in the code behind.
private void OnSizeChanged(object sender, SizeChangedEventArgs e)
{
TheScrollViewer.Height = MainWin.Height - 100;
}
Long term it will be better for you.
When your manager comes along and asks "make that a bit bigger" you won't to spend the afternoon messing about with layout controls trying to get it to work. Also you won't have to explain WHY you spent the afternoon trying to make it work.

Related

Silverlight: how to use a scroll viewer to wrap a list view without specifying height?

I have a control that has a list that varies in length greatly. This control appears in various places meaning that i cannot calculate its position and desired height easily.
Moreover all I want is for the scrollviewer to simply size itself according to its parent. currently it insists on sizing itself according to the content.
currently when i have a list that exceeds the height of the screen the whole control extends off the bottom and the scrollviewer shows no bar (because it has stretched to the heigth of the contents and so thinks it is not required).
I've not included code as the object graph is fairly deep.
What i am looking for is a set of conditions that would cause the scrollviewer to resize itself according to its content rather than its parent.
I have it working in a similar situation involving grids and datagrids, the unique part of this control is that there is a list containing controls.
Any ideas? I would prefer solutions that don't require use of code behind - but im really not in a position to be choosey.
Here are common reasons that come to mind that would allow a scroll viewer to size to its contents rather than to its "parent":-
It's placed on a Canvas or a StackPanel
It's assigned to a Grid row/column with it's Horizontal or Vertical alignment not set to Stretch and its content size is less than the size of the row or column.
Its ultimately upto the containing panel how it chooses to size a child element so its not really possible to dictate this completely from code inside the child.

Setting control height explicitly

I have a XamDataGrid in one of my user controls, inside of a stackpanel. I want the grid to maintain the same height regardless of how many rows are present in the grid. To do that, I set the grid's Height property to an explicit value.
Is that how things are done in WPF? Every time I do explicit sizing I feel like I am doing WinForms and not using WPF properly. Is setting the Height directly the only/correct solution?
There's nothing wrong with setting an explicit Height in situations where you want an element to always stay the same height. Where it's less appropriate is in situations where sizing is better handled by the parent layout Panel or the element's child content which can use the available space dynamically.
WPF uses a relative measurement system which at first glance is not intuitive. I have never found an example when I was forced to use explicit sizes ( once when I paint something on Canvas). I use styles in 90% cases where I define Padding, Margin, Aligment etc. Sometimes I use MinHeight and MinWidth for simple things.
About that Grid you can put it in the ScrollViewer or ViewBox to have dynamic sizing, yet If it won't be trouble set the explicit Height.

How to calculate a bounding box for an Expander?

I have an Expander control and i need to calculate its bounds without invisible elements and margins. It commonly can be done by VisualTreeHelper.GetDescendantsBounds. But it seems that the rect is calculated by VisualTreeHelper doesn't depend on the expander state. For example:
http://i.piccy.info/i5/58/39/273958/collapsed.jpg
(i can't post images. sorry)
The same result as for expanded state (light green rectangle on the image). Does anybody know how to solve this problem?
The Expander control will set its content's visibility to Collapsed, which means it won't be considered during layout and won't be included in GetDescendantBounds. However, the Expander can be forced to have a larger size by the layout engine, and the Expander's own size is included in GetDescendantBounds.
Try setting VerticalAlignment="Top" on the Expander. The default is Stretch, which will allow it to increase in size if the parent has more space available. Also make sure you aren't explicitly setting the Height property.
This is the sample application. The style is applied here to the TreeView control and its items. But the problem doesn't depend on the style.

Silverlight canvas scrollbars

I have read that placing a canvas inside a scrollviewer won't work because the canvas does not report its size. I have been experimenting with different containers (borders, grids, canvases and scrollviewers) and could really do with a simple explanation of how scrollviewers behave within nested containers. e.g. If I have a container hierarchy of UserControl>Grid1>Canvas1>ScrollViewer>Grid2>Canvas2 should scrollbars appear around Grid2 when Canvas2 becomes wider than Canvas1? (or indeed wider than UserControl) If not, how should I organise my containers so I can add loads of uielements to Canvas2 and have scrollbars appear as necessary. (My usercontrol width and height are set to 100%)
Canvas will work with a ScrollViewer if you explicitly give it a size. The problem comes from the fact that if you don't supply a Height and Width for any control it will try and determine it's available area based off it's parent container. A ScrollViewer however has infinite available area.
Take a Grid for example. If I define a Grid that has 2 Star Width columns and 2 Star Height rows. How does the Grid know how wide each of those columns should be? The star says they should be half of the available area, but inside a ScrollViewer the available area is infinity.
What controls are you adding to Canvas2? If you are adding them with fixed positions than the ScrollViewer will expand to house all elements. If you aren't giving them fixed positions than all the controls will stack on top of each other, because that is the arrange behavior of a Canvas

Panel with percentage coordinates

I would like use a panel whose children have coordinates specified as percentage of total panel's width/height. Moreover, I should be able to animate the coordinate property, for example to make a button move from 10% to 50% panel's width.
I've made 2 attempts:
Use a Grid and specify size as stars - this was not enough, because AFAIK by default WPF cannot animate distance properties specified by stars. I've found somewhere a custom class that enabled me to do so, it even worked, hovewer I consider that solution overly complicated an I am looking for something simpler.
Use a Canvas with fixed width and height and put it inside a Viewbox - this is a simple solution, but when resizing the Viewbox the whole content of Canvas is resized too. I want the content to have fixed size.
Is there a simple solution or should I implement my own panel (or maybe extend one of the existing ones, i.e. Canvas)?
Cheers!
I would:
subclass Canvas, perhaps calling it RelativeCanvas or RatioCanvas
add two attached properties: XRatio and YRatio
override ArrangeOverride and loop over all children. For each child, use their XRatio and YRatio along with the ActualWidth and ActualHeight of the RelativeCanvas to calculate and apply values for their Canvas.Left and Canvas.Top attached properties
You would use it as follows:
<local:RelativeCanvas>
<!-- the top-left of this button will be center of panel -->
<Button local:RelativeCanvas.XRatio="50" local:RelativeCanvas.YRatio="50"/>
</local:RelativeCanvas>
One thing you might like to add after you get that working is control over alignment. For example, I might to align the center of a control to the specified ratio, not its top-left corner.
There's one here: WPF Proportional Panel

Resources