첨부 실행 코드는 나눔고딕코딩 폰트를 사용합니다.
본 블로그는 광고를 포함하고 있습니다.
광고 클릭에서 발생하는 수익금은 모두 블로그 콘텐츠 향상을 위해 쓰여집니다.

728x90
반응형
728x170

TestProject.zip
0.03MB

▶ 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
반응형
그리드형(광고전용)
Posted by icodebroker

댓글을 달아 주세요