Chinaunix首页 | 论坛 | 博客
  • 博客访问: 5024310
  • 博文数量: 891
  • 博客积分: 17977
  • 博客等级: 上将
  • 技术积分: 8636
  • 用 户 组: 普通用户
  • 注册时间: 2005-08-26 09:59
个人简介

一个好老好老的老程序员了。

文章分类

全部博文(891)

文章存档

2020年(8)

2019年(40)

2018年(88)

2017年(130)

2015年(5)

2014年(12)

2013年(41)

2012年(36)

2011年(272)

2010年(1)

2009年(53)

2008年(65)

2007年(47)

2006年(81)

2005年(12)

分类: Android平台

2019-11-11 09:37:15

ViewModel时钟
假设您正在编写需要访问当前日期和时间的程序,并且您希望通过数据绑定来使用该信息。 .NET基类库通过DateTime结构提供日期和时间信息。要获取当前日期和时间,只需访问DateTime.Now属性。这是编写时钟应用程序的习惯方式。
但是出于数据绑定的目的,DateTime有一个严重的缺陷:它只提供静态信息,在日期或时间发生变化时没有通知。
在MVVM的上下文中,DateTime结构可能有资格作为模型,因为DateTime提供了我们需要的所有数据,但没有提供有利于数据绑定的形式。编写一个使用DateTime但在日期或时间发生变化时提供通知的ViewModel是必要的。
Xamarin.FormsBook.Toolkit库包含如下所示的DateTimeViewModel类。该类只有一个属性,名为DateTime,类型为DateTime,但由于在Device.StartTimer回调中频繁调用DateTime.Now,此属性会动态更改。
请注意,DateTimeViewModel类基于INotifyPropertyChanged接口,并包含用于定义此接口的System.ComponentModel命名空间的using指令。要实现此接口,该类定义名为PropertyChanged的公共事件。
注意:在类中定义PropertyChanged事件非常容易,而没有明确指定该类实现INotifyPropertyChanged!如果您没有明确指定该类基于INotifyPropertyChanged接口,则将忽略通知:

点击(此处)折叠或打开

  1. using System;
  2. using System.ComponentModel;
  3. using Xamarin.Forms;
  4. namespace Xamarin.FormsBook.Toolkit
  5. {
  6.     public class DateTimeViewModel : INotifyPropertyChanged
  7.     {
  8.         DateTime dateTime = DateTime.Now;
  9.         public event PropertyChangedEventHandler PropertyChanged;
  10.         public DateTimeViewModel()
  11.         {
  12.             Device.StartTimer(TimeSpan.FromMilliseconds(15), OnTimerTick);
  13.         }
  14.         bool OnTimerTick()
  15.         {
  16.             DateTime = DateTime.Now;
  17.             return true;
  18.         }
  19.         public DateTime DateTime
  20.         {
  21.             private set
  22.             {
  23.                 if (dateTime != value)
  24.                 {
  25.                     dateTime = value;
  26.                     // Fire the event.
  27.                     PropertyChangedEventHandler handler = PropertyChanged;
  28.                     if (handler != null)
  29.                     {
  30.                         handler(this, new PropertyChangedEventArgs("DateTime"));
  31.                     }
  32.                 }
  33.             }
  34.             get
  35.             {
  36.                 return dateTime;
  37.             }
  38.         }
  39.     }
  40. }

此类中唯一的公共属性称为DateTime类型的DateTime,它与名为dateTime的专用支持字段相关联。 ViewModels中的公共属性通常具有私有支持字段。 DateTime属性的set访问器对于类是私有的,并且它从计时器回调每15毫秒更新一次。
除此之外,set访问器以非常标准的方式构造用于ViewModel:它首先检查设置为属性的值是否与dateTime支持字段不同。 如果没有,它将从传入值设置该支持字段,并使用属性的名称触发PropertyChanged处理程序。 如果仅将属性设置为其现有值,则触发PropertyChanged处理程序被认为是非常糟糕的做法,甚至可能导致在双向绑定中涉及无限循环的递归属性设置的问题。
这是触发事件的set访问器中的代码:

点击(此处)折叠或打开

  1. PropertyChangedEventHandler handler = PropertyChanged;
  2. if (handler != null)
  3. {
  4.     handler(this, new PropertyChangedEventArgs("DateTime"));
  5. }

这种形式比这样的代码更好,它不会将处理程序保存在单独的变量中:

点击(此处)折叠或打开

  1. if (PropertyChanged != null)
  2. {
  3.     PropertyChanged(this, new PropertyChangedEventArgs("DateTime"));
  4. }

在多线程环境中,PropertyChanged处理程序可能在检查空值的if语句和事件的实际触发之间分离。 将处理程序保存在单独的变量中可防止导致问题,因此即使您尚未在多线程环境中工作,也应采用这种习惯。
get访问器只返回dateTime支持字段。
MvvmClock程序演示了DateTimeViewModel类如何通过数据绑定向用户界面提供更新的日期和时间信息:

点击(此处)折叠或打开

  1. <ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
  2.              xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
  3.              xmlns:sys="clr-namespace:System;assembly=mscorlib"
  4.              xmlns:toolkit=
  5.                  "clr-namespace:Xamarin.FormsBook.Toolkit;assembly=Xamarin.FormsBook.Toolkit"
  6.              x:Class="MvvmClock.MvvmClockPage">
  7.     <ContentPage.Resources>
  8.         <ResourceDictionary>
  9.             <toolkit:DateTimeViewModel x:Key="dateTimeViewModel" />
  10.             <Style TargetType="Label">
  11.                 <Setter Property="FontSize" Value="Large" />
  12.                 <Setter Property="HorizontalTextAlignment" Value="Center" />
  13.             </Style>
  14.         </ResourceDictionary>
  15.     </ContentPage.Resources>
  16.     <StackLayout VerticalOptions="Center">
  17.         <Label Text="{Binding Source={x:Static sys:DateTime.Now},
  18.                               StringFormat='This program started at {0:F}'}" />
  19.         <Label Text="But now..." />
  20.         <Label Text="{Binding Source={StaticResource dateTimeViewModel},
  21.                               Path=DateTime.Hour,
  22.                               StringFormat='The hour is {0}'}" />
  23.         <Label Text="{Binding Source={StaticResource dateTimeViewModel},
  24.                               Path=DateTime.Minute,
  25.                               StringFormat='The minute is {0}'}" />
  26.  
  27.         <Label Text="{Binding Source={StaticResource dateTimeViewModel},
  28.                               Path=DateTime.Second,
  29.                               StringFormat='The seconds are {0}'}" />
  30.  
  31.         <Label Text="{Binding Source={StaticResource dateTimeViewModel},
  32.                               Path=DateTime.Millisecond,
  33.                               StringFormat='The milliseconds are {0}'}" />
  34.     </StackLayout>
  35. </ContentPage>

页面的Resources部分实例化DateTimeViewModel,还为Label定义了一个隐式Style。
六个Label元素中的第一个将其Text属性设置为涉及实际.NET DateTime结构的Binding对象。该绑定的Source属性是一个x:Static标记扩展,它引用静态DateTime.Now属性以获取程序首次开始运行时的日期和时间。此绑定不需要路径。 “F”格式规范适用于完整日期/时间模式,具有日期和时间字符串的长版本。虽然此Label显示程序启动的日期和时间,但它永远不会更新。
最后的四个数据绑定将被更新。在这些数据绑定中,Source属性设置为引用DateTimeViewModel对象的StaticResource标记扩展。 Path设置为该ViewModel的DateTime属性的各种子属性。在幕后,绑定基础结构在DateTimeViewModel中的PropertyChanged事件上附加处理程序。此处理程序检查DateTime属性中的更改,并在该属性更改时更新Label的Text属性。
除了InitializeComponent调用之外,代码隐藏文件是空的。最后四个标签的数据绑定显示更新时间,其更新速度与视频刷新率一样快:
2018_10_10_130902
通过将StackLayout的BindingContext属性设置为引用ViewModel的StaticResource标记扩展,可以简化此XAML文件中的标记。 BindingContext通过可视树传播,以便您可以删除最后四个Label元素上的Source设置:

点击(此处)折叠或打开

  1. <StackLayout VerticalOptions="Center"
  2.              BindingContext="{StaticResource dateTimeViewModel}">
  3.  
  4.     <Label Text="{Binding Source={x:Static sys:DateTime.Now},
  5.            StringFormat='This program started at {0:F}'}" />
  6.     <Label Text="But now..." />
  7.     <Label Text="{Binding Path=DateTime.Hour,
  8.            StringFormat='The hour is {0}'}" />
  9.     <Label Text="{Binding Path=DateTime.Minute,
  10.            StringFormat='The minute is {0}'}" />
  11.  
  12.     <Label Text="{Binding Path=DateTime.Second,
  13.            StringFormat='The seconds are {0}'}" />
  14.  
  15.     <Label Text="{Binding Path=DateTime.Millisecond,
  16.            StringFormat='The milliseconds are {0}'}" />
  17. </StackLayout>

第一个Label上的Binding覆盖了BindingContext及其自己的Source设置。
您甚至可以从ResourceDictionary中删除DateTimeViewModel项,并在BindingContext属性元素标记之间的StackLayout中实例化它:

点击(此处)折叠或打开

  1. <StackLayout VerticalOptions="Center">
  2.     <StackLayout.BindingContext>
  3.         <toolkit:DateTimeViewModel />
  4.     </StackLayout.BindingContext>
  5.     <Label Text="{Binding Source={x:Static sys:DateTime.Now},
  6.            StringFormat='This program started at {0:F}'}" />
  7.     <Label Text="But now..." />
  8.     <Label Text="{Binding Path=DateTime.Hour,
  9.            StringFormat='The hour is {0}'}" />
  10.     <Label Text="{Binding Path=DateTime.Minute,
  11.            StringFormat='The minute is {0}'}" />
  12.     <Label Text="{Binding Path=DateTime.Second,
  13.            StringFormat='The seconds are {0}'}" />
  14.     <Label Text="{Binding Path=DateTime.Millisecond,
  15.            StringFormat='The milliseconds are {0}'}" />
  16. </StackLayout>

或者,您可以将StackLayout的BindingContext属性设置为包含的Binding
DateTime属性。 然后BindingContext成为DateTime值,它允许单独的绑定简单地引用.NET DateTime结构的属性:

点击(此处)折叠或打开

  1. <StackLayout VerticalOptions="Center"
  2.              BindingContext="{Binding Source={StaticResource dateTimeViewModel},
  3.                                       Path=DateTime}">
  4.     <Label Text="{Binding Source={x:Static sys:DateTime.Now},
  5.            StringFormat='This program started at {0:F}'}" />
  6.     <Label Text="But now..." />
  7.     <Label Text="{Binding Path=Hour,
  8.                           StringFormat='The hour is {0}'}" />
  9.     <Label Text="{Binding Path=Minute,
  10.                           StringFormat='The minute is {0}'}" />
  11.     <Label Text="{Binding Path=Second,
  12.                           StringFormat='The seconds are {0}'}" />
  13.     <Label Text="{Binding Path=Millisecond,
  14.                           StringFormat='The milliseconds are {0}'}" />
  15. </StackLayout>

你可能会怀疑这会起作用! 在幕后,数据绑定通常会安装PropertyChanged事件处理程序并监视正在更改的特定属性,但在这种情况下它不能,因为数据绑定的源是DateTime值,而DateTime不实现INotifyPropertyChanged。 但是,这些Label元素的BindingContext随着ViewModel中DateTime属性的每次更改而更改,因此绑定基础结构此时会访问这些属性的新值。
由于Text属性上的单个绑定在长度和复杂性方面都有所减少,因此您可以删除Path属性名称并将所有内容放在一行上,并且不会混淆任何内容:

点击(此处)折叠或打开

  1. <StackLayout VerticalOptions="Center">
  2.     <StackLayout.BindingContext>
  3.         <Binding Path="DateTime">
  4.             <Binding.Source>
  5.                 <toolkit:DateTimeViewModel />
  6.             </Binding.Source>
  7.         </Binding>
  8.     </StackLayout.BindingContext>
  9.     <Label Text="{Binding Source={x:Static sys:DateTime.Now},
  10.                           StringFormat='This program started at {0:F}'}" />
  11.     <Label Text="But now..." />
  12.     <Label Text="{Binding Hour, StringFormat='The hour is {0}'}" />
  13.     <Label Text="{Binding Minute, StringFormat='The minute is {0}'}" />
  14.     <Label Text="{Binding Second, StringFormat='The seconds are {0}'}" />
  15.     <Label Text="{Binding Millisecond, StringFormat='The milliseconds are {0}'}" />
  16. </StackLayout>

在本书的未来计划中,个人绑定将尽可能简短和优雅。

阅读(99) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~