Fix: Inconsistent size calculation of Grid when UseLayoutRounding is true (not paradox related) - #21933
Fix: Inconsistent size calculation of Grid when UseLayoutRounding is true (not paradox related)#219337imekeeper wants to merge 9 commits into
Conversation
…ess of preceding integer. This only affects Grids when UseLayoutRounding is true.
|
Visual testing was done using the following xaml added to the MainWindow of the Sandbox project. <StackPanel Spacing="12"
VerticalAlignment="Top"
HorizontalAlignment="Center">
<NumericUpDown x:Name="nud" Value="10" />
<StackPanel Spacing="12" Orientation="Horizontal">
<Grid RowDefinitions="Auto,Auto" Width="100">
<Border Grid.RowSpan="2" Background="Red">
<Border Width="50" Height="{Binding #nud.Value}" Background="Lime" />
</Border>
</Grid>
<Grid RowDefinitions="Auto,Auto,Auto" Width="100">
<Border Grid.RowSpan="3" Background="Red">
<Border Width="50" Height="{Binding #nud.Value}" Background="Lime" />
</Border>
</Grid>
<Grid RowDefinitions="Auto,Auto,Auto,Auto" Width="100">
<Border Grid.RowSpan="4" Background="Red">
<Border Width="50" Height="{Binding #nud.Value}" Background="Lime" />
</Border>
</Grid>
<Grid RowDefinitions="Auto,Auto,Auto,Auto,Auto" Width="100">
<Border Grid.RowSpan="5" Background="Red">
<Border Width="50" Height="{Binding #nud.Value}" Background="Lime" />
</Border>
</Grid>
<Grid RowDefinitions="2*,3*,2*" Width="100" Height="{Binding #nud.Value}">
<Border Grid.Row="0" Width="50" Background="Red" />
<Border Grid.Row="1" Width="50" Background="Lime" />
<Border Grid.Row="2" Width="50" Background="Blue" />
</Grid>
</StackPanel>
</StackPanel> |
|
|
@cla-avalonia agree |
|
You can test this PR using the following package version. |
jsuarezruiz
left a comment
There was a problem hiding this comment.
Thanks for working on this. The current tests cover two specific scale-1 paths:
3 / 2 = 1.5, where changingToEventoToZeroavoids rounding both rows up.13 / 3, which exercises the floating-point normalization added toRoundLayoutSizeUp.
The manual sample includes grids with 2–5 rows, but it does not explicitly exercise different layout scales, larger spans, or the relevant non-midpoint physical values. Those cases can still reach the original inconsistency because MidpointRounding.ToZero only changes exact .5 ties.
There is also a simple remaining failure at scale 1: a 3-DIP child spanning four Auto rows gives each row an unrounded size of 0.75, which rounds to 1. When phase 5 attempts to reduce the first row, the result is clamped to MinSizeForArrange == 0.75. The actual reduction is therefore only 0.25, but adjustedSize is reduced by the full dpiIncrement of 1. The loop believes it has reached the target size of 3, while the actual definition total is 3.75.
I also reproduced the problem on the current PR head with TestRoot.LayoutScaling = 1.25. A 47-device-pixel child (Height = 47 / 1.25) spanning eight Auto rows produces:
grid.Bounds.Height == 37.6sum(RowDefinitions.ActualHeight) == 38.3spanningBorder.Bounds.Height == 38.4(48 device pixels)
Each initial row allocation is 4.7 DIPs, or 5.875 physical pixels, so it rounds up to 4.8 DIPs. The correction can reduce a row only to its 4.7-DIP minimum, but adjustedSize still assumes the full 0.8-DIP decrement.
Could we please:
- Update phase 5 so that
adjustedSizetracks the actual change applied afterMinSizeForArrangeclamping, and continue distributing the discrepancy until the actual definition total matchesfinalSizewhenever the min/max constraints permit it. - Add test coverage for
Height = 3spanning fourAutorows at scale 1. - Add a test with
TestRoot.LayoutScaling = 1.25, eightAutorows, andHeight = 47 / 1.25, asserting that the Grid, spanning border, and sum ofActualHeightvalues agree. Ideally, this could be parameterized across scales such as1,1.25,1.5, and2.
The PR substantially reduces the existing failures, but we need to cover more cases. Let me know if can help with anything.
|
Thanks for the feedback. I've expanded the test method to allow scaling and added the two specified tests, but both of them actually pass.
I tried to reproduce the described behavior but was unable to do so. It seems that // Directed rounding: Round to the nearest value, toward to zero
case MidpointRounding.ToZero:
return Truncate(value);Meaning in this scenario all definitions get rounded to zero and are then sequentially re-allocated a single pixel until the target height matches (resulting in heights of Similarly in the other scenario, all of the definitions get rounded to It seems a (somewhat intentional) side-effect of this change is all (or nearly all) adjustment of I'm leery of making more changes to phase 5 without further discussion on exactly what behavior is desired. The current clamping is prone to undoing the rounding. |
|
You can test this PR using the following package version. |
Thanks for the detailed explanation! |
Ah, I see! Sorry for the misunderstanding. I've updated the test method to use double for targetHeight (really should have done this originally) and updated the specified test to target You are correct, this extra pixel comes from floating-point errors. Ultimately a comparison is done between I updated Something to note is these tiny floating-point inconsistencies are likely also affecting the order the definitions are sorted into, which could also potentially lead to some unexpected behavior. |
|
You can test this PR using the following package version. |
|
You can test this PR using the following package version. |
What does the pull request do?
Makes
GridRow/Column size calculation a bit more consistent whenUseLayoutRoundingistrue.Fixes #21052
What is the current behavior?
Gridchildren can sometimes incorrectly have more space allocated than necessary whenUseLayoutRoundingistrue.Minimal example:
What is the updated/expected behavior with this PR?
Gridchildren are now more accurately allocated their necessary space.How was the solution implemented (if it's not obvious)?
Row/Column size rounding now use MidpointRounding.ToZero to make equivalent decimal values behave consistently regardless of differing preceding integral values. (e.g., 3.5 and 4.5 now round to 3 and 4, respectively, instead of both rounding to 4) This means that several cases where space was over-allocated are now under-allocated instead.
LayoutHelper has had an additional helper method added to support this behavior without breaking anything.
Additionally, some LayoutHelper methods have had rounding behavior made more consistent between DPI-aware and unaware paths.
Checklist
Fixed issues
Fixes #21052