WP7有约(三):课堂重点
[2] WP7有约(三):课堂重点
[3] WP7有约(三):课堂重点
[4] WP7有约(三):课堂重点
[5] WP7有约(三):课堂重点
[6] WP7有约(三):课堂重点
现在,回到NoteBookPage页,为Application Bar上的新建按钮添加一个事件处理程序:
代码 9
然后按F5:
图 29
从上图可以看出,软键盘和Application Bar是并列一起的,所以我可以在任何时候单击Application Bar上的按钮。此外,当我在TextBox里输入较长的内容时,TextBox还会自动调整自身的高度,以便完整显示我输入的内容。当我单击第二个TextBox进行输入时,整个页面将会稍稍向上平移,这样做的好处非常明显——避免软键盘遮盖TextBox!从这里不难看出,WP7在用户体验上的设计确实很体贴!
连接前端和后端
接下来,我们要为这些用户界面创建对应的ViewModel类。我们知道,整个笔记本就是一个Pivot控件,而每门课程的笔记则对应一个Pivot项,这个结构和上节课的作业本类似,于是,我们可以仿效上节课的做法,分别创建NoteBookViewModel和NoteListViewModel两个类:
代码 10
代码 11
由于每个Pivot项都包含了一个标题和一组笔记,NoteListViewModel类自然需要提供两个对应的属性:
代码 12
值得注意的是,这里不直接使用ObservableCollection集合,而是和第一节课的课程表一样,通过CollectionViewSource来提供集合视图,这样做的好处是我们只需指定过滤条件,剩下的事情CollectionViewSource会代为处理,而无需我们亲自出手:
代码 13
而NoteBookViewModel类则和上节课的作业本一样,直接使用ObservableCollection集合:
代码 14
并根据课程表里的数据创建对应的NoteListViewModel对象:
代码 15
有了ViewModel类,我们就可以着手处理数据绑定了。
现在,打开NoteBookPage.xaml文件,切换到XAML模式,在页面的资源字典里添加两个数据模板:
代码 16
接着,把它们应用到Pivot控件上:
代码 17
把LayoutRoot的DataContext属性去掉,然后在代码隐藏文件的构造函数里设置DataContext属性:
代码 18
好了,按F5吧。在打开笔记本之前,我们得先新建一些课程,否则Pivot控件不会创建任何Pivot项的:
图 30
好了之后就可以打开笔记本了:
图 31
当然,现在的笔记本既没有笔记也不能创建笔记,因为这部分功能还没实现呢!
新建和编辑笔记的工作是由NewOrEditNotePage页负责的,根据上两节课的经验,我们需要创建NewOrEditNoteViewModel、NewNoteViewModel和EditNoteViewModel三个类,但我已经厌倦了每次都要手工创建这么多类,而且还有这么多重复的代码,所以这次我要对这部分进行重构。在ViewModels文件夹里创建一个NewOrEditItemViewModel泛型类,并让它继承NotificationObject类:
代码 19
仔细观察NewOrEditCourseViewModel和NewOrEditAssignmentViewModel两个抽象类,不难发现,它们的有效成分是页面标题、Model类的实例和提交数据的方法。页面标题很好处理,一个普通的类型为string的Title属性:
代码 20
Model类的实例有点棘手,因为我们有3个不同的Model类,怎么处理?我们有两个选择,一个是把属性的类型声明为object,另一个就是这里采用的做法——泛型:
代码 21
这也正是我把NewOrEditItemViewModel类声明为泛型类的缘由。我们知道,每个Model类的数据都会提交到不同的地方,我们显然不能把这部分代码固化在NewOrEditItemViewModel类里,所以我决定通过委托把代码外包出去:
代码 22
最后,我们需要在构造函数里初始化这三个成分:
代码 23
那么,我们如何使用这个类呢?
打开NewOrEditNotePage.xaml.cs文件,重写OnNavigatedTo方法:
代码 24
这个是我们根据查询字符串初始化DataContext属性的基本套路。当action的值是new时,初始化DataContext属性的代码应该是这样的:
代码 25
而当action的值是edit时,初始化DataContext属性的代码应该是这样的:
代码 26
这样,下次我们再有新的Model类就可以直接创建ViewModel类的实例了。
看到这里,你可能会问,代码24那个套路每次都是一样的,应该可以处理一下吧?嗯,可以的。我们有两种处理方案,第一种方案是创建一个NewOrEditItemViewModelFactoryBase抽象类,并在里面使用模板方法模式(Template Method Pattern)处理那个套路,这样做的代价是我们需要为每个Model类创建一个对应的工厂类。如果你不喜欢这种做法,你可以选择第二种方案,创建一个NewOrEditItemPage类,按照代码24重写OnNavigatedTo方法,然后应用模板方法模式,这样做的代价是我们需要让每个新建/编辑页面继承这个类。无论我们选择哪种方案,有一点是可以肯定的,那就是NewOrEditItemViewModel类的三个成分似乎无法避免,因为这些工作始终要做,而我们从一种方案改成另一种方案只不过是把这些工作从一个地方挪到另一个地方罢了。如果你确实希望减轻这些工作,(理论上)也不是不可能,不过你得做好心理准备,因为你需要创建一个足够灵活的子系统,并且提供充足的元数据,这些数据包括每个Model类在不同状态下分别对应的页面标题、新建Model类的实例需要初始化哪些属性以及这些属性的数据来自哪里或者如何计算、克隆现有Model类的实例的方法是哪个、数据提交到哪里以及调用哪个方法、提交数据的是否需要同时保存到独立存储区等等等等。当我写到这里的时候,我已经隐约感觉得出这将是个很复杂的子系统,如果你真的打算实现这个子系统,那么请你先把右手抬起来,捂在左边胸口,然后问问自己:
- 会有人愿意负责提供这些数据吗?如果有,会是谁呢?
- 会有人愿意负责维护这个子系统吗?如果有,会是谁呢?
- 当你的程序用上这个子系统之后,你能得到什么实质的好处?
- 这些好处能否抵消提供数据和维护子系统的付出?
如果你没有自欺,你的心将会告诉你这个决定是否值得。
创建好ViewModel类之后,我们就可以着手处理它和页面之间的关联了。首先是设置数据绑定,需要设置的控件以及对应的绑定表达式如下表所示:
描述 |
类型 |
属性 |
绑定表达式 |
页面标题 |
TextBlock |
Text |
{Binding Title} |
笔记内容 |
TextBox |
Text |
{Binding Item.Content, Mode=TwoWay} |
笔记标签 |
TextBox |
Text |
{Binding Item.Tags, Mode=TwoWay} |
表 2
接着,为Application Bar上的两个按钮创建事件处理程序:
代码 27
页面的功能有了,可打开页面的功能还没好呢!