메모리 오버헤드가 적은 Xamarin.Forms의 큰 SQLite 테이블을 원활하게 스크롤합니다.
이미 Infragistics' Xamarin.Forms/Xamarin.Android/Xamarin.iOS DataGrid(XamDataGrid)로 작업한 적이 있다면 몇 가지 매우 깔끔한 트릭을 알고 있음을 알게 될 것입니다.
원격 OData 서비스에 바인딩하고 행을 스크롤할 수 있으며, 이동 속도를 사용하여 데이터를 가져와야 할 시기를 예측하고 도달하기 전에 원활하게 로드해야 합니다. 아직 이 트릭을 못했다면 이것을 잘 보여주는 Ultimate UI for Xamarin 용 샘플 브라우저를 확인하십시오.
Running the Sample
이 문서에서 빌드할 샘플을 여기에서 가져올 수 있습니다. 샘플을 열면 로컬 리포지토리에 평가판 또는 RTM Nuget 패키지가 있는지 확인하고 Nuget 패키지를 복원해야 합니다.
다른 데이터 소스에 대한 액세스 가상화
샘플 브라우저 및 설명서의 원격 데이터 샘플은 원격 OData 서비스를 그리드에 로드하는 방법에 대한 많은 세부 정보를 제공합니다. 그러나 우리는 거기서 멈추지 않았으며, 또한 다른 유형의 원격 또는 로컬 데이터에 대한 액세스를 가상화하기 위해 고유한 사용자 지정 버전을 VirtualDataSource
만들 수도 있습니다. 실제로 최근에 고객들이 테이블의 모든 데이터를 메모리에 먼저 로드하지 않고 SQLite 데이터베이스와 함께 데이터 그리드를 사용할 수 있는지 문의했습니다. 그리드의 ItemsSource 속성에 일부 컬렉션을 직접 제공하려는 경우 이 작업이 필요하지만 확장하는 VirtualDataSource
경우 더 나은 방법이 있습니다. 운이 좋게도 나는 이미 그것을했다.
해당 프로젝트를 빌드하면 SQLite 특정 버전이 VirtualDataSource
생성됩니다. 이렇게 하면 테이블 또는 조인된 테이블 집합에 연결할 수 있으며, 끊어지지 않은 큰 연속 컬렉션을 스크롤하는 것처럼 원활하게 페이징할 수 있습니다. 더 좋은 점은 데이터 소스가 한 번에 메모리에 보유하는 데이터 페이지의 양을 제한할 수 있으므로 모바일 애플리케이션의 메모리 사용량에 상한을 설정할 수 있다는 것입니다.
SQLite Database Setup
자, 그럼 실행해 보겠습니다. 를 XamDataGrid
사용하여 Xamarin.Forms 프로젝트를 설정한 경우 먼저 Android 앱 및 iOS 앱에 SQLite 데이터베이스를 추가해야 합니다. Android 앱의 경우 다음과 같은 자산에 포함됩니다.
데이터베이스의 경우는 Build Action
다음과 같이 표시되어야 합니다. AndroidAsset
이를 감안할 때 이 논리는 Xamarin.Forms가 초기화되기 직전과 주 앱이 만들어지기 직전에 배치 MainActivity.cs
되면 런타임에 애플리케이션에서 SQLite 데이터베이스에 액세스할 수 있도록 합니다.
string targetPath = System.Environment.GetFolderPath( System.Environment.SpecialFolder.Personal ); var path = Path.Combine( targetPath, "chinook.db"); if (!File.Exists(path)) { using (Stream input = Assets.Open("chinook.db")) { using (var fs = new FileStream( path, FileMode.Create)) { input.CopyTo(fs); } } }
iOS의 경우 응용 프로그램의 경우 데이터베이스 파일을 배치해야 합니다. Resources
And make sure that the Build Action
is set to BundleResource
:
데이터베이스 파일이 제대로 포함되는 경우 이 논리는 Xamarin.Forms가 초기화되기 직전과 주 앱이 만들어지기 전에 배치 AppDelegate.cs
될 때 런타임에 iOS 애플리케이션에서 액세스할 수 있도록 합니다.
var targetPath = Environment.GetFolderPath( Environment.SpecialFolder.Personal); targetPath = Path.Combine(targetPath, "..", "Library"); var path = Path.Combine(targetPath, "chinook.db"); if (!File.Exists(path)) { var bundlePath = NSBundle.MainBundle.PathForResource( "chinook", "db" ); File.Copy(bundlePath, path); }
두 플랫폼 모두에서 SQLite 데이터베이스의 파일 경로는 이제 생성될 때 Xamarin.Forms App
에 전달될 수 있습니다.
LoadApplication(new App(path));
그런 다음 앱은 사용할 페이지에서 경로를 사용할 수 있는지 확인합니다.
public App(string dbPath) { InitializeComponent(); MainPage = new SQLDemo.MainPage(dbPath); }
SQLite 테이블을 통한 실시간 가상 스크롤
SQLite 데이터베이스에서 데이터를 읽으려면 먼저 PCL(이식 가능한 클래스 라이브러리) 및/또는 Xamarin.Android/Xamarin.iOS와 호환되는 SQLite 클라이언트가 필요하므로 sqlite-net-pcl
Nuget 패키지.
SQLite.NET 라이브러리에는 POCO 유형으로 읽는 데이터를 하이드레이션하는 데 사용할 경량 ORM 도구가 포함되어 있으므로 먼저 관심 있는 테이블에 대한 POCO 유형을 만들어야 합니다.
using SQLite; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace SQLDemo.Data { [Table("tracks")] public class Track { [PrimaryKey, AutoIncrement] public int TrackId { get; set; } [MaxLength(200)] public string Name { get; set; } public int AlbumId { get; set; } [Column("Title")] public string AlbumTitle { get; set; } public int MediaTypeId { get; set; } public int GenreId { get; set; } [MaxLength(220)] public string Composer { get; set; } public int Milliseconds { get; set; } public int Bytes { get; set; } public decimal UnitPrice { get; set; } } }
이 형식은 인기 있는 음악 앨범의 다양한 트랙에 대한 샘플 데이터를 저장하는 Chinook SQLite 샘플 데이터베이스의 테이블에 매핑 tracks
됩니다. 여기에서 속성을 통해 기본 키와 같은 테이블에 대한 다양한 메타 정보와 일부 문자열 열의 최대 길이를 표시했습니다.
이제 테이블에서 데이터를 로드할 tracks
수 있으므로 에서 해당 테이블을 스크롤할 수 있습니다. XamDataGrid
먼저 XAML에서 그리드를 배치할 수 있습니다.
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-namespace:SQLDemo" x:Class="SQLDemo.MainPage" xmlns:igGrid="clr-namespace:Infragistics.XamarinForms.Controls.Grids;assembly=Infragistics.XF.DataGrid"> <Grid> <Grid.RowDefinitions> <RowDefinition /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <igGrid:XamDataGrid x:Name="grid" RowHeight="90" SelectionMode="MultipleRow" HeaderClickAction="SortByMultipleColumnsTriState" AutoGenerateColumns="False"> <igGrid:XamDataGrid.Columns> <igGrid:TextColumn PropertyPath="Name" LineBreakMode="WordWrap" Width="1*" /> <igGrid:TextColumn PropertyPath="Composer" LineBreakMode="Ellipsis" Width="1.25*"/> <igGrid:TextColumn PropertyPath="AlbumTitle" HeaderText="Album Title" LineBreakMode="WordWrap" Width="1*"/> <igGrid:NumericColumn PropertyPath="UnitPrice" HeaderText="Unit Price" MinFractionDigits="2" Width="1*"/> </igGrid:XamDataGrid.Columns> </igGrid:XamDataGrid> </Grid> </ContentPage>
XAML에서는 마치 메모리의 일부 데이터를 그리드에 바인딩하려는 것처럼 a XamDataGrid
를 정의하고 일부 열을 구성했습니다. 열 정의를 건너뛰고 자동 생성을 허용할 수 있었지만 테이블에 충분한 수의 열이 tracks
있어 꽤 복잡해질 수 있습니다.
그렇다면, SQLite 테이블에 그리드를 어떻게 바인딩할까요? 먼저 SQLite 데이터베이스와 통신하기 위한 연결을 만들어야 합니다.
_connection = new SQLiteAsyncConnection(dbPath);
여기서 dbPath는 이전에 전달한 SQLite 데이터베이스의 파일 경로입니다. 그런 다음 SQLiteVirtualDataSource를 만들고 구성한 다음 그리드에 할당하기만 하면 됩니다.
var dataSource = new SQLiteVirtualDataSource(); dataSource.Connection = _connection; dataSource.TableExpression = "tracks left outer join albums on tracks.AlbumId = albums.AlbumId"; dataSource.ProjectionType = typeof(Track); grid.ItemsSource = dataSource;
여기 우리가:
- 가상 데이터 소스에 대해 만든 연결을 제공합니다.
- 가상 데이터 소스에 테이블 표현식을 제공하여 데이터를 가져올 테이블을 나타냅니다.
- 데이터 행을 하이드레이션하기 위해 만든 POCO 형식을 나타냅니다.
TableExpression
에서 우리는 단순히 제공할 tracks
수도 있었지만 이 예에서는 앨범 제목을 조회하여 속성에 채울 수 있도록 앨범 테이블에 대한 조인을 AlbumTitle
만듭니다.
그리고 그게 다야! 앱을 실행하면 테이블을 하나의 긴 연속 레코드 집합인 것처럼 스크롤할 수 있음을 알 수 있습니다. 그러나 실제로는 테이블의 일부만 한 번에 장치의 메모리에 있습니다. 로드되기 전에 일부 레코드에 도달하는 시나리오를 볼 수 있을 만큼 빠르게 스크롤하는 데 문제가 있을 수 있는데, 그리드가 실제로 커버 아래에서 레코드를 예측적으로 로드하기 때문입니다. 다음과 같습니다.

