9

I asked a question at http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/5c7f5cdf-4351-4969-990f-29ce9ec84b87/ , but still lack a good explanation for a strange behavior.

Running the following XAML shows that the TextBlock in column 0 is width greater than 100 even though the column is set to width 100. I think that the strangeness may have something to do with it being wrapped in a ScrollViewer, but I don't know why. If I set a MaxWidth on the columns, it works fine, but setting Width does not.

  1. Why is the width of column 0 not being honored?
  2. Why does the column sizing behave differently when you remove the scroll viewer?

I appreciate any explanation! This is a real puzzle to me.

<Window x:Class="WpfApplication2.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Width="300">
    <ScrollViewer HorizontalScrollBarVisibility="Auto" >
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="100" />
                <ColumnDefinition Width="100" />
                <ColumnDefinition />
            </Grid.ColumnDefinitions>
            <TextBlock x:Name="textBlock" Text="{Binding ElementName=textBlock, Path=ActualWidth}" />
            <TextBlock Text="column 1" Grid.Column="1" />
            <TextBlock Grid.Row="1" Grid.ColumnSpan="3" Text="text here that is wider than the first two columns combined" />
        </Grid>
    </ScrollViewer>
</Window>
Dale Barnard
  • 779
  • 1
  • 7
  • 16

4 Answers4

7

This is a very good question and tests the limits of our intuition. It reveals the implementation details of the Grid's layout logic.

The width of 100 is not being honored because:

  1. There is nothing in the third column that causes the grid to give it width.
  2. The long text in the second row is wider than can fit in the first two columns.
  3. When the width of the Grid is not constrained or set by its parent its layout logic evidently stretches the first column instead of the last column.

By putting a MaxWidth on the first column, you are constraining the Grid's layout logic, so it moves on to the second column and stretches it. You'll note it will be wider than 100 in that scenario.

However, as soon as the Grid's width is set to a specific value or is constrained by its parent (e.g. when no ScrollViewer in the Window), the Grid's width has a specific value, and the third column gets a width set even though it is empty. Now the Grid's auto-size code is deactivated, and it no longer stretches any of your columns to try to squeeze in that text. You can see this by putting a specific width on the Grid, even though it is still in the ScrollViewer.

Edit: Now that I read the answer of the MSDN support in your original thread, I believe it is correct, meaning this is probably the result of the implementation of the attached property and not the grid itself. However, the principle is the same, and hopefully my explanation is clear enough to make sense of the subtlety here.

Jerry Bullard
  • 6,116
  • 18
  • 27
  • Thanks for the thoughtful answer. As I said in a comment on an earlier answer in this thread, this makes me wonder: 1. Why is MaxWidth honored more than a fixed width? 2. Do you think that this is a defect in the Grid control or in the Grid control's documentation on MSDN? I don't see anything in the MSDN that comes close to describing this behavior. I'm tempted to log this as a defect. – Dale Barnard Apr 18 '11 at 18:19
  • 1
    It isn't a matter of "honoring" MaxWidth more than Width. One is a constraint, while the other is the initial starting value. If there is a bug, it is in the naive way the ColSpan attached property affects layout. The third column had no width at all until this ColSpan item came along, and even though it has a width of "*", the Grid starts widening the columns from left to right when there is not enough width to fit a ColSpan item. Intuitively, this feels wrong, but I would not go so far as to call it a bug. It is a leaky abstraction for sure. – Jerry Bullard Apr 29 '11 at 22:26
  • 1
    MS could change the layout behavior to look for a column with a width of "*" to widen first (before widening columns left to right). However, there may be a lot of code out there depending on the current behavior. It is likely Microsoft will just define this as the standard and move on. But because this is so counterintuitive and seemingly wrong, they may surprise us. – Jerry Bullard Apr 29 '11 at 22:34
  • I agree that since people are probably dependent on the current behavior, they won't change it. I would just suggest that they document the unintuitive behavior. Also, I see no where in the MSDN documentation that MaxWidth is a constraint, but Width is a starting value. Did you see that documented somewhere, or are you just deriving it from the evidence? Thanks for the comments! – Dale Barnard May 02 '11 at 14:02
3

Short Answer:
Its because of the combination of:
1. Presence of ScrollViewer which allows grid (if it wishes) to take any desired size.
2. The grid not having explicit width.
3. A column (Column 2) whose width has not been specified, which sets it to 1*, leaving its final size dependant on size of grid and other columns.
4. TextBlock which has colspan over three columns.

If you:
1. Remove the scrollviewer, the grid is allowed to grow only till the client area of window (which comes to be about 278 in your example), and the long textblock has to fit within this width otherwise its trimmed.
2. Set explicit width of Grid, which again trims textblock to fit.
3. Set explicit width of Column 2, which provides a fixed width to grid (100+100+width_of_col2), which again trims textblock to fit.
4. Remove the colspan, the columns which do not contain it and have fixed width defined, will take that width.

Here's what's happening:
This is crude and not exact explanation of the measure and arrange passes, however, it should give a fair idea.

