跳至正文
首页 » 博客 » Scroll Seamlessly Through Large SQLite Tables in Xamarin.Forms with Low Memory Overhead

Scroll Seamlessly Through Large SQLite Tables in Xamarin.Forms with Low Memory Overhead

如果你已经使用过infragistics的Xamarin.Forms/Xamarin.Android/Xamarin.iOS DataGrid (XamDataGrid) ,你会发现它知道一些非常巧妙的技巧。您可以将其绑定到远程OData服务并滚动浏览行 ,它将使用您的移动速度来预测何时需要获取数据并在您到达之前无缝加载它。如果你还没有看到这个技巧,一定要看看我们的终极UI Xamarin示例浏览器,它很好地展示了这一点。

运行示例

您可以在此处获得我们将在本文中构建的示例。打开示例后,您需要确保在本地存储库中具有我们的试用版或RTM Nuget软件包,并恢复Nuget软件包

虚拟化访问其他数据源

我们的示例浏览器和文档中的远程数据示例为您提供了有关如何将远程OData服务加载到网格中的大量详细信息。但我们并没有止步于此,此外,您还可以创建自己的自定义VirtualDataSource版本,以虚拟化对其他类型的远程甚至本地数据的访问。事实上,最近,客户问我们是否可以将我们的数据网格与SQLite数据库一起使用,而无需先将表的所有数据加载到内存中。如果您想直接向网格上的ItemsSource属性提供一些集合,这将是必需的,但是如果您扩展VirtualDataSource ,则有更好的方法。你很幸运我已经做到了.

如果您构建该项目,您将使用我们的VirtualDataSource的SQLite特定版本。这允许链接到一个表或一组联接的表,然后允许您无缝地翻页,就像您正在滚动一个大的、不间断的连续集合一样。更好的是,您可以限制数据源一次在内存中保留的数据页的数量,因此您可以在移动应用程序中设置内存使用的上限。

SQLite数据库设置

好,让我们把它付诸实践。鉴于您有一个使用XamDataGrid设置的Xamarin.Forms项目,您首先需要将SQLite数据库添加到Android应用程序和iOS应用程序。对于Android应用程序,这将在资产:

数据库的生成操作应标记为AndroidAsset:

考虑到这一点,这个逻辑,当放置在MainActivity.cs中,在Xamarin.Forms初始化之前和创建主应用程序之前,将确保SQLite数据库在运行时可供应用程序访问:

字符串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,您应该将数据库文件放在应用程序的资源中:

并确保构建操作设置为BundleResource:

鉴于数据库文件被正确包含,这个逻辑,当放置在AppDelegate.cs中,在Xamarin.Forms初始化之前和创建主应用程序之前,将确保它在运行时可以访问iOS应用程序:

var targetPath = Environment.GetFolderPath( 
Environment.SpecialFolder.Personal);
targetPath = Path.Combine(targetPath, "..","Library" );

var path = Path.Combine(targetPath, “chinook.db” );
如果 (!File.Exists(path))
{
var bundlePath = NSBundle.MainBundle.PathForResource(
"chinook",
"db"
);
File.Copy(bundlePath, path);
}

对于这两个平台,SQLite数据库的文件路径现在可以在创建时传递到Xamarin.Forms应用程序:

LoadApplication( 应用程序 (路径)); 

然后,应用程序将确保路径可用于我们将使用的页面:

public App (string dbPath) 
{
InitializeComponent();

MainPage = new SQLDemo.MainPage(dbPath);
}

实时虚拟滚动通过SQLite表

要从SQLite数据库读取数据,首先您需要一个与PCL (可移植类库) 和/或Xamarin.Android/Xamarin.iOS兼容的SQLite客户端,因此我们将安装Nuget包sqlite-net-pcl

SQLite.NET库包含一个轻量级的ORM工具 ,它将用于将数据读入POCO类型,因此我们首先需要为我们感兴趣的表创建一个POCO类型:

使用SQLite的

; 使用System的
; 使用System.Collections.Generic的
; 使用System.Linq的
; 使用System.Text的
; 使用System.Threading.Tasks的
SQLDemo.Data


命名空间 {
[Table( "tracks" )]
publicTrack
{
[PrimaryKey,AutoIncrement]
public int TrackId {get; set; }

[MaxLength( 200 )]
公共字符串名称 {get; set; }

公共int AlbumId {get; set; }

[Column( "Title" )]
公共字符串AlbumTitle {get; set; }

public int MediaTypeId {get; set; }

public int GenreId {get; set; }

[MaxLength( 220 )]
public string Composer {get; set; }

public int毫秒 {getset; }

public int Bytes {get; set; }

public decimal UnitPrice {get; set; }
}
}

