通用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> 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     }
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。

 

最后晒下图吧:

 

posted @ 2014-08-07 23:43  小五毛  阅读(1596)  评论(6编辑  收藏  举报