views:

1229

answers:

2

If I use a custom panel to layout my ListBoxItems, the ListBox won't respect their combined Height (it does respect their combined Width though) - even when my ArrangeOverride returns a size that surrounds all the items.

Setting the ListBox's Height explicitly makes everything work, but I want it to work that out for itself!

Has anyone seen this before?

Thanks

Update: In the example below, the ListBox uses a custom panel that stacks the Articles vertically according to the Row property and returns a size big enough to surround all of them. But unless I set the Height for the ListBox it collapses!

<UserControl x:Class="SilverlightApplication1.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
xmlns:local="clr-namespace:SilverlightApplication1">
<UserControl.Resources>
    <DataTemplate x:Key="ArticleTemplate">
        <TextBlock Text="{Binding Title}" />
    </DataTemplate>
</UserControl.Resources>
<ListBox Height="200"
        Background="AntiqueWhite"
        ItemTemplate="{StaticResource ArticleTemplate}"
        ItemsSource="{Binding}" VerticalAlignment="Top"
        Margin="0,0,0,0">
    <ListBox.ItemsPanel>
        <ItemsPanelTemplate>
            <local:MyPanel />
        </ItemsPanelTemplate>
    </ListBox.ItemsPanel>
</ListBox>
</UserControl>

Here is the panel:

public class MyPanel : Panel
{
    protected override Size ArrangeOverride(Size arrangeSize)
    {
        double width = 0, height = 0;
        foreach (UIElement child in this.Children)
        {
            var article = (Article)((ContentControl)child).DataContext;
            var y = child.DesiredSize.Height * article.Row;
            var location = new Point(0, y);
            var rect = new Rect(location, child.DesiredSize);
            child.Arrange(rect);
            width = Math.Max(width, child.DesiredSize.Width);
            height = Math.Max(height, y + child.DesiredSize.Height);
        }

        return new Size(width, height);
    }

    protected override Size MeasureOverride(Size availableSize)
    {
        foreach (UIElement child in this.Children)
        {
            if (child != null)
            {
                child.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
            }
        }

        return new Size();
    }
}

And the domain class:

public class Article
{
    private readonly int row;
    private readonly string title;

    public Article(string title, int row)
    {
        this.title = title;
        this.row = row;
    }

    public int Row { get { return this.row; } }

    public string Title { get { return this.title; } }
}
+1  A: 

Hi Stuart, I was not able to reproduce the problem that you described. Could you provide some example code/xaml so that I can take a look?

UPDATE:

I believe the problem here is that you are returning (0,0) as the Panel's DesiredSize from MeasureOverride. You probably want to do something like:

    protected override Size MeasureOverride(Size availableSize)
    {
        double width = 0;
        double height = 0;
        foreach (UIElement child in this.Children)
        {
            if (child != null)
            {
                child.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
                width = Math.Max(width, child.DesiredSize.Width);
                height = Math.Max(height, child.DesiredSize.Height);
            }
        }

        return new Size(width, height);
    }

The actual sizing logic is likely to be different depending on your requirements.

What is happening in your code is that by returning (0,0) from MeasureOverride, your Panel is essentially "asking" its parent for this amount of space to be reserved for it. So when it comes to the Arrange phase of the layout cycle the finalSize passed to ArrangeOverride is very small (0 in at least one dimension). You can verify this by setting a breakpoint in ArrangeOverride and then examining the finalSize parameter. To get your code to work correctly with the layout system, you need to return from MeasureOverride the minimum amount of space that your Panel needs to contain its children.

KeithMahoney
Hi Keith - thanks for your response. I added an example into the question. Hope that helps but let me know if you need more info.
Stuart Harris
Thanks Keith - I didn't realise that MeasureOverride had so much say! I really wanted to return a Size with infinity Height to say take as much vertical space as you can (like VerticalAlignment.Stretch) but that's not allowed in SL2. Your suggestion fixed it. Thanks.
Stuart Harris
+1  A: 

Dont forget that ListBoxes contain ScrollViewers. The ScrollViewer is probably seeing the height of its children and thinking "sweet as, I can handle that", then setting its own size.

Try setting

ScrollViewer.VerticalScrollBarVisibility = "Hidden"

or

ScrollViewer.VerticalScrollBarVisibility = "Disabled"

in your ListBox and see what happens.

geofftnz
Hi Geoff, thanks. I'd already removed the ScrollViewer from the control template - otherwise I can see how this may well have caused the problem.
Stuart Harris
Cool as... I've favourited this question because I know I'm going to strike the same issue later in my app.
geofftnz