Да, WPF предоставляет довольно элегантный способ реализовать это, потому что его механизм шаблонов позволяет вам заполнить неиспользуемую область в GridView
чем угодно.
Все, что вам нужно сделать, это изменить шаблон ListView
, чтобы закрасить неиспользуемый раздел с помощью VisualBrush
, который обычно состоит из двух GridViewItems
, расположенных вертикально (в общем случае это будет AlternationCount
GridViewItems
).
Единственная сложность - выбрать, с какого цвета начать рисовать неиспользуемый участок ScrollViewer
. Это рассчитывается как Items.Count
по модулю AlternationCount
. Решение состоит в том, чтобы создать простой Control
, который выполняет этот расчет, и использовать его в нашем шаблоне ListView
. Ради моего объяснения я назову элемент управления «ContinueAlternation».
Шаблон ListView
, который в основном будет шаблоном по умолчанию с элементом управления local:ContinueAlternation
, добавленным ниже ScrollViewer
с использованием DockPanel
, например:
<ControlTemplate TargetType="{x:Type ListView}">
<Border BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}"
Background="{TemplateBinding Background}"
SnapsToDevicePixels="True">
<DockPanel>
<ScrollViewer DockPanel.Dock="Top"
Style="{DynamicResource {x:Static GridView.GridViewScrollViewerStyleKey}}"
Padding="{TemplateBinding Padding}">
<ItemsPresenter SnapsToDevicePixels="True" />
</ScrollViewer>
<local:ContinueAlternation
ItemContainerStyle="{TemplateBinding ItemContainerStyle}"
AlternationCount="{TemplateBinding AlternationCount}"
ItemsCount="{Binding Items.Count,
RelativeSource={RelativeSource TemplatedParent}}" />
</DockPanel>
</Border>
</ControlTemplate>
Элемент управления ContinueAlternation
будет отображаться как Rectangle
, закрашенный мозаичным VisualBrush
, содержащим ItemsControl
, который показывает фиктивные строки, следующим образом:
<ControlTemplate TargetType="{x:Type local:ContinueAlternation}">
<Rectangle>
<Rectangle.Fill>
<VisualBrush TileMode="Tile" Stretch="None"
ViewPortUnits="Absolute"
ViewPort="{TemplateBinding ViewportSize}">
<ItemsControl x:Name="PART_ItemsControl"
ItemsSource="{Binding}" />
</VisualBrush>
</Rectangle.Fill>
</Rectangle>
</ControlTemplate>
Здесь DataContext
будет массивом пустышки ListViewItem
, сгенерированным в коде от заданных AlternationCount
и ItemsCount
:
public class ContinueAlternation
{
public Style ItemsContainerStyle ... // Declare as DependencyProperty using propdp snippet
public int AlternationCount ... // Declare as DependencyProperty using propdp snippet
public int ItemsCount ... // Declare as DependencyProperty using propdp snippet
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
if(e.Property==ItemsContainerStyleProperty ||
e.Property==AlternationCountProperty ||
e.Property==ItemsCountProperty)
{
// Here is where we build the items for display
DataContext =
from index in Enumerable.Range(ItemsCount,
ItemsCount + AlternationCount)
select BuildItem( index % AlternationCount);
}
}
ListViewItem BuildItem(int alternationIndex)
{
var item = new ListViewItem { Style = ItemsContainerStyle };
ItemsControl.SetAlternationIndex(item, alternationIndex);
return item;
}
protected override Size MeasureOverride(Size desiredSize)
{
var ic = (ItemsControl)GetTemplateChild("PART_ItemsControl");
ic.Width = desiredSize.Width;
Size result = base.MeasureOverride(desiredSize);
ViewportSize = new Size(ic.DesiredSize);
return result;
}
public Size ViewportSize ... // Declare as DependencyProperty using propdp snippet
}
Обратите внимание, что этот же код может быть записан с PropertyChangedCallback
вместо OnPropertyChanged
.
Вам также нужно что-то сделать, чтобы убедиться, что пустые строки имеют желаемую высоту. Самый простой способ сделать это - установить MinHeight
или Content
в вашем ItemsContainerStyle
. В качестве альтернативы ContinueAlternation
может установить высоту при построении каждого ListViewItem
.
Я набрал весь этот код на макушке, но он похож на код, который я написал и использовал ранее, поэтому он должен работать в основном как есть.