首页 > 代码库 > 通用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     }
Blog
    public class Author    {        /// <summary>        /// 博主名字        /// </summary>        public string Name { get; set; }        /// <summary>        /// 博主博客链接        /// </summary>        public string Uri { get; set; }        /// <summary>        /// 博主头像地址        /// </summary>        public string Avatar { get; set; }    }
Author

再看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     }
ViewModel

先看看 INotifyPropertyChanged 这个接口的定义:

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>

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     }
ViewModel属性

上面说到延迟加载,直至需要时。那么什么时候需要呢,当然是我们在页面上需要展示数据的时候。在MainPage.xaml的构造方法里,我们去创建ViewModel,并赋值给MainPage的数据上下文。

1     public MainPage()2     {3         this.InitializeComponent();4         DataContext = App.ViewModel;5     }
MainPage构造方法

并在导航到该页面的时候加载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     }
OnNavigatedTo

上面便实现了所谓的延时加载。

剩下的便是如何在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>
MainPage.xaml

布局可无视。

 

下面是广告时间,凭良心进。

 

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。

 

最后晒下图吧: