1

The Situation:

I am trying to create a panel class StickyInfoPanel for two child items laid out vertically. The lower of the two items shall be 100px in height. The upper item shall consume the rest of the total available height.

The upper item is a ScrollView with a stackpanel and two children inside (upper1 and upper2). So whenever there is not enough room for the stackpanel, the vertical scrollbar should appear, like in the image below:

enter image description here

The Problem:

Although the correct height is passed to the upper item during the Arrange phase, its resulting height is higher, and in consequence the scrollbar is not displayed. (See 2nd screenshot)

Only when the window will be decreased further, so that only upper1 can be displayed, the scrollbar appears. But its down-button is still gone (see 3rd screenshot)

Strangely, when passing the correct desired height to the item from MeasureOverride, everything works as expected.

From my understanding, MeasureOverride should be side-effect-free, which it obviously is not. Can anyone explain, what I am missing here?

enter image description here

enter image description here

The XAML:

<Window x:Class="GridTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:GridTest"
        Title="MainWindow" Height="190.57" Width="800">
    <local:StickyInfoPanel VerticalAlignment="Stretch" HorizontalAlignment="Stretch" >
        <ScrollViewer Background="LightYellow" VerticalScrollBarVisibility="Auto" CanContentScroll="false">
            <StackPanel Margin="30 0" CanVerticallyScroll="False">
                <TextBlock Background="AliceBlue" Height="100">upper1</TextBlock>
                <TextBlock Background="Azure" Height="100">upper2</TextBlock>
            </StackPanel>
        </ScrollViewer>
        <TextBlock Background="Gainsboro" Height="100">Lower</TextBlock>
    </local:StickyInfoPanel>
</Window>

The panel class:

class StickyInfoPanel : Panel
{
    public StickyInfoPanel()
        : base()
    {
    }
    protected override Size MeasureOverride(Size availableSize)
    {
        InternalChildren[0].Measure(availableSize);
        InternalChildren[1].Measure(availableSize);
        //this works:
        //InternalChildren[0].Measure(new Size(availableSize.Width, availableSize.Height - 100));
        return availableSize;
    }
    protected override Size ArrangeOverride(Size finalSize)
    {
        InternalChildren[0].Arrange(new Rect(new Point(0, 0),                      new Size(finalSize.Width, finalSize.Height - 100)));
        InternalChildren[1].Arrange(new Rect(new Point(0, finalSize.Height - 100), new Size(finalSize.Width, 100)));
        return finalSize; 
    }
}
Bradley Uffner
  • 16,641
  • 3
  • 39
  • 76
Frank im Wald
  • 896
  • 1
  • 11
  • 28
  • 1
    Why don't you use a Grid? – Clemens Jul 24 '18 at 14:07
  • @Clemens Because in reality what I need is more complicated. I just stripped it down as far as possible. – Frank im Wald Jul 24 '18 at 14:10
  • I'm afraid I don't understand your question. During measure, you pass the available size to a child element. Let `h` be the total available height for your panel, then obviously the available height for the first child is `h-100` and for the second one it's the remaining `100`. – Clemens Jul 24 '18 at 14:16
  • Well, I guess it's two questions:1) Why is the item ignoring what I telling it in ArrangeOverview? and 2) How can something I pass to the item during MeasureOverride influence the result? – Frank im Wald Jul 24 '18 at 14:38
  • After all, the purpose of MeasureOverride is to ask the items how big they'd like to be. Not to tell them, how big they gonna be. That should be up to ArrangeOverride, shouldn't it? – Frank im Wald Jul 24 '18 at 14:41
  • Oh, may be, the best question is the following: When I pass "child.Measure(availableSize)" and then later "child.Arrange(finalSize)", how is the real size computed from these two? My expectation would be: realSize = finalSize. But this is not true, as one can see from the problem description. – Frank im Wald Jul 24 '18 at 14:44
  • 1
    @FrankimWald, DockPanel fulfills requirement for layout which you described. Dock Lower TextBock to bottom and let ScrollViewer fill the remaining space – ASh Jul 24 '18 at 14:52
  • @ASh - as already pointed out, the actually desired behavior is more complex and cannot be achieved with DockPanels or Grids. I've just simplified the custom panel class for the sake of clarity here. – Frank im Wald Jul 24 '18 at 15:03
  • *"After all, the purpose of MeasureOverride is to ask the items how big they'd like to be. Not to tell them, how big they gonna be."* - that's not true. The availableSize parameter would be meaningless then. Measure is meant to tell an element how much (available) space it may occupy. Then it can calculate how big it wants to be. – Clemens Jul 24 '18 at 15:35
  • @FrankimWald I've read the question multiple times, and all of the comments. I'm still not seeing why a `DockPanel` isn't acceptable. – Bradley Uffner Jul 26 '18 at 10:10

1 Answers1

1

So after some measurements, here is the final formula for resulting child element sizes inside custom panels (min and max operators work element-wise here):

actualSize = max( min( availableSize , desiredSizeTheoretical), finalSize )

Where

  • actualSize is the resulting size of the child element
  • availableSize is the size that the panel passed to the element's Measure method from its own MeasureOverride
  • desiredSizeTheoretical is the size the child element would want to occupy if there was infinite space available
  • finalSize is the size that the panel passed to the child element's Arrange method from its own ArrangeOverride

Or put in words: The resulting size will always be finalSize, unless the child element wanted more space and was promised more space in MeasureOverride. In that case the resulting size would be the smaller of those two values.

This has been found for a ScrollViewer as child element. I hope, other classes will behave the same.

Clemens
  • 123,504
  • 12
  • 155
  • 268
Frank im Wald
  • 896
  • 1
  • 11
  • 28
  • You confirmed my suspicions that `Measure` is not the functional-style, side effect-free measuring function that it ought to be, but in fact participates in arranging already. What was missing was the clear algorithm/formula that communicated the relation between all the input values and the actual outcome. And I fear it's still not complete. It probably still ignores the subtleties of what's happening when a child element returns a desired value different from desiredSizeTheoretical (based on availableSize). Didn't have time to figure that out. – Frank im Wald Jul 26 '18 at 08:15