5 using System.Windows.Controls;
6 using System.Windows.Controls.Primitives;
7 using System.Windows.Media;
8 using System.Windows.Threading;
10 namespace SiliconStudio.Presentation.Controls
18 private int itemsPerLine = -1;
19 private int lineCount = -1;
20 private int itemCount = -1;
25 public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register(
29 new FrameworkPropertyMetadata(
Orientation.Vertical, FrameworkPropertyMetadataOptions.AffectsMeasure));
34 public static readonly DependencyProperty MinimumItemSpacingProperty = DependencyProperty.Register(
38 new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsMeasure),
39 ValidateMinMaxItemSpacing);
44 public static readonly DependencyProperty MaximumItemSpacingProperty = DependencyProperty.Register(
48 new FrameworkPropertyMetadata(
double.MaxValue, FrameworkPropertyMetadataOptions.AffectsMeasure),
49 ValidateMinMaxItemSpacing);
54 public static readonly DependencyProperty ItemSlotSizeProperty = DependencyProperty.Register(
58 new FrameworkPropertyMetadata(
new Size(64.0, 64.0), FrameworkPropertyMetadataOptions.AffectsMeasure),
69 public double MinimumItemSpacing {
get {
return (
double)GetValue(MinimumItemSpacingProperty); } set { SetValue(MinimumItemSpacingProperty, value); } }
74 public double MaximumItemSpacing {
get {
return (
double)GetValue(MaximumItemSpacingProperty); } set { SetValue(MaximumItemSpacingProperty, value); } }
79 public Size ItemSlotSize {
get {
return (Size)GetValue(ItemSlotSizeProperty); } set { SetValue(ItemSlotSizeProperty, value); } }
81 public int ItemsPerLine {
get {
return itemsPerLine; } }
83 public int ItemCount {
get {
return itemCount; } }
85 private static bool ValidateMinMaxItemSpacing(
object value)
87 if ((value is
double) ==
false)
90 var v = (double)value;
92 return v >= 0.0 && double.IsInfinity(v) ==
false;
95 private static bool ValidateSize(
object value)
97 if ((value is Size) ==
false)
100 var
size = (Size)value;
102 return size.Width >= 1.0 &&
103 size.Height >= 1.0 &&
104 double.IsInfinity(size.Width) ==
false &&
105 double.IsInfinity(size.Height) ==
false;
108 public void GetVisibilityRange(Size panelSize, out
int firstVisibleItemIndex, out
int lastVisibleItemIndex)
110 int firstVisibleLine;
115 var itemHeightSpace = (int)Math.Ceiling(MinimumItemSpacing + ItemSlotSize.Height);
116 firstVisibleLine = (int)Math.Ceiling(offset.Y) / itemHeightSpace;
117 lastVisibleLine = firstVisibleLine + (int)Math.Ceiling(panelSize.Height) / itemHeightSpace + 1;
121 var itemWidthSpace = (int)Math.Ceiling(MinimumItemSpacing + ItemSlotSize.Width);
122 firstVisibleLine = (int)Math.Ceiling(offset.X) / itemWidthSpace;
123 lastVisibleLine = firstVisibleLine + (int)Math.Ceiling(panelSize.Width) / itemWidthSpace + 1;
126 firstVisibleItemIndex = firstVisibleLine * itemsPerLine;
127 lastVisibleItemIndex = lastVisibleLine * itemsPerLine + itemsPerLine - 1;
129 if (lastVisibleItemIndex >= itemCount)
130 lastVisibleItemIndex = itemCount - 1;
140 var doNotRemove = Children;
144 IItemContainerGenerator generator = ItemContainerGenerator;
145 if (generator == null)
146 return base.MeasureOverride(availableSize);
148 var parentItemsControl = ItemsControl.GetItemsOwner(
this);
149 if (parentItemsControl == null)
150 return base.MeasureOverride(availableSize);
152 itemCount = parentItemsControl.Items.Count;
153 itemsPerLine = itemCount;
154 lineCount = itemsPerLine;
159 if (
double.IsPositiveInfinity(availableSize.Width))
160 throw new InvalidOperationException(
"Width must not be infinite when virtualizing vertically.");
162 itemsPerLine = (int)Math.Ceiling(availableSize.Width - MinimumItemSpacing) / (int)(ItemSlotSize.Width + MinimumItemSpacing);
163 itemsPerLine = Math.Max(1, Math.Min(itemsPerLine, itemCount));
164 lineCount = ComputeLineCount(itemCount);
165 desiredSize =
new Size(availableSize.Width, lineCount * ItemSlotSize.Height);
169 if (
double.IsPositiveInfinity(availableSize.Height))
170 throw new InvalidOperationException(
"Height must not be infinite when virtualizing horizontally.");
172 itemsPerLine = (int)Math.Ceiling(availableSize.Height - MinimumItemSpacing) / (int)Math.Ceiling(ItemSlotSize.Height + MinimumItemSpacing);
173 itemsPerLine = Math.Max(1, Math.Min(itemsPerLine, itemCount));
174 lineCount = ComputeLineCount(itemCount);
175 desiredSize =
new Size(lineCount * ItemSlotSize.Width, availableSize.Height);
178 UpdateScrollInfo(availableSize);
180 int firstVisibleItemIndex, lastVisibleItemIndex;
181 GetVisibilityRange(availableSize, out firstVisibleItemIndex, out lastVisibleItemIndex);
184 GeneratorPosition startPos = generator.GeneratorPositionFromIndex(firstVisibleItemIndex);
186 int childIndex = (startPos.Offset == 0) ? startPos.Index : startPos.Index + 1;
188 using (generator.StartAt(startPos, GeneratorDirection.Forward,
true))
190 for (
int itemIndex = firstVisibleItemIndex; itemIndex <= lastVisibleItemIndex; itemIndex++, childIndex++)
195 var child = generator.GenerateNext(out newlyRealized) as UIElement;
202 if (childIndex >= itemCount)
203 AddInternalChild(child);
205 InsertInternalChild(childIndex, child);
207 generator.PrepareItemContainer(child);
210 child.Measure(ItemSlotSize);
214 CleanUpItems(firstVisibleItemIndex, lastVisibleItemIndex);
226 var doNotRemove = Children;
228 IItemContainerGenerator generator = ItemContainerGenerator;
229 if (generator == null)
230 return base.ArrangeOverride(finalSize);
232 var parentItemsControl = ItemsControl.GetItemsOwner(
this);
233 if (parentItemsControl == null)
234 return base.ArrangeOverride(finalSize);
236 var space = ComputeItemSpacing(finalSize);
238 for (
int i = 0; i < Children.Count; i++)
240 UIElement child = Children[i];
243 int itemIndex = generator.IndexFromGeneratorPosition(
new GeneratorPosition(i, 0));
247 int row = itemIndex / itemsPerLine;
248 int column = itemIndex % itemsPerLine;
250 var absoluteY = MinimumItemSpacing + row * (ItemSlotSize.Height + MinimumItemSpacing);
255 column * (ItemSlotSize.Width + space) - offset.X + MinimumItemSpacing,
262 int row = itemIndex % itemsPerLine;
263 int column = itemIndex / itemsPerLine;
265 var absoluteX = MinimumItemSpacing + column * (ItemSlotSize.Width + MinimumItemSpacing);
270 absoluteX - offset.X,
271 row * (ItemSlotSize.Height + space) - offset.Y + MinimumItemSpacing
280 private void CleanUpItems(
int minDesiredGenerated,
int maxDesiredGenerated)
282 UIElementCollection children = InternalChildren;
283 IItemContainerGenerator generator = ItemContainerGenerator;
285 for (
int i = children.Count - 1; i >= 0; i--)
287 var childGeneratorPos =
new GeneratorPosition(i, 0);
288 int itemIndex = generator.IndexFromGeneratorPosition(childGeneratorPos);
289 if (itemIndex >= 0 && (itemIndex < minDesiredGenerated || itemIndex > maxDesiredGenerated))
291 generator.Remove(childGeneratorPos, 1);
292 RemoveInternalChildRange(i, 1);
297 private double ComputeItemSpacing(Size finalSize)
299 double itemsInnerSpace;
303 var totalItemWidth = itemsPerLine * ItemSlotSize.Width;
304 itemsInnerSpace = Math.Max(0.0, finalSize.Width - totalItemWidth - 2 * MinimumItemSpacing);
308 var totalItemHeight = itemsPerLine * ItemSlotSize.Height;
309 itemsInnerSpace = Math.Max(0.0, finalSize.Height - totalItemHeight - 2 * MinimumItemSpacing);
312 var intervalCount = itemsPerLine - 1;
313 if (intervalCount <= 0)
314 return MinimumItemSpacing;
316 return Math.Max(MinimumItemSpacing, Math.Min(itemsInnerSpace / intervalCount, MaximumItemSpacing));
319 private void UpdateScrollInfo(Size availableSize)
325 localExtent =
new Size(
326 Math.Max(availableSize.Width, 2.0 * MinimumItemSpacing + ItemSlotSize.Width),
327 MinimumItemSpacing + lineCount * (ItemSlotSize.Height + MinimumItemSpacing));
331 localExtent =
new Size(
332 MinimumItemSpacing + lineCount * (ItemSlotSize.Width + MinimumItemSpacing),
333 Math.Max(availableSize.Height, 2.0 * MinimumItemSpacing + ItemSlotSize.Height));
337 if (localExtent != extent)
339 extent = localExtent;
340 if (ScrollOwner != null)
341 ScrollOwner.InvalidateScrollInfo();
343 Dispatcher.CurrentDispatcher.BeginInvoke((Action)InvalidateMeasure);
345 SetHorizontalOffset(offset.X);
346 SetVerticalOffset(offset.Y);
350 if (availableSize != viewport)
352 viewport = availableSize;
354 if (ScrollOwner != null)
355 ScrollOwner.InvalidateScrollInfo();
357 SetHorizontalOffset(offset.X);
358 SetVerticalOffset(offset.Y);
362 private int ComputeLineCount(
int totalItemCount)
364 return (totalItemCount + itemsPerLine - 1) / itemsPerLine;
367 public bool CanHorizontallyScroll
373 public bool CanVerticallyScroll
381 public double ExtentWidth
383 get {
return extent.Width; }
386 public double ExtentHeight
388 get {
return extent.Height; }
393 SetVerticalOffset(VerticalOffset - ItemSlotSize.Height);
398 SetVerticalOffset(VerticalOffset + ItemSlotSize.Height);
403 SetHorizontalOffset(HorizontalOffset - ItemSlotSize.Width);
408 SetHorizontalOffset(HorizontalOffset + ItemSlotSize.Width);
413 BringIndexIntoView(index);
418 base.BringIndexIntoView(index);
420 int n = index / itemsPerLine;
422 var space = ComputeItemSpacing(RenderSize);
426 double newTop = n * (ItemSlotSize.Height + space);
428 if (newTop < offset.Y)
429 SetVerticalOffset(newTop);
430 else if (newTop + ItemSlotSize.Height + space > offset.Y + viewport.Height)
431 SetVerticalOffset(newTop + ItemSlotSize.Height + space - viewport.Height);
435 double newLeft = n * (ItemSlotSize.Width + space);
437 if (newLeft < offset.X)
438 SetHorizontalOffset(newLeft);
439 else if (newLeft + ItemSlotSize.Width + space > offset.X + viewport.Width)
440 SetHorizontalOffset(newLeft + ItemSlotSize.Width + space - viewport.Width);
451 var parentItemsControl = ItemsControl.GetItemsOwner(
this);
452 if (parentItemsControl == null)
455 IItemContainerGenerator generator = ItemContainerGenerator;
457 for (
int i = 0; i < InternalChildren.Count; i++)
459 if (Equals(InternalChildren[i], visual))
461 int itemIndex = generator.IndexFromGeneratorPosition(
new GeneratorPosition(i, 0));
463 int row = itemIndex / itemsPerLine;
464 int column = itemIndex % itemsPerLine;
466 return new Rect(
new Point(column * ItemSlotSize.Width, row * ItemSlotSize.Height), ItemSlotSize);
475 SetVerticalOffset(VerticalOffset - ItemSlotSize.Height);
480 SetVerticalOffset(VerticalOffset + ItemSlotSize.Height);
485 SetHorizontalOffset(HorizontalOffset - ItemSlotSize.Width);
490 SetHorizontalOffset(HorizontalOffset + ItemSlotSize.Width);
495 SetVerticalOffset(VerticalOffset - viewport.Height);
500 SetVerticalOffset(VerticalOffset + viewport.Height);
505 SetHorizontalOffset(HorizontalOffset - viewport.Width);
510 SetHorizontalOffset(HorizontalOffset + viewport.Width);
513 public ScrollViewer ScrollOwner
519 private Point offset;
523 if (horizontalOffset < 0.0 || viewport.Width >= extent.Width)
524 horizontalOffset = 0.0;
527 if (horizontalOffset + viewport.Width >= extent.Width)
528 horizontalOffset = extent.Width - viewport.Width;
531 offset.X = horizontalOffset;
533 if (ScrollOwner != null)
534 ScrollOwner.InvalidateScrollInfo();
541 if (verticalOffset < 0.0 || viewport.Height >= extent.Height)
542 verticalOffset = 0.0;
545 if (verticalOffset + viewport.Height >= extent.Height)
546 verticalOffset = extent.Height - viewport.Height;
549 offset.Y = verticalOffset;
551 if (ScrollOwner != null)
552 ScrollOwner.InvalidateScrollInfo();
557 public double HorizontalOffset
559 get {
return offset.X; }
562 public double VerticalOffset
564 get {
return offset.Y; }
567 private Size viewport;
569 public double ViewportHeight
571 get {
return viewport.Height; }
574 public double ViewportWidth
576 get {
return viewport.Width; }
579 protected override void OnItemsChanged(
object sender, ItemsChangedEventArgs args)
581 if (args.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove)
583 int index = args.Position.Index;
584 if (args.Position.Offset > 0)
588 if (index < InternalChildren.Count && args.ItemUICount > 0)
590 RemoveInternalChildRange(index, args.ItemUICount);
593 base.OnItemsChanged(sender, args);
void SetVerticalOffset(double verticalOffset)
Rect MakeVisible(Visual visual, Rect rectangle)
void GetVisibilityRange(Size panelSize, out int firstVisibleItemIndex, out int lastVisibleItemIndex)
override void OnItemsChanged(object sender, ItemsChangedEventArgs args)
override Size MeasureOverride(Size availableSize)
void SetHorizontalOffset(double horizontalOffset)
override void BringIndexIntoView(int index)
void ScrollToIndexedItem(int index)
This class describes a Panel similar to a WrapPanel except that items have a reserved space that is e...
override Size ArrangeOverride(Size finalSize)
Android.Widget.Orientation Orientation
_In_ size_t _In_ size_t size
System.Windows.Point Point