前面三节课我们分别实现了课程表、作业本和笔记本三个主要功能然而它们的内容分散在三个不同的页面试想一下如果我想查看某门课下一次什么时候上课、今天有哪些作业要做以及今天记了哪些笔记我就不得不在多个不同的页面之间来回切换了这显然降低了应用程序的可用性usability因此这节课的任务是化零为整把相关内容整合起来。在WP7里内容的整合一般是通过Hub来实现的比如说People Hub、Pictures Hub、Music Video Hub、Office Hub、Games Hub以及Marketplace Hub等都是典型的代表。那么如何创建这样的页面非常简单右击Projects面板里的项目节点选择Add New Item图 1在弹出的New Item对话框里选择Windows Phone Panorama Page输入页面的名字然后按OK图 2此时Expression Blend会为你创建一个Panorama页里面包含了两个Panorama项。在Properties面板上把Panorama控件的Title属性设为组织行为学接着添加一个Panorama项然后把页面上的三个Panorama项的Header属性分别设为课程概况、今天笔记和今天作业图 3这三个Panorama项分别用于显示当前课程的基本信息、今天记录的笔记以及今天要做的作业。接下来我们将会详细探讨每个Panorama项的设计。首先是课程概况我希望它能告诉我每周星期几有课以及下节课的相关信息图 4看到这里你可能会问这是怎么弄的你可以在Panorama项里放置两个TextBlock也可以放置一个ListBox你的决定将会影响到后面的实现这里没有谁对谁错你要做的只是权衡利弊做出决定然后承担责任。这里选择了后一种做法主要是考虑到将来添加其它信息时可以更加方便。接着是今天笔记要显示今天的笔记并不难也就是一个ListBox的功夫图 5然而仅仅这样就够了吗我们知道新建和查看是最常用的两项操作可以满足用户绝大多数时候的需求我们应该尽可能让用户最快地接触到常用的功能这意味着我们可以考虑把新建操作集成到这里但完整的新建操作需要打开NewOrEditNotePage页才能完成啊而CourseHubPage页的设计原则是尽可能让涉及到的操作就地完成并且用户可以马上看到结果怎么处理要回答这个问题我们得先搞清楚用户在什么情况下会通过CourseHubPage页来新建笔记。我们知道用户在新建笔记的时候需要提供笔记内容和笔记标签然而只有笔记内容是必须提供的笔记标签的提供可以延后如果用户要在极短时间内新建笔记比如说在课堂上那么暂时忽略笔记标签只输入笔记内容显然会极大地加速整个操作过程换句话说用户只需一个TextBox和一个Button就可以完成新建操作图 6需要注意的是TextBox的TextWrapping属性的值默认是Wrap这是为了让TextBox能够根据内容的长度自动调整自身的高度然而这个好处在这里会导致下面的ListBox被挤压从而使得笔记内容的显示空间减少这显然不是我们希望看到的因此我们需要把TextBox的TextWrapping属性的值设为NoWrap。最后是今天作业出于相同的理由我也为它配备了新建功能图 7然而仅仅这样就够了吗当然不够首先用户无从知晓哪些作业还没完成其次当用户完成一项作业时很自然地想把它标记为已完成然而就目前的设计而言用户得先退回主菜单然后进入作业本找到并编辑这项作业这个繁琐的过程无疑降低了应用程序的可用性。有没有办法可以简化这个过程我们知道作业的完成状态是通过IsCompleted属性来标识的这个属性的类型是bool一般而言如果我们要在用户界面上表达这个类型的数据我们会选择CheckBox因此我们不妨为每项作业配备一个CheckBox图 8这样用户既可以直观地了解作业的完成状态又可以轻易地更改作业的完成状态而我们唯一要做的只是创建一个双向数据绑定连接前端和后端有了用户界面接下来就是为它创建对应的ViewModel类了。首先在ViewModel文件夹里创建一个CourseHubViewModel类并让它继承NotificationObject类代码 1接着创建以下三个属性代码 2它们将会分别绑到课程概况、今天笔记和今天作业上的ListBox。那么我们应该如何初始化它们呢Assignments属性的初始化最简单因为Assignment类里有个StartDate属性我们可以根据这个属性在CouseHubViewModel类的构造函数里筛选出今天的作业代码 3但是Note类没有类似的属性啊怎么办创建一个吧代码 4这样我们就可以像筛选作业那样筛选笔记了代码 5最麻烦的是Overview属性它需要我们重新组织/聚合课程的信息比如图4的第一条信息——逢星期一、星期二、星期五有课其中逢XXX有课是固定部分星期一、星期二、星期五是可变部分这部分信息保存在Course对象的Day属性里我们可以通过LINQ查询课程表里特定课程的所有Course对象然后提取它们的Day属性并按照我们期望的格式聚合起来代码 6至于第二条信息我们得先找到下节课的Course对象怎么找想想看如果我们手头上有一份课程表我们会怎么找呢我们会先看看明天有没有这节课如果有那就是它了如果没有看看后天有没有如此类推下星期的今天为止。我们可以模拟这个过程查找下节课的Course对象代码 7需要说明的是某个课程Course对象星期几有课是以字符串的形式表示的而某天DateTime对象星期几却是以DayOfWeek枚举的形式表示的因此我们需要一个GetChineseDayName方法把DayOfWeek枚举转换成对应的中文字符串代码 8现在请思考一下GetChineseDayName方法有没有可能抛出ArgumentException异常如果有什么情况下会抛出这个异常如果没有为什么好了找到下节课之后我们就可以着手相关信息的聚合了代码 9值得提醒的是这里的做法是不具备多语言扩展的不过就目前而言这已足够了。接下来我们将会实现今天笔记的新建操作从用户界面上看这个功能是由一个TextBox和一个Button组成的前者只需配备一个对应的字符串属性代码 10至于后者根据上节课的经验我们需要为它创建一个NewNoteCommand属性代码 11然后在构造函数里把它初始化为一个DelegateCommand对象代码 12DelegateCommand类的构造函数接受两个参数第一个是执行这个命令时将会调用的代码第二个是判断这个命令能否调用的代码。当用户单击按钮时我们需要根据当前课程、笔记内容以及今天日期等信息创建一个Note对象然后保存这个Note对象。现在的问题是如何通知页面的ListBox更新办法其实有很多比如说我们可以学第二节课那样手动监听CollectionChanged事件也可以学上节课那样通过CollectionViewSource间接监听CollectionChanged事件不过这次我打算采用更加直接的办法在保存Note对象之后手动把它添加到Notes属性代码 13需要说明的是这里不是通过Add方法把Note对象添加到Notes属性的末尾而是通过Insert方法把Note对象添加到Notes属性的首位为什么这样呢想想看当用户单击按钮时其视线将会落在按钮及其附近如果新建的笔记显示在ListBox的首位那就正好落在用户的视线范围里面这样用户就无需挪动视线寻找并确认新建的笔记了。完成这些操作之后我们需要把TextBox清空为下次输入做好准备因为TextBox是绑到NoteContent属性的所以我们只需把NoteContent属性的值重设为空字符串就行了。至于DelegateCommand类的构造函数的第二个参数即判断这个命令能否调用的代码也很简单只有当TextBox里有内容那么单击按钮才会执行这个命令代码 14今天作业的新建操作的实现方式和这里的一样我打算把它留给你当今天作业值得提醒的是在创建Assignment对象时你需要把相关属性初始化为恰当的值这可以参考第二节课的做法。最后页面的课程名称也需要一个对应的属性代码 15看到这里你可能会问为什么这里直接使用自动属性而不像代码10的NoteContent属性那样在set访问器里调用RaisePropertyChanged方法这是因为在用户访问CourseHubPage页期间当前课程是不变的即CourseName属性的值不会发生改变因此简单的自动属性已经足够了。CourseName属性的初始化也非常简单只需把传给页面的课程名称赋给它就行了代码 16创建好ViewModel类之后我们就可以着手处理它和CourseHubPage页之间的关联了。首先是设置数据绑定需要设置的控件以及对应的绑定表达式如下表所示描述类型属性绑定表达式页面标题TextBlockText{Binding CourseName}课程概况ListBoxItemsSource{Binding Overview}今日笔记ListBoxItemsSource{Binding Notes}笔记内容TextBoxText{Binding NoteContent, ModeTwoWay}今日作业ListBoxItemsSource{Binding Assignments}作业内容TextBoxText{Binding AssignmentContent, ModeTwoWay}表 1看到这里你可能会问还有两个按钮呢上节课我们曾经说过SL for WP的Button控件没有Command属性不能直接绑定命令对象我们需要通过Behavior间接实现Button控件和命令对象之间的绑定但Prism没有为Button控件提供现成的Behavior怎么办我们可以仿照Prism的ApplicationBarButtonCommand创建一个适用于Button控件的ButtonCommand也可以直接使用Windows Phone 7 Developer Guide – Code Samples里面提供的ButtonCommand还可以使用MVVM Light Toolkit的EventToCommand。毫无疑问第二种方案是最简单的而第三种方案则是最强大的它不但可以把任意事件映射到任意命令对象最新版本还支持把事件处理程序的参数传给命令对象。下面将会示范如何通过EventToCommand给Button控件绑定命令对象。首先引用GalaSoft.MvvmLight.Extras.WP7.dll类库接着打开Assets面板选择Behaviors然后把EventToCommand拖到Objects and Timeline面板的Button上图 9此时Objects and Timeline面板将会变成这样图 10确保EventToCommand处于选中状态在Properties面板上把Command属性的值设为{Binding NewNoteCommand}图 11看到这里你可能会问不用设置事件吗如果你查看Properties面板上的EventName属性你会发现它已经设为Click了因为Button控件的默认事件就是Click。是不是很简单呢剩下那个Button控件也是这样处理哦。现在万事俱备只欠……嗯创建一个CourseHubViewModel对象并把它赋
WP7有约(四):课程全景
前面三节课我们分别实现了课程表、作业本和笔记本三个主要功能然而它们的内容分散在三个不同的页面试想一下如果我想查看某门课下一次什么时候上课、今天有哪些作业要做以及今天记了哪些笔记我就不得不在多个不同的页面之间来回切换了这显然降低了应用程序的可用性usability因此这节课的任务是化零为整把相关内容整合起来。在WP7里内容的整合一般是通过Hub来实现的比如说People Hub、Pictures Hub、Music Video Hub、Office Hub、Games Hub以及Marketplace Hub等都是典型的代表。那么如何创建这样的页面非常简单右击Projects面板里的项目节点选择Add New Item图 1在弹出的New Item对话框里选择Windows Phone Panorama Page输入页面的名字然后按OK图 2此时Expression Blend会为你创建一个Panorama页里面包含了两个Panorama项。在Properties面板上把Panorama控件的Title属性设为组织行为学接着添加一个Panorama项然后把页面上的三个Panorama项的Header属性分别设为课程概况、今天笔记和今天作业图 3这三个Panorama项分别用于显示当前课程的基本信息、今天记录的笔记以及今天要做的作业。接下来我们将会详细探讨每个Panorama项的设计。首先是课程概况我希望它能告诉我每周星期几有课以及下节课的相关信息图 4看到这里你可能会问这是怎么弄的你可以在Panorama项里放置两个TextBlock也可以放置一个ListBox你的决定将会影响到后面的实现这里没有谁对谁错你要做的只是权衡利弊做出决定然后承担责任。这里选择了后一种做法主要是考虑到将来添加其它信息时可以更加方便。接着是今天笔记要显示今天的笔记并不难也就是一个ListBox的功夫图 5然而仅仅这样就够了吗我们知道新建和查看是最常用的两项操作可以满足用户绝大多数时候的需求我们应该尽可能让用户最快地接触到常用的功能这意味着我们可以考虑把新建操作集成到这里但完整的新建操作需要打开NewOrEditNotePage页才能完成啊而CourseHubPage页的设计原则是尽可能让涉及到的操作就地完成并且用户可以马上看到结果怎么处理要回答这个问题我们得先搞清楚用户在什么情况下会通过CourseHubPage页来新建笔记。我们知道用户在新建笔记的时候需要提供笔记内容和笔记标签然而只有笔记内容是必须提供的笔记标签的提供可以延后如果用户要在极短时间内新建笔记比如说在课堂上那么暂时忽略笔记标签只输入笔记内容显然会极大地加速整个操作过程换句话说用户只需一个TextBox和一个Button就可以完成新建操作图 6需要注意的是TextBox的TextWrapping属性的值默认是Wrap这是为了让TextBox能够根据内容的长度自动调整自身的高度然而这个好处在这里会导致下面的ListBox被挤压从而使得笔记内容的显示空间减少这显然不是我们希望看到的因此我们需要把TextBox的TextWrapping属性的值设为NoWrap。最后是今天作业出于相同的理由我也为它配备了新建功能图 7然而仅仅这样就够了吗当然不够首先用户无从知晓哪些作业还没完成其次当用户完成一项作业时很自然地想把它标记为已完成然而就目前的设计而言用户得先退回主菜单然后进入作业本找到并编辑这项作业这个繁琐的过程无疑降低了应用程序的可用性。有没有办法可以简化这个过程我们知道作业的完成状态是通过IsCompleted属性来标识的这个属性的类型是bool一般而言如果我们要在用户界面上表达这个类型的数据我们会选择CheckBox因此我们不妨为每项作业配备一个CheckBox图 8这样用户既可以直观地了解作业的完成状态又可以轻易地更改作业的完成状态而我们唯一要做的只是创建一个双向数据绑定连接前端和后端有了用户界面接下来就是为它创建对应的ViewModel类了。首先在ViewModel文件夹里创建一个CourseHubViewModel类并让它继承NotificationObject类代码 1接着创建以下三个属性代码 2它们将会分别绑到课程概况、今天笔记和今天作业上的ListBox。那么我们应该如何初始化它们呢Assignments属性的初始化最简单因为Assignment类里有个StartDate属性我们可以根据这个属性在CouseHubViewModel类的构造函数里筛选出今天的作业代码 3但是Note类没有类似的属性啊怎么办创建一个吧代码 4这样我们就可以像筛选作业那样筛选笔记了代码 5最麻烦的是Overview属性它需要我们重新组织/聚合课程的信息比如图4的第一条信息——逢星期一、星期二、星期五有课其中逢XXX有课是固定部分星期一、星期二、星期五是可变部分这部分信息保存在Course对象的Day属性里我们可以通过LINQ查询课程表里特定课程的所有Course对象然后提取它们的Day属性并按照我们期望的格式聚合起来代码 6至于第二条信息我们得先找到下节课的Course对象怎么找想想看如果我们手头上有一份课程表我们会怎么找呢我们会先看看明天有没有这节课如果有那就是它了如果没有看看后天有没有如此类推下星期的今天为止。我们可以模拟这个过程查找下节课的Course对象代码 7需要说明的是某个课程Course对象星期几有课是以字符串的形式表示的而某天DateTime对象星期几却是以DayOfWeek枚举的形式表示的因此我们需要一个GetChineseDayName方法把DayOfWeek枚举转换成对应的中文字符串代码 8现在请思考一下GetChineseDayName方法有没有可能抛出ArgumentException异常如果有什么情况下会抛出这个异常如果没有为什么好了找到下节课之后我们就可以着手相关信息的聚合了代码 9值得提醒的是这里的做法是不具备多语言扩展的不过就目前而言这已足够了。接下来我们将会实现今天笔记的新建操作从用户界面上看这个功能是由一个TextBox和一个Button组成的前者只需配备一个对应的字符串属性代码 10至于后者根据上节课的经验我们需要为它创建一个NewNoteCommand属性代码 11然后在构造函数里把它初始化为一个DelegateCommand对象代码 12DelegateCommand类的构造函数接受两个参数第一个是执行这个命令时将会调用的代码第二个是判断这个命令能否调用的代码。当用户单击按钮时我们需要根据当前课程、笔记内容以及今天日期等信息创建一个Note对象然后保存这个Note对象。现在的问题是如何通知页面的ListBox更新办法其实有很多比如说我们可以学第二节课那样手动监听CollectionChanged事件也可以学上节课那样通过CollectionViewSource间接监听CollectionChanged事件不过这次我打算采用更加直接的办法在保存Note对象之后手动把它添加到Notes属性代码 13需要说明的是这里不是通过Add方法把Note对象添加到Notes属性的末尾而是通过Insert方法把Note对象添加到Notes属性的首位为什么这样呢想想看当用户单击按钮时其视线将会落在按钮及其附近如果新建的笔记显示在ListBox的首位那就正好落在用户的视线范围里面这样用户就无需挪动视线寻找并确认新建的笔记了。完成这些操作之后我们需要把TextBox清空为下次输入做好准备因为TextBox是绑到NoteContent属性的所以我们只需把NoteContent属性的值重设为空字符串就行了。至于DelegateCommand类的构造函数的第二个参数即判断这个命令能否调用的代码也很简单只有当TextBox里有内容那么单击按钮才会执行这个命令代码 14今天作业的新建操作的实现方式和这里的一样我打算把它留给你当今天作业值得提醒的是在创建Assignment对象时你需要把相关属性初始化为恰当的值这可以参考第二节课的做法。最后页面的课程名称也需要一个对应的属性代码 15看到这里你可能会问为什么这里直接使用自动属性而不像代码10的NoteContent属性那样在set访问器里调用RaisePropertyChanged方法这是因为在用户访问CourseHubPage页期间当前课程是不变的即CourseName属性的值不会发生改变因此简单的自动属性已经足够了。CourseName属性的初始化也非常简单只需把传给页面的课程名称赋给它就行了代码 16创建好ViewModel类之后我们就可以着手处理它和CourseHubPage页之间的关联了。首先是设置数据绑定需要设置的控件以及对应的绑定表达式如下表所示描述类型属性绑定表达式页面标题TextBlockText{Binding CourseName}课程概况ListBoxItemsSource{Binding Overview}今日笔记ListBoxItemsSource{Binding Notes}笔记内容TextBoxText{Binding NoteContent, ModeTwoWay}今日作业ListBoxItemsSource{Binding Assignments}作业内容TextBoxText{Binding AssignmentContent, ModeTwoWay}表 1看到这里你可能会问还有两个按钮呢上节课我们曾经说过SL for WP的Button控件没有Command属性不能直接绑定命令对象我们需要通过Behavior间接实现Button控件和命令对象之间的绑定但Prism没有为Button控件提供现成的Behavior怎么办我们可以仿照Prism的ApplicationBarButtonCommand创建一个适用于Button控件的ButtonCommand也可以直接使用Windows Phone 7 Developer Guide – Code Samples里面提供的ButtonCommand还可以使用MVVM Light Toolkit的EventToCommand。毫无疑问第二种方案是最简单的而第三种方案则是最强大的它不但可以把任意事件映射到任意命令对象最新版本还支持把事件处理程序的参数传给命令对象。下面将会示范如何通过EventToCommand给Button控件绑定命令对象。首先引用GalaSoft.MvvmLight.Extras.WP7.dll类库接着打开Assets面板选择Behaviors然后把EventToCommand拖到Objects and Timeline面板的Button上图 9此时Objects and Timeline面板将会变成这样图 10确保EventToCommand处于选中状态在Properties面板上把Command属性的值设为{Binding NewNoteCommand}图 11看到这里你可能会问不用设置事件吗如果你查看Properties面板上的EventName属性你会发现它已经设为Click了因为Button控件的默认事件就是Click。是不是很简单呢剩下那个Button控件也是这样处理哦。现在万事俱备只欠……嗯创建一个CourseHubViewModel对象并把它赋