첨부 실행 코드는 나눔고딕코딩 폰트를 사용합니다.
------------------------------------------------------------------------------------------------------------------------------------------------------
728x90
728x170

TestProject.zip
0.02MB

▶ Customer.cs

namespace TestProject
{
    /// <summary>
    /// 고객
    /// </summary>
    public class Customer
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region Field

        /// <summary>
        /// 바이트 배열
        /// </summary>
        /// <remarks>테스트를 위한 더미 데이터</remarks>
        private byte[] byteArray = new byte[100];

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Property
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region ID - ID

        /// <summary>
        /// ID
        /// </summary>
        public int ID { get; set; }

        #endregion
        #region 성명 - Name

        /// <summary>
        /// 성명
        /// </summary>
        public string Name { get; set; }

        #endregion
    }
}

 

728x90

 

▶ IItemProvider.cs

using System.Collections.Generic;

namespace TestProject
{
    /// <summary>
    /// 항목 공급자 인터페이스
    /// </summary>
    /// <typeparam name="TItem">항목 타입</typeparam>
    public interface IItemProvider<TItem>
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Method

        #region 카운트 가져오기 - FetchCount()

        /// <summary>
        /// 카운트 가져오기
        /// </summary>
        /// <returns>카운트</returns>
        int FetchCount();

        #endregion
        #region 범위 가져오기 - FetchRange(startIndex, count)

        /// <summary>
        /// 범위 가져오기
        /// </summary>
        /// <param name="startIndex">시작 인덱스</param>
        /// <param name="count">카운트</param>
        /// <returns>항목 리스트</returns>
        IList<TItem> FetchRange(int startIndex, int count);

        #endregion
    }
}

 

300x250

 

▶ CustomerProvider.cs

using System.Collections.Generic;
using System.Threading;

namespace TestProject
{
    /// <summary>
    /// 고객 공급자
    /// </summary>
    public class CustomerProvider : IItemProvider<Customer>
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region Field

        /// <summary>
        /// 카운트
        /// </summary>
        private readonly int count;

        /// <summary>
        /// 페치 지연 시간
        /// </summary>
        private readonly int fetchDelay;

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 생성자 - CustomerProvider(count, fetchDelay)

        /// <summary>
        /// 생성자
        /// </summary>
        /// <param name="count">카운트</param>
        /// <param name="fetchDelay">페치 지연 시간</param>
        public CustomerProvider(int count, int fetchDelay)
        {
            this.count      = count;
            this.fetchDelay = fetchDelay;
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 카운트 가져오기 - FetchCount()

        /// <summary>
        /// 카운트 가져오기
        /// </summary>
        /// <returns>카운트</returns>
        public int FetchCount()
        {
            Thread.Sleep(this.fetchDelay);

            return this.count; 
        }

        #endregion
        #region 범위 가져오기 - FetchRange(startIndex, count)

        /// <summary>
        /// 범위 가져오기
        /// </summary>
        /// <param name="startIndex">시작 인덱스</param>
        /// <param name="count">카운트</param>
        /// <returns>고객 리스트</returns>
        public IList<Customer> FetchRange(int startIndex, int count)
        {
            Thread.Sleep(this.fetchDelay);

            List<Customer> list = new List<Customer>();

            for(int i = startIndex; i < startIndex + count; i++)
            {
                Customer customer = new Customer { ID = i + 1, Name = "고객 " + (i + 1) };

                list.Add(customer);
            }

            return list;
        }

        #endregion
    }
}

 

 

▶ VirtualizingCollection.cs

using System;
using System.Collections;
using System.Collections.Generic;

namespace TestProject
{
    /// <summary>
    /// 가상화 컬렉션
    /// </summary>
    /// <typeparam name="TItem">항목 타입</typeparam>
    public class VirtualizingCollection<TItem> : IList<TItem>, IList
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region Field

        /// <summary>
        /// 항목 공급자
        /// </summary>
        private readonly IItemProvider<TItem> itemProvider;

        /// <summary>
        /// 페이지 크기
        /// </summary>
        private readonly int pageSize = 100;

