首页 > 代码库 > 通用Windows应用《博客园-开发者的网上家园》开发(1)——MVVM模式
通用Windows应用《博客园-开发者的网上家园》开发(1)——MVVM模式
最近开发了个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> Blogs37 {38 get { return _blogs; }39 private set40 {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。
最后晒下图吧: