第5部分.把数据绑定到MVVM——Model-View-ViewModel体系结构的介绍

本文涉及的产品
数据可视化DataV,5个大屏 1个月
可视分析地图(DataV-Atlas),3 个项目,100M 存储空间
简介: Model-View-ViewModel(MVVM)体系结构模式是在XAML的基础上发明的。 该模式强制三个软件层之间的分离 - XAML用户界面,称为视图; 基础数据,称为模型; 以及View和Model之间的中介,称为ViewModel。

Model-View-ViewModel(MVVM)体系结构模式是在XAML的基础上发明的。 该模式强制三个软件层之间的分离 - XAML用户界面,称为视图; 基础数据,称为模型; 以及View和Model之间的中介,称为ViewModel。 View和ViewModel通常通过XAML文件中定义的数据绑定进行连接。 视图的BindingContext通常是ViewModel的一个实例。

一个简单的ViewModel

作为ViewModels的介绍,我们先来看一个没有的程序。 早些时候,您看到了如何定义一个新的XML名称空间声明,以允许XAML文件引用其他程序集中的类。 这是一个为System命名空间定义XML名称空间声明的程序:

点击(此处)折叠或打开

  1. xmlns:sys="clr-namespace:System;assembly=mscorlib"
该程序可以使用x:Static从静态DateTime.Now属性获取当前日期和时间,并将该DateTime值设置为StackLayout上的BindingContext:

点击(此处)折叠或打开

  1. StackLayout BindingContext="{x:Static sys:DateTime.Now}">
BindingContext是一个非常特殊的属性:当你在一个元素上设置BindingContext时,它被该元素的所有子元素继承。 这意味着StackLayout的所有子节点都具有相同的BindingContext,并且可以包含对该对象属性的简单绑定。
在One-Shot DateTime程序中,其中两个子项包含对该DateTime值的属性的绑定,但另外两个子项包含似乎缺少绑定路径的绑定。 这意味着DateTime值本身用于StringFormat:
	

点击(此处)折叠或打开

  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.              x:Class="XamlSamples.OneShotDateTimePage"
  5.              Title="One-Shot DateTime Page">
  6.     StackLayout BindingContext="{x:Static sys:DateTime.Now}"
  7.                  HorizontalOptions="Center"
  8.                  VerticalOptions="Center">
  9.         Label Text="{Binding Year, StringFormat='The year is {0}'}" />
  10.         Label Text="{Binding StringFormat='The month is {0:MMMM}'}" />
  11.         Label Text="{Binding Day, StringFormat='The day is {0}'}" />
  12.         Label Text="{Binding StringFormat='The time is {0:T}'}" />
  13.     /StackLayout>
  14. /ContentPage>

当然,最大的问题是,页面初建时的日期和时间是一次设置的,绝不会改变:

点击(此处)折叠或打开

  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.              x:Class="XamlSamples.OneShotDateTimePage"
  5.              Title="One-Shot DateTime Page">

  6.     StackLayout BindingContext="{x:Static sys:DateTime.Now}"
  7.                  HorizontalOptions="Center"
  8.                  VerticalOptions="Center">

  9.         Label Text="{Binding Year, StringFormat='The year is {0}'}" />
  10.         Label Text="{Binding StringFormat='The month is {0:MMMM}'}" />
  11.         Label Text="{Binding Day, StringFormat='The day is {0}'}" />
  12.         Label Text="{Binding StringFormat='The time is {0:T}'}" />

  13.     /StackLayout>
  14. /ContentPage>
当然,最大的问题是,页面初建时的日期和时间是一次设置的,绝不会改变:


