Как мне разрешить 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's
ScrollViewer
как-то (так как я думаю ScrollViewer
это то, что на самом деле выкладывает ребенка ListViewItem
контроль?)? Есть ли другой способ сделать это? Или это просто невозможно?
1 ответ
В идеале это было бы ScrollViewer
работа по добавлению дополнительного пробела после того, как он организовал свой дочерний элемент (в данном случае панель). Тем не менее, он должен будет посмотреть на последний дочерний размер панели, чтобы точно знать, сколько еще места он должен добавить.
Но с тех пор ScrollViewer
Я не знаю, возможно ли это на самом деле.
Еще один хак, было бы создать кастом StackPanel
где мы можем отрегулировать измеренную высоту так, чтобы последний элемент прокручивался сверху.
Фактическое заполнение снизу зависит от ScrollViewer's
ViewportHeight
следовательно, несколько проходов проходят через шаг макета меры.
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>