Xamarin.Forms MVVM介绍

MVVM是Model-View-ViewModel的简写,是由微软在WPF中提供的新技术,MVVM 架构使用的是数据绑定基础架构。更多介绍:http://baike.baidu.com/view/3507915.htm

MVVM架构分为三层:
• Model负责访问数据,为ViewModel提供数据。
• ViewModel连接Model层和View层. ViewModel帮助管理Model提供的数据.
• View层表现数据,通常为XAML定义的用户界面.

三层之间的调用关系:

View层不允许直接调用Model层获取数据,View层获取数据需要通过调用ViewModel提供的方法或属性简介获取Model层提供的数据。

同时为了实现双向通信,ViewModel和View会提供事件处理逐层向下反馈用户处理。

再简单的MVVM示例中Model层是被省略的,ViewModel负责全部业务逻辑。ViewModel和View之间通过Data Binding通信,其中View作为Target,ViewModel作为Source。

ViewModels 和 Data Binding

再Data Binding介绍中提到,Source必须是实现了INotifyPropertyChanged(INPC)接口的对象。

INotifyPropertyChanged定义

INotifyPropertyChanged接口包含一个PropertyChanged事件委托类型为PropertyChangedEventHandler。PropertyChangedEventHandler委托第二个参数类型为PropertyChangedEventArgs,PropertyChangedEventArgs包含一个string 类型的PropertyName属性,根据这个属性可以判断ViewModel的哪个属性发生改变。

对Xamarin.Forms平台而言,ViewModel通常继承BindableObject
即可,ViewModel公共属性定义为BindableProperty类型。BindableObject已经实现了INotifyPropertyChanged接口,定义为BindableProperty类型的属性改变时会自动触发PropertyChanged事件。

简单INotifyPropertyChanged接口使用示例:一个Label标签显示当前时间,每过一秒Label时间更新一次。INotifyPropertyChanged ➕ Data Binding实现。
自定义ViewModel实现INotifyPropertyChanged接口。

public class DateTimeViewModel : INotifyPropertyChanged
{
    DateTime dateTime = DateTime.Now;

    public event PropertyChangedEventHandler PropertyChanged;

    public DateTimeViewModel()
    {
        Device.StartTimer(TimeSpan.FromSeconds(1), () =>
        {
            DateTime = DateTime.Now;
            return true;
        });
    }

    public DateTime DateTime
    {
        private set
        {
            if (dateTime != value)
            {
                dateTime = value;
                OnPropertyChanged("DateTime");
            }
        }

        get
        {
            return dateTime;
        }
    }

    protected void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

}

ViewModel除实现了INotifyPropertyChanged接口外还定义了DateTime类型的DateTime属性。在DateTime属性的set操作中检查属性对应的字段与value是否相等,如不想等对dateTime赋值的同时检查PropertyChanged的值是否为null,不为null调用PropertyChanged。

OnPropertyChanged方法传入的string类型参数表示当前改变属性的字符串名,一个ViewModel内定义多个属性应确保调用OnPropertyChanged方法时传入一个正确的字符串,错误的字符串导致会使绑定失败。.Net 4.5之后的版本提供了一个特性类CallerMemberName,表调用者名且对应参数应设为可选参数具有默认值。修改后的OnPropertyChanged方法:

protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
    if (PropertyChanged != null)
    {
        PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}

属性的set操作中调用OnPropertyChanged方法的代码修改为OnPropertyChanged();。由于定义了CallerMemberNameAttribute可以省略参数传递。事实上CallerMemberNameAttribute类就是为了INotifyPropertyChanged接口使用定义的。

进一步修改代码,定义ViewModel时定义一个SetProperty通用方法,第一个参数标记为ref,表示Property对应的字段,第二个参数为set提供的value。

bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = "")
{
    if (Object.Equals(storage, value))
        return false;

    storage = value;
    if (PropertyChanged != null)
    {
        PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
    return true;
}

如果属性的set操作中只提供简单的赋值,调用定义的SetProperty方法即可。本例中DateTime属性的示例:

public DateTime DateTime
{
    private set
    {
        SetProperty<DateTime>(ref dateTime,value);
    }

    get
    {
        return dateTime;
    }
}

《Creating Mobile Apps with Xamarin.Forms》中代码示例,可以定义一个ViewModel基类,定义ViewModel时即成ViewModelBase类即可,不再考虑INotifyPropertyChanged接口。

ViewModelBase定义

XAML界面定义:

<ContentPage.Resources>
     <ResourceDictionary>
         <local:DateTimeViewModel x:Key="dateTimeViewModel" />
     </ResourceDictionary>
</ContentPage.Resources>
    
<Label Text="{Binding Source={StaticResource dateTimeViewModel} ,Path=DateTime }" 
       VerticalOptions="Center" HorizontalOptions="Center" />

运行效果:


MVVM可以使用户界面与逻辑代码分离,如修改Label为Button,只需要编辑XAML代码即可。

<Button Text="{Binding Source={StaticResource dateTimeViewModel} ,Path=DateTime }" 
        VerticalOptions="Center" HorizontalOptions="Center" />

ICommand 接口介绍

Data Binding 可以方便的连接View的属性和ViewModel的属性,有时候ViewModel还会定义公共方法给View调用,如ViewModel定义一个公共方法响应Button的点击事件。MVVM提供了command interface协议绑定方法,command interface只支持极少数的几个类:Button、MenuItem、ToolbarItem、SearchBar、TextCell、ImageCell、ListView、TapGestureRecognizer。当然也可以自定义类实现command interface的支持。

通过command interface代替Button的Clicked事件示例。Button提供了Command和CommandParameter两个属性支持commanding。ViewModel需要定义一个类型为ICommand的公告属性连接Button的Command属性。ICommand定义在System.Windows.Input命名空间下。

ICommand接口定义

ICommand中定义了两个方法一个事件,当Button的Command属性绑定一个实现了ICommand接口的对象时,点击按钮触发Clicked事件的同时还会调用Command属性的Excute方法,Excute方法的参数通过Button的CommandParameter属性设置。CanExecute方法会在Command属性第一次赋值时调用,如果CanExecute返回false,表示Button不可用且不会发生Excute方法的调用。Button还会监听CanExecuteChanged事件,当CanExecuteChanged事件触发时会继续调用CanExecute方法判断Button是否可用。
实际使用通常不会自己实现ICommand接口,Forms提供了ICommand的实现类Command,同时也提供了Command<T>类.

Command类

Command出了实现ICommand接口还提供了ChangeCanExecute方法,调用该方法会触发CanExecuteChanged事件。

Command如上所示,构造函数提供的两个参数类型分别为Action和Func<bool>,excute表示Execute方法的调用,canExecute表示CanExecute方法的调用。

通过一个简单示例展示Commanding的用法。添加两个按钮使Label上显示的数字在+3~-3之间增加和减小,效果:

首先定义NumberViewModel类

public class NumberViewModel : INotifyPropertyChanged
{
    private int number = 0;

    private const int Increase_Limit = 3, Decrease_Limit = -3;

    public event PropertyChangedEventHandler PropertyChanged;


    public NumberViewModel()
    {
        IncreaseCommand = new Command(() => { Number++; }, () => { return number < Increase_Limit; });
        DecreaseCommand = new Command(() => { Number--; }, () => { return number > Decrease_Limit; });
    }

    public int Number
    {
        private set
        {
            if (SetProperty<int>(ref number, value))
            {
                IncreaseCommand.ChangeCanExecute();
                DecreaseCommand.ChangeCanExecute();
            }
        }

        get
        {
            return number;
        }
    }

    bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = "")
    {
        if (Object.Equals(storage, value))
            return false;

        storage = value;
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
        return true;
    }

    #region Command
    public Command IncreaseCommand
    {
        get;
        set;
    }
    public Command DecreaseCommand
    {
        get;
        set;
    }
    #endregion
}

NumberViewModel类不止实现了INotifyPropertyChanged类还定义了两个Command类型(实现了ICommand接口)属性IncreaseCommand和DecreaseCommand,这两个属性分别会绑定到加1按钮和减1按钮的Command属性。两个属性在构造函数中初始化时第一个参数的匿名委托中一定要直接操作Number属性而不是number字段,否则不会触发PropertyChanged事件,第二个参数根据number的值判断按钮是否可以用从而控制number的变化范围。修改Number的set代码,改变number值的同时调用Command的ChangeCanExecute方法触发CanExecuteChanged事件从而调用CanExecute方法(前文提到的第二个参数)。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,723评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,080评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,604评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,440评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,431评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,499评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,893评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,541评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,751评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,547评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,619评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,320评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,890评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,896评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,137评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,796评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,335评论 2 342

推荐阅读更多精彩内容

  • 前言 一直觉得对于具体功能模块的设计模式来说,没有什么值得研究和讨论的。觉得iOS提供的MVC模式已经能够很好的利...
    淡淡如水舟阅读 2,865评论 3 30
  • 原文:MVVM Tutorial with ReactiveCocoa: Part 1/2 你可能之前在推特看过这...
    吸氧阅读 2,157评论 9 11
  • 1、概述 Databinding 是一种框架,MVVM是一种模式,两者的概念是不一样的。我的理解DataBindi...
    Kelin阅读 76,746评论 68 521
  • 今天要说的是通过卖服务赚钱。 每个人都有自己的特长,就算没有特长也能够通过做自己力所能及的事情赚到钱。关键是看你是...
    墨染花开阅读 155评论 0 1
  • 军训第一天。 累, 是必须的, 但同时也有满满的充实感和成就感。 比起暑假里的浑浑噩噩, 现在的生活真的很有意义。...
    Mean_me阅读 158评论 0 0