一个XAML文件可以显示一个始终显示当前时间的时钟,但它需要一些代码来帮助。从MVVM的角度来看,Model和ViewModel是完全用代码编写的类。 View通常是一个XAML文件,通过数据绑定引用ViewModel中定义的属性。
一个合适的Model对于ViewModel是无知的,一个合适的ViewModel对这个View是无知的。但是,程序员通常会将ViewModel公开的数据类型定制为与特定用户界面相关的数据类型。例如,如果一个Model访问包含8位字符ASCII字符串的数据库,则ViewModel需要将这些字符串转换为Unicode字符串,以便在用户界面中独占使用Unicode。
在MVVM的简单例子中(例如这里所示的例子),通常根本不存在Model,而模式只涉及与数据绑定关联的View和ViewModel。
下面是一个时钟的ViewModel,只有一个名为DateTime的属性,但是每秒更新一次DateTime属性:

点击(此处)折叠或打开

  1. using System;
  2. using System.ComponentModel;
  3. using Xamarin.Forms;

  4. namespace XamlSamples
  5. {
  6.     class ClockViewModel : INotifyPropertyChanged
  7.     {
  8.         DateTime dateTime;

  9.         public event PropertyChangedEventHandler PropertyChanged;

  10.         public ClockViewModel()
  11.         {
  12.             this.DateTime = DateTime.Now;

  13.             Device.StartTimer(TimeSpan.FromSeconds(1), () =>
  14.                 {
  15.                     this.DateTime = DateTime.Now;
  16.                     return true;
  17.                 });
  18.         }

  19.         public DateTime DateTime
  20.         {
  21.             set
  22.             {
  23.                 if (dateTime != value)
  24.                 {
  25.                     dateTime = value;

  26.                     if (PropertyChanged != null)
  27.                     {
  28.                         PropertyChanged(this, new PropertyChangedEventArgs("DateTime"));
  29.                     }
  30.                 }
  31.             }
  32.             get
  33.             {
  34.                 return dateTime;
  35.             }
  36.         }
  37.     }
  38. }
ViewModels通常实现INotifyPropertyChanged接口,这意味着只要其中一个属性发生变化,该类就会触发一个PropertyChanged事件。 Xamarin.Forms中的数据绑定机制将一个处理程序附加到此PropertyChanged事件,以便在属性更改时通知它,并使目标更新为新值。
基于这个ViewModel的时钟可以像这样简单:

点击(此处)折叠或打开

  1. ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
  2.              xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
  3.              xmlns:local="clr-namespace:XamlSamples;assembly=XamlSamples"
  4.              x:Class="XamlSamples.ClockPage"
  5.              Title="Clock Page">

  6.     Label Text="{Binding DateTime, StringFormat='{0:T}'}"
  7.            FontSize="Large"
  8.            HorizontalOptions="Center"
  9.            VerticalOptions="Center">
  10.         Label.BindingContext>
  11.             local:ClockViewModel />
  12.         /Label.BindingContext>
  13.     /Label>
  14. /ContentPage>
请注意ClockViewModel如何使用属性元素标签设置为Label的BindingContext。 或者,您可以在Resources集合中实例化ClockViewModel,并通过StaticResource标记扩展将其设置为BindingContext。 或者,代码隐藏文件可以实例化ViewModel。
标签的文本属性上的绑定标记扩展名格式的日期时间属性。 这是显示器:



通过使用句点分隔属性,也可以访问ViewModel的DateTime属性的单独属性:

点击(此处)折叠或打开

  1. Label Text="{Binding DateTime.Second, StringFormat='{0}'}">


 
 

交互式MVVMWS

MVVM通常用于基于底层数据模型的交互式视图的双向数据绑定。
这是一个名为HslViewModel的类,它将Color值转换为Hue,Saturation和Luminosity值,反之亦然:

