Как мне разрешить UWP ListView прокручивать последний элемент?

У меня есть ListView с кучей предметов неправильного размера. При прокрутке ListView нижняя часть последнего элемента окажется в нижней части элемента управления: вы не можете продолжать прокрутку.

Если последний элемент меньше, чем элемент управления, я хочу, чтобы верхняя часть последнего элемента могла прокручиваться до верхней части элемента управления (если элемент больше, чем элемент управления, меня устраивает поведение по умолчанию; в этом случае верхняя часть элемента будет прокручиваться за верхнюю часть элемента управления ListView, что удовлетворяет моему дизайну). (Это похоже на поведение в чем-то вроде кода Visual Studio с editor.scrollBeyondLastLine установка установлена ​​в true)

Я почти получил это работает, но это не совсем там. В моей попытке я подкласс ListView чтобы я мог переопределить PrepareContainerForItemOverride,

Затем я пытаюсь добавить достаточно свободного места в нижнее поле элемента, чтобы верхняя часть элемента оставалась в верхней части ListView.

  protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
    {
        base.PrepareContainerForItemOverride(element, item);

        var lvi = element as ListViewItem;
        if (lvi == null) { return; }

        var currentIndex = this.Items.IndexOf(item);
        if (currentIndex == (this.Items.Count - 1))
        {
            var sv = this.ScrollViewer();
            lvi.Measure(new Size(sv.ViewportWidth, double.PositiveInfinity));

            var slackSpace = sv.ViewportHeight - lvi.DesiredSize.Height;
            if (slackSpace > 0)
            {
                lvi.Margin = new Thickness(lvi.Margin.Left, lvi.Margin.Top, lvi.Margin.Right, slackSpace + lvi.Margin.Bottom);
            }                
        }
    }
}

(В этом коде this.ScrollViewer() это просто маленький метод расширения, который вылавливает ScrollViewer от любого ListView, Я использую это здесь, чтобы попытаться передать что-то разумное lvi.Measure.)

Это не совсем работает, потому что DesiredSize.Height из ListViewItem в конечном итоге будет немного больше, чем Height из оказанных ListViewItem (Я не знаю почему).

Я мог бы на самом деле жить с этим поведением в качестве компромисса, если бы я знал, что DesiredSize.Height всегда будет больше, чем нужно... но держу пари, что на самом деле это не так.

Поэтому мне явно нужно подключиться к другой части конвейера рендеринга, чтобы справиться с этим. Но я не могу понять, где.

Должен ли я подкласс ListView'sScrollViewer как-то (так как я думаю ScrollViewer это то, что на самом деле выкладывает ребенка ListViewItem контроль?)? Есть ли другой способ сделать это? Или это просто невозможно?

1 ответ

Решение

В идеале это было бы ScrollViewerработа по добавлению дополнительного пробела после того, как он организовал свой дочерний элемент (в данном случае панель). Тем не менее, он должен будет посмотреть на последний дочерний размер панели, чтобы точно знать, сколько еще места он должен добавить.

Но с тех пор ScrollViewer Я не знаю, возможно ли это на самом деле.

Еще один хак, было бы создать кастом StackPanel где мы можем отрегулировать измеренную высоту так, чтобы последний элемент прокручивался сверху.

Фактическое заполнение снизу зависит от ScrollViewer'sViewportHeight следовательно, несколько проходов проходят через шаг макета меры.

public class BottomPaddedStackPanel: StackPanel
{
    private ScrollViewer scroll = null;

    public BottomPaddedStackPanel()
    {
    }

    protected override Size MeasureOverride(Size availableSize)
    {
        if (scroll == null)
            InitScrollViewerData();

        Size panelSize = base.MeasureOverride(availableSize);
        var lastChild = this.Children[this.Children.Count - 1];

        if (scroll == null || scroll.ViewportHeight == 0)
            return panelSize;

        // add more space at the bottom to be able to scroll the last item at the top
        if (lastChild.DesiredSize.Height < scroll.ViewportHeight)
            panelSize.Height += scroll.ViewportHeight - lastChild.DesiredSize.Height;

        return panelSize;
    }

    private void InitScrollViewerData()
    {
        var lastChild = this.Children[this.Children.Count - 1];
        var lv = ItemsControl.ItemsControlFromItemContainer(lastChild);
        Border rootBorder = VisualTreeHelper.GetChild(lv, 0) as Border;
        scroll = rootBorder.Child as ScrollViewer;
        scroll.SizeChanged += (o, ev) => InvalidateMeasure();
    }
}

Установить в качестве панели макета списка:

<ListView ItemsSource="{x:Bind Items}">
    <ListView.ItemsPanel>
        <ItemsPanelTemplate>
            <local:BottomPaddedStackPanel />
        </ItemsPanelTemplate>
    </ListView.ItemsPanel>
</ListView>
Другие вопросы по тегам