欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

第十八章:MVVM(四)

程序员文章站 2023-12-23 19:38:39
...

ViewModel中的交互式属性
ViewModel的第二个例子做了一些非常基本的事情,你永远不会为此目的编写ViewModel。 SimpleMultiplierViewModel类简单地将两个数字相乘。 但它是一个很好的例子,用于演示具有多个交互属性的ViewModel的开销和机制。 (虽然你永远不会编写一个ViewModel来将两个数字相乘,但你可以编写一个ViewModel来解决二次方程或更复杂的东西。)
SimpleMultiplierViewModel类是SimpleMultiplier项目的一部分:

using System;
using System.ComponentModel;
namespace SimpleMultiplier
{
    class SimpleMultiplierViewModel : INotifyPropertyChanged
    {
        double multiplicand, multiplier, product;
        public event PropertyChangedEventHandler PropertyChanged;
        public double Multiplicand 
        {
            set
            {
                if (multiplicand != value)
                {
                    multiplicand = value;
                    OnPropertyChanged("Multiplicand");
                    UpdateProduct();
                }
            }
            get
            {
                return multiplicand;
            }
        }
        public double Multiplier
        {
            set
            {
                if (multiplier != value)
                {
                    multiplier = value;
                    OnPropertyChanged("Multiplier");
                    UpdateProduct();
                }
            }
            get
            {
                return multiplier;
            }
        }
        public double Product
        {
            protected set
            {
                if (product != value)
                {
                    product = value;
                    OnPropertyChanged("Product");
                } 
            }            
            get
            {
                return product;
            }
        }
        void UpdateProduct() 
        {
            Product = Multiplicand * Multiplier;
        }
        protected void OnPropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
}

该类定义了double类型的三个公共属性,名为Multiplicand,Multiplier和Product。每个属性都由私有字段支持。前两个属性的set和get访问器是公共的,但Product属性的set访问器受到保护,以防止它在类外部设置,同时仍允许后代类更改它。
每个属性的set访问器首先检查属性值是否实际更改,如果是,则将支持字段设置为该值,并使用该属性名称调用名为OnPropertyChanged的方法。
INotifyPropertyChanged接口不需要OnPropertyChanged方法,但ViewModel类通常包含一个用于减少代码重复的方法。它通常被定义为受保护,以防您需要从另一个ViewModel派生一个ViewModel并在派生类中触发该事件。在本章的后面,您将看到在INotifyPropertyChanged类中减少代码重复的技术。
Multiplicand和Multiplier属性的set访问器通过调用UpdateProduct方法结束。这是执行将两个属性的值相乘并为Product属性设置新值的方法,然后触发其自己的PropertyChanged事件。
这是使用此ViewModel的XAML文件:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:SimpleMultiplier"
             x:Class="SimpleMultiplier.SimpleMultiplierPage"
             Padding="10, 0">
    <ContentPage.Resources>
        <ResourceDictionary>
            <local:SimpleMultiplierViewModel x:Key="viewModel" />
            <Style TargetType="Label">
                <Setter Property="FontSize" Value="Large" />
            </Style>
        </ResourceDictionary>
    </ContentPage.Resources>
    <StackLayout BindingContext="{StaticResource viewModel}">
 