点击(此处)折叠或打开

  1. using System;
  2. using System.ComponentModel;
  3. using Xamarin.Forms;

  4. namespace XamlSamples
  5. {
  6.     public class HslViewModel : INotifyPropertyChanged
  7.     {
  8.         double hue, saturation, luminosity;
  9.         Color color;

  10.         public event PropertyChangedEventHandler PropertyChanged;

  11.         public double Hue
  12.         {
  13.             set
  14.             {
  15.                 if (hue != value)
  16.                 {
  17.                     hue = value;
  18.                     OnPropertyChanged("Hue");
  19.                     SetNewColor();
  20.                 }
  21.             }
  22.             get
  23.             {
  24.                 return hue;
  25.             }
  26.         }

  27.         public double Saturation
  28.         {
  29.             set
  30.             {
  31.                 if (saturation != value)
  32.                 {
  33.                     saturation = value;
  34.                     OnPropertyChanged("Saturation");
  35.                     SetNewColor();
  36.                 }
  37.             }
  38.             get
  39.             {
  40.                 return saturation;
  41.             }
  42.         }

  43.         public double Luminosity
  44.         {
  45.             set
  46.             {
  47.                 if (luminosity != value)
  48.                 {
  49.                     luminosity = value;
  50.                     OnPropertyChanged("Luminosity");
  51.                     SetNewColor();
  52.                 }
  53.             }
  54.             get
  55.             {
  56.                 return luminosity;
  57.             }
  58.         }

  59.         public Color Color
  60.         {
  61.             set
  62.             {
  63.                 if (color != value)
  64.                 {
  65.                     color = value;
  66.                     OnPropertyChanged("Color");

  67.                     Hue = value.Hue;
  68.                     Saturation = value.Saturation;
  69.                     Luminosity = value.Luminosity;
  70.                 }
  71.             }
  72.             get
  73.             {
  74.                 return color;
  75.             }
  76.         }

  77.         void SetNewColor()
  78.         {
  79.             Color = Color.FromHsla(Hue, Saturation, Luminosity);
  80.         }

  81.         protected virtual void OnPropertyChanged(string propertyName)
  82.         {
  83.             PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
  84.         }
  85.     }
  86. }
对“色相”,“饱和度”和“亮度”属性所做的更改会导致Color属性发生更改,而更改为Color将导致其他三个属性发生更改。 这可能看起来像一个无限循环,除非该类不调用PropertyChanged事件,除非该属性实际上已经改变。 这终止了不可控制的反馈回路。
以下XAML文件包含其Color属性绑定到ViewModel的Color属性的BoxView,以及绑定到Hue,Saturation和Luminosity属性的三个Slider和三个Label视图:

点击(此处)折叠或打开

  1. ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
  2.              xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
  3.              xmlns:local="clr-namespace:XamlSamples;assembly=XamlSamples"
  4.              x:Class="XamlSamples.HslColorScrollPage"
  5.              Title="HSL Color Scroll Page">
  6.     ContentPage.BindingContext>
  7.         local:HslViewModel Color="Aqua" />
  8.     /ContentPage.BindingContext>

  9.     StackLayout Padding="10, 0">
  10.         BoxView Color="{Binding Color}"
  11.                  VerticalOptions="FillAndExpand" />

  12.         Label Text="{Binding Hue, StringFormat='Hue = {0:F2}'}"
  13.                HorizontalOptions="Center" />

  14.         Slider Value="{Binding Hue, Mode=TwoWay}" />

  15.         Label Text="{Binding Saturation, StringFormat='Saturation = {0:F2}'}"
  16.                HorizontalOptions="Center" />

  17.         Slider Value="{Binding Saturation, Mode=TwoWay}" />

  18.         Label Text="{Binding Luminosity, StringFormat='Luminosity = {0:F2}'}"
  19.                HorizontalOptions="Center" />

  20.         Slider Value="{Binding Luminosity, Mode=TwoWay}" />
  21.     /StackLayout>
  22. /ContentPage>
每个Label上的绑定是默认的OneWay。 它只需要显示值。 但每个滑块的绑定是双向的。 这允许Slider从ViewModel初始化。 注意,当ViewModel被实例化时,Color属性被设置为蓝色。 但是滑块的改变也需要为ViewModel中的属性设置一个新的值,然后计算一个新的颜色。



用ViewModels命令

在许多情况下,MVVM模式仅限于处理ViewModel中View数据对象中的数据项:用户界面对象。
但是,View有时需要包含在ViewModel中触发各种操作的按钮。 但是ViewModel不能包含按钮的单击处理程序,因为这将把ViewModel绑定到特定的用户界面范例。
为了允许ViewModel更独立于特定的用户界面对象,但仍允许在ViewModel中调用方法,则存在命令界面。 Xamarin.Forms中的以下元素支持此命令接口:

  • Button
  • MenuItem
  • ToolbarItem
  • SearchBar
  • TextCell (ImageCell也是如此)
  • ListView
  • TapGestureRecognizer

除SearchBar和ListView元素外,这些元素定义了两个属性:

  • Command ,类型是System.Windows.Input.ICommand
  • CommandParameter,类型是Object

SearchBar定义了SearchCommand和SearchCommandParameter属性,而ListView定义了一个ICommand类型的RefreshCommand属性。
ICommand接口定义了两个方法和一个事件:


  • void Execute(object arg)
  • bool CanExecute(object arg)
  • event EventHandler CanExecuteChanged

ViewModel可以定义ICommand类型的属性。然后,您可以将这些属性绑定到每个Button或其他元素的Command属性,或者实现此接口的自定义视图。您可以选择设置CommandParameter属性来标识绑定到此ViewModel属性的各个Button对象(或其他元素)。在内部,只要用户点击Button,传递给Execute方法的CommandParameter,Button就会调用Execute方法。
CanExecute方法和CanExecuteChanged事件用于Button按钮可能当前无效的情况,在这种情况下,Button应该禁用它自己。当Command属性第一次被设置和CanExecuteChanged事件被触发时,Button调用CanExecute。如果CanExecute返回false,则Button将自行禁用,并不会生成执行调用。
这两个类定义了几个构造函数以及ViewModel可以调用的ChangeCanExecute方法来强制Command对象触发CanExecuteChanged事件。
这是一个用于输入电话号码的简单键盘的ViewModel。注意Execute和CanExecute方法在构造函数中被定义为lambda函数:

点击(此处)折叠或打开

  1. using System;
  2. using System.ComponentModel;
  3. using System.Windows.Input;
  4. using Xamarin.Forms;

  5. namespace XamlSamples
  6. {
  7.     class KeypadViewModel : INotifyPropertyChanged
  8.     {
  9.         string inputString = "";
  10.         string displayText = "";
  11.         char[] specialChars = { '*', '#' };

  12.         public event PropertyChangedEventHandler PropertyChanged;

  13.         // Constructor
  14.         public KeypadViewModel()
  15.         {
  16.             AddCharCommand = new Commandstring>((key) =>
  17.                 {
  18.                     // Add the key to the input string.
  19.                     InputString += key;
  20.                 });

  21.             DeleteCharCommand = new Command(() =>
  22.                 {
  23.                     // Strip a character from the input string.
  24.                     InputString = InputString.Substring(0, InputString.Length - 1);
  25.                 },
  26.                 () =>
  27.                 {
  28.                     // Return true if there's something to delete.
  29.                     return InputString.Length > 0;
  30.                 });
  31.         }

  32.         // Public properties
  33.         public string InputString
  34.         {
  35.             protected set
  36.             {
  37.                 if (inputString != value)
  38.                 {
  39.                     inputString = value;
  40.                     OnPropertyChanged("InputString");
  41.                     DisplayText = FormatText(inputString);

  42.                     // Perhaps the delete button must be enabled/disabled.
  43.                     ((Command)DeleteCharCommand).ChangeCanExecute();
  44.                 }
  45.             }

  46.             get { return inputString; }
  47.         }

  48.         public string DisplayText
  49.         {
  50.             protected set
  51.             {
  52.                 if (displayText != value)
  53.                 {
  54.                     displayText = value;
  55.                     OnPropertyChanged("DisplayText");
  56.                 }
  57.             }
  58.             get { return displayText; }
  59.         }

  60.         // ICommand implementations
  61.         public ICommand AddCharCommand { protected set; get; }

  62.         public ICommand DeleteCharCommand { protected set; get; }

  63.         string FormatText(string str)
  64.         {
  65.             bool hasNonNumbers = str.IndexOfAny(specialChars) != -1;
  66.             string formatted = str;

  67.             if (hasNonNumbers || str.Length 4 || str.Length > 10)
  68.             {
  69.             }
  70.             else if (str.Length 8)
  71.             {
  72.                 formatted = String.Format("{0}-{1}",
  73.                                           str.Substring(0, 3),
  74.                                           str.Substring(3));
  75.             }
  76.             else
  77.             {
  78.                 formatted = String.Format("({0}) {1}-{2}",
  79.                                           str.Substring(0, 3),
  80.                                           str.Substring(3, 3),
  81.                                           str.Substring(6));
  82.             }
  83.             return formatted;
  84.         }

  85.         protected void OnPropertyChanged(string propertyName)
  86.         {
  87.             PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
  88.         }
  89.     }
  90. }
