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 con
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>
Create a method in the code-behind that sets the ListBox's MaxHeight to the height of whatever control is containing it and other controls. If the Listbox has any controls/margins/padding above or below it, subtract their heights from the container height assigned to MaxHeight. Call this method in the main windows "loaded" and "window resize" event handlers.
This should give you the best of both worlds. You are giving the ListBox a "fixed" size that will cause it to scroll in spite of the fact that the main window has its own scrollbar.
You problem arises, because Control
s 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 Visual
s found by using VisualChildrenCount and GetVisualChild(int).
I used Daniels solution. That works great. Thank you.
Then I added two boolean dependency properties to the decorator class: KeepWidth
and KeepHeight
. So the new feature can be suppressed for one dimension.
This requires a change in MeasureOverride
:
protected override Size MeasureOverride(Size constraint)
{
var innerWidth = Math.Min(this._lastArrangeSize.Width, constraint.Width);
var innerHeight = Math.Min(this._lastArrangeSize.Height, constraint.Height);
base.MeasureOverride(new Size(innerWidth, innerHeight));
var outerWidth = KeepWidth ? Child.DesiredSize.Width : 0;
var outerHeight = KeepHeight ? Child.DesiredSize.Height : 0;
return new Size(outerWidth, outerHeight);
}
for 2 ScrollViewer
public class ScrollExt: ScrollViewer
{
Size lastArrangeSize = new Size(double.PositiveInfinity, double.PositiveInfinity);
public ScrollExt()
{
}
protected override Size MeasureOverride(Size 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)
{
if (lastArrangeSize != arrangeSize)
{
lastArrangeSize = arrangeSize;
base.MeasureOverride(arrangeSize);
}
return base.ArrangeOverride(arrangeSize);
}
}
code:
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Background="Beige" Width="600" Text="Example"/>
<Grid Grid.Column="1" x:Name="grid">
<Grid Grid.Column="1" Margin="25" Background="Green">
<local:ScrollExt HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<Grid Width="10000" Margin="25" Background="Red" />
</local:ScrollExt>
</Grid>
</Grid>
</Grid>
</ScrollViewer>
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?