tags:

views:

1009

answers:

3

I creating a control for WPF, and I have a question for you WPF gurus out there.

I want my control to be able to expand to fit a resizable window.

In my control, I have a list box that I want to expand with the window. I also have other controls around the list box (buttons, text, etc).

I want to be able to set a minimum size on my control, but I want the window to be able to be sized smaller by creating scroll bars for viewing the control.

This creates nested scroll areas: One for the list box and a ScrollViewer wrapping the whole control.

Now, if the list box is set to auto size, it will never have a scroll bar because it is always drawn full size within the ScrollViewer.

I only want the control to scroll if the content can't get any smaller, otherwise I don't want to scroll the control; instead I want to scroll the list box inside the control.

How can I alter the default behavior of the ScrollViewer class? I tried inheriting from the ScrollViewer class and overriding the MeasureOverride and ArrangeOverride classes, but I couldn't figure out how to measure and arrange the child properly. It appears that the arrange has to affect the ScrollContentPresenter somehow, not the actual content child.

Any help/suggestions would be much appreciated.

A: 

While I wouldn't recommend creating a UI that requires outer scroll bars you can accomplish this pretty easily:

<Window
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  >    
    <ScrollViewer HorizontalScrollBarVisibility="Auto" 
                  VerticalScrollBarVisibility="Auto">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="Auto"/>
            </Grid.ColumnDefinitions>
            <ListBox Grid.Row="0" Grid.RowSpan="3" Grid.Column="0" MinWidth="200"/>
            <Button Grid.Row="0" Grid.Column="1" Content="Button1"/>
            <Button Grid.Row="1" Grid.Column="1" Content="Button2"/>
            <Button Grid.Row="2" Grid.Column="1" Content="Button3"/>
        </Grid>
    </ScrollViewer>
</Window>

I don't really recommend this. WPF provides exceptional layout systems, like Grid, and you should try to allow the app to resize itself as needed. Perhaps you can set a MinWidth/MinHeight on the window itself to prevent this resizing?

Bob King
The difficulty is that I want to be able to shrink the window below the minimum size without cropping the controls.
Do you *have* to let the window shrink down below that minimum size? What are you trying to accomplish by allowing that?
Bob King
No, don't have to, but the whole problem goes away if I don't. I want to shrink below minimum size to allow flexible layouts. I'm actually building an app that contains "window-like" controls that can be sized and positioned.
Then the above code should work for you. Just make sure your pieces are in the right "*" and "Auto" sized columns, and make sure the outer ScrollViewer is using "Auto" for Horizontal/VerticalScrollbarVisibility.
Bob King
And, you may want to leave those as always "Visible" to prevent the jump when one needs to be visible suddenly.
Bob King
Your solution does not work. Add a few items to the ListBox and you'll see that the listbox never becomes scrollable; it's always the parent ScrollViewer that will scroll.I think the Josh G was (like me) looking for a solution where the outer ScrollViewer only will be used only if necessary for MinWidth/MinHeight.
Daniel
+1  A: 

You problem arises, because Controls within a ScrollViewer have virtually unlimited space available. Therefore your inner ListBox thinks it can avoid scrolling by taking up the complete height necessary to display all its elements. Of course in your case that behaviour has the unwanted side effect of exercising the outer ScrollViewer too much.

The objective therefore is to get the ListBox to use the visible height within the ScrollViewer iff there is enough of it and a certain minimal height otherwise. To achieve this, the most direct way is to inherit from ScrollViewer and override MeasureOverride() to pass an appropriately sized availableSize (that is the given availableSize blown up to the minimal size instead of the "usual" infinity) to the Visuals found by using VisualChildrenCount and GetVisualChild(int).

David Schmitt
+1 for the explanation regarding the unlimited available space.
Ridcully
+2  A: 

I've created a class to work around this problem:

public class RestrictDesiredSize : Decorator
{
 Size lastArrangeSize = new Size(double.PositiveInfinity, double.PositiveInfinity);

 protected override Size MeasureOverride(Size constraint)
 {
  Debug.WriteLine("Measure: " + constraint);
  base.MeasureOverride(new Size(Math.Min(lastArrangeSize.Width, constraint.Width),
                                Math.Min(lastArrangeSize.Height, constraint.Height)));
  return new Size(0, 0);
 }

 protected override Size ArrangeOverride(Size arrangeSize)
 {
  Debug.WriteLine("Arrange: " + arrangeSize);
  if (lastArrangeSize != arrangeSize) {
   lastArrangeSize = arrangeSize;
   base.MeasureOverride(arrangeSize);
  }
  return base.ArrangeOverride(arrangeSize);
 }
}

It will always return a desired size of (0,0), even if the containing element wants to be bigger. Usage:

<local:RestrictDesiredSize MinWidth="200" MinHeight="200">
     <ListBox />
</local>
Daniel
This is the solution that worked for us.
cplotts