这个ViewModel假定AddCharCommand属性绑定到几个按钮的Command属性(或其他任何具有命令接口的属性),每个按钮都由CommandParameter标识。 这些按钮将字符添加到InputString属性,然后将其格式化为DisplayText属性的电话号码。
另外还有一个名为DeleteCharCommand的ICommand类型的第二个属性。 这是绑定到一个后退间隔按钮,但该按钮应该被禁用,如果没有字符删除。
下面的键盘不像视觉上那么复杂。 相反,标记已经降到最低,以更清楚地展示命令接口的使用:

点击(此处)折叠或打开

  1. ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
  2.              xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
  3.              xmlns:local="clr-namespace:XamlSamples;assembly=XamlSamples"
  4.              x:Class="XamlSamples.KeypadPage"
  5.              Title="Keypad Page">

  6.     Grid HorizontalOptions="Center"
  7.           VerticalOptions="Center">
  8.         Grid.BindingContext>
  9.             local:KeypadViewModel />
  10.         /Grid.BindingContext>

  11.         Grid.RowDefinitions>
  12.             RowDefinition Height="Auto" />
  13.             RowDefinition Height="Auto" />
  14.             RowDefinition Height="Auto" />
  15.             RowDefinition Height="Auto" />
  16.             RowDefinition Height="Auto" />
  17.         /Grid.RowDefinitions>

  18.         Grid.ColumnDefinitions>
  19.             ColumnDefinition Width="80" />
  20.             ColumnDefinition Width="80" />
  21.             ColumnDefinition Width="80" />
  22.         /Grid.ColumnDefinitions>

  23.         !-- Internal Grid for top row of items -->
  24.         Grid Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="3">
  25.             Grid.ColumnDefinitions>
  26.                 ColumnDefinition Width="*" />
  27.                 ColumnDefinition Width="Auto" />
  28.             /Grid.ColumnDefinitions>

  29.             Frame Grid.Column="0"
  30.                    OutlineColor="Accent">
  31.                 Label Text="{Binding DisplayText}" />
  32.             /Frame>

  33.             Button Text="?"
  34.                     Command="{Binding DeleteCharCommand}"
  35.                     Grid.Column="1"
  36.                     BorderWidth="0" />
  37.         /Grid>

  38.         Button Text="1"
  39.                 Command="{Binding AddCharCommand}"
  40.                 CommandParameter="1"
  41.                 Grid.Row="1" Grid.Column="0" />

  42.         Button Text="2"
  43.                 Command="{Binding AddCharCommand}"
  44.                 CommandParameter="2"
  45.                 Grid.Row="1" Grid.Column="1" />

  46.         Button Text="3"
  47.                 Command="{Binding AddCharCommand}"
  48.                 CommandParameter="3"
  49.                 Grid.Row="1" Grid.Column="2" />

  50.         Button Text="4"
  51.                 Command="{Binding AddCharCommand}"
  52.                 CommandParameter="4"
  53.                 Grid.Row="2" Grid.Column="0" />

  54.         Button Text="5"
  55.                 Command="{Binding AddCharCommand}"
  56.                 CommandParameter="5"
  57.                 Grid.Row="2" Grid.Column="1" />

  58.         Button Text="6"
  59.                 Command="{Binding AddCharCommand}"
  60.                 CommandParameter="6"
  61.                 Grid.Row="2" Grid.Column="2" />

  62.         Button Text="7"
  63.                 Command="{Binding AddCharCommand}"
  64.                 CommandParameter="7"
  65.                 Grid.Row="3" Grid.Column="0" />

  66.         Button Text="8"
  67.                 Command="{Binding AddCharCommand}"
  68.                 CommandParameter="8"
  69.                 Grid.Row="3" Grid.Column="1" />

  70.         Button Text="9"
  71.                 Command="{Binding AddCharCommand}"
  72.                 CommandParameter="9"
  73.                 Grid.Row="3" Grid.Column="2" />

  74.         Button Text="*"
  75.                 Command="{Binding AddCharCommand}"
  76.                 CommandParameter="*"
  77.                 Grid.Row="4" Grid.Column="0" />

  78.         Button Text="0"
  79.                 Command="{Binding AddCharCommand}"
  80.                 CommandParameter="0"
  81.                 Grid.Row="4" Grid.Column="1" />

  82.         Button Text="#"
  83.                 Command="{Binding AddCharCommand}"
  84.                 CommandParameter="#"
  85.                 Grid.Row="4" Grid.Column="2" />
  86.     /Grid>
  87. /ContentPage>