그리드가 따라잡는 것을 볼 수 있지만 열 헤더를 탭하여 그리드 종류를 변경하면 됩니다. 이로 인해 현재 클라이언트 측 데이터가 무효화되고 요청에 따라 정렬 된 새 데이터를 가져 오지만 다시 필요한만큼만 가져옵니다.
일부 필터링 추가
좋아, 그걸 가지고 좀 더 멋지게 만들어 볼까요? 먼저 페이지에 대한 XAML의 그리드에 다음을 추가합니다.
<StackLayout Orientation="Horizontal" Grid.Row="1"> <Label Text="Filter" /> <Entry TextChanged="Entry_TextChanged" WidthRequest="300" /> </StackLayout>
이 태그에는 입력 필드가 추가되어 표시되는 테이블을 필터링하는 데 사용할 필터 값을 수집할 수 있습니다. 항목의 텍스트가 변경될 때마다 이벤트가 발생합니다. 이제 코드 숨김에 이에 대한 핸들러를 추가해 보겠습니다.
private void Entry_TextChanged(object sender, TextChangedEventArgs e) { if (String.IsNullOrEmpty(e.NewTextValue)) { grid.FilterExpressions.Clear(); } else { grid.FilterExpressions.Clear(); grid.FilterExpressions.Add(FilterFactory.Build( (f) => { return f.Property("Name").Contains(e.NewTextValue) .Or(f.Property("AlbumTitle").Contains(e.NewTextValue)) .Or(f.Property("Composer").Contains(e.NewTextValue)); })); } }
이 코드는 입력 필드가 비어 있으면 그리드 필터를 지우지만, 그렇지 않으면 Name 또는 AlbumTitle 또는 Composer 가 제공된 문자열과 일치하는지 확인하고 SQLite에 전달된 쿼리에서 필터가 사용되는지 확인하는 필터를 빌드합니다.
이제 샘플의 모양은 다음과 같습니다.

보시다시피 문자를 입력할 때마다 로컬 그리드는 새로 필터링된 콘텐츠로 콘텐츠를 새로 고쳐야 하며, 그런 다음 전체를 스크롤할 수 있습니다.
"빨리 쓰기" 및 "빨리 실행" 레슨과 비디오를 확인하여 더 자세히 알아볼 수 있습니다. 또한 Infragistics Ultimate UI for Xamarin의 무료 평가판을 다운로드하고 싶을 것입니다.
Graham Murray는 소프트웨어 아키텍트이자 작가입니다. 그는 데스크톱, 웹 및 모바일을 아우르는 Infragistics를 위한 고성능 크로스 플랫폼 UI 구성 요소를 구축합니다. Twitter에서 그를 팔로우하세요 @the_graham.