        <StackLayout VerticalOptions="CenterAndExpand">
            <Slider Value="{Binding Multiplicand}" />
            <Slider Value="{Binding Multiplier}" />
        </StackLayout>
        <StackLayout Orientation="Horizontal"
                     Spacing="0"
                     VerticalOptions="CenterAndExpand"
                     HorizontalOptions="Center">
            <Label Text="{Binding Multiplicand, StringFormat='{0:F3}'}" />
            <Label Text="{Binding Multiplier, StringFormat=' x {0:F3}'}" />
            <Label Text="{Binding Product, StringFormat=' = {0:F3}'}" /> 
        </StackLayout>
    </StackLayout>
</ContentPage>

SimpleMultiplierViewModel在Resources字典中实例化,并使用StaticResource标记扩展设置为StackLayout的BindingContext属性。 BindingContext由StackLayout的所有子孙继承,其中包括两个Slider和三个Label元素。 BindingContext的使用允许这些绑定尽可能简单。
Slider的Value属性的默认绑定模式是TwoWay。 每个Slider的Value属性的更改会导致ViewModel的属性发生更改。
三个Label元素显示ViewModel的所有三个属性的值,其中一些格式插入时间,并且等号与数字相符:

<Label Text="{Binding Multiplicand, StringFormat='{0:F3}'}" />
<Label Text="{Binding Multiplier, StringFormat=' x {0:F3}'}" />
<Label Text="{Binding Product, StringFormat=' = {0:F3}'}" />

对于前两个,您可以将Label元素的Text属性直接绑定到相应Slider的Value属性,但这需要为每个Slider指定一个带有x的名称:Name并在Source参数中引用该名称 使用x:Reference标记扩展。 此程序中使用的方法更加清晰,并验证数据是否正在通过ViewModel从每个Slider到每个Label进行完整的旅行。
除了在构造函数中调用InitializeComponent之外,代码隐藏文件中没有任何内容。 所有业务逻辑都在ViewModel中,整个用户界面在XAML中定义:
第十八章:MVVM(四)
如果您愿意,可以在资源字典中实例化时初始化ViewModel:

<local:SimpleMultiplierViewModel x:Key="viewModel"
                                 Multiplicand="0.5"
                                 Multiplier="0.5" />

作为双向绑定的结果,Slider元素将获得这些初始值。
当您想要稍微改变用户界面时,将用户界面与底层业务逻辑分离的好处变得很明显,可能通过将Stepper替换为Slider中的一个或两个数字:

<StackLayout VerticalOptions="CenterAndExpand">
    <Slider Value="{Binding Multiplicand}" />
    <Stepper Value="{Binding Multiplier}" />
</StackLayout>

除了两个元素的不同范围外,功能相同:
第十八章:MVVM(四)
您也可以替换条目:

<StackLayout VerticalOptions="CenterAndExpand">
    <Slider Value="{Binding Multiplicand}" />
    <Entry Text="{Binding Multiplier}" />
</StackLayout>

Entry的Text属性的默认绑定模式也是TwoWay,因此您需要担心的是source属性double和target属性字符串之间的转换。 幸运的是,此转换由绑定基础结构自动处理:
第十八章:MVVM(四)
如果键入一系列无法​​转换为double的字符,则绑定将保留最后一个有效值。如果您需要更复杂的验证,则必须实现自己的验证(例如使用触发器,将在第23章中讨论)。
一个有趣的实验是键入1E-1,这是可转换为双精度的科学记数法。你会看到它在条目中立即变为“0.1”。这是TwoWay绑定的效果:Multiplier属性从Entry设置为1E-1,但绑定基础结构在值返回Entry时调用的ToString方法返回文本“0.1”。因为这与现有的Entry文本,新文本已设置。为防止这种情况发生,您可以将绑定模式设置为OneWayToSource:

<StackLayout VerticalOptions="CenterAndExpand">
    <Slider Value="{Binding Multiplicand}" />
    <Entry Text="{Binding Multiplier, Mode=OneWayToSource}" />
</StackLayout>

现在,ViewModel的Multiplier属性是从Entry的Text属性设置的,而不是相反。如果您不需要从ViewModel更新这两个视图,则可以将它们都设置为OneWayToSource。但通常你会希望MVVM绑定是TwoWay。
您是否应该担心双向绑定中的无限循环?通常不会,因为PropertyChanged事件仅在属性实际更改时触发,而不是仅在它设置为相同值时触发。通常,源和目标将在反弹或反弹后停止相互更新。但是,可以编写一个“病态”值转换器,它不提供往返转换,并且确实可以在双向绑定中导致无限更新周期。

上一篇:

下一篇: