AvaloniaUI / Avalonia.Controls.TreeDataGrid

A combined TreeView/DataGrid for Avalonia.
MIT License
233 stars 48 forks source link

Setting MinWidth and using Auto columns breaks layout when scrolling #256

Open wieslawsoltes opened 4 months ago

wieslawsoltes commented 4 months ago

Repro


       public CountriesPageViewModel()
        {
            _data = new ObservableCollection<Country>(Countries.All);

            Source = new FlatTreeDataGridSource<Country>(_data)
            {
                Columns =
                {
                    new TextColumn<Country, string>("Country", x => x.Name, (r, v) => r.Name = v, new GridLength(1, GridUnitType.Star), new()
                    {
                        IsTextSearchEnabled = true,
                    }),
                    new TemplateColumn<Country>("Region", "RegionCell", "RegionEditCell"),
                    new TextColumn<Country, int>("Population", x => x.Population, new GridLength(200, GridUnitType.Auto), new TextColumnOptions<Country>() { MinWidth = new GridLength(300)}),
                    new TextColumn<Country, int>("Area", x => x.Area, new GridLength(200, GridUnitType.Auto), new TextColumnOptions<Country>() { MinWidth = new GridLength(200)}),
                    new TextColumn<Country, int>("GDP", x => x.GDP, new GridLength(200, GridUnitType.Auto), new()
                    {
                        TextAlignment = Avalonia.Media.TextAlignment.Right,
                        MaxWidth = new GridLength(150)
                    }),
                }
            };
            Source.RowSelection!.SingleSelect = false;
        }
wieslawsoltes commented 4 months ago

Repro video:

https://github.com/AvaloniaUI/Avalonia.Controls.TreeDataGrid/assets/2297442/036f3f0b-6316-4dbc-b6c9-65fbba38c2fc

artizzq commented 4 months ago

Same issue

wieslawsoltes commented 4 months ago

Tested it can reproed also in 11.0.0 and 11.0.1

wieslawsoltes commented 4 months ago

Tested 11.0.0-preview1 and seems to be working properly

wieslawsoltes commented 4 months ago

Repro branch: https://github.com/AvaloniaUI/Avalonia.Controls.TreeDataGrid/tree/issues-256-repro

wieslawsoltes commented 4 months ago

Seems to be related to this: https://github.com/AvaloniaUI/Avalonia.Controls.TreeDataGrid/blob/95632c9430f879bc212c7665f7e7be4951d1de3c/src/Avalonia.Controls.TreeDataGrid/Primitives/TreeDataGridPresenterBase.cs#L424-L427

wieslawsoltes commented 4 months ago

Actually even without min/max width the layout breaks when scrolling.

repro:

                Columns =
                {
                    new TextColumn<Country, string>("Country", x => x.Name, (r, v) => r.Name = v, new GridLength(1, GridUnitType.Star), new()
                    {
                        IsTextSearchEnabled = true,
                    }),
                    new TemplateColumn<Country>("Region", "RegionCell", "RegionEditCell"),
                    new TextColumn<Country, int>("Population", x => x.Population, new GridLength(200, GridUnitType.Auto), new TextColumnOptions<Country>() ),
                    new TextColumn<Country, int>("Area", x => x.Area, new GridLength(200, GridUnitType.Auto), new TextColumnOptions<Country>() ),
                    new TextColumn<Country, int>("GDP", x => x.GDP, new GridLength(200, GridUnitType.Auto), new()
                    {
                        TextAlignment = Avalonia.Media.TextAlignment.Right,
                    }),
                }

screenshots: image image

artizzq commented 4 months ago

@wieslawsoltes, Isn't it something with the RealizeElements function inside TreeDataGridPresenterBase? Is it really related to viewport?

Seems to be related to this:

https://github.com/AvaloniaUI/Avalonia.Controls.TreeDataGrid/blob/95632c9430f879bc212c7665f7e7be4951d1de3c/src/Avalonia.Controls.TreeDataGrid/Primitives/TreeDataGridPresenterBase.cs#L424-L427

At least, I've tried to catch those problematic rows, but dont know what to do with them exactly and how to update them properly... Does it make sense?

