Wednesday, October 26, 2016

Prevent a control from stretching beyond the viewable area

Making a good WPF dialog window can be a real challenge. What looks great in the designer might not once your list fills beyond the windows capacity. In this post I show you an easy way to ensure that your controls will stay within the limits of your window, without using the Height property.

For the purpose of this blogpost, i created a simple example program, with some extra borders to visualize what happens. Here is the structure  for extra clarity:
  • Grid (2 columns, no border)
    • Stackpanel (Blue)
      •  Scrollviewer (Green)
        • Itemscontrol, (Red)
      • Stackpanel  (Yellow)
        • Button1
        • Button2
About the itemscontrol: This is a list just like a listbox, but without selection or scrollbar. We can scroll its contents anyway using a scrollviewer: If the height of the child of the scrollviewer (in this case the itemscontrol) is greater than the height of the scrollviewer, the scrollbar is enabled. (Subject to the property 'VerticalScrollBarVisibility').

Now the idea is to make a compact list of items you can scroll through. We made a nice, simple window, everything put in place from top to bottom.
But lets see what happens when we fill the list:




OOPS. That wasn't quite the intended result. No active scrollbar, and the bottom of the scrollviewer and the itemscontrol are out of sight. Also, the two buttons that we put in are nowhere to be seen.

What happened becomes clearer if we increase the height of the window, until we reach the bottom: The scrollviewer has stretched all the way down to include ALL of the items in the list, and pushed the rest of the controls down with it.


Now to fix this we need to change not one, but two things:
Firstly, the stackpanel will allow child controls to stretch beyond its edges. That is, it does not force the height of its children to be smaller that its own height. (The wrappanel does this, too.)  The only way to fix this is to replace the stackpanel with another control which does. This could be a grid, or a dockpanel. In the examples below I use the latter.














Now lets see what happens when we fill the list inside a dockpanel:

This is a little better. We can now see the bottom end of the scrollviewer (note the green border, and the bottom of the scrollbar), but the buttons are still missing. If you stretch the window it looks like the previous image.

The reason is the way XAML is parsed: from top to bottom, WITHOUT looking ahead. This means that when the scrollviewer requests the remaining space inside its parent, it gets ALL of the remaining space. No room is left over for any controls which come after it, like our buttons.
Which brings us to item number two:  To get all of the controls to share the available room, we need to put the scrollviewer in last, so that it gets however much space remains after all the other controls have been placed:


Now when we run the program, it looks like this:


Everything is now shown! Just for kicks, I have done the same thing with a grid. It has 3 rows, with a height of: "Auto", "1*", "Auto".
In this case, the  ScrollViewBorder has the property Grid.Row="1", with a height of "1*". (Any number followed by a star will work.) This works just as well a dockpanel, and gives the same result. Setting the height of the second row to auto does NOT constrain the height of the Scrollviewer, and gives the result at the Top of the page.
Finally: You might have noticed that the bottom of the red border is NOT contained inside its parent control. This is desired behavior: If it was, the scrollbar of the ScrollViewer would not become active.

I hope this helps someone out, and happy coding!

No comments:

Post a Comment