public StaggeredLayoutState(VirtualizingLayoutContext context) { _context = context; }
/// <inheritdoc/> protected override Size MeasureOverride(VirtualizingLayoutContext context, Size availableSize) { if (context.ItemCount == 0) { return(new Size(availableSize.Width, 0)); } if ((context.RealizationRect.Width == 0) && (context.RealizationRect.Height == 0)) { return(new Size(availableSize.Width, 0.0)); } var state = (StaggeredLayoutState)context.LayoutState; double availableWidth = availableSize.Width; double availableHeight = availableSize.Height; double columnWidth = Math.Min(DesiredColumnWidth, availableWidth); if (columnWidth != state.ColumnWidth) { // The items will need to be remeasured state.Clear(); } state.ColumnWidth = Math.Min(DesiredColumnWidth, availableWidth); int numColumns = Math.Max(1, (int)Math.Floor(availableWidth / state.ColumnWidth)); // adjust for column spacing on all columns expect the first double totalWidth = state.ColumnWidth + ((numColumns - 1) * (state.ColumnWidth + ColumnSpacing)); if (totalWidth > availableWidth) { numColumns--; } else if (double.IsInfinity(availableWidth)) { availableWidth = totalWidth; } if (numColumns != state.NumberOfColumns) { // The items will not need to be remeasured, but they will need to go into new columns state.ClearColumns(); } if (RowSpacing != state.RowSpacing) { // If the RowSpacing changes the height of the rows will be different. // The columns stores the height so we'll want to clear them out to // get the proper height state.ClearColumns(); state.RowSpacing = RowSpacing; } var columnHeights = new double[numColumns]; var itemsPerColumn = new int[numColumns]; var deadColumns = new HashSet <int>(); for (int i = 0; i < context.ItemCount; i++) { var columnIndex = GetColumnIndex(columnHeights); bool measured = false; StaggeredItem item = state.GetItemAt(i); if (item.Height == 0) { // Item has not been measured yet. Get the element and store the values item.Element = context.GetOrCreateElementAt(i); item.Element.Measure(new Size(state.ColumnWidth, availableHeight)); item.Height = item.Element.DesiredSize.Height; measured = true; } double spacing = itemsPerColumn[columnIndex] > 0 ? RowSpacing : 0; item.Top = columnHeights[columnIndex] + spacing; double bottom = item.Top + item.Height; columnHeights[columnIndex] = bottom; itemsPerColumn[columnIndex]++; state.AddItemToColumn(item, columnIndex); if (bottom < context.RealizationRect.Top) { // The bottom of the element is above the realization area if (item.Element != null) { context.RecycleElement(item.Element); item.Element = null; } } else if (item.Top > context.RealizationRect.Bottom) { // The top of the element is below the realization area if (item.Element != null) { context.RecycleElement(item.Element); item.Element = null; } deadColumns.Add(columnIndex); } else if (measured == false) { // We ALWAYS want to measure an item that will be in the bounds item.Element = context.GetOrCreateElementAt(i); item.Element.Measure(new Size(state.ColumnWidth, availableHeight)); if (item.Height != item.Element.DesiredSize.Height) { // this item changed size; we need to recalculate layout for everything after this state.RemoveFromIndex(i + 1); item.Height = item.Element.DesiredSize.Height; columnHeights[columnIndex] = item.Top + item.Height; } } if (deadColumns.Count == numColumns) { break; } } double desiredHeight = state.GetHeight(); return(new Size(availableWidth, desiredHeight)); }
/// <inheritdoc/> protected override void InitializeForContextCore(VirtualizingLayoutContext context) { context.LayoutState = new StaggeredLayoutState(context); base.InitializeForContextCore(context); }
protected override Size MeasureOverride(VirtualizingLayoutContext context, Size availableSize) { availableSize.Width = availableSize.Width /* - Padding.Left - Padding.Right*/; availableSize.Height = availableSize.Height /* - Padding.Top - Padding.Bottom*/; // For when width is even less than desired col width of even one column: _columnWidth = Math.Min(DesiredColumnWidth, availableSize.Width); int numColumns = (int)Math.Floor(availableSize.Width / _columnWidth); _columnWidth = availableSize.Width / numColumns; if (state == null || state.Width != context.RealizationRect.Width) { state = new StaggeredLayoutState(numColumns, _columnWidth, context.RealizationRect.Width); } if (context.ItemCount == 0) { return(new Size(availableSize.Width, 0)); } // The viewport consists the current view, one view before, and one view after. double viewUpperBound = context.RealizationRect.Top; double viewLowerBound = context.RealizationRect.Bottom; int firstIndex = state.GetFirstItemInView(viewUpperBound); int lastIndex = state.GetLastItemInView(viewLowerBound); int index = firstIndex; while (state.CacheCount < context.ItemCount) { Rect rect; bool cached = state.TryGetRect(lastIndex, out rect); if (cached) { break; } // The culpit might be auto recycling. Supressing it solves the problem, // but defeats virtualization and raises runtime expections, so must fix it. // Okay, the real problem is the custom image control. Without that this implementation works just fine. // The culpit is the video control. Even using that alone produces the issue. // So might consider extract the first frame and put it in an image box. var child = context.GetOrCreateElementAt(lastIndex /*, ElementRealizationOptions.SuppressAutoRecycle*/); rect = state.CreateRectForChild(lastIndex, child); lastIndex++; if (rect.Top > viewLowerBound) { break; } } while (index < lastIndex) { var child = context.GetOrCreateElementAt(index); Rect rect; if (index < state.CacheCount) { rect = state.GetRect(index); } else { rect = state.CreateRectForChild(index, child); } // It turns out this measurement is pretty important for elements to stay within their bounds. child.Measure(new Size(rect.Width, rect.Height)); // I know this is rather inelegant, but since ItemsRepeater has no click support whatsoever, so... SubscribeClickEvent(child); child.Arrange(rect); index++; } // Use this to reproduce bug. //var firstElement = context.GetOrCreateElementAt(0); //context.RecycleElement(firstElement); //firstElement = context.GetOrCreateElementAt(0); //firstElement.Measure(new Size(_columnWidth, availableSize.Height)); //Rect r = state.GetRect(0); //firstElement.Arrange(r); double desiredHeight = state.GetTotalHeight(); return(new Size(availableSize.Width, desiredHeight)); }
protected override Size MeasureOverride(VirtualizingLayoutContext context, Size availableSize) { var requiredSize = new Size(Padding.Left + Padding.Right, Padding.Top + Padding.Bottom); if (context.ItemCount == 0 || context.RealizationRect.Width == 0 || context.RealizationRect.Height == 0) { return(requiredSize); } var state = (AutoFillLayoutState)context.LayoutState; var clientSize = new Size( availableSize.Width - Padding.Left - Padding.Right, availableSize.Height - Padding.Top - Padding.Bottom); //Save layout properties //If changed every thing need redo if (state.SavePropertiesIfChange(Orientation, Padding, HorizontalSpacing, VerticalSpacing, availableSize)) { state.Reset(); } if (!state.Initialized) { if (Orientation == Orientation.Horizontal) { state.Initialize( new Rect(Padding.Left, Padding.Top, clientSize.Width + HorizontalSpacing, double.PositiveInfinity) ); } else { state.Initialize( new Rect(Padding.Left, Padding.Top, double.PositiveInfinity, clientSize.Height + VerticalSpacing) ); } } for (int i = 0; i < context.ItemCount; i++) { var item = state.GetOrCreateItemAt(i); bool measured = false; if (item.Bounds.IsEmpty())//measure empty { item.Element = context.GetOrCreateElementAt(i); item.Element.Measure(clientSize); item.Width = item.Element.DesiredSize.Width; item.Height = item.Element.DesiredSize.Height; state.FitItem(item); measured = true; } item.ShouldDisplay = context.RealizationRect.IsIntersect(item.Bounds); if (!item.ShouldDisplay) { if (item.Element != null) { context.RecycleElement(item.Element); item.Element = null; } } else if (!measured)//measure in view rect { item.Element = context.GetOrCreateElementAt(i); item.Element.Measure(clientSize); var childSize = item.Element.DesiredSize; if (item.Width != childSize.Width || item.Height != childSize.Height) //size changed { item.Width = childSize.Width; item.Height = childSize.Height; state.RemoveItemsFrom(i + 1);//remove after this state.FitItem(item); } } } var size = state.GetTotalSize(); //var size = state.GetVirtualizedSize(); requiredSize.Width += size.Width; requiredSize.Height += size.Height; return(requiredSize); }
/// <inheritdoc /> protected override Size MeasureOverride(VirtualizingLayoutContext context, Size availableSize) { var totalMeasure = UvMeasure.Zero; var parentMeasure = new UvMeasure(Orientation, availableSize.Width, availableSize.Height); var spacingMeasure = new UvMeasure(Orientation, HorizontalSpacing, VerticalSpacing); var realizationBounds = new UvBounds(Orientation, context.RealizationRect); var position = UvMeasure.Zero; var state = (WrapLayoutState)context.LayoutState; if (state.Orientation != Orientation) { state.SetOrientation(Orientation); } if (spacingMeasure.Equals(state.Spacing) == false) { state.ClearPositions(); state.Spacing = spacingMeasure; } if (state.AvailableU != parentMeasure.U) { state.ClearPositions(); state.AvailableU = parentMeasure.U; } double currentV = 0; for (int i = 0; i < context.ItemCount; i++) { bool measured = false; WrapItem item = state.GetItemAt(i); if (item.Measure == null) { item.Element = context.GetOrCreateElementAt(i); item.Element.Measure(availableSize); item.Measure = new UvMeasure(Orientation, item.Element.DesiredSize.Width, item.Element.DesiredSize.Height); measured = true; } UvMeasure currentMeasure = item.Measure.Value; if (currentMeasure.U == 0) { continue; // ignore collapsed items } if (item.Position == null) { if (parentMeasure.U < position.U + currentMeasure.U) { // New Row position.U = 0; position.V += currentV + spacingMeasure.V; currentV = 0; } item.Position = position; } position = item.Position.Value; double vEnd = position.V + currentMeasure.V; if (vEnd < realizationBounds.VMin) { // Item is "above" the bounds if (item.Element != null) { context.RecycleElement(item.Element); item.Element = null; } } else if (position.V > realizationBounds.VMax) { // Item is "below" the bounds. if (item.Element != null) { context.RecycleElement(item.Element); item.Element = null; } // We don't need to measure anything below the bounds break; } else if (measured == false) { // Always measure elements that are within the bounds item.Element = context.GetOrCreateElementAt(i); item.Element.Measure(availableSize); currentMeasure = new UvMeasure(Orientation, item.Element.DesiredSize.Width, item.Element.DesiredSize.Height); if (currentMeasure.Equals(item.Measure) == false) { // this item changed size; we need to recalculate layout for everything after this state.RemoveFromIndex(i + 1); item.Measure = currentMeasure; // did the change make it go into the new row? if (parentMeasure.U < position.U + currentMeasure.U) { // New Row position.U = 0; position.V += currentV + spacingMeasure.V; currentV = 0; } item.Position = position; } } position.U += currentMeasure.U + spacingMeasure.U; currentV = Math.Max(currentMeasure.V, currentV); } // update value with the last line // if the the last loop is(parentMeasure.U > currentMeasure.U + lineMeasure.U) the total isn't calculated then calculate it // if the last loop is (parentMeasure.U > currentMeasure.U) the currentMeasure isn't added to the total so add it here // for the last condition it is zeros so adding it will make no difference // this way is faster than an if condition in every loop for checking the last item totalMeasure.U = parentMeasure.U; // Propagating an infinite size causes a crash. This can happen if the parent is scrollable and infinite in the opposite // axis to the panel. Clearing to zero prevents the crash. // This is likely an incorrect use of the control by the developer, however we need stability here so setting a default that wont crash. if (double.IsInfinity(totalMeasure.U)) { totalMeasure.U = 0.0; } totalMeasure.V = state.GetHeight(); totalMeasure.U = Math.Ceiling(totalMeasure.U); return(Orientation == Orientation.Horizontal ? new Size(totalMeasure.U, totalMeasure.V) : new Size(totalMeasure.V, totalMeasure.U)); }
protected override Size ArrangeOverride(VirtualizingLayoutContext context, Size finalSize) { // Note that you MUST call GetOrCreateElement on any items that are in the realized region... that's how // the control tracks which elements it shouldn't dispose. // It's easier to not support infinite width, and none of my scenarios need it, so not worth spending time implementing it if (double.IsInfinity(finalSize.Width)) { throw new ArgumentException("This panel doesn't support infinite width"); } // If there's no width, there's nothing we can do. if (finalSize.Width == 0) { return(new Size(0, 0)); } var state = (GroupedLayoutState)context.LayoutState; var colInfo = GetColumnInfo(finalSize); bool widthChanged = state.AvailableWidth != finalSize.Width; double y = 0; int i = 0; int col = 0; double currRowHeight = 0; while (i < context.ItemCount) { if (col >= colInfo.NumberOfColumns) { // Start a new row y += currRowHeight + AfterGroupSpacing; col = 0; currRowHeight = 0; } var colSize = ArrangeColumn( context, state, colInfo, col, headerIndex: i, startingY: y, widthChanged: widthChanged, nextHeaderIndex: out i); currRowHeight = Math.Max(currRowHeight, colSize.Height); col++; } y += currRowHeight; var actualFinalSize = new Size(finalSize.Width, y); if (_currMeasuredSize != actualFinalSize) { #if DEBUG Debugger.Break(); #endif } return(actualFinalSize); }
public WrapLayoutState(VirtualizingLayoutContext context) { this._context = context; }
protected override Size MeasureOverride(VirtualizingLayoutContext context, Size availableSize) { if (MinItemSize == Size.Empty) { var firstElement = context.GetOrCreateElementAt(0); firstElement.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); // setting the member value directly to skip invalidating layout _minItemSize = firstElement.DesiredSize; } // Determine which rows need to be realized. We know every row will have the same height and // only contain 3 items. Use that to determine the index for the first and last item that // will be within that realization rect. var firstRowIndex = Math.Max( (int)(context.RealizationRect.Y / (MinItemSize.Height + RowSpacing)) - 1, 0); var lastRowIndex = Math.Min( (int)(context.RealizationRect.Bottom / (MinItemSize.Height + RowSpacing)) + 1, context.ItemCount / 3); // Determine which items will appear on those rows and what the rect will be for each item var state = context.LayoutState as ActivityFeedLayoutState; state.LayoutRects.Clear(); // Save the index of the first realized item. We'll use it as a starting point during arrange. state.FirstRealizedIndex = firstRowIndex * 3; // ideal item width that will expand/shrink to fill available space double desiredItemWidth = Math.Max(MinItemSize.Width, (availableSize.Width - ColumnSpacing * 3) / 4); // Foreach item between the first and last index, // Call GetElementOrCreateElementAt which causes an element to either be realized or retrieved // from a recycle pool // Measure the element using an appropriate size // // Any element that was previously realized which we don't retrieve in this pass (via a call to // GetElementOrCreateAt) will be automatically cleared and set aside for later re-use. // Note: While this work fine, it does mean that more elements than are required may be // created because it isn't until after our MeasureOverride completes that the unused elements // will be recycled and available to use. We could avoid this by choosing to track the first/last // index from the previous layout pass. The diff between the previous range and current range // would represent the elements that we can pre-emptively make available for re-use by calling // context.RecycleElement(element). for (int rowIndex = firstRowIndex; rowIndex < lastRowIndex; rowIndex++) { int firstItemIndex = rowIndex * 3; var boundsForCurrentRow = CalculateLayoutBoundsForRow(rowIndex, desiredItemWidth); for (int columnIndex = 0; columnIndex < 3; columnIndex++) { var index = firstItemIndex + columnIndex; var rect = boundsForCurrentRow[index % 3]; var container = context.GetOrCreateElementAt(index); container.Measure( new Size(boundsForCurrentRow[columnIndex].Width, boundsForCurrentRow[columnIndex].Height)); state.LayoutRects.Add(boundsForCurrentRow[columnIndex]); } } // Calculate and return the size of all the content (realized or not) by figuring out // what the bottom/right position of the last item would be. var extentHeight = (context.ItemCount / 3 - 1) * (MinItemSize.Height + RowSpacing) + MinItemSize.Height; // Report this as the desired size for the layout return(new Size(desiredItemWidth * 4 + ColumnSpacing * 2, extentHeight)); }
protected override Size MeasureOverride(VirtualizingLayoutContext context, Size availableSize) { return(base.MeasureOverride(context, availableSize)); }
protected override void InitializeForContextCore(VirtualizingLayoutContext context) { context.LayoutState = new WaterFallLayoutState(); }
protected override Size ArrangeOverride(VirtualizingLayoutContext context, Size finalSize) { return(base.ArrangeOverride(context, finalSize)); }
protected override void OnItemsChangedCore(VirtualizingLayoutContext context, object source, NotifyCollectionChangedEventArgs args) { base.OnItemsChangedCore(context, source, args); }
public GroupedLayoutState(VirtualizingLayoutContext context) { this._context = context; }
protected override Size MeasureOverride(VirtualizingLayoutContext context, Size availableSize) { var viewport = context.RealizationRect; Debug.WriteLine("Measure: " + viewport); if (availableSize.Width != m_lastAvailableWidth) { UpdateCachedBounds(availableSize); m_lastAvailableWidth = availableSize.Width; } // Initialize column offsets int numColumns = (int)(availableSize.Width / Width); if (m_columnOffsets.Count == 0) { for (int i = 0; i < numColumns; i++) { m_columnOffsets.Add(0); } } m_firstIndex = GetStartIndex(viewport); int currentIndex = m_firstIndex; double nextOffset = -1.0; // Measure items from start index to when we hit the end of the viewport. while (currentIndex < context.ItemCount && nextOffset < viewport.Bottom) { Debug.WriteLine("Measuring " + currentIndex); var child = context.GetOrCreateElementAt(currentIndex); child.Measure(new Size(Width, availableSize.Height)); if (currentIndex >= m_cachedBounds.Count) { // We do not have bounds for this index. Lay it out and cache it. int columnIndex = GetIndexOfLowestColumn(m_columnOffsets, out nextOffset); m_cachedBounds.Add(new Rect(columnIndex * Width, nextOffset, Width, child.DesiredSize.Height)); m_columnOffsets[columnIndex] += child.DesiredSize.Height; } else { if (currentIndex + 1 == m_cachedBounds.Count) { // Last element. Use the next offset. GetIndexOfLowestColumn(m_columnOffsets, out nextOffset); } else { nextOffset = m_cachedBounds[currentIndex + 1].Top; } } child.Arrange(m_cachedBounds[currentIndex]); m_lastIndex = currentIndex; currentIndex++; } var extent = GetExtentSize(availableSize); return(extent); }
protected override Size MeasureOverride(VirtualizingLayoutContext context, Size availableSize) { if (context.ItemCount < 1) { return(new Size(0, 0)); } // Item size is 96 + 2px of margin var state = context.LayoutState as MosaicLayoutState; if (state.LayoutRects.Count < 1) { state.LayoutRects.Clear(); state.ActualWidth = availableSize.Width; var items = GetItems(context); var mosaic = MosaicMedia.Calculate(items); var top = 0d; foreach (var row in mosaic) { var left = 0d; foreach (var item in row) { state.LayoutRects.Add(new Rect(left, top, item.Width, 98)); left += item.Width; } top += 98; } state.Rows = mosaic; state.ExtentHeight = top - 2; } if (state.LayoutRects.Count < 1) { return(new Size(0, 0)); } var firstRowIndex = Math.Min(Math.Max((int)(context.RealizationRect.Y / 98) - 1, 0), state.Rows.Count - 1); var lastRowIndex = Math.Max(Math.Min((int)(context.RealizationRect.Bottom / 98) + 1, state.Rows.Count - 1), 0); var firstItemIndex = state.Rows[firstRowIndex][0].Index; var lastItemIndex = state.Rows[lastRowIndex].Last().Index; var availableWidth = availableSize.Width + 2; for (int i = firstItemIndex; i <= lastItemIndex; i++) { var container = context.GetOrCreateElementAt(i); container.Measure(new Size( state.LayoutRects[i].Width * availableWidth, state.LayoutRects[i].Height)); } state.FirstRealizedIndex = firstItemIndex; state.LastRealizedIndex = lastItemIndex; // Report this as the desired size for the layout return(new Size(availableSize.Width, state.ExtentHeight)); }
/// <inheritdoc /> protected override void UninitializeForContextCore(VirtualizingLayoutContext context) { context.LayoutState = null; base.UninitializeForContextCore(context); }
protected override void InitializeForContextCore(VirtualizingLayoutContext context) { context.LayoutState = new CustomGridLayoutState(this); base.InitializeForContextCore(context); }
/// <inheritdoc /> protected override Size ArrangeOverride(VirtualizingLayoutContext context, Size finalSize) { if (context.ItemCount > 0) { var parentMeasure = new UvMeasure(Orientation, finalSize.Width, finalSize.Height); var spacingMeasure = new UvMeasure(Orientation, HorizontalSpacing, VerticalSpacing); var realizationBounds = new UvBounds(Orientation, context.RealizationRect); var state = (WrapLayoutState)context.LayoutState; bool Arrange(WrapItem item, bool isLast = false) { if (item.Measure.HasValue == false) { return(false); } if (item.Position == null) { return(false); } var desiredMeasure = item.Measure.Value; if (desiredMeasure.U == 0) { return(true); // if an item is collapsed, avoid adding the spacing } UvMeasure position = item.Position.Value; // Stretch the last item to fill the available space if (isLast) { desiredMeasure.U = parentMeasure.U - position.U; } if (((position.V + desiredMeasure.V) >= realizationBounds.VMin) && (position.V <= realizationBounds.VMax)) { // place the item UIElement child = context.GetOrCreateElementAt(item.Index); if (Orientation == Orientation.Horizontal) { child.Arrange(new Rect(position.U, position.V, desiredMeasure.U, desiredMeasure.V)); } else { child.Arrange(new Rect(position.V, position.U, desiredMeasure.V, desiredMeasure.U)); } } else if (position.V > realizationBounds.VMax) { return(false); } return(true); } for (var i = 0; i < context.ItemCount; i++) { bool continueArranging = Arrange(state.GetItemAt(i)); if (continueArranging == false) { break; } } } return(finalSize); }
private Size MeasureColumn(VirtualizingLayoutContext context, GroupedLayoutState state, Size availableSizeForElements, int headerIndex, double startingY, bool widthChanged, out int nextHeaderIndex) { // Note that you MUST call GetOrCreateElement on any items that are in the realized region... that's how // the control tracks which elements it shouldn't dispose. double y = startingY; int i = headerIndex; var header = state.GetItemAt(headerIndex); //if (widthChanged || header.Height == null) { var headerEl = context.GetOrCreateElementAt(headerIndex); headerEl.Measure(availableSizeForElements); header.Height = headerEl.DesiredSize.Height; } y += header.Height.Value + AfterHeaderSpacing; i++; while (i < context.ItemCount) { if (!(context.GetItemAt(i) is ViewItemTaskOrEvent)) { // End of column break; } var item = state.GetItemAt(i); // Logic technically ignores height of item, but heights are short enough it shouldn't matter if (y < context.RealizationRect.Top || y > context.RealizationRect.Bottom) { y += item.Height.GetValueOrDefault(40); } else { // Theoretically since tasks/events are removed and then added back when edited, we know they can never change size... // Therefore we can store their measured size, and only call measure if their width changed or height unknown... // However this didn't work in reality, not sure why. //if (widthChanged || item.Height == null) { var itemEl = context.GetOrCreateElementAt(i); itemEl.Measure(availableSizeForElements); item.Height = itemEl.DesiredSize.Height; } y += item.Height.Value; } // Include item spacing y += ItemSpacing; i++; } // Remove last item spacing (since no item after it) y -= ItemSpacing; nextHeaderIndex = i; return(new Size(availableSizeForElements.Width, y - startingY)); }