ListView uses on-demand loading to load items as the user scrolls through the items and pages. The ListView control supports this feature by means of C1DataCollection. Loading items or pages on demand improves the application's performance by requesting less data at a given time.
The following example loads YouTube videos on-demand when the user searches for a video in a textbox.
C# |
コードのコピー
|
---|---|
using System; using System.Collections.Generic; using System.Globalization; using System.Net.Http; using System.Runtime.Serialization; using System.Runtime.Serialization.Json; using System.Threading; using System.Threading.Tasks; using C1.DataCollection; namespace OnDemand { public class YouTubeDataCollection : C1CursorDataCollection<YouTubeVideo> { private string _q; private string _orderBy = "relevance"; public YouTubeDataCollection() { PageSize = 20; } public int PageSize { get; set; } public override bool HasMoreItems { get { return _q != null && base.HasMoreItems; } } private SemaphoreSlim _searchSemaphore = new SemaphoreSlim(1); public async Task SearchAsync(string q) { //フィルタ文字列を設定し、遅延時間を待ちます。 _q = q; try { await _searchSemaphore.WaitAsync(); if (_q != q)//文字列が変更された場合は、別のキーストロークがあったことを意味します。 return; await RefreshAsync(); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex.Message); } finally { _searchSemaphore.Release(); } } public async Task OrderAsync(string orderBy) { _orderBy = orderBy; if (_q != null) await RefreshAsync(); } protected override async Task<Tuple<string, IReadOnlyList<YouTubeVideo>>> GetPageAsync(int startingIndex, string pageToken, int? count = null, IReadOnlyList<SortDescription> sortDescriptions = null, FilterExpression filterExpresssion = null, CancellationToken cancellationToken = default(CancellationToken)) { return await LoadVideosAsync(_q, "date", pageToken, PageSize, cancellationToken); } public static async Task<Tuple<string, IReadOnlyList<YouTubeVideo>>> LoadVideosAsync(string q, string orderBy, string pageToken, int maxResults, CancellationToken cancellationToken = default(CancellationToken)) { if (string.IsNullOrWhiteSpace(q)) return new Tuple<string, IReadOnlyList<YouTubeVideo>>(default, new YouTubeVideo[0]); var youtubeUrl = string.Format("https://www.googleapis.com/youtube/v3/search?part=snippet&type=video&q={0}&order={1}&maxResults={2}{3}&key={4}", Uri.EscapeUriString(q), orderBy, maxResults, string.IsNullOrWhiteSpace(pageToken) ? "" : "&pageToken=" + pageToken, "AIzaSyCtwKIq-Td5FBNOlvOiWEJaClRBDyq-ZsU"); var client = new HttpClient(); var response = await client.GetAsync(youtubeUrl, cancellationToken); if (response.IsSuccessStatusCode) { var videos = new List<YouTubeVideo>(); var serializer = new DataContractJsonSerializer(typeof(YouTubeSearchResult)); var result = serializer.ReadObject(await response.Content.ReadAsStreamAsync()) as YouTubeSearchResult; foreach (var item in result.Items) { var video = new YouTubeVideo() { Title = item.Snippet.Title, Description = item.Snippet.Description, Thumbnail = item.Snippet.Thumbnails.Default.Url, Link = "http://www.youtube.com/watch?v=" + item.Id.VideoId, ChannelTitle = item.Snippet.ChannelTitle, PublishedAt = DateTime.Parse(item.Snippet.PublishedAt), }; videos.Add(video); } return new Tuple<string, IReadOnlyList<YouTubeVideo>>(result.NextPageToken, videos); } else { throw new Exception(await response.Content.ReadAsStringAsync()); } } } public class YouTubeSearchResult { [DataMember(Name = "nextPageToken")] public string NextPageToken { get; set; } [DataMember(Name = "items")] public YouTubeSearchItemResult[] Items { get; set; } } public class YouTubeSearchItemResult { [DataMember(Name = "id")] public YouTubeVideoId Id { get; set; } [DataMember(Name = "snippet")] public YouTubeSnippet Snippet { get; set; } } public class YouTubeVideoId { [DataMember(Name = "videoId")] public string VideoId { get; set; } } [DataContract] public class YouTubeSnippet { [DataMember(Name = "title")] public string Title { get; set; } [DataMember(Name = "description")] public string Description { get; set; } [DataMember(Name = "thumbnails")] public YouTubeThumbnails Thumbnails { get; set; } [DataMember(Name = "channelTitle")] public string ChannelTitle { get; set; } [DataMember(Name = "publishedAt")] public string PublishedAt { get; set; } } public class YouTubeThumbnails { [DataMember(Name = "default")] public YouTubeThumbnail Default { get; set; } [DataMember(Name = "medium")] public YouTubeThumbnail Medium { get; set; } [DataMember(Name = "high")] public YouTubeThumbnail High { get; set; } } public class YouTubeThumbnail { [DataMember(Name = "url")] public string Url { get; set; } } public class YouTubeVideo { public string Title { get; set; } public string Description { get; set; } public string Thumbnail { get; set; } public string Link { get; set; } public string ChannelTitle { get; set; } public DateTime PublishedAt { get; set; } public string PublishedDay { get { var today = DateTime.Today; var publishedDate = PublishedAt.Date; var firstDayOfThisWeek = today.AddDays(CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek - today.DayOfWeek); var firstDayOfLastWeek = firstDayOfThisWeek - TimeSpan.FromDays(7); var firstDayOfTwoWeeksAgo = firstDayOfThisWeek - TimeSpan.FromDays(14); var firstDayOfThreeWeeksAgo = firstDayOfThisWeek - TimeSpan.FromDays(21); var firstDayOfThisMonth = new DateTime(today.Year, today.Month, 1); var firstDayOfLastMonth = today.Month == 1 ? new DateTime(today.Year - 1, 12, 1) : new DateTime(today.Year, today.Month - 1, 1); if (publishedDate == today) { return "Today"; } else if (today.Subtract(publishedDate).TotalDays == 1) { return "Yesterday"; } else if (publishedDate >= firstDayOfThisWeek) { return publishedDate.DayOfWeek.ToString(); } else if (publishedDate >= firstDayOfLastWeek) { return "Last Week"; } else if (publishedDate >= firstDayOfTwoWeeksAgo) { return "Two Weeks Ago"; } else if (publishedDate >= firstDayOfThreeWeeksAgo) { return "Three Weeks Ago"; } else if (publishedDate >= firstDayOfThisMonth) { return "This Month"; } else if (publishedDate >= firstDayOfLastMonth) { return "Last Month"; } else if (publishedDate >= new DateTime(today.Year, 1, 1)) { return "This Year"; } else if (publishedDate >= new DateTime(today.Year - 1, 1, 1)) { return "Last Year"; } else if (publishedDate >= new DateTime(today.Year - 1, 1, 1)) { return "Two Years Ago"; } else { return "Older"; } } } } } |
Razor |
コードのコピー
|
---|---|
@inject IJSRuntime JsRuntime @using System.Threading @using C1.Blazor.Core @using C1.Blazor.ListView @using C1.Blazor.Input <C1TextBox @bind-Text="Filter" Placeholder="ここに入力して、YouTubeで動画を検索します。" Style="@("width:640px;margin:8px 0")" /> <C1ListView @ref="list" ItemsSource="@videos" T="YouTubeVideo" ItemStyle="@("height:100px")" ItemTapped="OnItemTapped" LoadingItem="OnLoadingItem" Style="@("max-height:50vh")"> <ItemTemplate Context="video"> <table> <tr> <td rowspan="2"><img src="@video.Thumbnail" style="width:120px;height:80px" /> </td> <td style="padding-left:4px;vertical-align:bottom">@video.Title</td> </tr> <tr> <td style="padding-left:4px;font-size:small;color:dimgray;vertical-align:top">@video.ChannelTitle</td> </tr> </table> </ItemTemplate> </C1ListView> @code { C1ListView<YouTubeVideo> list; string filterValue; YouTubeDataCollection videos = new YouTubeDataCollection(); SemaphoreSlim filterSemaphore = new System.Threading.SemaphoreSlim(1); public string Filter { get { return filterValue; } set { filterValue = value; OnFilterChanged(value); } } private async void OnFilterChanged(string filter) { Console.WriteLine($"OnTextChange({filterValue})"); try { filterValue = filter; await Task.Delay(400); await filterSemaphore.WaitAsync(); if (filterValue == filter) { await list.ChangeViewAsync(0); await videos.SearchAsync(filterValue); } } finally { filterSemaphore.Release(); } } private void OnItemTapped(object sender, ListViewInputEventArgs e) { var video = videos[e.Range.Row] as YouTubeVideo; if (video != null) { JsRuntime.InvokeVoidAsync("open", video.Link, "_blank"); } } private void OnLoadingItem(object sender, ListViewItemStyleEventArgs e) { e.ItemStyle["cursor"] = "pointer"; } } |