Imports System.Windows.Controls.Primitives Imports System.Windows.Controls Imports System.Windows Imports System.Windows.Media Imports System Imports System.Diagnostics Imports System.Collections.Specialized Imports System.Windows.Data Public Class VirtualizingTilePanel Inherits VirtualizingPanel Implements IScrollInfo Public Sub New() ' For use in the IScrollInfo implementation Me.RenderTransform = _trans End Sub Public Shared ReadOnly ChildSizeProperty As DependencyProperty = DependencyProperty.RegisterAttached("ChildSize", GetType(Double), GetType(VirtualizingTilePanel), New FrameworkPropertyMetadata(200.0, FrameworkPropertyMetadataOptions.AffectsMeasure Or FrameworkPropertyMetadataOptions.AffectsArrange)) Public Property ChildSize As Double Get Return CDbl(GetValue(ChildSizeProperty)) End Get Set(ByVal value As Double) SetValue(ChildSizeProperty, value) End Set End Property Protected Overrides Function MeasureOverride(ByVal availableSize As Size) As Size UpdateScrollInfo(availableSize) Dim firstVisibleItemIndex, lastVisibleItemIndex As Integer GetVisibleRange(firstVisibleItemIndex, lastVisibleItemIndex) Dim children As UIElementCollection = Me.InternalChildren Dim generator As IItemContainerGenerator = Me.ItemContainerGenerator Dim startPos As GeneratorPosition = generator.GeneratorPositionFromIndex(firstVisibleItemIndex) Dim childIndex As Integer = If((startPos.Offset = 0), startPos.Index, startPos.Index + 1) Using generator.StartAt(startPos, GeneratorDirection.Forward, True) Dim itemIndex As Integer = firstVisibleItemIndex While itemIndex <= lastVisibleItemIndex Dim newlyRealized As Boolean Dim child As UIElement = TryCast(generator.GenerateNext(newlyRealized), UIElement) If newlyRealized Then If childIndex >= children.Count Then MyBase.AddInternalChild(child) Else MyBase.InsertInternalChild(childIndex, child) End If generator.PrepareItemContainer(child) Else Debug.Assert(child Is children(childIndex), "Wrong child was generated") End If child.Measure(GetChildSize()) System.Threading.Interlocked.Increment(itemIndex) System.Threading.Interlocked.Increment(childIndex) End While End Using CleanUpItems(firstVisibleItemIndex, lastVisibleItemIndex) Return availableSize End Function Protected Overrides Function ArrangeOverride(ByVal finalSize As Size) As Size Dim generator As IItemContainerGenerator = Me.ItemContainerGenerator UpdateScrollInfo(finalSize) For i As Integer = 0 To Me.Children.Count - 1 Dim child As UIElement = Me.Children(i) Dim itemIndex As Integer = generator.IndexFromGeneratorPosition(New GeneratorPosition(i, 0)) ArrangeChild(itemIndex, child, finalSize) Next Return finalSize End Function Private Sub CleanUpItems(ByVal minDesiredGenerated As Integer, ByVal maxDesiredGenerated As Integer) Dim children As UIElementCollection = Me.InternalChildren Dim generator As IItemContainerGenerator = Me.ItemContainerGenerator Dim i As Integer = children.Count - 1 While i >= 0 Dim childGeneratorPos As GeneratorPosition = New GeneratorPosition(i, 0) Dim itemIndex As Integer = generator.IndexFromGeneratorPosition(childGeneratorPos) If itemIndex < minDesiredGenerated OrElse itemIndex > maxDesiredGenerated Then generator.Remove(childGeneratorPos, 1) RemoveInternalChildRange(i, 1) End If i -= 1 End While End Sub Protected Overrides Sub OnItemsChanged(ByVal sender As Object, ByVal args As ItemsChangedEventArgs) Select Case args.Action Case NotifyCollectionChangedAction.Remove, NotifyCollectionChangedAction.Replace, NotifyCollectionChangedAction.Move RemoveInternalChildRange(args.Position.Index, args.ItemUICount) End Select End Sub Private Function CalculateExtent(ByVal availableSize As Size, ByVal itemCount As Integer) As Size Dim childrenPerRow As Integer = CalculateChildrenPerRow(availableSize) Return New Size(childrenPerRow * Me.ChildSize, Me.ChildSize * Math.Ceiling(CDbl(itemCount) / childrenPerRow)) End Function Private Sub GetVisibleRange(ByRef firstVisibleItemIndex As Integer, ByRef lastVisibleItemIndex As Integer) Dim childrenPerRow As Integer = CalculateChildrenPerRow(_extent) firstVisibleItemIndex = CInt(Math.Floor(_offset.Y / Me.ChildSize)) * childrenPerRow lastVisibleItemIndex = CInt(Math.Ceiling((_offset.Y + _viewport.Height) / Me.ChildSize)) * childrenPerRow - 1 Dim itemsControl As ItemsControl = itemsControl.GetItemsOwner(Me) Dim itemCount As Integer = If(itemsControl.HasItems, itemsControl.Items.Count, 0) If lastVisibleItemIndex >= itemCount Then lastVisibleItemIndex = itemCount - 1 End Sub Private Function GetChildSize() As Size Return New Size(Me.ChildSize, Me.ChildSize) End Function Private Sub ArrangeChild(ByVal itemIndex As Integer, ByVal child As UIElement, ByVal finalSize As Size) Dim childrenPerRow As Integer = CalculateChildrenPerRow(finalSize) Dim row As Integer = itemIndex \ childrenPerRow ' IMPORTANTE L'operatore è di divisione intera, quindi \ NON / che invece arrotonda all'intero pari più vicino!!!! Dim column As Integer = itemIndex Mod childrenPerRow child.Arrange(New Rect(column * Me.ChildSize, row * Me.ChildSize, Me.ChildSize, Me.ChildSize)) End Sub Private Function CalculateChildrenPerRow(ByVal availableSize As Size) As Integer Dim childrenPerRow As Integer If availableSize.Width = Double.PositiveInfinity Then childrenPerRow = Me.Children.Count Else childrenPerRow = Math.Max(1, CInt(Math.Floor(availableSize.Width / Me.ChildSize))) Return childrenPerRow End Function Private Sub UpdateScrollInfo(ByVal availableSize As Size) Dim itemsControl As ItemsControl = itemsControl.GetItemsOwner(Me) Dim itemCount As Integer = If(itemsControl.HasItems, itemsControl.Items.Count, 0) Dim extent As Size = CalculateExtent(availableSize, itemCount) If extent <> _extent Then _extent = extent If _owner IsNot Nothing Then _owner.InvalidateScrollInfo() End If If availableSize <> _viewport Then _viewport = availableSize If _owner IsNot Nothing Then _owner.InvalidateScrollInfo() End If End Sub Public Property ScrollOwner As ScrollViewer Implements IScrollInfo.ScrollOwner Get Return _owner End Get Set(ByVal value As ScrollViewer) _owner = value End Set End Property Public Property CanHorizontallyScroll As Boolean Implements IScrollInfo.CanHorizontallyScroll Get Return _canHScroll End Get Set(ByVal value As Boolean) _canHScroll = value End Set End Property Public Property CanVerticallyScroll As Boolean Implements IScrollInfo.CanVerticallyScroll Get Return _canVScroll End Get Set(ByVal value As Boolean) _canVScroll = value End Set End Property Public ReadOnly Property HorizontalOffset As Double Implements IScrollInfo.HorizontalOffset Get Return _offset.X End Get End Property Public ReadOnly Property VerticalOffset As Double Implements IScrollInfo.VerticalOffset Get Return _offset.Y End Get End Property Public ReadOnly Property ExtentHeight As Double Implements IScrollInfo.ExtentHeight Get Return _extent.Height End Get End Property Public ReadOnly Property ExtentWidth As Double Implements IScrollInfo.ExtentWidth Get Return _extent.Width End Get End Property Public ReadOnly Property ViewportHeight As Double Implements IScrollInfo.ViewportHeight Get Return _viewport.Height End Get End Property Public ReadOnly Property ViewportWidth As Double Implements IScrollInfo.ViewportWidth Get Return _viewport.Width End Get End Property Public Sub LineUp() Implements IScrollInfo.LineUp SetVerticalOffset(Me.VerticalOffset - 10) End Sub Public Sub LineDown() Implements IScrollInfo.LineDown SetVerticalOffset(Me.VerticalOffset + 10) End Sub Public Sub PageUp() Implements IScrollInfo.PageUp SetVerticalOffset(Me.VerticalOffset - _viewport.Height) End Sub Public Sub PageDown() Implements IScrollInfo.PageDown SetVerticalOffset(Me.VerticalOffset + _viewport.Height) End Sub Public Sub MouseWheelUp() Implements IScrollInfo.MouseWheelUp SetVerticalOffset(Me.VerticalOffset - 50) End Sub Public Sub MouseWheelDown() Implements IScrollInfo.MouseWheelDown SetVerticalOffset(Me.VerticalOffset + 50) End Sub Public Sub LineLeft() Implements IScrollInfo.LineLeft Throw New InvalidOperationException() End Sub Public Sub LineRight() Implements IScrollInfo.LineRight Throw New InvalidOperationException() End Sub Public Function MakeVisible(ByVal visual As Visual, ByVal rectangle As Rect) As Rect Implements IScrollInfo.MakeVisible Return New Rect() End Function Public Sub MouseWheelLeft() Implements IScrollInfo.MouseWheelLeft Throw New InvalidOperationException() End Sub Public Sub MouseWheelRight() Implements IScrollInfo.MouseWheelRight Throw New InvalidOperationException() End Sub Public Sub PageLeft() Implements IScrollInfo.PageLeft Throw New InvalidOperationException() End Sub Public Sub PageRight() Implements IScrollInfo.PageRight Throw New InvalidOperationException() End Sub Public Sub SetHorizontalOffset(ByVal offset As Double) Implements IScrollInfo.SetHorizontalOffset Throw New InvalidOperationException() End Sub Public Sub SetVerticalOffset(ByVal offset As Double) Implements IScrollInfo.SetVerticalOffset If offset < 0 OrElse _viewport.Height >= _extent.Height Then offset = 0 Else If offset + _viewport.Height >= _extent.Height Then offset = _extent.Height - _viewport.Height End If End If _offset.Y = offset If _owner IsNot Nothing Then _owner.InvalidateScrollInfo() _trans.Y = -offset InvalidateMeasure() End Sub Private _trans As TranslateTransform = New TranslateTransform() Private _owner As ScrollViewer Private _canHScroll As Boolean = False Private _canVScroll As Boolean = False Private _extent As Size = New Size(0, 0) Private _viewport As Size = New Size(0, 0) Private _offset As Point End Class