我正在尝试创建一个看起来像书架的视图,其中的项目位于带有虚拟化的架子上。我通过向VirtualizingTilePanel
添加ListView
课程来实现这一目标。我正在使用的课程来自Dan Crevier的博客:
http://blogs.msdn.com/b/dancre/archive/2006/02/16/implementing-a-virtualizingpanel-part-4-the-goods.aspx
我正在将VirtualizingTilePanel
和背景添加到我的ListView
,如下所示:
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<local:VirtualizingTilePanel>
<local:VirtualizingTilePanel.Background>
<ImageBrush ImageSource="..\Images\bookshelf.png" AlignmentX="Left" AlignmentY="Top" TileMode="Tile" Stretch="None" ViewportUnits="Absolute" Viewport="0,0,319,203" />
</local:VirtualizingTilePanel.Background>
</local:VirtualizingTilePanel>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
我遇到的问题是背景仅填充原始可见区域。一旦我开始向下滚动,背景向上移动(就像我想要的那样),但进入视野的背景只是白色。
如何让背景填满整个可滚动区域?
希望这是有道理的,我可以根据需要添加图片。
修改
我的VirtualizingTilePanel
课程:
class VirtualizingTilePanel : VirtualizingPanel, IScrollInfo
{
public VirtualizingTilePanel()
{
// For use in the IScrollInfo implementation
this.RenderTransform = _trans;
}
// Dependency property that controls the size of the child elements
public static readonly DependencyProperty ChildSizeProperty
= DependencyProperty.RegisterAttached("ChildSize", typeof(double), typeof(VirtualizingTilePanel),
new FrameworkPropertyMetadata(168.0d, FrameworkPropertyMetadataOptions.AffectsMeasure |
FrameworkPropertyMetadataOptions.AffectsArrange));
// Accessor for the child size dependency property
public double ChildSize
{
get { return (double)GetValue(ChildSizeProperty); }
set { SetValue(ChildSizeProperty, value); }
}
/// <summary>
/// Measure the children
/// </summary>
/// <param name="availableSize">Size available</param>
/// <returns>Size desired</returns>
IItemContainerGenerator generator;
protected override Size MeasureOverride(Size availableSize)
{
UpdateScrollInfo(availableSize);
// Figure out range that's visible based on layout algorithm
int firstVisibleItemIndex, lastVisibleItemIndex;
GetVisibleRange(out firstVisibleItemIndex, out lastVisibleItemIndex);
// We need to access InternalChildren before the generator to work around a bug
UIElementCollection children = this.InternalChildren;
generator = this.ItemContainerGenerator;
// Get the generator position of the first visible data item
GeneratorPosition startPos = generator.GeneratorPositionFromIndex(firstVisibleItemIndex);
// Get index where we'd insert the child for this position. If the item is realized
// (position.Offset == 0), it's just position.Index, otherwise we have to add one to
// insert after the corresponding child
int childIndex = (startPos.Offset == 0) ? startPos.Index : startPos.Index + 1;
using (generator.StartAt(startPos, GeneratorDirection.Forward, true))
{
for (int itemIndex = firstVisibleItemIndex; itemIndex <= lastVisibleItemIndex; ++itemIndex, ++childIndex)
{
bool newlyRealized;
// Get or create the child
UIElement child = generator.GenerateNext(out newlyRealized) as UIElement;
if (newlyRealized)
{
// Figure out if we need to insert the child at the end or somewhere in the middle
if (childIndex >= children.Count)
{
base.AddInternalChild(child);
}
else
{
base.InsertInternalChild(childIndex, child);
}
generator.PrepareItemContainer(child);
}
else
{
// The child has already been created, let's be sure it's in the right spot
Debug.Assert(child == children[childIndex], "Wrong child was generated");
}
// Measurements will depend on layout algorithm
child.Measure(GetChildSize());
}
}
// Note: this could be deferred to idle time for efficiency
CleanUpItems(firstVisibleItemIndex, lastVisibleItemIndex);
return availableSize;
}
/// <summary>
/// Arrange the children
/// </summary>
/// <param name="finalSize">Size available</param>
/// <returns>Size used</returns>
protected override Size ArrangeOverride(Size finalSize)
{
IItemContainerGenerator generator = this.ItemContainerGenerator;
UpdateScrollInfo(finalSize);
for (int i = 0; i < this.Children.Count; i++)
{
UIElement child = this.Children[i];
// Map the child offset to an item offset
int itemIndex = generator.IndexFromGeneratorPosition(new GeneratorPosition(i, 0));
ArrangeChild(itemIndex, child, finalSize);
}
return finalSize;
}
/// <summary>
/// Revirtualize items that are no longer visible
/// </summary>
/// <param name="minDesiredGenerated">first item index that should be visible</param>
/// <param name="maxDesiredGenerated">last item index that should be visible</param>
private void CleanUpItems(int minDesiredGenerated, int maxDesiredGenerated)
{
UIElementCollection children = this.InternalChildren;
IItemContainerGenerator generator = this.ItemContainerGenerator;
for (int i = children.Count - 1; i >= 0; i--)
{
GeneratorPosition childGeneratorPos = new GeneratorPosition(i, 0);
int itemIndex = generator.IndexFromGeneratorPosition(childGeneratorPos);
if (itemIndex < minDesiredGenerated || itemIndex > maxDesiredGenerated)
{
generator.Remove(childGeneratorPos, 1);
RemoveInternalChildRange(i, 1);
}
}
}
/// <summary>
/// When items are removed, remove the corresponding UI if necessary
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
protected override void OnItemsChanged(object sender, ItemsChangedEventArgs args)
{
switch (args.Action)
{
case NotifyCollectionChangedAction.Remove:
case NotifyCollectionChangedAction.Replace:
case NotifyCollectionChangedAction.Move:
RemoveInternalChildRange(args.Position.Index, args.ItemUICount);
break;
}
}
#region Layout specific code
// I've isolated the layout specific code to this region. If you want to do something other than tiling, this is
// where you'll make your changes
int width = 100;
int height = 203;
/// <summary>
/// Calculate the extent of the view based on the available size
/// </summary>
/// <param name="availableSize">available size</param>
/// <param name="itemCount">number of data items</param>
/// <returns></returns>
private Size CalculateExtent(Size availableSize, int itemCount)
{
int childrenPerRow = CalculateChildrenPerRow(availableSize);
// See how big we are
return new Size(childrenPerRow * width,
height * Math.Ceiling((double)itemCount / childrenPerRow));
}
/// <summary>
/// Get the range of children that are visible
/// </summary>
/// <param name="firstVisibleItemIndex">The item index of the first visible item</param>
/// <param name="lastVisibleItemIndex">The item index of the last visible item</param>
private void GetVisibleRange(out int firstVisibleItemIndex, out int lastVisibleItemIndex)
{
int childrenPerRow = CalculateChildrenPerRow(_extent);
firstVisibleItemIndex = (int) Math.Floor(_offset.Y / height) * childrenPerRow;
lastVisibleItemIndex = (int) Math.Ceiling((_offset.Y + _viewport.Height) / height) * childrenPerRow - 1;
ItemsControl itemsControl = ItemsControl.GetItemsOwner(this);
int itemCount = itemsControl.HasItems ? itemsControl.Items.Count : 0;
if (lastVisibleItemIndex >= itemCount)
lastVisibleItemIndex = itemCount-1;
}
/// <summary>
/// Get the size of the children. We assume they are all the same
/// </summary>
/// <returns>The size</returns>
private Size GetChildSize()
{
//return new Size(this.ChildSize, this.ChildSize);
return new Size(width, height);
}
/// <summary>
/// Position a child
/// </summary>
/// <param name="itemIndex">The data item index of the child</param>
/// <param name="child">The element to position</param>
/// <param name="finalSize">The size of the panel</param>
private void ArrangeChild(int itemIndex, UIElement child, Size finalSize)
{
int childrenPerRow = CalculateChildrenPerRow(finalSize);
int row = itemIndex / childrenPerRow;
int column = itemIndex % childrenPerRow;
child.Arrange(new Rect(column * width, row * height, width, height));
}
/// <summary>
/// Helper function for tiling layout
/// </summary>
/// <param name="availableSize">Size available</param>
/// <returns></returns>
private int CalculateChildrenPerRow(Size availableSize)
{
// Figure out how many children fit on each row
int childrenPerRow;
if (availableSize.Width == Double.PositiveInfinity)
childrenPerRow = this.Children.Count;
else
childrenPerRow = Math.Max(1, (int)Math.Floor(availableSize.Width / width));
return childrenPerRow;
}
#endregion
#region IScrollInfo Members
private Size _extent = new Size(0, 0);
private Size _viewport = new Size(0, 0);
private Point _offset;
private TranslateTransform _trans = new TranslateTransform();
private void UpdateScrollInfo(Size availableSize)
{
// See how many items there are
ItemsControl itemsControl = ItemsControl.GetItemsOwner(this);
int itemCount = itemsControl.HasItems ? itemsControl.Items.Count : 0;
Size extent = CalculateExtent(availableSize, itemCount);
// Update extent
if (extent != _extent)
{
_extent = extent;
if (_owner != null)
_owner.InvalidateScrollInfo();
}
// Update viewport
if (availableSize != _viewport)
{
_viewport = availableSize;
if (_owner != null)
_owner.InvalidateScrollInfo();
}
}
private bool _canHScroll = false;
public bool CanHorizontallyScroll
{
get { return _canHScroll; }
set { _canHScroll = value; }
}
private bool _canVScroll = false;
public bool CanVerticallyScroll
{
get { return _canVScroll; }
set { _canVScroll = value; }
}
public double ExtentHeight
{
get { return _extent.Height; }
}
public double ExtentWidth
{
get { return _extent.Width; }
}
public double HorizontalOffset
{
get { return _offset.X; }
}
public double VerticalOffset
{
get { return _offset.Y; }
}
public Rect MakeVisible(Visual visual, Rect rectangle)
{
return new Rect();
}
public void MouseWheelDown()
{
PageDown();
}
public void MouseWheelLeft()
{
throw new InvalidOperationException();
}
public void MouseWheelRight()
{
throw new InvalidOperationException();
}
public void MouseWheelUp()
{
PageUp();
}
public void PageDown()
{
SetVerticalOffset(VerticalOffset + _viewport.Height * 0.1);
}
public void PageLeft()
{
SetHorizontalOffset(HorizontalOffset - _viewport.Width * 0.1);
}
public void PageRight()
{
SetHorizontalOffset(HorizontalOffset + _viewport.Width * 0.8);
}
public void PageUp()
{
SetVerticalOffset(VerticalOffset - _viewport.Height * 0.1);
}
private ScrollViewer _owner;
public ScrollViewer ScrollOwner
{
get { return _owner; }
set { _owner = value; }
}
public void SetHorizontalOffset(double offset)
{
if (offset < 0 || _viewport.Width >= _extent.Width)
{
offset = 0;
}
else
{
if (offset + _viewport.Width >= _extent.Width)
{
offset = _extent.Width - _viewport.Width;
}
}
_offset.X = offset;
if (_owner != null)
_owner.InvalidateScrollInfo();
InvalidateMeasure();
}
public void SetVerticalOffset(double offset)
{
if (offset < 0 || _viewport.Height >= _extent.Height)
{
offset = 0;
}
else
{
if (offset + _viewport.Height >= _extent.Height)
{
offset = _extent.Height - _viewport.Height;
}
}
_offset.Y = offset;
if (_owner != null)
_owner.InvalidateScrollInfo();
_trans.Y = -offset;
InvalidateMeasure();
}
public double ViewportHeight
{
get { return _viewport.Height; }
}
public double ViewportWidth
{
get { return _viewport.Width; }
}
public void LineUp()
{
SetVerticalOffset(this.VerticalOffset - 10);
}
public void LineDown()
{
SetVerticalOffset(this.VerticalOffset + 10);
}
public void LineLeft()
{
throw new InvalidOperationException();
}
public void LineRight()
{
throw new InvalidOperationException();
}
#endregion
#region helper data structures
class ItemAbstraction
{
public ItemAbstraction(WrapPanelAbstraction panel, int index)
{
_panel = panel;
_index = index;
}
WrapPanelAbstraction _panel;
public readonly int _index;
int _sectionIndex = -1;
public int SectionIndex
{
get
{
if (_sectionIndex == -1)
{
return _index % _panel._averageItemsPerSection - 1;
}
return _sectionIndex;
}
set
{
if (_sectionIndex == -1)
_sectionIndex = value;
}
}
int _section = -1;
public int Section
{
get
{
if (_section == -1)
{
return _index / _panel._averageItemsPerSection;
}
return _section;
}
set
{
if (_section == -1)
_section = value;
}
}
}
class WrapPanelAbstraction : IEnumerable<ItemAbstraction>
{
public WrapPanelAbstraction(int itemCount)
{
List<ItemAbstraction> items = new List<ItemAbstraction>(itemCount);
for (int i = 0; i < itemCount; i++)
{
ItemAbstraction item = new ItemAbstraction(this, i);
items.Add(item);
}
Items = new ReadOnlyCollection<ItemAbstraction>(items);
_averageItemsPerSection = itemCount;
_itemCount = itemCount;
}
public readonly int _itemCount;
public int _averageItemsPerSection;
private int _currentSetSection = -1;
private int _currentSetItemIndex = -1;
private int _itemsInCurrentSecction = 0;
private object _syncRoot = new object();
public int SectionCount
{
get
{
int ret = _currentSetSection + 1;
if (_currentSetItemIndex + 1 < Items.Count)
{
int itemsLeft = Items.Count - _currentSetItemIndex;
ret += itemsLeft / _averageItemsPerSection + 1;
}
return ret;
}
}
private ReadOnlyCollection<ItemAbstraction> Items { get; set; }
public void SetItemSection(int index, int section)
{
lock (_syncRoot)
{
if (section <= _currentSetSection + 1 && index == _currentSetItemIndex + 1)
{
_currentSetItemIndex++;
Items[index].Section = section;
if (section == _currentSetSection + 1)
{
_currentSetSection = section;
if (section > 0)
{
_averageItemsPerSection = (index) / (section);
}
_itemsInCurrentSecction = 1;
}
else
_itemsInCurrentSecction++;
Items[index].SectionIndex = _itemsInCurrentSecction - 1;
}
}
}
public ItemAbstraction this[int index]
{
get { return Items[index]; }
}
#region IEnumerable<ItemAbstraction> Members
public IEnumerator<ItemAbstraction> GetEnumerator()
{
return Items.GetEnumerator();
}
#endregion
#region IEnumerable Members
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
}
#endregion
}
答案 0 :(得分:0)
我的自定义面板实现IScrollInfo
出现了类似的问题。在我的实现中,背景根本没有移动(我认为你的背景因为你正在设置RenderTransform
而移动)。我最终在SetHorizontalOffset
/ SetVerticalOffset
中手动更新了指定画笔上的视口,如下所示:
public void SetHorizontalOffset(double offset)
{
offsetX_ = offset;
((TileBrush)Background).Viewport = new Rect(-offsetX_, -offsetY_, width_, height_);
((TileBrush)Background).ViewportUnits = BrushMappingMode.Absolute;
}
首先需要在画笔项目上设置ViewboxUnits = BrushMappingMode.Absolute
。