出现在该标记中的第一个Button的Command属性绑定到DeleteCharCommand; 剩下的都绑定到AddCharCommand,CommandParameter与Button面上出现的字符相同。 以下是正在实施的计划:



调用异步方法

命令也可以调用异步方法。 这是通过在指定Execute方法时使用async和await关键字来实现的:

点击(此处)折叠或打开

  1. DownloadCommand = new Command (async () => await DownloadAsync ());
这表明DownloadAsync方法是一个任务,应该等待:

点击(此处)折叠或打开

  1. async Task DownloadAsync ()
  2. {
  3.     await Task.Run (() => Download ());
  4. }

  5. void Download ()
  6. {
  7.     ...
  8. }


 
 

实现一个导航菜单

包含本系列文章中所有源代码的XamlSamples程序使用ViewModel作为其主页。 这个ViewModel是一个短类的定义,它有三个名为Type,Title和Description的属性,它们包含了每个样例页面的类型,一个标题和一个简短描述。 另外,ViewModel定义了一个名为All的静态属性,它是程序中所有页面的集合:

点击(此处)折叠或打开

  1. public class PageDataViewModel
  2. {
  3.     public PageDataViewModel(Type type, string title, string description)
  4.     {
  5.         Type = type;
  6.         Title = title;
  7.         Description = description;
  8.     }

  9.     public Type Type { private set; get; }

  10.     public string Title { private set; get; }

  11.     public string Description { private set; get; }

  12.     static PageDataViewModel()
  13.     {
  14.         All = new ListPageDataViewModel>
  15.         {
  16.             // Part 1. Getting Started with XAML
  17.             new PageDataViewModel(typeof(HelloXamlPage), "Hello, XAML",
  18.                                   "Display a Label with many properties set"),

  19.             new PageDataViewModel(typeof(XamlPlusCodePage), "XAML + Code",
  20.                                   "Interact with a Slider and Button"),

  21.             // Part 2. Essential XAML Syntax
  22.             new PageDataViewModel(typeof(GridDemoPage), "Grid Demo",
  23.                                   "Explore XAML syntax with the Grid"),

  24.             new PageDataViewModel(typeof(AbsoluteDemoPage), "Absolute Demo",
  25.                                   "Explore XAML syntax with AbsoluteLayout"),

  26.             // Part 3. XAML Markup Extensions
  27.             new PageDataViewModel(typeof(SharedResourcesPage), "Shared Resources",
  28.                                   "Using resource dictionaries to share resources"),

  29.             new PageDataViewModel(typeof(StaticConstantsPage), "Static Constants",
  30.                                   "Using the x:Static markup extensions"),

  31.             new PageDataViewModel(typeof(RelativeLayoutPage), "Relative Layout",
  32.                                   "Explore XAML markup extensions"),

  33.             // Part 4. Data Binding Basics
  34.             new PageDataViewModel(typeof(SliderBindingsPage), "Slider Bindings",
  35.                                   "Bind properties of two views on the page"),

  36.             new PageDataViewModel(typeof(SliderTransformsPage), "Slider Transforms",
  37.                                   "Use Sliders with reverse bindings"),

  38.             new PageDataViewModel(typeof(ListViewDemoPage), "ListView Demo",
  39.                                   "Use a ListView with data bindings"),

  40.             // Part 5. From Data Bindings to MVVM
  41.             new PageDataViewModel(typeof(OneShotDateTimePage), "One-Shot DateTime",
  42.                                   "Obtain the current DateTime and display it"),

  43.             new PageDataViewModel(typeof(ClockPage), "Clock",
  44.                                   "Dynamically display the current time"),

  45.             new PageDataViewModel(typeof(HslColorScrollPage), "HSL Color Scroll",
  46.                                   "Use a view model to select HSL colors"),

  47.             new PageDataViewModel(typeof(KeypadPage), "Keypad",
  48.                                   "Use a view model for numeric keypad logic")
  49.         };
  50.     }

  51.     public static IListPageDataViewModel> All { private set; get; }
  52. }
