728x90
728x170
▶ CustomVirtualizingLayoutState.cs
using System.Collections.Generic;
using Windows.Foundation;
namespace TestProject
{
/// <summary>
/// 커스텀 가상화 레이아웃 상태
/// </summary>
public class CustomVirtualizingLayoutState
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Field
////////////////////////////////////////////////////////////////////////////////////////// Private
#region Field
/// <summary>
/// 레이아웃 사각형 리스트
/// </summary>
private List<Rect> layoutRectangleList;
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Property
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 최초 실현 인덱스 - FirstRealizedIndex
/// <summary>
/// 최초 실현 인덱스
/// </summary>
public int FirstRealizedIndex { get; set; }
#endregion
#region 레이아웃 사각형 리스트 - LayoutRectangleList
/// <summary>
/// 레이아웃 사각형 리스트
/// </summary>
public List<Rect> LayoutRectangleList
{
get
{
if(this.layoutRectangleList == null)
{
this.layoutRectangleList = new List<Rect>();
}
return this.layoutRectangleList;
}
}
#endregion
}
}
728x90
▶ CustomVirtualizingLayout.cs
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
}
}
300x250
▶ CustomDataTemplateSelector.cs
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace TestProject
{
/// <summary>
/// 커스텀 데이터 템플리트 선택자
/// </summary>
public class CustomDataTemplateSelector : DataTemplateSelector
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Property
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 일반 - Normal
/// <summary>
/// 일반
/// </summary>
public DataTemplate Normal { get; set; }
#endregion
#region 강조 - Accent
/// <summary>
/// 강조
/// </summary>
public DataTemplate Accent { get; set; }
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Method
////////////////////////////////////////////////////////////////////////////////////////// Protected
#region 템플리트 선택하기 (코어) - SelectTemplateCore(item)
/// <summary>
/// 템플리트 선택하기 (코어)
/// </summary>
/// <param name="item">항목</param>
/// <returns>템플리트</returns>
protected override DataTemplate SelectTemplateCore(object item)
{
if((int)item % 2 == 0)
{
return Normal;
}
else
{
return Accent;
}
}
#endregion
}
}
▶ MainPage.xaml
<Page x:Class="TestProject.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
xmlns:local="using:TestProject"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
FontFamily="나눔고딕코딩"
FontSize="16">
<Page.Resources>
<DataTemplate x:Key="NormalItemDataTemplateKey" x:DataType="x:Int32">
<Button
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Background="{ThemeResource SystemChromeLowColor}">
<TextBlock Text="{x:Bind}" />
</Button>
</DataTemplate>
<DataTemplate x:Key="AccentItemDataTemplateKey" x:DataType="x:Int32">
<Button
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Background="{ThemeResource SystemAccentColor}">
<TextBlock Text="{x:Bind}" />
</Button>
</DataTemplate>
<local:CustomDataTemplateSelector x:Key="CustomDataTemplateSelectorKey"
Normal="{StaticResource NormalItemDataTemplateKey}"
Accent="{StaticResource AccentItemDataTemplateKey}" />
<muxc:UniformGridLayout x:Key="UniformGridLayoutKey"
MinItemWidth="100"
MinItemHeight="100"
MinRowSpacing="10"
MinColumnSpacing="10" />
<local:CustomVirtualizingLayout x:Key="CustomVirtualizingLayoutKey"
RowSpacing="10"
ColumnSpacing="10"
MinimumItemSize="80 100" />
</Page.Resources>
<Grid>
<Grid
HorizontalAlignment="Center"
VerticalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="50" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ScrollViewer Grid.Column="0"
Width="500"
Height="400"
IsVerticalScrollChainingEnabled="False">
<muxc:ItemsRepeater Name="itemsRepeater"
Layout="{StaticResource CustomVirtualizingLayoutKey}"
ItemTemplate="{StaticResource CustomDataTemplateSelectorKey}"
HorizontalAlignment="Stretch" />
</ScrollViewer>
<StackPanel Spacing="10" Grid.Column="2">
<RadioButton
GroupName="Layout"
Tag="UniformGridLayoutKey"
Content="UniformGridLayout"
Click="layoutRadioButton_Click" />
<RadioButton
GroupName="Layout"
Tag="CustomVirtualizingLayoutKey"
IsChecked="True"
Content="커스텀 가상화 레이아웃"
Click="layoutRadioButton_Click" />
</StackPanel>
</Grid>
</Grid>
</Page>
▶ MainPage.xaml.cs
using Microsoft.UI.Xaml.Controls;
using System.Linq;
using Windows.Foundation;
using Windows.Graphics.Display;
using Windows.UI.ViewManagement;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace TestProject
{
/// <summary>
/// 메인 페이지
/// </summary>
public sealed partial class MainPage : Page
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 생성자 - MainPage()
/// <summary>
/// 생성자
/// </summary>
public MainPage()
{
InitializeComponent();
#region 윈도우 크기를 설정한다.
double width = 800d;
double height = 600d;
double dpi = (double)DisplayInformation.GetForCurrentView().LogicalDpi;
ApplicationView.PreferredLaunchWindowingMode = ApplicationViewWindowingMode.PreferredLaunchViewSize;
Size windowSize = new Size(width * 96d / dpi, height * 96d / dpi);
ApplicationView.PreferredLaunchViewSize = windowSize;
Window.Current.Activate();
ApplicationView.GetForCurrentView().TryResizeView(windowSize);
#endregion
#region 윈도우 제목을 설정한다.
ApplicationView.GetForCurrentView().Title = "ItemsRepeater 엘리먼트 : Layout 속성을 사용해 커스텀 가상화 레이아웃 사용하기";
#endregion
this.itemsRepeater.ItemsSource = Enumerable.Range(0, 500);
}
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Method
////////////////////////////////////////////////////////////////////////////////////////// Private
#region 레이아웃 라디오 버튼 클릭시 처리하기 - layoutRadioButton_Click(sender, e)
/// <summary>
/// 레이아웃 라디오 버튼 클릭시 처리하기
/// </summary>
/// <param name="sender">이벤트 발생자</param>
/// <param name="e">이벤트 인자</param>
private void layoutRadioButton_Click(object sender, RoutedEventArgs e)
{
string layoutKey = ((FrameworkElement)sender).Tag as string;
this.itemsRepeater.Layout = Resources[layoutKey] as VirtualizingLayout;
}
#endregion
}
}
728x90
그리드형(광고전용)
'C# > UWP' 카테고리의 다른 글
[C#/UWP] Grid 엘리먼트 사용하기 (0) | 2021.07.03 |
---|---|
[C#/UWP] ItemsRepeater 엘리먼트 : 필터링/정렬 기능 사용하기 (0) | 2021.07.01 |
[C#/UWP] ItemsRepeater 엘리먼트 : 애니메이션 스크롤 사용하기 (0) | 2021.06.29 |
[C#/UWP] ItemsRepeater 엘리먼트 : 중첩 레이아웃 사용하기 (0) | 2021.06.29 |
[C#/UWP] ItemsRepeater 엘리먼트 : ItemsSource 속성을 사용해 혼합 타입 컬렉션 사용하기 (0) | 2021.06.29 |
[C#/UWP] ItemsRepeater 엘리먼트 사용하기 (0) | 2021.06.29 |
[C#/UWP] Canvas 엘리먼트 사용하기 (0) | 2021.06.28 |
[C#/UWP] Border 엘리먼트 사용하기 (0) | 2021.06.28 |
[C#/UWP] SymbolIconSource 엘리먼트 사용하기 (0) | 2021.06.26 |
[C#/UWP] Hyperlink 엘리먼트 : NavigateUri 속성을 사용해 설정 앱의 색 섹션 표시하기 (0) | 2021.06.26 |