■ ScrollViewer 클래스의 ViewChanged 이벤트를 사용해 ItemsRepeater 객체에서 데이터를 증분 로드하는 방법을 보여준다.
※ 비주얼 스튜디오에서 TestProject(Unpackaged) 모드로 빌드한다.
※ TestProject.csproj 프로젝트 파일에서 WindowsPackageType 태그를 None으로 추가했다.
▶ MainPage.xaml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
<?xml version="1.0" encoding="utf-8"?> <Page x:Class="TestProject.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" FontFamily="나눔고딕코딩" FontSize="16"> <Grid Margin="10"> <ScrollViewer Name="scrollViewer" BorderThickness="1" BorderBrush="DarkGray"> <StackPanel> <ItemsRepeater ="{x:Bind StringCollection}"> <ItemsRepeater.ItemTemplate> <DataTemplate x:DataType="x:String"> <TextBlock Margin="10" Text="{x:Bind}" /> </DataTemplate> </ItemsRepeater.ItemTemplate> </ItemsRepeater> <ProgressRing Name="progressRing" IsActive="False" Visibility="Collapsed" /> </StackPanel> </ScrollViewer> </Grid> </Page> |
▶ MainPage.xaml.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 |
using System.Collections.ObjectModel; using System.Threading.Tasks; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; namespace TestProject; /// <summary> /// 메인 페이지 /// </summary> public sealed partial class MainPage : Page { //////////////////////////////////////////////////////////////////////////////////////////////////// Field ////////////////////////////////////////////////////////////////////////////////////////// Private #region Field /// <summary> /// 추가 데이터 여부 /// </summary> private bool hasMoreData = true; /// <summary> /// 작업 여부 /// </summary> private bool isBusy = false; /// <summary> /// 현재 페이지 /// </summary> private int currentPage = 0; /// <summary> /// 페이지당 항목 수 /// </summary> private readonly int itemCountPerPage = 10; /// <summary> /// 최대 페이지 수 /// </summary> private readonly int maximumPageCount = 100; #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Property ////////////////////////////////////////////////////////////////////////////////////////// Public #region 문자열 컬렉션 - StringCollection /// <summary> /// 문자열 컬렉션 /// </summary> public ObservableCollection<string> StringCollection { get; } = new ObservableCollection<string>(); #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Public #region 생성자 - MainPage() /// <summary> /// 생성자 /// </summary> public MainPage() { InitializeComponent(); Loaded += Page_Loaded; this.scrollViewer.ViewChanged += scrollViewer_ViewChanged; } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Private //////////////////////////////////////////////////////////////////////////////// Event #region 페이지 로드시 처리하기 - Page_Loaded(sender, e) /// <summary> /// 페이지 로드시 처리하기 /// </summary> /// <param name="sender">이벤트 발생자</param> /// <param name="e">이벤트 인자</param> private async void Page_Loaded(object sender, RoutedEventArgs e) { await LoadInitialDataAsync(); } #endregion #region 스크롤 뷰어 뷰 변경시 처리하기 - scrollViewer_ViewChanged(sender, e) /// <summary> /// 스크롤 뷰어 뷰 변경시 처리하기 /// </summary> /// <param name="sender">이벤트 발생자</param> /// <param name="e">이벤트 인자</param> private async void scrollViewer_ViewChanged(object sender, ScrollViewerViewChangedEventArgs e) { if(!e.IsIntermediate) { ScrollViewer scrollViewer = sender as ScrollViewer; double distanceToEnd = scrollViewer.ExtentHeight - (scrollViewer.VerticalOffset + scrollViewer.ViewportHeight); if(distanceToEnd <= 2.0 * scrollViewer.ViewportHeight && this.hasMoreData && !this.isBusy) { await LoadMoreDataAsync(); } } } #endregion //////////////////////////////////////////////////////////////////////////////// Function #region 추가 데이터 로드하기 (비동기) - LoadMoreDataAsync() /// <summary> /// 추가 데이터 로드하기 (비동기) /// </summary> /// <returns>태스크</returns> private async Task LoadMoreDataAsync() { if(this.isBusy) { return; } this.isBusy = true; this.progressRing.IsActive = true; this.progressRing.Visibility = Visibility.Visible; await Task.Delay(1000); // 데이터 로딩을 시뮬레이션하기 위한 지연 for(int i = 0; i < this.itemCountPerPage; i++) { StringCollection.Add($"항목 {this.currentPage * this.itemCountPerPage + i + 1}"); } this.currentPage++; this.hasMoreData = this.currentPage < this.maximumPageCount; this.progressRing.IsActive = false; this.progressRing.Visibility = Visibility.Collapsed; this.isBusy = false; } #endregion #region 필요시 추가 데이터 로드하기 (비동기) - LoadMoreDataIfNeededAsync() /// <summary> /// 필요시 추가 데이터 로드하기 (비동기) /// </summary> /// <returns></returns> private async Task LoadMoreDataIfNeededAsync() { // ScrollViewer의 ViewportHeight가 0이 아닌 값을 가질 때까지 대기한다. while(this.scrollViewer.ViewportHeight == 0) { await Task.Delay(50); } while(this.scrollViewer.ExtentHeight <= this.scrollViewer.ViewportHeight && this.hasMoreData) { await LoadMoreDataAsync(); } } #endregion #region 초기 데이터 로드하기 (비동기) - LoadInitialDataAsync() /// <summary> /// 초기 데이터 로드하기 (비동기) /// </summary> /// <returns>태스크</returns> private async Task LoadInitialDataAsync() { await LoadMoreDataAsync(); await LoadMoreDataIfNeededAsync(); } #endregion } |