此类型映射到Chinook SQLite示例数据库中的曲目表,该数据库存储有关流行音乐专辑中各种曲目的示例数据。我们在这里通过属性指出了有关表的各种元信息,例如主键和某些字符串列的最大长度。

现在可以从tracks表加载数据,我们都设置为在XamDataGrid中滚动该表。

首先,我们可以在XAML中布置网格:

<?xml版本 ="1.0"编码 ="utf-8" ?>
<ContentPage xmlns=" http://xamarin.com/schemas/ 2014/表格"
xmlns:x=" http://schemas.microsoft.com/winfx/ 2009/xaml"
xmlns: 本地="clr-namespace:SQLDemo"
x: 类="SQLDemo.MainPage"
xmlns:igGrid="clr-namespace:Infragistics.XamarinForms.Controls.Grids;assembly = Infragistics.XF.DataGrid">


<网格>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition 高度="自动"/>
Grid.RowDefinitions>

<igGrid:XamDataGrid x: 名称="网格" RowHeight="90"
选择模式="多倍"
HeaderClickAction="排序"
AutoGenerateColumns="假">

<igGrid:XamDataGrid.Columns>
<igGrid:TextColumn PropertyPath="名称"
LineBreakMode="自动换行"
& nbsp;宽度="1 *"
/>

<igGrid:TextColumn PropertyPath="作曲家"
LineBreakMode="省略号"
宽度="1.25"/>

<igGrid:TextColumn PropertyPath="AlbumTitle"
HeaderText="专辑名称"
LineBreakMode="自动换行"
宽度="1 *"/>

<igGrid:NumericColumn PropertyPath="单位价格"
HeaderText="单价"
MinFractionDigits="2"
宽度="1 *"/>

igGrid:XamDataGrid.Columns>
igGrid:XamDataGrid>
网格>

ContentPage>

在XAML中,我们定义了一个XamDataGrid ,并配置了一些列,就像我们要将内存中的一些数据绑定到网格一样。我们可以跳过定义列并允许它们自动生成,但是tracks表上有足够数量的列,这将变得非常拥挤。

好,那么我们如何将网格绑定到SQLite表呢?首先,我们需要创建一个连接到SQLite数据库:

_connection = 的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中,我们可以简单地提供曲目 ,但是此示例针对专辑表创建了一个联接,以查找专辑标题,以便可以在AlbumTitle属性中填充它们。

就这样!如果你运行应用程序,你会看到你可以滚动通过表,如果它只是一个长连续的记录集。但是,实际上,只有一小部分表一次位于设备的内存中。您可能无法快速滚动以查看在加载之前获取某些记录的场景,因为网格实际上会预测地将它们加载到封面下。下面是它的样子:

但是,如果您通过点击列标题来更改网格的排序,则可以看到网格正在追赶。这将导致当前客户端数据无效,并按照您的请求进行排序的新数据将被提取,但同样,仅在必要时提取。

添加一些过滤

好,让我们把它变得更有趣,好吗?首先,将其添加到页面的XAML中的网格中:

<StackLayout 方向="水平" Grid.Row="1">
<标签 文本="筛选器"/>
<进入 文本已更改="Entry_TextChanged" WidthRequest="300"/>
StackLayout>

该标记添加了一个输入字段,以便我们可以收集过滤器值来过滤我们显示的表。每当条目的文本发生更改时,都会触发事件。因此,让我们将处理程序添加到后面的代码中:

{
if (String.IsNullOrEmpty(e.NewTextValue)) {
grid.FilterExpressions.Clear();

private void条目 _textchanged (object sender,TextChangedEventArgs e)

}
else
{
grid.FilterExpressions.Clear();
grid.FilterExpressions.Add(FilterFactory.Build(
(f) =>
{
返回f.Property( "Name" )).Contains(e.NewTextValue)
.Or(f.Property( "AlbumTitle)).Contains(e.NewTextValue))
。或 (f.Property( “ Composer ” )。Contains(e.NewTextValue));
}));
}
}

如果输入字段变为空白,此代码将清除网格过滤器,否则将构建一个过滤器,以查看Name或AlbumTitle或Composer是否与提供的字符串匹配,并确保在传递给SQLite的查询中使用过滤器。

下面是示例现在的样子:

如您所见,每次键入字母时,本地网格都需要使用新的过滤内容刷新其内容,然后您可以完整地滚动这些内容。

您可以通过查看我们的 “ 快速书写” 和 “ 快速运行” 课程和视频了解更多信息。你还需要确保下载Infragistics Ultimate UI for Xamarin的免费试用版。

格雷厄姆·穆雷是软件架构师和作者。他为Infragistics构建了高性能的跨平台UI组件,涵盖了桌面、web和移动设备。在Twitter上关注他@ the_graham</p