MainPage的XAML文件定义了一个ListBox,其ItemsSource属性被设置为All属性,并包含一个TextCell用于显示每个页面的Title和Description属性:

点击(此处)折叠或打开

  1. ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
  2.              xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
  3.              xmlns:local="clr-namespace:XamlSamples"
  4.              x:Class="XamlSamples.MainPage"
  5.              Padding="5, 0"
  6.              Title="XAML Samples">

  7.     ListView ItemsSource="{x:Static local:PageDataViewModel.All}"
  8.               ItemSelected="OnListViewItemSelected">
  9.         ListView.ItemTemplate>
  10.             DataTemplate>
  11.                 TextCell Text="{Binding Title}"
  12.                           Detail="{Binding Description}" />
  13.             /DataTemplate>
  14.         /ListView.ItemTemplate>
  15.     /ListView>
  16. /ContentPage>
页面显示在一个可滚动列表中:



代码隐藏文件中的处理程序在用户选择某个项目时被触发。 该处理程序将ListBox的SelectedItem属性设置为null,然后实例化所选页面并导航到它:

点击(此处)折叠或打开

  1. private async void OnListViewItemSelected(object sender, SelectedItemChangedEventArgs args)
  2. {
  3.     (sender as ListView).SelectedItem = null;

  4.     if (args.SelectedItem != null)
  5.     {
  6.         PageDataViewModel pageData = args.SelectedItem as PageDataViewModel;
  7.         Page page = (Page)Activator.CreateInstance(pageData.Type);
  8.         await Navigation.PushAsync(page);
  9.     }
  10. }


 
 

概要

XAML是在Xamarin.Forms应用程序中定义用户界面的强大工具,特别是在使用数据绑定和MVVM时。 其结果是一个干净,优雅,并可能toolable表示的用户界面代码中的所有后台支持。