        /// <summary>
        /// 페이지 타임아웃
        /// </summary>
        private readonly long pageTimeout = 10000;

        /// <summary>
        /// 카운트
        /// </summary>
        private int count = -1;

        /// <summary>
        /// 페이지 딕셔너리
        /// </summary>
        private readonly Dictionary<int, IList<TItem>> pageDictionary = new Dictionary<int, IList<TItem>>();

        /// <summary>
        /// 페이지 터치 시간 딕셔너리
        /// </summary>
        private readonly Dictionary<int, DateTime> pageTouchTimeDictionary = new Dictionary<int, DateTime>();

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Property
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 항목 공급자 - ItemProvider

        /// <summary>
        /// 항목 공급자
        /// </summary>
        public IItemProvider<TItem> ItemProvider
        {
            get
            {
                return this.itemProvider;
            }
        }

        #endregion
        #region 페이지 크기 - PageSize

        /// <summary>
        /// 페이지 크기
        /// </summary>
        public int PageSize
        {
            get
            {
                return this.pageSize;
            }
        }

        #endregion
        #region 페이지 타임아웃 - PageTimeout

        /// <summary>
        /// 페이지 타임아웃
        /// </summary>
        public long PageTimeout
        {
            get
            {
                return this.pageTimeout;
            }
        }

        #endregion

        #region 카운트 - Count

        /// <summary>
        /// 카운트
        /// </summary>
        public virtual int Count
        {
            get
            {
                if(this.count == -1)
                {
                    LoadCount();
                }

                return this.count;
            }
            protected set
            {
                this.count = value;
            }
        }

        #endregion
        #region 인덱서 - this[index]

        /// <summary>
        /// 인덱서
        /// </summary>
        /// <param name="index">인덱스</param>
        /// <returns>항목</returns>
        public TItem this[int index]
        {
            get
            {
                int pageIndex  = index / PageSize;
                int pageOffset = index % PageSize;

                RequestPage(pageIndex);

                if(pageOffset > PageSize / 2 && pageIndex < Count / PageSize)
                {
                    RequestPage(pageIndex + 1);
                }

                if(pageOffset < PageSize / 2 && pageIndex > 0)
                {
                    RequestPage(pageIndex - 1);
                }

                CleanUpPages();

                if(this.pageDictionary[pageIndex] == null)
                {
                    return default(TItem);
                }

                return this.pageDictionary[pageIndex][pageOffset];
            }
            set
            {
                throw new NotSupportedException();
            }
        }

        #endregion
        #region 동기 루트 - SyncRoot

        /// <summary>
        /// 동기 루트
        /// </summary>
        public object SyncRoot
        {
            get
            {
                return this;
            }
        }

        #endregion
        #region 동기화 여부 - IsSynchronized

        /// <summary>
        /// 동기화 여부
        /// </summary>
        public bool IsSynchronized
        {
            get
            {
                return false;
            }
        }

        #endregion
        #region 읽기 전용 여부 - IsReadOnly

        /// <summary>
        /// 읽기 전용 여부
        /// </summary>
        public bool IsReadOnly
        {
            get
            {
                return true;
            }
        }

        #endregion
        #region 고정 크기 여부 - IsFixedSize

        /// <summary>
        /// 고정 크기 여부
        /// </summary>
        public bool IsFixedSize
        {
            get
            {
                return false;
            }
        }

        #endregion

        #region (IList) 인덱서 - this[index]

        /// <summary>
        /// 인덱서
        /// </summary>
        /// <param name="index">인덱스</param>
        /// <returns>항목</returns>
        object IList.this[int index]
        {
            get
            {
                return this[index];
            }
            set
            {
                throw new NotSupportedException();
            }
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 생성자 - VirtualizingCollection(itemProvider)

        /// <summary>
        /// 생성자
        /// </summary>
        /// <param name="itemProvider">항목 공급자</param>
        public VirtualizingCollection(IItemProvider<TItem> itemProvider)
        {
            this.itemProvider = itemProvider;
        }

        #endregion
        #region 생성자 - VirtualizingCollection(itemProvider, pageSize)

