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

Grid using IsSharedSizeScope cause strange scroll behaviour #3608

Open aguahombre opened 4 years ago

aguahombre commented 4 years ago

Avalonia 0.9.3 I have a ItemsControl containing Grids with several columns with shared width implemented using via SharedSizeGroup inside a scroll viewer. If I scroll to the right limit and then add a new item to the list, the view scrolls a bit to the left. Why? The width of the scroll area has not changed so I would expect the view to remain scrolled to the right limit. This only occurs if one of the column headers is longer than the column contents.

Before Add image

After Add image

Repo: XAML

<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:vm="clr-namespace:AvaloniaApplication22"
        mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
        x:Class="AvaloniaApplication22.MainWindow"
        Width="500" Height="350"
        Title="AvaloniaApplication22">
  <Window.Styles>
    <Style Selector="TextBlock">
      <Setter Property="FontSize" Value="16"/>
      <Setter Property="Margin" Value="0,0,10,0"/>
    </Style>
  </Window.Styles>

  <Grid RowDefinitions="Auto,*">
    <Button Grid.Row="0" Content="Add" Command="{Binding Add}"/>
    <ScrollViewer Grid.Row="1" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" Grid.IsSharedSizeScope="True">
      <Grid RowDefinitions="Auto,*">
        <Grid Name="_header" Grid.Row="0" Background="{DynamicResource ThemeControlMidBrush}"/>
        <ItemsControl Grid.Row="1" Items="{Binding Rows}"/>
      </Grid>
    </ScrollViewer>
  </Grid>
</Window>

And the code behind

using System.Collections.ObjectModel;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;

namespace AvaloniaApplication22
{
    public class MainWindow : Window
    {
        private readonly int[] _counts = { 30, 20, 5, 10, 15, };
        private readonly Grid _header;

        public ObservableCollection<Control> Rows { get; } = new ObservableCollection<Control>();

        public MainWindow()
        {
            AvaloniaXamlLoader.Load(this);
            DataContext = this;
            _header = this.FindControl<Grid>(nameof(_header));
            if (_header != null)
            {
                int columnIndex = 0;

                foreach (var item in _counts)
                {
                    _header.ColumnDefinitions.Add(new ColumnDefinition
                    {
                        Width = GridLength.Auto,
                        SharedSizeGroup = $"Group{columnIndex}",
                    });

                    var control = new TextBlock { Text = $"Column Header {columnIndex}" };
                    control.SetValue(Grid.ColumnProperty, columnIndex);
                    _header.Children.Add(control);

                    ++columnIndex;
                }
            }
        }

        public void Add()
        {
            var grid = new Grid { Height = 20d };
            int columnIndex = 0;

            foreach (var count in _counts)
            {
                grid.ColumnDefinitions.Add(new ColumnDefinition
                {
                    Width = GridLength.Auto,
                    SharedSizeGroup = $"Group{columnIndex}",
                });

                var control = new TextBlock { Text = new string((char)('A' + columnIndex), count) };
                control.SetValue(Grid.ColumnProperty, columnIndex);
                grid.Children.Add(control);
                ++columnIndex;
            }
            Rows.Add(grid);
        }
    }
}
Gillibald commented 4 years ago

This only occurs if one of the column headers is longer than the column contents.

So your Grid grows in size. That's why your Scrollbar grows

aguahombre commented 4 years ago

No, the header exists before the row is added. The new row added does NOT increase or change the scroll area width.

I added some diagnostics and can see that the ScrollViewer is calling LayoutUpdated twice for every add click. The first time with an incorrect smaller width, the second with the correct width.

The first update with incorrect width is causing the scroll bar to move left.

Gillibald commented 4 years ago

You are defining Grid.IsSharedSizeScope="True" on the ScrollViewer so all Grids within that ScrollViewer share the column width when they have the same SharedSizeGroup. If you add an item that doesn't fit into the column the group is resized so the newly added content fits into the column. So items added to your header can resize the group and items added to your ItemsControl can resize the group.

aguahombre commented 4 years ago

Understood, but as I said, the add row does not change the width of any of the columns once the header and first item rows are present as per the images. All the added item rows have shared columns with the same widths 30, 20, 5, 10, 15 characters respectively. Only the header row differs from the item rows and this is the first row in the shared scope. It is the 2nd and subsequent adds that cause the unwanted scrolling.

aguahombre commented 4 years ago

FYI I ported the code back to WPF and you don't get the erroneous scroll left on add.

aguahombre commented 4 years ago

I added diagnostics output to the scroll viewers PropertyChanged event.

private void _scrollViewer_PropertyChanged(object sender, Avalonia.AvaloniaPropertyChangedEventArgs e)
{
     System.Diagnostics.Debug.WriteLine($"ScrollViewer.PropertyChanged {e.Property} {e.OldValue} -> {e.NewValue}");
}

This clearly shows that the Extent property is being set twice per add. The first set with the wrong extent causes the scroll bar to move left.

Add
ScrollViewer.PropertyChanged Extent 916, 300 -> 829, 300
ScrollViewer.PropertyChanged HorizontalScrollBarMaximum 0 -> 329
ScrollViewer.PropertyChanged Offset 416, 0 -> 329, 0
ScrollViewer.PropertyChanged HorizontalScrollBarMaximum 0 -> 329
ScrollViewer.PropertyChanged HorizontalScrollBarValue 0 -> 329
ScrollViewer.PropertyChanged HorizontalScrollBarViewportSize 0 -> 500
ScrollViewer.PropertyChanged VerticalScrollBarMaximum 0 -> 0
ScrollViewer.PropertyChanged VerticalScrollBarValue 0 -> 0
ScrollViewer.PropertyChanged VerticalScrollBarViewportSize 0 -> 300
ScrollViewer.PropertyChanged HorizontalScrollBarValue 416 -> 329
ScrollViewer.PropertyChanged HorizontalScrollBarValue 0 -> 329
ScrollViewer.PropertyChanged HorizontalScrollBarViewportSize 0 -> 500
ScrollViewer.PropertyChanged VerticalScrollBarMaximum 0 -> 0
ScrollViewer.PropertyChanged VerticalScrollBarValue 0 -> 0
ScrollViewer.PropertyChanged VerticalScrollBarViewportSize 0 -> 300
ScrollViewer.LayoutUpdated 500, 80
ScrollViewer.PropertyChanged Extent 829, 300 -> 916, 300
ScrollViewer.PropertyChanged HorizontalScrollBarMaximum 0 -> 416
ScrollViewer.PropertyChanged HorizontalScrollBarValue 0 -> 329
ScrollViewer.PropertyChanged HorizontalScrollBarViewportSize 0 -> 500
ScrollViewer.PropertyChanged VerticalScrollBarMaximum 0 -> 0
ScrollViewer.PropertyChanged VerticalScrollBarValue 0 -> 0
ScrollViewer.PropertyChanged VerticalScrollBarViewportSize 0 -> 300

I have seen a similar problem when scrolling a ItemsControl containing Grids with SharedSizeGroups.