相关实践学习
DataV Board用户界面概览
本实验带领用户熟悉DataV Board这款可视化产品的用户界面
阿里云实时数仓实战 - 项目介绍及架构设计
课程简介 1)学习搭建一个数据仓库的过程,理解数据在整个数仓架构的从采集、存储、计算、输出、展示的整个业务流程。 2)整个数仓体系完全搭建在阿里云架构上,理解并学会运用各个服务组件,了解各个组件之间如何配合联动。 3 )前置知识要求   课程大纲 第一章 了解数据仓库概念 初步了解数据仓库是干什么的 第二章 按照企业开发的标准去搭建一个数据仓库 数据仓库的需求是什么 架构 怎么选型怎么购买服务器 第三章 数据生成模块 用户形成数据的一个准备 按照企业的标准,准备了十一张用户行为表 方便使用 第四章 采集模块的搭建 购买阿里云服务器 安装 JDK 安装 Flume 第五章 用户行为数据仓库 严格按照企业的标准开发 第六章 搭建业务数仓理论基础和对表的分类同步 第七章 业务数仓的搭建  业务行为数仓效果图  
目录
相关文章
|
15天前
|
监控 前端开发 UED
理解 MVVM 中的数据双向绑定
【10月更文挑战第21天】数据双向绑定是 MVVM 架构中的一个核心特性,它为前端开发带来了诸多便利和优势。理解并熟练运用数据双向绑定,有助于我们构建更加高效、交互性更强的应用程序。同时,我们也需要在实际应用中注意性能和复杂性等方面的问题,以确保应用的良好运行和用户体验。还可以结合具体的项目经验和实际案例,进一步深入探讨数据双向绑定在不同场景下的应用和优化策略。
|
3月前
|
设计模式 前端开发
MVVM视图模型
这篇文章介绍了MVVM(Model-View-ViewModel)设计模式的基本概念和应用,以及如何利用它来构建高效、可维护的应用程序。
MVVM视图模型
|
6月前
|
前端开发
MVVM LiveData+DataBinding+Lifecycle+ViewModel架构
MVVM LiveData+DataBinding+Lifecycle+ViewModel架构
64 1
|
前端开发 JavaScript
08avalon - 数据模型($model)
08avalon - 数据模型($model)
44 0
|
前端开发 .NET 数据库
一起谈.NET技术,使用View Model从表现层分离领域模型
MVC架构模式是近年来编程世界里最长被提及的模式之一,Model-View-Controller(模型-视图-控制器,MVC) 模式将你的软件组织并分解成三个截然不同的角色: Model 封装了你的应用数据、应用流程和业务逻辑。
982 0
|
C#
WPF ViewModel与多个View绑定后如何解决的问题
原文:WPF ViewModel与多个View绑定后如何解决的问题 当重复创建View并绑定同一个ViewModel后,ViewModel中的字段更新,在新的View中的没有反应或者在View中找不到相应的视觉树(如ListBox的ListBoxItem) 初始的解决方案:View关闭后,注销属性Unregister Dependency。
1395 0
|
前端开发 C#
WPF MVVM 架构 Step By Step(6)(把actions从view model解耦)
原文:WPF MVVM 架构 Step By Step(6)(把actions从view model解耦)   到现在为止,我们创建了一个简单的MVVM的例子,包含了实现了的属性和命令。我们现在有这样一个包含了例如textbox类似的输入元素的视图,textbox用绑定来和view model联系,像点击button这样的行为用命令来联系。
1552 0
|
前端开发
利刃 MVVMLight 2:Model、View、ViewModel结构以及全局视图模型注入器的说明
原文:利刃 MVVMLight 2:Model、View、ViewModel结构以及全局视图模型注入器的说明      上一篇我们已经介绍了如何使用NuGet把MVVMLight应用到我们的WPF项目中。
1021 0
|
数据库 数据安全/隐私保护