        /// <summary>
        /// 생성자
        /// </summary>
        /// <param name="itemProvider">항목 공급자</param>
        /// <param name="pageSize">페이지 크기</param>
        public VirtualizingCollection(IItemProvider<TItem> itemProvider, int pageSize)
        {
            this.itemProvider = itemProvider;
            this.pageSize     = pageSize;
        }

        #endregion
        #region 생성자 - VirtualizingCollection(itemProvider, pageSize, pageTimeout)

        /// <summary>
        /// 생성자
        /// </summary>
        /// <param name="itemProvider">항목 공급자</param>
        /// <param name="pageSize">페이지 크기</param>
        /// <param name="pageTimeout">페이지 타임아웃</param>
        public VirtualizingCollection(IItemProvider<TItem> itemProvider, int pageSize, int pageTimeout)
        {
            this.itemProvider = itemProvider;
            this.pageSize     = pageSize;
            this.pageTimeout  = pageTimeout;
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 페이지들 지우기 - CleanUpPages()

        /// <summary>
        /// 페이지들 지우기
        /// </summary>
        public void CleanUpPages()
        {
            List<int> keyList = new List<int>(this.pageTouchTimeDictionary.Keys);

            foreach(int key in keyList)
            {
                if(key != 0 && (DateTime.Now - this.pageTouchTimeDictionary[key]).TotalMilliseconds > PageTimeout)
                {
                    this.pageDictionary.Remove(key);

                    this.pageTouchTimeDictionary.Remove(key);
                }
            }
        }

        #endregion
        #region 열거자 구하기 - GetEnumerator()

        /// <summary>
        /// 열거자 구하기
        /// </summary>
        /// <returns>열거자</returns>
        public IEnumerator<TItem> GetEnumerator()
        {
            for(int i = 0; i < Count; i++)
            {
                yield return this[i];
            }
        }

        #endregion
        #region 포함 여부 구하기 - Contains(item)

        /// <summary>
        /// 포함 여부 구하기
        /// </summary>
        /// <param name="item">항목</param>
        /// <returns>포함 여부</returns>
        public bool Contains(TItem item)
        {
            return false;
        }

        #endregion
        #region 추가하기 - Add(item)

        /// <summary>
        /// 추가하기
        /// </summary>
        /// <param name="item">항목</param>
        public void Add(TItem item)
        {
            throw new NotSupportedException();
        }

        #endregion
        #region 삽입하기 - Insert(index, item)

        /// <summary>
        /// 삽입하기
        /// </summary>
        /// <param name="index">인덱스</param>
        /// <param name="item">항목</param>
        public void Insert(int index, TItem item)
        {
            throw new NotSupportedException();
        }

        #endregion
        #region 인덱스 구하기 - IndexOf(item)

        /// <summary>
        /// 인덱스 구하기
        /// </summary>
        /// <param name="item">항목</param>
        /// <returns>인덱스</returns>
        public int IndexOf(TItem item)
        {
            return -1;
        }

        #endregion
        #region 제거하기 - RemoveAt(index)

        /// <summary>
        /// 제거하기
        /// </summary>
        /// <param name="index">인덱스</param>
        public void RemoveAt(int index)
        {
            throw new NotSupportedException();
        }

        #endregion
        #region 제거하기 - Remove(item)

        /// <summary>
        /// 제거하기
        /// </summary>
        /// <param name="item">항목</param>
        /// <returns>처리 결과</returns>
        public bool Remove(TItem item)
        {
            throw new NotSupportedException();
        }

        #endregion
        #region 지우기 - Clear()

        /// <summary>
        /// 지우기
        /// </summary>
        public void Clear()
        {
            throw new NotSupportedException();
        }

        #endregion
        #region 복사하기 - CopyTo(itemArray, arrayIndex)

        /// <summary>
        /// 복사하기
        /// </summary>
        /// <param name="itemArray">항목 배열</param>
        /// <param name="arrayIndex">배열 인덱스</param>
        public void CopyTo(TItem[] itemArray, int arrayIndex)
        {
            throw new NotSupportedException();
        }

        #endregion

        #region (IEnumerable) 열거자 구하기 - GetEnumerator()

        /// <summary>
        /// 열거자 구하기
        /// </summary>
        /// <returns>열거자</returns>
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        #endregion

        #region (IList) 포함 여부 구하기 - Contains(value)

        /// <summary>
        /// 포함 여부 구하기
        /// </summary>
        /// <param name="value">값</param>
        /// <returns>포함 여부</returns>
        bool IList.Contains(object value)
        {
            return Contains((TItem)value);
        }

        #endregion
        #region (IList) 추가하기 - Add(value)

        /// <summary>
        /// 추가하기
        /// </summary>
        /// <param name="value">값</param>
        /// <returns>인덱스</returns>
        int IList.Add(object value)
        {
            throw new NotSupportedException();
        }

        #endregion
        #region (IList) 삽입하기 - Insert(index, value)

        /// <summary>
        /// 삽입하기
        /// </summary>
        /// <param name="index">인덱스</param>
        /// <param name="value">값</param>
        void IList.Insert(int index, object value)
        {
            Insert(index, (TItem)value);
        }

        #endregion
        #region (IList) 인덱스 구하기 - IndexOf(value)

        /// <summary>
        /// 인덱스 구하기
        /// </summary>
        /// <param name="value">값</param>
        /// <returns>인덱스</returns>
        int IList.IndexOf(object value)
        {
            return IndexOf((TItem) value);
        }

        #endregion
        #region (IList) 제거하기 - Remove(value)

        /// <summary>
        /// 제거하기
        /// </summary>
        /// <param name="value">값</param>
        void IList.Remove(object value)
        {
            throw new NotSupportedException();
        }

        #endregion

        #region (ICollection) 복사하기 - CopyTo(array, index)

        /// <summary>
        /// 복사하기
        /// </summary>
        /// <param name="array">배열</param>
        /// <param name="index">인덱스</param>
        void ICollection.CopyTo(Array array, int index)
        {
            throw new NotSupportedException();
        }

        #endregion

        ////////////////////////////////////////////////////////////////////////////////////////// Protected

        #region 카운트 가져오기 - FetchCount()

        /// <summary>
        /// 카운트 가져오기
        /// </summary>
        /// <returns>카운트</returns>
        protected int FetchCount()
        {
            return ItemProvider.FetchCount();
        }

        #endregion
        #region 카운트 로드하기 - LoadCount()

        /// <summary>
        /// 카운트 로드하기
        /// </summary>
        protected virtual void LoadCount()
        {
            Count = FetchCount();
        }

        #endregion

        #region 페이지 채우기 - PopulatePage(pageIndex, itemList)

        /// <summary>
        /// 페이지 채우기
        /// </summary>
        /// <param name="pageIndex">페이지 인덱스</param>
        /// <param name="itemList">항목 리스트</param>
        protected virtual void PopulatePage(int pageIndex, IList<TItem> itemList)
        {
            if(this.pageDictionary.ContainsKey(pageIndex))
            {
                this.pageDictionary[pageIndex] = itemList;
            }
        }

        #endregion
        #region 페이지 가져오기 - FetchPage(pageIndex)

        /// <summary>
        /// 페이지 가져오기
        /// </summary>
        /// <param name="pageIndex">페이지 인덱스</param>
        /// <returns>항목 리스트</returns>
        protected IList<TItem> FetchPage(int pageIndex)
        {
            return ItemProvider.FetchRange(pageIndex * PageSize, PageSize);
        }

        #endregion
        #region 페이지 로드하기 - LoadPage(pageIndex)

        /// <summary>
        /// 페이지 로드하기
        /// </summary>
        /// <param name="pageIndex">페이지 인덱스</param>
        protected virtual void LoadPage(int pageIndex)
        {
            PopulatePage(pageIndex, FetchPage(pageIndex));
        }

        #endregion
        #region 페이지 요청하기 - RequestPage(pageIndex)

        /// <summary>
        /// 페이지 요청하기
        /// </summary>
        /// <param name="pageIndex">페이지 인덱스</param>
        protected virtual void RequestPage(int pageIndex)
        {
            if(!this.pageDictionary.ContainsKey(pageIndex))
            {
                this.pageDictionary.Add(pageIndex, null);

                this.pageTouchTimeDictionary.Add(pageIndex, DateTime.Now);

                LoadPage(pageIndex);
            }
            else
            {
                this.pageTouchTimeDictionary[pageIndex] = DateTime.Now;
            }
        }

        #endregion
    }
}

 

▶ AsyncVirtualizingCollection.cs

using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Threading;

namespace TestProject
{
    /// <summary>
    /// 비동기 가상화 컬렉션
    /// </summary>
    /// <typeparam name="TItem">항목 타입</typeparam>
    public class AsyncVirtualizingCollection<TItem> : VirtualizingCollection<TItem>, INotifyCollectionChanged, INotifyPropertyChanged
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Event
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 속성 변경시 이벤트 - PropertyChanged