To start with col0 is happy with 100, col1 with 100 and col2 with 0. Based on this grid's size would be 100+100+0=200. When Grid requests its children (textblocks) to be measured, it sees that first two textblocks fit within the width of their columns. However, the third textblock needs 288. Since, grid isn't having any width defined and its within a scrollviewer, it can increase its size if one of its child needs it. The Grid has now to increase its size from 200 to 288 (i.e. by 88). This means each column to which that textblock spans (all three of them) will expand by 88/3~=29 pixels. This makes col0=100+29=129, col1=100+29=129, col2=0+29.

Try this:
Include a rectangle, put it in col2 and set width of rectangle to 20.

This is what's happening:
To start with col0 and col1 are happy with 100 each as their individual textblocks need less than that. col2 is happy with 20 as rectangle in it needs that. Based on this grid's width would be 100+100+20=220. However, because of the columnspanning textblock the Grid has to increase its size from 220 to 288 (i.e. by 68). This means each column to which that textblock spans (all three of them) will expand by 68/3~=23 pixels. This makes col0=100+23=123, col1=100+23=123, col2=20+23=43.

HTH.

publicgk
  • 3,170
  • 1
  • 26
  • 45
  • Thanks for the thoughtful answer. This makes me wonder: 1. Why is MaxWidth honored more than a fixed width? 2. Do you think that this is a defect in the Grid control or in the Grid control's documentation on MSDN? I don't see anything in the MSDN that comes close to describing this behavior. I'm tempted to log this as a defect. (I repeated this comment on the next answer, too, since it applies equally there.) – Dale Barnard Apr 18 '11 at 18:22
  • No. I don't believe this to be a defect. I believe the width and height are kind of requested values and may not be honoured considering that the measure and arrange passes are recursive. That's why MaxWidth and MaxHeight (or even MinWidth and MinHeight) are provided to help users put a constraint. – publicgk Apr 19 '11 at 04:46
  • [From MSDN](http://msdn.microsoft.com/en-us/library/system.windows.frameworkelement.maxwidth.aspx) to answer your question 1: The FrameworkElement class exposes four properties that describe the width characteristics of an element. These four properties can conflict, and when they do, the value that takes precedence is determined as follows: the MinWidth value takes precedence over the MaxWidth value, which in turn takes precedence over the Width value. A fourth property, ActualWidth, is read-only, and reports the actual width as determined by interactions with the layout process. – publicgk Apr 19 '11 at 04:49
  • it doesn't seem to say in the MSDN that Width is "a kind of requested value" and not an absolute, thus suggesting a documentation defect. I'm also not clear what the point of your other comment about the precedence is. If a MaxWidth and MinWidth is not specified, what takes precedence over Width? I appreciate your responses. – Dale Barnard Apr 26 '11 at 20:48
  • ahh, I guess the reason is that we are discussing the width of a ColumnDefinition which is not a FrameworkElement but a FrameworkContentElement. Apologies for mixing up. The answer lies in how (and why so) the Grid is implemented. Time to offer a bounty to lure experts to answer. :) – publicgk Apr 27 '11 at 03:28
2

Here is another example that shows the problem using a Canvas instead of a ScrollViewer:

<Canvas>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="100"/>
            <ColumnDefinition Width="100"/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <TextBlock Grid.Column="0" x:Name="textBlock1" Text="{Binding ElementName=textBlock1, Path=ActualWidth}"/>
        <TextBlock Grid.Column="1" x:Name="textBlock2" Text="{Binding ElementName=textBlock2, Path=ActualWidth}"/>
        <TextBlock Grid.Row="1" Grid.ColumnSpan="3" Width="300"/>
    </Grid>
</Canvas>

This example shows that when given unlimited space, the first two columns are incorrectly expanded by 33%. I do not have working reference source to debug this right now because SP1 broke .NET4 reference source but frankly pinpointing this to the line in the source file is not going to help you so let's not go that route.

Instead, we'll agree that this is definitely a bug and we can prove that it's a bug by setting Grid.MaxWidth to progressively larger values and the widths of two columns remain both at 100 no matter how large it gets. But if you leave Grid.MaxWidth unset and place the Grid inside of a Canvas then the value during measure will be double.PositiveInfinity and this value with produce column widths of 133. As a result we can speculate that some how the special case of a size constraint of positive infinity is not handled correctly during the column sizing calculations.

Luckily, that same experiment provides a simple workaround: simply supply an absurdly large value for Grid.MaxWidth when the Grid is used inside another control that allows it unlimited space, such as a ScrollViewer or a Canvas. I recommend something like:

<Grid MaxWidth="1000000">

This approach avoids the bug by preventing the size constraint from having the probematic value of positive infinity, while practically achieving the same effect.

But this:

<Grid MaxWidth="{x:Static sys:Double.PositiveInfinity}">

will trigger the bug.

Rick Sladkey
  • 33,988
  • 6
  • 71
  • 95
  • Interesting what you discovered about setting Grid.MaxWidth to something other than NaN. I guess I'd better report this as a defect. – Dale Barnard Apr 28 '11 at 12:53
0

I reported this issue as a bug:

https://connect.microsoft.com/VisualStudio/feedback/details/665448/wpf-grids-columns-width-property-not-honored-when-columnspan-row-forces-grid-to-grow

Please vote it up at that link if you agree that it is a bug.

Dale Barnard
  • 779
  • 1
  • 7
  • 16