最近開發了個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。
最后曬下圖吧: