【IT168 专稿】Sterling是一款开源的嵌入式数据库软件。在本篇中,我们将分析如何把Sterling数据库集成到Windows Phone 7程序中及相应的编程技巧和注意事项。
现在,继续上文中的讨论。让我们接着观察如何把上篇中定义的两个数据库表格封装到WP7视图模型中。
一、创建MainViewModel
为了实现更加模块化的设计,我们将创建一个视图模型,用以封装上篇中定义的两个数据库表格。为简单起见,我们主要提供了数据加载方面的支持。使用MainViewModel的另一个原因是下载的示例程序中正是使用了这种系统架构方式。然而,请注意这个例子中并没有利用流行的MVVM设计模式。
列表1:定义主视图模型实现与Sterling数据库层的关联
{
public MainViewModel()
{
this.Groups = new ObservableCollection();
this.Contacts = new ObservableCollection();
}
public ObservableCollection Groups { get; private set; }
public ObservableCollection Contacts { get; private set; }
public bool IsDataLoaded
{
get;
private set;
}
public void LoadData()
{
bool hasKeys = false;
foreach (var item in App.Database.Query())
{
hasKeys = true;
break;
}
bool hasKeys2 = false;
foreach (var item in App.Database.Query())
{
hasKeys2 = true;
break;
}
{
_SetupData();
}
foreach (var item in App.Database.Query())
{
Groups.Add(item.LazyValue.Value);
}
foreach (var item in App.Database.Query())
{
Contacts.Add(item.LazyValue.Value);
}
this.IsDataLoaded = true;
}
private void _SetupData()
{
var groupData = new List()
{
new GroupViewModel() { GroupName = "GP1"},
//others omitted…
new GroupViewModel() { GroupName = "GP10"}
};
foreach (var item in groupData)
{
App.Database.Save(item);
}
var contactData = new List()
{
new ContactViewModel() { GroupId =1,Name="Name11",Email="Name11@hotmail.com",ThumbNail="/WP7SterlingLearning;Component/ThumbNails/11.jpg"},
new ContactViewModel() { GroupId =1,Name="Name12",Email="Name12@hotmail.com",ThumbNail="/WP7SterlingLearning;Component/ThumbNails/12.jpg"},
//others omitted…
new ContactViewModel() { GroupId =10,Name="Name105",Email="Name105@hotmail.com",ThumbNail="/WP7SterlingLearning;Component/ThumbNails/105.jpg"}
};
{
App.Database.Save(item);
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
很容易看出,上面代码的关键在于方法LoadData。在这个方法中,我们首先判断这两个表GroupViewModel和ContactViewModel是否已建立。如果已经建立,我们只需要用简单的Sterling查询操作填充两个集合;否则,我们调用另一个方法_SetupData生成新表中的记录。创建记录简单地对应于创建相关类的实例。最后,我们设置全局变量IsDataLoaded的值以方便随后的判断之用。
到目前为止,所有数据层相关编程已经完成。接下来,我们将介绍如何把Sterling集成到Windows Phone7应用程序中。
二、把Sterling集成到WP7项目中
现在,我们已经定义了一个Sterling数据库、两个表及相应的索引。那么,接下来我们来分析如何把Sterling数据库集成到我们的示例应用程序WP7SterlingLearning中。请注意,WP7SterlingLearning仅是一个普通的Windows Phone7应用程序,没有什么特别之处。从总体上看,关键的问题主要发生在文件App.xaml.cs中。
1.添加所需的组件
若要使用Sterling数据库,首先要添加使用Sterling引擎所需的组件。下面给出关键代码部分:
列表2:添加使用Sterling引擎所需的组件的关键代码
using Wintellect.Sterling;
using WP7SterlingLearning.Database;
namespace WP7SterlingLearning
{
public partial class App : Application
{
private static MainViewModel viewModel = null;
private static ISterlingDatabaseInstance _database = null;
private static SterlingEngine _engine = null;
private static SterlingDefaultLogger _logger = null;
//The MainViewModel object
public static MainViewModel ViewModel
{
get
if (viewModel == null)
viewModel = new MainViewModel();
return viewModel;
}
}
public static ISterlingDatabaseInstance Database
{
get
{
return _database;
}
}
//…
注意,上面的全局静态属性Database的定义有助于在应用程序的各处引用这一数据库。此外,另一个全局静态属性ViewModel也起着类似的作用。
其次,我们定义了两个辅助方法。第一个方法_ActivateEngine旨在当程序首次启动时或者当程序从墓碑(tombstone)事件中唤醒时激活数据库ContactsDatabase。第二个方法_DeactivateEngine用于当应用程序退出或进入到墓碑(tombstone)事件中时停用Sterling引擎。
列表3:方法_ActivateEngine的关键代码部分
{
_engine = new SterlingEngine();
_logger = new SterlingDefaultLogger(SterlingLogLevel.Information);
_engine.Activate();
_database = _engine.SterlingDatabase.RegisterDatabase();
//register triggers
var maxIdx1 =
_database.Query().Any() ?
(from id in _database.Query()
select id.Key).Max() + 1 : 1;
_database.RegisterTrigger(new ContactsDatabase.GroupTrigger(maxIdx1));
var maxIdx2 =
_database.Query().Any() ?
(from id in _database.Query()
select id.Key).Max() + 1 : 1;
_database.RegisterTrigger(new ContactsDatabase.ContactTrigger(maxIdx2));
}
private void _DeactivateEngine()
{
_logger.Detach();
_engine.Dispose();
_database = null;
_engine = null;
}
这里有几点值得注意。首先,我们使用内置的Sterling日志记录器简单地把执行结果输出到调试窗口。事实上,Sterling也支持编写自定义日志记录器并注册到Sterling引擎。然而,有关编写自定义日志记录器的问题已经超出了本文的范围。其次,通过调用方法RegisterDatabase我们把数据库注册到Sterling引擎中。第三,我们把两个先前定义的触发器通过数据库的RegisterTrigger方法注册到Sterling引擎。
2.在WP7生命周期事件中控制Sterling引擎
现在,我们可以使用Windows Phone 7应用程序的四个典型的生命周期事件的编程来调用上面的两个辅助方法。
列表4:在WP7应用程序生命周期事件中控制Sterling引擎
{
_ActivateEngine();
}
private void Application_Activated(object sender, ActivatedEventArgs e)
{
_ActivateEngine();
if (!App.ViewModel.IsDataLoaded)
{
App.ViewModel.LoadData();
}
}
private void Application_Deactivated(object sender, DeactivatedEventArgs e)
{
_DeactivateEngine();
}
private void Application_Closing(object sender, ClosingEventArgs e)
{
_DeactivateEngine();
}
有关Windows Phone 7应用程序的上述四个生命周期事件的解释,请参考其他的Windows Phone 7入门性文章,在此不再赘述。
截至目前,Sterling数据库大部分的相关工作已经完成。随后的任务是把Sterling数据库结合进WP7视图页面的编程中。
三、编写视图页面
现在,让我们来看看如何编写两个示例网页。首先,第一个页面MainPage.xaml的构建非常简单,仅仅使用一个ListBox控件来显示组数据列表。
列表5: 主视图页面XAML关键标记代码
SelectionChanged="MainListBox_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,17" Width="432" Orientation="Horizontal">
<TextBlock Text="{Binding GroupId}" TextWrapping="Wrap"
Style="{StaticResource PhoneTextExtraLargeStyle}"/>
<TextBlock Text="{Binding GroupName}" TextWrapping="Wrap" Margin="12,-
6,12,0" Style="{StaticResource PhoneTextSubtleStyle}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
现在,稍有Siverlight经验的读者应当都会理解上面的标记代码。这里通过使用基本的数据绑定技术(我们使用ListBox)来渲染GroupViewModel实例的每一个属性值。
接下来,让我们看看主视图页面后台代码编写的情况。
列表6:主视图页面后台关键代码
{
public partial class MainPage : PhoneApplicationPage
{
public MainPage()
{
InitializeComponent();
DataContext = App.ViewModel;
this.Loaded += new RoutedEventHandler(MainPage_Loaded);
}
private void MainListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (MainListBox.SelectedIndex == -1)
return;
NavigationService.Navigate(new Uri("/ContactList.xaml?selectedGroup=" + MainListBox.SelectedIndex, UriKind.Relative));
MainListBox.SelectedIndex = -1;
}
private void MainPage_Loaded(object sender, RoutedEventArgs e)
{
if (!App.ViewModel.IsDataLoaded)
{
App.ViewModel.LoadData();
}
}
}
}
首先,在MainPage类的构造函数中,我们把DataContext属性绑定到App.ViewModel。这一点很重要,也展示了整个系统的用户描述层是如何实现的。
接下来,让我们来分析一下MainPage_Loaded方法-每次示例页面加载时都调用这个方法。预先检查一下是否有必要再次加载视图模型数据是一个很好的设计习惯。
最后一点值得注意的是ListBox控件的SelectionChanged事件处理程序。在这个方法中,我们首先判断用户是否点击了ListBox控件中的项目之一。如果没有选中某个项目,即可返回;否则,我们把当前用户导航到另一个网页ContactList.xaml,以显示对应于所选组对应的联系人信息。注意,在页面ContactList.xaml的OnNavigatedTo事件处理器中我们传递进参数selectedGroup。
至于页面ContactList.xaml,有点复杂,但仍然很容易理解。首先,让我们来看一下这个页面的XAML标记代码的情况。
列表7: 页面ContactList.xaml的XAML标记代码
<ListBox x:Name="MainListBox" Margin="0,0,-12,0" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,17" Width="432">
<TextBlock Text="{Binding GroupId}" TextWrapping="Wrap"
Style="{StaticResource PhoneTextExtraLargeStyle}"/>
<TextBlock Text="{Binding Id}" TextWrapping="Wrap" Margin="12,-6,12,0"
Style="{StaticResource PhoneTextSubtleStyle}"/>
<TextBlock Text="{Binding Name}" TextWrapping="Wrap"
Style="{StaticResource PhoneTextExtraLargeStyle}"/>
<TextBlock Text="{Binding Email}" TextWrapping="Wrap" Margin="12,-
6,12,0" Style="{StaticResource PhoneTextSubtleStyle}"/>
ImageConverter1}}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
为了简单起见,我们还利用一个ListBox控件来展示联系人信息。同时,我们也使用了典型的Silverlight数据绑定技术。总体来看,上面代码中只有一点值得注意,即图像数据的显示问题。为此,代码中使用了一个自定义的类型转换器。
列表8: 自定义的类型转换器代码
{
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
if (value == null || parameter == null)
{
return value;
}
return new BitmapImage(new Uri((string)value, UriKind.RelativeOrAbsolute));
}
public object ConvertBack(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
注意,在本示例实现方案中,在构建联系人Contact的实例时,我们只持有在ThumbNail属性中存储的联系人相关缩略图的路径信息。上面转换器的关键在于Convert方法。在这个方法中,我们通过图片路径返回对应于缩略图的BitmapImage实例。然后,我们将它设置为图像控件的Source属性。
至于页面ContactList.xaml后台代码的实现就很简单了,如下所示。
列表9: 页面ContactList.xaml后台代码的实现
{
string selectedGroup = "";
if (NavigationContext.QueryString.TryGetValue("selectedGroup", out selectedGroup))
{
int iGroup = int.Parse(selectedGroup);
// Query from Database by key
var contactList =from key in
App.Database.Query().Where(
x => x.LazyValue.Value.GroupId == iGroup + 1).ToList() select key.LazyValue.Value;
MainListBox.ItemsSource=contactList;
}
}
OnNavigatedTo方法是Silverlight for WP7中的基本导航方法,不必再赘述了。在上面代码中,一旦我们接收下selectedGroup参数,我们即使用它来创建一个标准的Sterling查询语句。注意,要想全面领会这里的查询语句,你应该先知道LazyValue在Sterling使用中所起的重要作用。最后,我们把查询结果绑定到ListBox控件的ItemsSource属性。这就是全部!
四、总结
在本系列文章中,我们只是初步介绍了Sterling这款开源的面向对象嵌入式数据库在Silverlight for Windows Phone 7中的基本应用思路和操作技巧。很显然,如果读者以前未曾有过面向对象数据库方面的知识,你会感觉到Sterling的实现架构有些陌生。然而,如果您已经有扎实的C#面向对象和LINQ to Object基础,那么,快速入门Sterling也不是什么难事。当然,不同于传统的关系数据库,在使用Sterling前,你最好预先掌握一些面向对象程序设计技术,特别是掌握一些面向对象的数据库理论。随着Sterling在你心中不断扎根,你会发现Sterling是一个相当不错的工具-轻量级而且高效率,非常适合Windows Phone 7数据驱动应用程序的开发。实际上,本文介绍的只不过是触及了Sterling的一点皮毛,还有更多更好的东西等着您自己去深入挖掘。