using Microsoft.UI.Xaml.Controls;
using System;
using Windows.Foundation;
using Windows.UI.Xaml;
namespace TestProject
{
/// <summary>
/// 커스텀 가상화 레이아웃
/// </summary>
public class CustomVirtualizingLayout : VirtualizingLayout
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Dependency Property
////////////////////////////////////////////////////////////////////////////////////////// Static
//////////////////////////////////////////////////////////////////////////////// Public
#region 행 간격 속성 - RowSpacingProperty
/// <summary>
/// 행 간격 속성
/// </summary>
public static readonly DependencyProperty RowSpacingProperty = DependencyProperty.Register
(
"RowSpacing",
typeof(double),
typeof(CustomVirtualizingLayout),
new PropertyMetadata(0, PropertyChangedCallback)
);
#endregion
#region 열 간격 속성 - ColumnSpacingProperty
/// <summary>
/// 열 간격 속성
/// </summary>
public static readonly DependencyProperty ColumnSpacingProperty = DependencyProperty.Register
(
"ColumnSpacing",
typeof(double),
typeof(CustomVirtualizingLayout),
new PropertyMetadata(0, PropertyChangedCallback)
);
#endregion
#region 최소 항목 크기 속성 - MinimumItemSizeProperty
/// <summary>
/// 최소 항목 크기 속성
/// </summary>
public static readonly DependencyProperty MinimumItemSizeProperty = DependencyProperty.Register
(
"MinimumItemSize",
typeof(Size),
typeof(CustomVirtualizingLayout),
new PropertyMetadata(Size.Empty, PropertyChangedCallback)
);
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Field
////////////////////////////////////////////////////////////////////////////////////////// Private
#region Field
/// <summary>
/// 행 간격
/// </summary>
private double rowSpacing;
/// <summary>
/// 열 간격
/// </summary>
private double columnSpacing;
/// <summary>
/// 최소 항목 크기
/// </summary>
private Size minimumItemSize = Size.Empty;
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Property
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 행 간격 - RowSpacing
/// <summary>
/// 행 간격
/// </summary>
public double RowSpacing
{
get
{
return this.rowSpacing;
}
set
{
SetValue(RowSpacingProperty, value);
}
}
#endregion
#region 열 간격 - ColumnSpacing
/// <summary>
/// 열 간격
/// </summary>
public double ColumnSpacing
{
get
{
return this.columnSpacing;
}
set
{
SetValue(ColumnSpacingProperty, value);
}
}
#endregion
#region 최소 항목 크기 - MinimumItemSize
/// <summary>
/// 최소 항목 크기
/// </summary>
public Size MinimumItemSize
{
get
{
return this.minimumItemSize;
}
set
{
SetValue(MinimumItemSizeProperty, value);
}
}
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Method
////////////////////////////////////////////////////////////////////////////////////////// Static
//////////////////////////////////////////////////////////////////////////////// Private
#region 속성 변경시 콜백 처리하기 - PropertyChangedCallback(d, e)
/// <summary>
/// 속성 변경시 콜백 처리하기
/// </summary>
/// <param name="d">의존 객체</param>
/// <param name="e">이벤트 인자</param>
private static void PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
CustomVirtualizingLayout layout = d as CustomVirtualizingLayout;
if(e.Property == RowSpacingProperty)
{
layout.rowSpacing = (double)e.NewValue;
}
else if (e.Property == ColumnSpacingProperty)
{
layout.columnSpacing = (double)e.NewValue;
}
else if (e.Property == MinimumItemSizeProperty)
{
layout.minimumItemSize = (Size)e.NewValue;
}
else
{
throw new InvalidOperationException("Don't know what you are talking about!");
}
layout.InvalidateMeasure();
}
#endregion
////////////////////////////////////////////////////////////////////////////////////////// Instance
//////////////////////////////////////////////////////////////////////////////// Protected
#region 컨텍스트용 초기화하기 (코어) - InitializeForContextCore(context)
/// <summary>
/// 컨텍스트용 초기화하기 (코어)
/// </summary>
/// <param name="context">컨텍스트</param>
protected override void InitializeForContextCore(VirtualizingLayoutContext context)
{
base.InitializeForContextCore(context);
if(!(context.LayoutState is CustomVirtualizingLayoutState state))
{
context.LayoutState = new CustomVirtualizingLayoutState();
}
}
#endregion
#region 컨텍스트용 초기화 해제하기 (코어) - UninitializeForContextCore(context)
/// <summary>
/// 컨텍스트용 초기화 해제하기 (코어)
/// </summary>
/// <param name="context">컨텍스트</param>
protected override void UninitializeForContextCore(VirtualizingLayoutContext context)
{
base.UninitializeForContextCore(context);
context.LayoutState = null;
}
#endregion
#region 측정하기 (오버라이드) - MeasureOverride(context, availableSize)
/// <summary>
/// 측정하기 (오버라이드)
/// </summary>
/// <param name="context">컨텍스트</param>
/// <param name="availableSize">이용 가능한 크기</param>
/// <returns>측정 크기</returns>
protected override Size MeasureOverride(VirtualizingLayoutContext context, Size availableSize)
{
if(MinimumItemSize == Size.Empty)
{
UIElement firstElement = context.GetOrCreateElementAt(0);
firstElement.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
this.minimumItemSize = firstElement.DesiredSize;
}
int firstRowIndex = Math.Max((int)(context.RealizationRect.Y / (MinimumItemSize.Height + RowSpacing)) - 1, 0 );
int lastRowIndex = Math.Min((int)(context.RealizationRect.Bottom / (MinimumItemSize.Height + RowSpacing)) + 1, (int)(context.ItemCount / 3));
CustomVirtualizingLayoutState state = context.LayoutState as CustomVirtualizingLayoutState;
state.LayoutRectangleList.Clear();
state.FirstRealizedIndex = firstRowIndex * 3;
double desiredItemWidth = Math.Max(MinimumItemSize.Width, (availableSize.Width - ColumnSpacing * 3) / 4);
for(int rowIndex = firstRowIndex; rowIndex < lastRowIndex; rowIndex++)
{
int firstItemIndex = rowIndex * 3;
Rect[] currentRowRectangleArray = CalculateLayoutRectangleArrayForRow(rowIndex, desiredItemWidth);
for(int columnIndex = 0; columnIndex < 3; columnIndex++)
{
int index = firstItemIndex + columnIndex;
Rect currentRowRectangle = currentRowRectangleArray[index % 3];
UIElement containerElement = context.GetOrCreateElementAt(index);
containerElement.Measure
(
new Size
(
currentRowRectangleArray[columnIndex].Width,
currentRowRectangleArray[columnIndex].Height
)
);
state.LayoutRectangleList.Add(currentRowRectangleArray[columnIndex]);
}
}
var extentHeight = ((int)(context.ItemCount / 3) - 1) * (MinimumItemSize.Height + RowSpacing) + MinimumItemSize.Height;
return new Size(desiredItemWidth * 4 + ColumnSpacing * 2, extentHeight);
}
#endregion
#region 배열하기 (오버라이드) - ArrangeOverride(context, finalSize)
/// <summary>
/// 배열하기 (오버라이드)
/// </summary>
/// <param name="context">컨텍스트</param>
/// <param name="finalSize">최종 크기</param>
/// <returns>배열 크기</returns>
protected override Size ArrangeOverride(VirtualizingLayoutContext context, Size finalSize)
{
CustomVirtualizingLayoutState state = context.LayoutState as CustomVirtualizingLayoutState;
VirtualizingLayoutContext virtualContext = context as VirtualizingLayoutContext;
int currentIndex = state.FirstRealizedIndex;
foreach(Rect layoutRectangle in state.LayoutRectangleList)
{
UIElement containerElement = virtualContext.GetOrCreateElementAt(currentIndex);
containerElement.Arrange(layoutRectangle);
currentIndex++;
}
return finalSize;
}
#endregion
//////////////////////////////////////////////////////////////////////////////// Private
#region 행을 위한 레이아웃 사각형 배열 계산하기 - CalculateLayoutRectangleArrayForRow(rowIndex, desiredItemWidth)
/// <summary>
/// 행을 위한 레이아웃 사각형 배열 계산하기
/// </summary>
/// <param name="rowIndex">행 인덱스</param>
/// <param name="desiredItemWidth">희망 항목 너비</param>
/// <returns>행을 위한 레이아웃 사각형 배열</returns>
private Rect[] CalculateLayoutRectangleArrayForRow(int rowIndex, double desiredItemWidth)
{
Rect[] rectangleArrayForRow = new Rect[3];
double yOffset = rowIndex * (this.MinimumItemSize.Height + this.RowSpacing);
rectangleArrayForRow[0].Y = rectangleArrayForRow[1].Y = rectangleArrayForRow[2].Y = yOffset;
rectangleArrayForRow[0].Height = rectangleArrayForRow[1].Height = rectangleArrayForRow[2].Height = this.MinimumItemSize.Height;
if(rowIndex % 2 == 0)
{
rectangleArrayForRow[0].X = 0;
rectangleArrayForRow[0].Width = desiredItemWidth;
rectangleArrayForRow[1].X = rectangleArrayForRow[0].Right + this.ColumnSpacing;
rectangleArrayForRow[1].Width = desiredItemWidth;
rectangleArrayForRow[2].X = rectangleArrayForRow[1].Right + this.ColumnSpacing;
rectangleArrayForRow[2].Width = desiredItemWidth * 2 + this.ColumnSpacing;
}
else
{
rectangleArrayForRow[0].X = 0;
rectangleArrayForRow[0].Width = (desiredItemWidth * 2 + this.ColumnSpacing);
rectangleArrayForRow[1].X = rectangleArrayForRow[0].Right + this.ColumnSpacing;
rectangleArrayForRow[1].Width = desiredItemWidth;
rectangleArrayForRow[2].X = rectangleArrayForRow[1].Right + this.ColumnSpacing;
rectangleArrayForRow[2].Width = desiredItemWidth;
}
return rectangleArrayForRow;
}
#endregion
}
}