private void RealizeElements(
    IReadOnlyList<TItem> items,
    Size availableSize,
    ref MeasureViewport viewport)
{
    Debug.Assert(_measureElements is not null);
    Debug.Assert(_realizedElements is not null);
    Debug.Assert(items.Count > 0);

    var index = viewport.anchorIndex;
    var horizontal = Orientation == Orientation.Horizontal;
    var u = viewport.anchorU;

    // If the anchor element is at the beginning of, or before, the start of the viewport
    // then we can recycle all elements before it.
    if (u <= viewport.anchorU)
        _realizedElements.RecycleElementsBefore(viewport.anchorIndex, _recycleElement);

    // Start at the anchor element and move forwards, realizing elements.
    do
    {
        var e = GetOrCreateElement(items, index);
        var constraint = GetInitialConstraint(e, index, availableSize);
        var slot = MeasureElement(index, e, constraint);

        var sizeU = horizontal ? slot.Width : slot.Height;
        var sizeV = horizontal ? slot.Height : slot.Width;

        _measureElements!.Add(index, e, u, sizeU);

        //viewport.measuredV = Math.Max(viewport.measuredV, sizeV);
        if (horizontal == false && items.GetType() == typeof(AnonymousSortableRows<DataRowView>))
        {
            if (sizeV - viewport.measuredV == sizeV)
                viewport.measuredV = Math.Max(viewport.measuredV, sizeV);
            else
            {
                if (viewport.measuredV != sizeV)
                    if (Math.Abs(sizeV - viewport.measuredV) <= 200 || Math.Abs(sizeV - viewport.measuredV) > 200)
                    {
                        if (sizeV > viewport.measuredV)
                        {
                            sizeV = viewport.measuredV;
                            e.Arrange(new Rect(e.Bounds.Left, e.Bounds.Top, viewport.measuredV, sizeU));
                            e.Measure(e.Bounds.Size);
                        } else
                        {
                            viewport.measuredV = sizeV;
                            e.Arrange(new Rect(e.Bounds.Left, e.Bounds.Top, viewport.measuredV, sizeU));
                            e.Measure(e.Bounds.Size);
                        }
                    }
                    else if (viewport.measuredV == sizeV)
                    {

                    }
            }

        }
        else 
            viewport.measuredV = Math.Max(viewport.measuredV, sizeV);

        u += sizeU;
        ++index;
    } while (u < viewport.viewportUEnd && index < items.Count);

    // Store the last index and end U position for the desired size calculation.
    viewport.lastIndex = index - 1;
    viewport.realizedEndU = u;

    // We can now recycle elements after the last element.
    _realizedElements.RecycleElementsAfter(viewport.lastIndex, _recycleElement);

    // Next move backwards from the anchor element, realizing elements.
    index = viewport.anchorIndex - 1;
    u = viewport.anchorU;

    while (u > viewport.viewportUStart && index >= 0)
    {
        var e = GetOrCreateElement(items, index);
        var constraint = GetInitialConstraint(e, index, availableSize);
        var slot = MeasureElement(index, e, constraint);

        var sizeU = horizontal ? slot.Width : slot.Height;
        var sizeV = horizontal ? slot.Height : slot.Width;
        u -= sizeU;

        _measureElements.Add(index, e, u, sizeU);

        //viewport.measuredV = Math.Max(viewport.measuredV, sizeV);
        if (horizontal == false && items.GetType() == typeof(AnonymousSortableRows<DataRowView>))
        {
            if (sizeV - viewport.measuredV == sizeV)
                viewport.measuredV = Math.Max(viewport.measuredV, sizeV);
            else
            {
                if (viewport.measuredV != sizeV)
                    if (Math.Abs(sizeV - viewport.measuredV) <= 200 || Math.Abs(sizeV - viewport.measuredV) > 200)
                    {
                        if (sizeV > viewport.measuredV)
                        {
                            sizeV = viewport.measuredV;
                            e.Arrange(new Rect(e.Bounds.Left, e.Bounds.Top, viewport.measuredV, sizeU));
                            e.Measure(e.Bounds.Size);
                        }
                        else
                        {
                            viewport.measuredV = sizeV;
                            e.Arrange(new Rect(e.Bounds.Left, e.Bounds.Top, viewport.measuredV, sizeU));
                            e.Measure(e.Bounds.Size);
                        }
                    }
                    else if (viewport.measuredV == sizeV)
                    {

                    }
            }

        }
        else
            viewport.measuredV = Math.Max(viewport.measuredV, sizeV);
        --index;
    }

    // We can now recycle elements before the first element.
    _realizedElements.RecycleElementsBefore(index + 1, _recycleElement);
}

Also, I've noticed when columns have equal widths there's no bug at all, and when I change width manually it happens.

Hover233 commented 3 months ago

The sorting operation also triggers this bug, hopefully it will be fixed sooner! QQ截图20240315104826