AvaloniaUI / Avalonia

Develop Desktop, Embedded, Mobile and WebAssembly apps with C# and XAML. The most popular .NET UI client technology
https://avaloniaui.net
MIT License
26.05k stars 2.25k forks source link

VirtualizingStackPanel render only first item #12432

Open Meloman19 opened 1 year ago

Meloman19 commented 1 year ago

Describe the bug VirtualizingStackPanel render only first item when move from out of bounds with rendertransform.

To Reproduce

  1. Create blank project.
    2. On MainView.axaml insert:
<Border Width="300"
        ClipToBounds="True">
    <Grid RowDefinitions="Auto, *">
        <UniformGrid Rows="1">
            <Button Content="Left"
                    Click="Left_Click"
                    HorizontalAlignment="Center"/>
            <Button Content="Right"
                    Click="Right_Click"
                    HorizontalAlignment="Center"/>
        </UniformGrid>
            <StackPanel x:Name="Panel"
                    Orientation="Horizontal"
                    Grid.Row="1"
                    Spacing="10">
            <Border Background="Beige"
                    Width="300">
                <ItemsControl Theme="{StaticResource ItemsControlTheme}">
                    <TextBlock Text="1111" />
                    <TextBlock Text="2222" />
                    <TextBlock Text="3333" />
                </ItemsControl>
            </Border>
            <Border Background="LightBlue"
                    Width="300">
                <ItemsControl Theme="{StaticResource ItemsControlTheme}">
                    <TextBlock Text="4444" />
                    <TextBlock Text="5555" />
                    <TextBlock Text="6666" />
                </ItemsControl>
            </Border>
        </StackPanel>
    </Grid>
</Border>

3. MainView code-behind: ```cs private TranslateTransform _translate; public MainView() { InitializeComponent(); Panel.RenderTransform = _translate = new TranslateTransform { Transitions = new Avalonia.Animation.Transitions { new DoubleTransition { Duration = System.TimeSpan.FromMilliseconds(300), Property = TranslateTransform.XProperty } } }; } private void Left_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e) { _translate.X = 0; } private void Right_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e) { _translate.X = -310; } ```
4. ItemsControlTheme: ```xml ```

Expected behavior All items from ItemsControls are rendered

Screenshots

https://github.com/AvaloniaUI/Avalonia/assets/23280622/adf44924-f775-4103-a943-c53bc3f460b5

Desktop (please complete the following information):

Additional context VirtualizingStackPanel render other items when visual is invalidated. For example, button change state from pointerover to normal or window resized etc. Also all right if use StackPanel instead VirtualizingStackPanel.

timunie commented 1 year ago

@Meloman19 I think LayouTransform is what you need. Render transform will not have enough information about transformed bounds.

https://docs.avaloniaui.net/docs/next/reference/controls/layouttransformcontrol#more-information

Meloman19 commented 1 year ago

@timunie Yes, LayoutTransform is works, but losing the benefit of RenderTransform.

As I can see from the source code, RenderTransform used on bounds transform. But main problem in EffectiveViewport. When panel is out of bounds, value of calculated viewport becomes empty and VirtualizedStackPanel prepare only one entity. And after change render position not raised EffectiveViewport event, because it's part of layout pass.

Instead of using an EffectiveViewport event, VirtualizingPanel can use directly parent IScrollable interface. But in this case invalidating event is missing. Or move EffectiveViewport to render pass...

timunie commented 1 year ago

I guess this is by design, but will ask @grokys first

grokys commented 1 year ago

Yeah, not sure how to fix this. You've correctly diagnosed it I think: the listbox that is being transitioned in is arranged outside the effective viewport of the containing control, so the control believes that it doesn't need to realize any items; not knowing that it's going to be transitioned into view by a render transform.

Your suggestions for solving it have a few problems:

  1. Instead of using an EffectiveViewport event, VirtualizingPanel can use directly parent IScrollable interface: this is how virtualization used to work, but it doesn't allow for multiple virtualized lists to be stacked for e.g. grouping and makes smooth scrolling a lot more difficult
  2. Move EffectiveViewport to render pass: EffectiveViewportChanged is a layout-level event, and we can't raise it on render unless we want to run a layout pass on each render frame (we don't)

I think some sort of hint where a transition can communicate to the virtualizing items control that it's being transitioned might be the best solution, but we'd need to think about how that would work. Alternatively we could just ignore render transforms when calculating the viewport for items controls.