最近开发了个WP8.1和Windows8.1平台上的应用——《博客园-开发者的网上家园》,基于 Windows Runtime 。在此有必要说明一下,WP8.0以前的应用程序是基于Silverlight的,微软为了统一Windows Phone OS 和 Windows RT,从开发人员的角度上,也统一了两个平台上大部分的API,使得开发人员可以共享代码(而不是一次编写,跨平台运行)。
本文着重描述MVVM在Windows Runtime应用程序下的表现,关于MVVM模式的理解,可参考园子里 天神一 的博客——《MVVM架构的简单解析》。
来看Model的代码
1 public class Blog 2 { 3 /// <summary> 4 /// 博客Id 5 /// </summary> 6 public long Id { get; set; } 7 /// <summary> 8 /// 博客标题 9 /// </summary> 10 public string Title { get; set; } 11 /// <summary> 12 /// 博客摘要 13 /// </summary> 14 public string Summary { get; set; } 15 /// <summary> 16 /// 博客发表时间 17 /// </summary> 18 public string Published { get; set; } 19 /// <summary> 20 /// 博客更新时间 21 /// </summary> 22 public string Updated { get; set; } 23 /// <summary> 24 /// 博主 25 /// </summary> 26 public Author Author { get; set; } 27 /// <summary> 28 /// 博客链接 29 /// </summary> 30 public string Link { get; set; } 31 /// <summary> 32 /// 博主博客名称 33 /// </summary> 34 public string BlogApp { get; set; } 35 /// <summary> 36 /// 推荐数 37 /// </summary> 38 public int Diggs { get; set; } 39 /// <summary> 40 /// 阅读数 41 /// </summary> 42 public int Views { get; set; } 43 /// <summary> 44 /// 评论数 45 /// </summary> 46 public int Comments { get; set; } 47 }
public class Author { /// <summary> /// 博主名字 /// </summary> public string Name { get; set; } /// <summary> /// 博主博客链接 /// </summary> public string Uri { get; set; } /// <summary> /// 博主头像地址 /// </summary> public string Avatar { get; set; } }
再看ViewModel
1 public class ViewModel : INotifyPropertyChanged 2 { 3 public ViewModel() 4 { 5 this.Blogs = new ObservableCollection<Blog>(); 6 } 7 8 /// <summary> 9 /// 加载某一页博客 10 /// </summary> 11 /// <param name="pageIndex">页码</param> 12 /// <param name="pageSize">多少条</param> 13 /// <returns></returns> 14 public async Task LoadBlogsAsync(int pageIndex, int pageSize) 15 { 16 string url = string.Format(BlogUrl, pageIndex, pageSize); 17 this.Blogs = XmlHelper.XmlToBlog(await new HttpClient().GetStringAsync(new Uri(url))); 18 IsBlogLoaded = true; 19 } 20 21 /// <summary> 22 /// 加载下一页博客,并追加到当前数据源中 23 /// </summary> 24 /// <param name="count">加载多少条</param> 25 /// <returns></returns> 26 public async Task LoadMoreBlogsAsync(int count) 27 { 28 string url = string.Format(BlogUrl, ++App.CurrentBlogPage, count); 29 foreach (var item in XmlHelper.XmlToBlog(await new HttpClient().GetStringAsync(new Uri(url)))) 30 { 31 this.Blogs.Add(item); 32 } 33 } 34 35 private ObservableCollection<Blog> _blogs; 36 public ObservableCollection<Blog> Blogs 37 { 38 get { return _blogs; } 39 private set 40 { 41 if (value != _blogs) 42 { 43 _blogs = value; 44 NotifyPropertyChanged("Blogs"); 45 } 46 } 47 } 48 49 private const string BlogUrl = "http://wcf.open.cnblogs.com/blog/sitehome/paged/{0}/{1}"; 50 51 public bool IsLoaded { get; private set; } 52 53 public event PropertyChangedEventHandler PropertyChanged; 54 private void NotifyPropertyChanged(string propertyName) 55 { 56 PropertyChangedEventHandler handler = PropertyChanged; 57 if (null != handler) 58 { 59 handler(this, new PropertyChangedEventArgs(propertyName)); 60 } 61 } 62 }
先看看 INotifyPropertyChanged 这个接口的定义:
这个接口只定义了一个事件:PropertyChanged,属性改变。接口的说明告诉我们,这个接口的作用在于当我用于绑定在UI上的数据源发生改变的时候,可以向界面发出通知,让界面做出相应的改变。
ViewModel实现了INotifyPropertyChanged接口,当ViewModel改变时可以通知UI做出相应的改变,同时,不使用List<T>作为数据集,而是使用ObservableCollection<T>,看看ObservableCollection<T>的定义:
1 // 摘要: 2 // 表示一个动态数据集合,在添加项、移除项或刷新整个列表时,此集合将提供通知。 3 // 4 // 类型参数: 5 // T: 6 // 集合中的元素类型。 7 public class ObservableCollection<T> : Collection<T>, INotifyCollectionChanged, INotifyPropertyChanged 8 { 9 // 摘要: 10 // 初始化 System.Collections.ObjectModel.ObservableCollection<T> 类的新实例。 11 public ObservableCollection(); 12 // 13 // 摘要: 14 // 初始化 System.Collections.ObjectModel.ObservableCollection<T> 类的新实例,该类包含从指定集合中复制的元素。 15 // 16 // 参数: 17 // collection: 18 // 从中复制元素的集合。 19 // 20 // 异常: 21 // System.ArgumentNullException: 22 // collection 参数不能为 null。 23 public ObservableCollection(IEnumerable<T> collection); 24 25 // 摘要: 26 // 在添加、移除、更改或移动项或者在刷新整个列表时发生。 27 public virtual event NotifyCollectionChangedEventHandler CollectionChanged; 28 // 29 // 摘要: 30 // 在属性值更改时发生。 31 protected virtual event PropertyChangedEventHandler PropertyChanged; 32 33 // 摘要: 34 // 不允许可重入的更改此集合的尝试。 35 // 36 // 返回结果: 37 // 可用于释放对象的 System.IDisposable 对象。 38 protected IDisposable BlockReentrancy(); 39 // 40 // 摘要: 41 // 检查可重入的更改此集合的尝试。 42 // 43 // 异常: 44 // System.InvalidOperationException: 45 // 如果存在对 System.Collections.ObjectModel.ObservableCollection<T>.BlockReentrancy()(尚未释放其 46 // System.IDisposable 返回值)的调用。 通常,这意味着在 System.Collections.ObjectModel.ObservableCollection<T>.CollectionChanged 47 // 事件期间进行了额外的更改此集合的尝试。 但是,这取决于派生类何时选择调用 System.Collections.ObjectModel.ObservableCollection<T>.BlockReentrancy()。 48 protected void CheckReentrancy(); 49 // 50 // 摘要: 51 // 从集合中移除所有项。 52 protected override void ClearItems(); 53 // 54 // 摘要: 55 // 将一项插入集合中指定索引处。 56 // 57 // 参数: 58 // index: 59 // 从零开始的索引,应在该位置插入 item。 60 // 61 // item: 62 // 要插入的对象。 63 protected override void InsertItem(int index, T item); 64 // 65 // 摘要: 66 // 将指定索引处的项移至集合中的新位置。 67 // 68 // 参数: 69 // oldIndex: 70 // 从零开始的索引,用于指定要移动的项的位置。 71 // 72 // newIndex: 73 // 从零开始的索引,用于指定项的新位置。 74 public void Move(int oldIndex, int newIndex); 75 // 76 // 摘要: 77 // 将指定索引处的项移至集合中的新位置。 78 // 79 // 参数: 80 // oldIndex: 81 // 从零开始的索引,用于指定要移动的项的位置。 82 // 83 // newIndex: 84 // 从零开始的索引,用于指定项的新位置。 85 protected virtual void MoveItem(int oldIndex, int newIndex); 86 // 87 // 摘要: 88 // 引发带有提供的参数的 System.Collections.ObjectModel.ObservableCollection<T>.CollectionChanged 89 // 事件。 90 // 91 // 参数: 92 // e: 93 // 要引发的事件的参数。 94 protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e); 95 // 96 // 摘要: 97 // 引发带有提供的参数的 System.Collections.ObjectModel.ObservableCollection<T>.PropertyChanged 98 // 事件。 99 // 100 // 参数: 101 // e: 102 // 要引发的事件的参数。 103 protected virtual void OnPropertyChanged(PropertyChangedEventArgs e); 104 // 105 // 摘要: 106 // 移除集合中指定索引处的项。 107 // 108 // 参数: 109 // index: 110 // 要移除的元素的从零开始的索引。 111 protected override void RemoveItem(int index); 112 // 113 // 摘要: 114 // 替换指定索引处的元素。 115 // 116 // 参数: 117 // index: 118 // 待替换元素的从零开始的索引。 119 // 120 // item: 121 // 位于指定索引处的元素的新值。 122 protected override void SetItem(int index, T item); 123 }
ObservableCollection<T>集成于Collection<T>,同时实现了两个接口:INotifyCollectionChanged 和 INotifyPropertyChanged,前者用于通知UI数据集改变,后者用于通知UI数据集中的属性改变。
另外在ViewModel中自定义了两个方法:LoadBlogsAsync(int pageIndex, int pageSize) 和 LoadMoreBlogsAsync(int count),都是异步方法。
在App.xaml.cs里定义一个静态属性ViewModel,用于全局访问
1 private static ViewModel viewModel = null; 2 /// <summary> 3 /// 视图用于进行绑定的静态 ViewModel。 4 /// </summary> 5 /// <returns>ViewModel 对象。</returns> 6 public static ViewModel ViewModel 7 { 8 get 9 { 10 // 延迟创建视图模型,直至需要时 11 if (viewModel == null) 12 viewModel = new ViewModel(); 13 return viewModel; 14 } 15 }
上面说到延迟加载,直至需要时。那么什么时候需要呢,当然是我们在页面上需要展示数据的时候。在MainPage.xaml的构造方法里,我们去创建ViewModel,并赋值给MainPage的数据上下文。
1 public MainPage() 2 { 3 this.InitializeComponent(); 4 DataContext = App.ViewModel; 5 }
并在导航到该页面的时候加载ViewModel的数据:
1 protected override async void OnNavigatedTo(Windows.UI.Xaml.Navigation.NavigationEventArgs e) 2 { 3 MyProgressBar.Visibility = Visibility.Visible; 4 if (!App.ViewModel.IsLoaded) 5 { 6 await App.ViewModel.LoadBlogsAsync(1, App.PageSizeBlog); 7 } 8 MyProgressBar.Visibility = Visibility.Collapsed; 9 }
上面便实现了所谓的延时加载。
剩下的便是如何在UI上绑定了
1 <ListBox Loaded="GridViewData_Loaded" SelectionChanged="GridViewData_SelectionChanged" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" Foreground="{ThemeResource ApplicationForegroundThemeBrush}" Name="GridViewData" ItemsSource="{Binding Blogs,Mode=TwoWay}"> 2 <ListBox.ItemTemplate> 3 <DataTemplate> 4 <StackPanel> 5 <TextBlock FontSize="18" Text="{Binding Title}" TextWrapping="Wrap"></TextBlock> 6 <StackPanel Orientation="Horizontal" Margin="0 12"> 7 <TextBlock Text="{Binding Author.Name}" Foreground="#FF2B6695"></TextBlock> 8 <TextBlock Text="发布于" Margin="6 0"></TextBlock> 9 <TextBlock Text="{Binding Published}" Margin="0 0 6 0"></TextBlock> 10 <TextBlock Text="评论("></TextBlock> 11 <TextBlock Text="{Binding Comments}"></TextBlock> 12 <TextBlock Text=")" Margin="0 0 6 0"></TextBlock> 13 <TextBlock Text="阅读("></TextBlock> 14 <TextBlock Text="{Binding Views}"></TextBlock> 15 <TextBlock Text=")"></TextBlock> 16 </StackPanel> 17 <Grid HorizontalAlignment="Left"> 18 <Grid.ColumnDefinitions> 19 <ColumnDefinition Width="Auto"/> 20 <ColumnDefinition/> 21 </Grid.ColumnDefinitions> 22 <Image HorizontalAlignment="Left" Width="48" Height="48" Source="{Binding Author.Avatar}" Grid.Column="0" VerticalAlignment="Top"/> 23 <TextBlock HorizontalAlignment="Left" TextWrapping="Wrap" Padding="12 0" Grid.Column="1" Text="{Binding Summary}"></TextBlock> 24 </Grid> 25 <Canvas Background="#FF2B6695" Height="2" Width="1600" Margin="0 12 12 0"/> 26 </StackPanel> 27 </DataTemplate> 28 </ListBox.ItemTemplate> 29 </ListBox>
布局可无视。
下面是广告时间,凭良心进。
windows 应用商店app(windows 8.1 +):http://apps.microsoft.com/windows/zh-cn/app/4f20c8c7-2dfa-4e93-adcb-87acde53d4be
windows phone 应用商店app(windows phone 8.1 +):http://www.windowsphone.com/s?appid=71e79c48-ad5d-4563-a42f-06d59d969eb8
第一版功能比较鸡肋,后续版本将添加更多功能。如果有什么好的建议的,希望在商店里提出,或者博客里留言,我将综合各方意见打造一个体验更好的win平台的博客园app。
最后晒下图吧: