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

 

最后曬下圖吧:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM