通用Windows应用《博客园-开发者的网上家园》开发(1)——MVVM模式
分类:
IT文章
•
2022-10-13 00:26:20
最近开发了个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。
最后晒下图吧:







