I have a ListView with a bunch of irregularly sized items. As you scroll the ListView, the bottom of the last item will end up at the bottom of the control: you can't keep scrolling.
If the last item is smaller than the control, I want the top of the last item to be able to scroll to the top of the control (if the item is larger than the control, I am fine with the default behavior; in this case, the top of the item will have scrolled past the top of the ListView control which satisfies my design). (This is similar to the behavior in something like Visual Studio Code with editor.scrollBeyondLastLine
setting set to true)
I've almost got this working but it's not quite there. In my attempt, I subclassed ListView
so I could override PrepareContainerForItemOverride
.
I then attempt to add enough extra space to the item's bottom margin so the item's top will rest at the ListView's top.
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);
}
}
}
}
(In this code, this.ScrollViewer()
is just a little extension method that fishes out the ScrollViewer
from any ListView
. I'm using it here to try to pass something sensible to lvi.Measure
.)
This doesn't quite work because the DesiredSize.Height
of the ListViewItem
ends up being a good bit larger than the Height
of the rendered ListViewItem
(I don't know why).
I could actually live with this behavior as a compromise if I knew that the DesiredSize.Height
would always be larger than needed...but I bet that's not actually true.
So I clearly need to plug into a different part of the render pipeline to manage this. But I can't figure out where.
Should I be subclassing the ListView's
ScrollViewer
somehow (since I think the ScrollViewer
is what is actually laying out the child ListViewItem
controls?)? Is there some other way to do this? Or is it just impossible?
Ideally it would be
ScrollViewer
's job to add extra spacing after it had arranged its child (panel in this case). Still it will have to look into panel's last child size to know exactly how much more space it should add.But since
ScrollViewer
is sealed, I don't know if that's actually possible.Another Hack, would be to create a custom
StackPanel
where we can adjust the measured height so that the last item will be scrolled on top.The actual padding on bottom depends on the
ScrollViewer's
ViewportHeight
hence the multiple passes through measure layout step.Set as the list's layout panel: