Use the Right Container for ListBox Scrolling
I learned something interesting about ListBox and scrolling content this week. In hindsight it is obvious, but trust me: that’s the last thing you want to hear after two hours of beating your head on your keyboard trying to figure out why the stinking ListBox isn’t scrolling. Read on for the gory details.
What I learned is this: if you have an Auto sized ListBox, meaning that it will stretch its Width and Height to match its contents, the ListBox will never produce a Scrollbar. Even if the content stretches far off the window: in that case, it just so happens that the ListBox will also stretch far off the window. Since the content is not larger than the ListBox itself, no Scroll bars.
Obvious, right? Not so: I have recently had several frustrating experiences with the Auto settings for Width and Height. Don’t get me wrong! I use them a lot, and I tell others to do the same because they greatly assist in laying out elements, especially in Grids. The problem comes when they don’t do what I *think* they are going to do. In the case of the ListBox, I was operating under the assumption that they would Stretch the ListBox to fit the available space in the container, and that is not how they behave.
Realizing my error, I understand now that the scrolling behavior is controlled by the relationship of the size of the Content to the defined (as opposed to visible)size of the ListBox itself. In my case, I had the ListBox as the last element of a StackPanel. I needed the ListBox to fill the remaining space, and I needed the Content to scroll vertically.
The Width of the ListBoxItems was set to the width of the ListBox, minus a little to allow for the Scrollbar, so that wasn’t an issue. I wanted the Height to fill the remaining space, but I had no way to bind to the measurement and I wanted to avoid a lot of code behind hijinx. As discussed above, setting it to Auto would actually prevent the desired behavior, so that option was out.
In this particular case, the application is a fixed size, so I was able to set the size of the ListBox manually to fill the remaining space in the StackPanel, and lo and behold my Scrollbar appeared. All was well, or so I thought…
The Case of the Changing StackPanel
In testing, we realized that the element above the ListBox (an Image control) should only be visible when it had an Image to show. So naturally I wired up a ValueConverter and bound the Visibility to a boolean property in the ViewModel reflecting whether or not there was an Image to display.
This worked great for the Image, but an ugly thing occurred with the ListBox. Since its Height was hardcoded, it shifted up in the StackPanel and no longer stretched to the bottom. It look pretty strange just hanging there, Scrollbar happily in view, but a bunch of empty space beneath it.
So just when I thought I had it licked, I’m back in the same boat with the same problem: I need my ListBox to automatically fill all the remaining space in the StackPanel. Wait, what was that? I want a child to fill all remaining space? That sounds familiar…
Enter the DockPanel
Once I got back up from having the ton of bricks fall on my head, I realized that I was using the wrong container! What I really needed in that scenario was a DockPanel. I was just using StackPanel out of habit, I had no specific need for it, and the items within it would never be more than the Image and the ListBox.
So using Blend’s trusty “Change Layout Type” feature, I changed my StackPanel to a DockPanel. I made sure the LastChildFill property was checked and docked the Image control to the Top and set the VerticalAlignment to Top. I then did the same thing for the ListBox and set its Height property to Auto. Running the application gave me exactly the result I was after in the first place: an automatically resizing ListBox restrained to it’s Parent container’s remaining available space.
So aside from learning a lesson about ListBox and scrolling, I also learned that I need to probably pay a little more attention to the container type I am using. Grid and StackPanel have always been my defaults since they cover most scenarios, but I also need to remember we have other options.