        /// <summary>
        /// 속성 변경시 이벤트
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        #endregion
        #region 컬렉션 변경시 이벤트 - CollectionChanged

        /// <summary>
        /// 컬렉션 변경시 이벤트
        /// </summary>
        public event NotifyCollectionChangedEventHandler CollectionChanged;

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region Field

        /// <summary>
        /// 동기화 컨텍스트
        /// </summary>
        private readonly SynchronizationContext synchronizationContext;

        /// <summary>
        /// 로딩 여부
        /// </summary>
        private bool isLoading;

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Property
        ////////////////////////////////////////////////////////////////////////////////////////// Protected

        #region 동기화 컨텍스트 - SynchronizationContext

        /// <summary>
        /// 동기화 컨텍스트
        /// </summary>
        protected SynchronizationContext SynchronizationContext
        {
            get
            {
                return this.synchronizationContext;
            }
        }

        #endregion
        #region 로딩 여부 - IsLoading

        /// <summary>
        /// 로딩 여부
        /// </summary>
        public bool IsLoading
        {
            get
            {
                return this.isLoading;
            }
            set
            {
                if(value != this.isLoading)
                {
                    this.isLoading = value;
                }

                FirePropertyChangedEvent("IsLoading");
            }
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 생성자 - AsyncVirtualizingCollection(itemProvider)

        /// <summary>
        /// 생성자
        /// </summary>
        /// <param name="itemProvider">항목 공급자</param>
        public AsyncVirtualizingCollection(IItemProvider<TItem> itemProvider) : base(itemProvider)
        {
            this.synchronizationContext = SynchronizationContext.Current;
        }

        #endregion
        #region 생성자 - AsyncVirtualizingCollection(itemProvider, pageSize)

        /// <summary>
        /// 생성자
        /// </summary>
        /// <param name="itemProvider">항목 공급자</param>
        /// <param name="pageSize">페이지 크기</param>
        public AsyncVirtualizingCollection(IItemProvider<TItem> itemProvider, int pageSize) : base(itemProvider, pageSize)
        {
            this.synchronizationContext = SynchronizationContext.Current;
        }

        #endregion
        #region 생성자 - AsyncVirtualizingCollection(itemProvider, pageSize, pageTimeout)

        /// <summary>
        /// 생성자
        /// </summary>
        /// <param name="itemProvider">항목 공급자</param>
        /// <param name="pageSize">페이지 크기</param>
        /// <param name="pageTimeout">페이지 타임아웃</param>
        public AsyncVirtualizingCollection(IItemProvider<TItem> itemProvider, int pageSize, int pageTimeout) : base(itemProvider, pageSize, pageTimeout)
        {
            this.synchronizationContext = SynchronizationContext.Current;
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Protected

        #region 속성 변경시 이벤트 발생시키기 - FirePropertyChangedEvent(e)

        /// <summary>
        /// 속성 변경시 이벤트 발생시키기
        /// </summary>
        /// <param name="e">이벤트 인자</param>
        protected virtual void FirePropertyChangedEvent(PropertyChangedEventArgs e)
        {
            PropertyChanged?.Invoke(this, e);
        }

        #endregion
        #region 컬렉션 변경시 이벤트 발생시키기 - FireCollectionChangedEvent(e)

        /// <summary>
        /// 컬렉션 변경시 이벤트 발생시키기
        /// </summary>
        /// <param name="e">이벤트 인자</param>
        protected virtual void FireCollectionChangedEvent(NotifyCollectionChangedEventArgs e)
        {
            CollectionChanged?.Invoke(this, e);
        }

        #endregion

        #region 카운트 로드하기 - LoadCount()

        /// <summary>
        /// 카운트 로드하기
        /// </summary>
        protected override void LoadCount()
        {
            Count = 0;

            IsLoading = true;

            ThreadPool.QueueUserWorkItem(LoadCount);
        }

        #endregion
        #region 페이지 로드하기 - LoadPage(index)

        /// <summary>
        /// 페이지 로드하기
        /// </summary>
        /// <param name="index">인덱스</param>
        protected override void LoadPage(int index)
        {
            IsLoading = true;

            ThreadPool.QueueUserWorkItem(LoadPage, index);
        }

        #endregion

        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region 속성 변경시 이벤트 발생시키기 - FirePropertyChangedEvent(propertyName)

        /// <summary>
        /// 속성 변경시 이벤트 발생시키기
        /// </summary>
        /// <param name="propertyName">속성명</param>
        private void FirePropertyChangedEvent(string propertyName)
        {
            FirePropertyChangedEvent(new PropertyChangedEventArgs(propertyName));
        }

        #endregion
        #region 컬렉션 변경시 리셋 이벤트 발생시키기 - FireCollectionChangedResetEvent()

        /// <summary>
        /// 컬렉션 변경시 리셋 이벤트 발생시키기
        /// </summary>
        private void FireCollectionChangedResetEvent()
        {
            FireCollectionChangedEvent(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
        }

        #endregion

        #region 카운트 로드하기 - LoadCount(argument)

        /// <summary>
        /// 카운트 로드하기
        /// </summary>
        /// <param name="argument">인자</param>
        private void LoadCount(object argument)
        {
            int count = FetchCount();

            SynchronizationContext.Send(LoadCountCompleted, count);
        }

        #endregion
        #region 카운트 로드 완료시 처리하기 - LoadCountCompleted(argument)

        /// <summary>
        /// 카운트 로드 완료시 처리하기
        /// </summary>
        /// <param name="argument">인자</param>
        private void LoadCountCompleted(object argument)
        {
            Count = (int)argument;

            IsLoading = false;

            FireCollectionChangedResetEvent();
        }

        #endregion
        #region 페이지 로드하기 - LoadPage(argument)

        /// <summary>
        /// 페이지 로드하기
        /// </summary>
        /// <param name="argument">인자</param>
        private void LoadPage(object argument)
        {
            int pageIndex = (int)argument;

            IList<TItem> itemList = FetchPage(pageIndex);

            SynchronizationContext.Send(LoadPageCompleted, new object[]{ pageIndex, itemList });
        }

        #endregion
        #region 페이지 로드 완료시 처리하기 - LoadPageCompleted(argument)

        /// <summary>
        /// 페이지 로드 완료시 처리하기
        /// </summary>
        /// <param name="argument">인자</param>
        private void LoadPageCompleted(object argument)
        {
            int pageIndex = (int)((object[]) argument)[0];

            IList<TItem> itemList = (IList<TItem>)((object[])argument)[1];

            PopulatePage(pageIndex, itemList);

            IsLoading = false;

            FireCollectionChangedResetEvent();
        }

        #endregion
    }
}

 

▶ MainWindow.xaml

<Window x:Class="TestProject.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Width="800"
    Height="600"
    Title="데이터 가상화 사용하기"
    FontFamily="나눔고딕코딩"
    FontSize="16">
    <Window.Resources>
        <Style x:Key="ListViewStyleKey" TargetType="{x:Type ListView}">
            <Setter Property="VirtualizingStackPanel.IsVirtualizing"     Value="True"      />
            <Setter Property="VirtualizingStackPanel.VirtualizationMode" Value="Recycling" />
            <Setter Property="ScrollViewer.IsDeferredScrollingEnabled"   Value="True"      />
            <Setter Property="ListView.ItemsSource"                      Value="{Binding}" />
            <Setter Property="ListView.View">
                <Setter.Value>
                    <GridView>
                        <GridViewColumn Header="ID"
                            Width="100">
                            <GridViewColumn.CellTemplate>
                                <DataTemplate>
                                    <TextBlock Text="{Binding ID}" />
                                </DataTemplate>
                            </GridViewColumn.CellTemplate>
                        </GridViewColumn>
                        <GridViewColumn Header="성명"
                            Width="150">
                            <GridViewColumn.CellTemplate>
                                <DataTemplate>
                                    <TextBlock Text="{Binding Name}" />
                                </DataTemplate>
                            </GridViewColumn.CellTemplate>
                        </GridViewColumn>
                    </GridView>
                </Setter.Value>
            </Setter>
            <Style.Triggers>
                <DataTrigger Binding="{Binding IsLoading}" Value="True">
                    <Setter Property="ListView.Cursor"     Value="Wait"      />
                    <Setter Property="ListView.Background" Value="LightGray" />
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </Window.Resources>
    <Grid Margin="10">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*"    />
        </Grid.RowDefinitions>
        <GroupBox Header="항목 공급자" Grid.Row="0">
            <StackPanel
                Orientation="Horizontal"
                Margin="0 10 0 0">
                <TextBlock
                    VerticalAlignment="Center"
                    Margin="10 0 0 0"
                    TextAlignment="Right"
                    Text="항목 수 : " />
                <TextBox Name="itemCountTextBox"
                    VerticalAlignment="Center"
                    Width="70"
                    Height="25"
                    HorizontalContentAlignment="Right"
                    VerticalContentAlignment="Center"
                    Text="1000000" />
                <TextBlock
                    VerticalAlignment="Center"
                    Margin="10 0 0 0"
                    TextAlignment="Right"
                    Text="조회 시간 (밀리초) : " />
                <TextBox Name="fetchDelayTextBox"
                    VerticalAlignment="Center"
                    Width="50"
                    Height="25"
                    HorizontalContentAlignment="Right"
                    VerticalContentAlignment="Center"
                    Text="1000" />
            </StackPanel>
        </GroupBox>
        <GroupBox Header="컬렉션" Grid.Row="1"
            Margin="0 10 0 0">
            <StackPanel Margin="10 10 0 0">
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="타입 :"
                        VerticalAlignment="Center"
                        TextAlignment="Right" />
                    <RadioButton Name="normalRadioButton"
                        VerticalAlignment="Center"
                        Margin="10 0 0 0"
                        GroupName="rbGroup"
                        Content="List&lt;T&gt;" />
                    <RadioButton Name="virtualizingRadioButton"
                        VerticalAlignment="Center"
                        Margin="10 0 0 0"
                        GroupName="rbGroup"
                        Content="VirtualizingList&lt;T&gt;" />
                    <RadioButton Name="asyncRadioButton"
                        VerticalAlignment="Center"
                        Margin="10 0 0 0"
                        GroupName="rbGroup"
                        IsChecked="True"
                        Content="AsyncVirtualizingList&lt;T&gt;" />
                </StackPanel>
                <StackPanel
                    Margin="0 10 0 0"
                    Orientation="Horizontal">
                    <TextBlock
                        VerticalAlignment="Center"
                        TextAlignment="Right"
                        Text="페이지 크기 : " />
                    <TextBox Name="pageSizeTextBox"
                        VerticalAlignment="Center"
                        Width="40"
                        Height="25"
                        HorizontalContentAlignment="Right"
                        VerticalContentAlignment="Center"
                        Text="100" />
                    <TextBlock
                        VerticalAlignment="Center"
                        Margin="10 0 0 0"
                        TextAlignment="Right"
                        Text="페이지 타임아웃 (초) : " />
                    <TextBox Name="pageTimeoutTextBox"
                        VerticalAlignment="Center"
                        Width="30"
                        Height="25"
                        HorizontalContentAlignment="Right"
                        VerticalContentAlignment="Center"
                        Text="30" />
                </StackPanel>
             </StackPanel>
        </GroupBox>
        <StackPanel Grid.Row="2"
            Margin="0 10 0 0"
            Orientation="Horizontal">
            <TextBlock
                Margin="10 0 0 0"
                VerticalAlignment="Center"
                Text="메모리 사용량 :" />
            <TextBlock Name="memoryUsageTextBox"
                VerticalAlignment="Center"
                Margin="10 0 0 0"
                MinWidth="40" />
            <Button
                VerticalAlignment="Center"
                Margin="10 0 0 0"
                Width="100"
                Height="30"
                Content="갱신"
                Click="refreshButton_Click" />
            <Rectangle Name="rectangle"
                VerticalAlignment="Center"
                Margin="10 0 0 0"
                Width="20"
                Height="20"
                Fill="Blue">
                <Rectangle.RenderTransform>
                    <RotateTransform
                        CenterX="10"
                        CenterY="10"
                        Angle="0" />
                </Rectangle.RenderTransform>
                <Rectangle.Triggers>
                    <EventTrigger RoutedEvent="Rectangle.Loaded">
                        <BeginStoryboard>
                            <Storyboard>
                                <DoubleAnimation
                                    Storyboard.TargetName="rectangle"
                                    Storyboard.TargetProperty="(TextBlock.RenderTransform).(RotateTransform.Angle)"
                                    From="0"
                                    To="360"
                                    Duration="0:0:5"
                                    RepeatBehavior="Forever" />
                            </Storyboard>
                        </BeginStoryboard>
                    </EventTrigger>
                </Rectangle.Triggers>
            </Rectangle>
            <TextBlock
                Margin="10 0 0 0"
                VerticalAlignment="Center"
                FontStyle="Italic"
                FontSize="12"
                Text="애니메이션 일시 중지는 UI 스레드가 중단되었음을 나타냅니다." />
        </StackPanel>
        <ListView Grid.Row="3"
            Style="{DynamicResource ListViewStyleKey}"
            Margin="0 10 0 0" />
    </Grid>
</Window>

 

▶ MainWindow.xaml.cs

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Threading;

namespace TestProject
{
    /// <summary>
    /// 메인 윈도우
    /// </summary>
    public partial class MainWindow : Window
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 생성자 - MainWindow()

        /// <summary>
        /// 생성자
        /// </summary>
        public MainWindow()
        {
            InitializeComponent();
            
            DispatcherTimer timer = new DispatcherTimer();

            timer.Interval = new TimeSpan(0, 0, 1);

            timer.Tick += timer_Tick;

            timer.Start();
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region 타이머 틱 처리하기 - timer_Tick(sender, e)

        /// <summary>
        /// 타이머 틱 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private void timer_Tick(object sender, EventArgs e)
        {
            this.memoryUsageTextBox.Text = string.Format("{0:0.00} MB", GC.GetTotalMemory(true) / 1024.0 / 1024.0);
        }

        #endregion
        #region 갱신 버튼 클릭시 처리하기 - refreshButton_Click(sender, e)

        /// <summary>
        /// 갱신 버튼 클릭시 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private void refreshButton_Click(object sender, RoutedEventArgs e)
        {
            int itemCount  = int.Parse(this.itemCountTextBox.Text );
            int fetchDelay = int.Parse(this.fetchDelayTextBox.Text);

            CustomerProvider customerProvider = new CustomerProvider(itemCount, fetchDelay);

            int pageSize    = int.Parse(this.pageSizeTextBox.Text   );
            int pageTimeout = int.Parse(this.pageTimeoutTextBox.Text);

            if(this.normalRadioButton.IsChecked.Value)
            {
                DataContext = new List<Customer>(customerProvider.FetchRange(0, customerProvider.FetchCount()));
            }
            else if(this.virtualizingRadioButton.IsChecked.Value)
            {
                DataContext = new VirtualizingCollection<Customer>(customerProvider, pageSize);
            }
            else if(this.asyncRadioButton.IsChecked.Value)
            {
                DataContext = new AsyncVirtualizingCollection<Customer>(customerProvider, pageSize, pageTimeout * 1000);
            }
        }

        #endregion
    }
}

※ .NET 버전 3.5에서만 작동함에 주의한다.

728x90
그리드형(광고전용)
Posted by icodebroker
,