dotnet / wpf

WPF is a .NET Core UI framework for building Windows desktop applications.
MIT License
7.1k stars 1.17k forks source link

StackPanel in ScrollViewer : invalid visual descendant bounds after adding items (making vertical scrollbar appear) then removing items #9077

Open JPDelprat opened 6 months ago

JPDelprat commented 6 months ago

Description

StackPanel in ScrollViewer : when adding items to StackPanel (to make vertical scrollbar appear than removing them), than removing some of them (which makes scrollbar disppear), VisualTreeHelper.GetDescendantBounds() provides an incorrect value, which corresponds to the vertical scrollbar height before removing items.

=> Several possible side effects. In my case, an OpacityMask was stretched.

Note : VisualTreeHelper.GetContentBounds is correct though.

Reproduction Steps

I have a StackPanel in a scrollviewer.

I add items to this StackPanel so that vertical scrollbar appears, than I remove some them (to make scrollbar disappear). I do it programmatically (stackPanel.Children.Add/Remove).

VisualTreeHelper.GetDescendantBounds then gives an incorrect height for my stackPanel, which corresponds to the scrollbar height before I remove some items.

If I spy the track control inside the scrollbar, its actual height is the invalid one.

Expected behavior

VisualTreeHelper.GetDescendantBounds() should provide the real height

Actual behavior

VisualTreeHelper.GetDescendantBounds() provide the bad height (see above).

Regression?

No response

Known Workarounds

My issue was related to OpacityMask => I've forced the mask ViewPort height from my ScrollViewer ActualHeight.

Impact

No response

Configuration

No response

Other information

No response

rchauhan18 commented 6 months ago

@JPDelprat Can you provide a sample repro for this?

JPDelprat commented 6 months ago

Here is a test program which reproduce the problem.

BadDescendantsBoundsAfterRemovingItems.zip

Step 1 image

Step2 image

huiliuss commented 5 months ago

I tried to test your project but got the error "C:...\BadDescendantsBoundsAfterRemovingItems\BadDescendantsBoundsAfterRemovingItems\BadDescendantsBoundsAfterRemovingItems\obj\Debug\net6.0-windows\BadDescendantsBoundsAfterRemovingItems_0ja55byk_wpftmp.GeneratedMSBuildEditorConfig.editorconfig" could not be found. I checked the project files and the file was not found. I tried changing the file name but the above file name exceeded the character length. Am I missing something?

JPDelprat commented 5 months ago

I've just downloaded the project, extracted it to a specific directory, opened the solution with Visual Studio 2022, hit F5 and I encountered no issue. Maybe the length of file name is too long after you extracted it.

Could you extract the archive into the root folder of your disk (or close to root) and try again, please ?

LiuHuiii commented 5 months ago

If the VisualTreeHelper.GetDescendantBounds() method still returns incorrect bounds after removing items from the StackPanel, and forcing a layout update doesn't resolve the issue, there might be another approach to consider. Instead of relying on VisualTreeHelper.GetDescendantBounds(), you can calculate the bounds manually based on the actual size of the elements.

    private async void channelControl_Loaded(object sender, RoutedEventArgs e)
    {
        Rect bounds = VisualTreeHelper.GetDescendantBounds(channelControl);
        MessageBox.Show($"Descendant bounds height = {bounds.Height}{Environment.NewLine}Expected value = {channelControl.ActualHeight}{Environment.NewLine}{Environment.NewLine}Press Ok button to make bug appear");

        channelControl.RemoveSomeItems();
        channelControl.UpdateLayout();

       bounds = VisualTreeHelper.GetDescendantBounds(channelControl);

         double stackPanelHeight = CalculateStackPanelHeight(channelControl.ChannelContainer);

        MessageBox.Show($"Actual height after removing items: {stackPanelHeight}");
        MessageBox.Show($"After removing items.{Environment.NewLine}{Environment.NewLine}Descendant bounds height = {bounds.Height}{Environment.NewLine}Expected value = {channelControl.ActualHeight}");
    }

    private double CalculateStackPanelHeight(StackPanel stackPanel)
    {
        double height = 0;

        foreach (UIElement child in stackPanel.Children)
        {
            child.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
            height += child.DesiredSize.Height;
        }

        return height;
    }
JPDelprat commented 5 months ago

Hello,

Thanks a lot for your answer indeed.

However, I'm not looking for a workaround. I am just reporting the issue so that it is fixed.

The sample I've provided is a sample to reproduce the problem, so that someone can investigate and fix the bug, not my real case.

Thanks