Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
wieslawsoltes opened this issue Feb 7, 2024 · 10 comments
Open

Comments

@wieslawsoltes
Copy link
Contributor

Repro

  • resize window so horizontal scrollbar is visible
  • scroll to right
  • scroll up/down
  • some rows are shifted (see screenshot)
    image

       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
Copy link
Contributor Author

Repro video:

TreeDataGridDemo_Dop2Rhbv73.mp4

@artizzq
Copy link

artizzq commented Feb 8, 2024

Same issue

@wieslawsoltes
Copy link
Contributor Author

Tested it can reproed also in 11.0.0 and 11.0.1

@wieslawsoltes
Copy link
Contributor Author

Tested 11.0.0-preview1 and seems to be working properly

@wieslawsoltes
Copy link
Contributor Author

@wieslawsoltes
Copy link
Contributor Author

Seems to be related to this:

// We sometimes get sent a viewport of 0,0 because the EffectiveViewportChanged event
// is being raised when the parent control hasn't yet been arranged. This is a bug in
// Avalonia, but we can work around it by forcing MeasureOverride to estimate the
// viewport.

@wieslawsoltes
Copy link
Contributor Author

wieslawsoltes commented Feb 13, 2024

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
Copy link

artizzq commented Feb 14, 2024

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

Seems to be related to this:

// We sometimes get sent a viewport of 0,0 because the EffectiveViewportChanged event
// is being raised when the parent control hasn't yet been arranged. This is a bug in
// Avalonia, but we can work around it by forcing MeasureOverride to estimate the
// viewport.

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
Copy link

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

@danwalmsley
Copy link
Member

danwalmsley commented Oct 17, 2024

more info:

What is happening here is there are 2 scroll viewers, one containing the header columns and one containing the rows.

This is so the headers can stay pinned at the top.

What happens is as you scroll, etc... or do the sorting function, the layout pass of the recycled elements, causes the extent of the ScrollViewer that surrounds the rows to become wider than the one controlling the header.

This basically means the horizontal scrolling becomes totally messed up.

Suggestions:

new rows are materialised and measured, and are bigger than has been seen before, perhaps in the scenarios indicated by the repos, the columns do not get adjusted to match the cell sizes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants