Post

MAUI的数据绑定

MAUI 的数据绑定

一些概念

  • 在MAUI中,表达界面的类被称为 View 视图。这些类继承自 ContentView 。布局、控件都是一种视图。
  • 数据绑定描述的是两种对象之间的“订阅-发布”关系。两个对象之间的属性可以互相操作,互相影响。

视图与视图的绑定

这种关系描述两个视图组件的关联,例如滑块视图控制了文本的值,或者控制某个组件的属性(颜色,位置等)

绑定方式1:

  1. 为绑定目标配置绑定上下文(源)
  2. 为绑定目标的某个属性设置绑定源属性
1
2
3
4
label.BindingContext = slider;
label.SetBinding(Label.RotationProperty, "Value");
// BindingContext 会保存绑定源 Object
// SetBinding 则会从源中使用反射方式获取到属性 Value

绑定方式2

<Label ...
       BindingContext="{x:Reference Name=slider}"
       Rotation="{Binding Path=Value}"/>

两种方式原理相同,等效。

指明绑定源

  1. 单独设定 BindingContext

  2. 在SetBinding时使用Binding构造函数。

    label.SetBinding(Label.ScaleProperty, new Binding("Value", source: slider))

    <Label Scale="{Binding Path="Value" Source={x:Reference slider}}"/>

总之,一个可绑定的对象(通常的View都是可绑定对象),拥有一个绑定源“BindingContext”,在设置绑定时,默认就是从这里搜索数据源,当然也可以使用某个绑定源,需要内联方式配置Binding的Source属性。

绑定源有一种继承关系,当配置绑定后,MAUI会顺着View树向上搜索符合条件的绑定源。因此,使用Source设置的绑定元一定是最优先的。

依赖注入

首先搞清楚,xaml其实是对UI对象的一种描述,在UI对象构造时,InitializeComponent 会按照xaml的描述创建需要的对象。其中包括 <BindingContext><local:SomeType/></BindingContext>. 这种绑定方式在文档中叫编译绑定

编译绑定就是在UI对象创建时,在对象内部创建的数据对象,一定程度上可以提升性能。

这种编译绑定导致UI对象与数据模型形成强依赖关系,使用依赖注入可以解决此问题。

在MAUI样例程序中就是用了此方式,将View与ViewModel全部注册到IOC容器中,使用构造函数注入的方式,将ViewModel注入对应的View。

这种方式有一个问题,就是,View对象没有了默认无参构造函数,如果在xaml中直接使用,会有警告信息。

这就引出一个概念上的区别:”视图” 与 “页面”

视图(ContentView及其派生类)是“自定义控件”的基本类型,是“可重用”的基础类。

页面(ContentPage 及其派生类)是“单个页面”,表示应用中实际的一页内容的结构。

页面并不是用来重用的,因此,在样例程序中,./Views文件夹下的所有页面,都代表应用中某个可展示页面,都继承自 ContentPage,且使用了依赖注入。而./Views/Templates则继承自ContentView,代表自定义控件,未使用依赖注入,这里大多是一些数据模板,也就是Collection型组件的数据展示模板。

页面的创建,只有这几种情况:初始页面,Tab类型组件,路由。在Shell应用中,这些都是由Shell来负责的,而且,Shell的路由(导航)功能已经与IOC融合,可以自动从IOC容器中获取指定的页面实例。

绑定模式

Default 默认模式,每个属性有自己的默认模式

TwoWay 双向绑定

OneWay 数据源到目标单向绑定

OneWayToSource 反过来单向绑定

OneTime OneWay但只有绑定上下文改变才触发一次。

视图组件的属性可以被绑定是因为,属性实现了绑定接口。但如果绑定源是一个普通类型(比如ViewModel),那需要实现 INotifyPropertyChanged 接口。

MVVM 中 viewmodel

假设一个需求,三个slider控制rgb值,改变box的颜色。

这里三个可变参数,一起影响box的背景色属性,且还可能有特殊处理。这里定义Viewmodel,包含rgb和Color四个监控属性,实现INotifyPropertyChange接口。将rgb绑定到slider的Value中,这里是单向绑定默认,使得slider改变会引起rgb的更改。在ViewModel中编写rgb的更改器,重新计算color。

StringFormat 可以格式化绑定

格式转换器

Binding 对象中 除了 Path Source StringFormat 还有一个 Converter属性,用来配置格式转换。

只要你的转换器类实现了IValueConverter接口,即可被注册为转换器。

1
2
3
4
5
6
7
8
9
10
11
12
public class IntToBoolConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return (int)value != 0;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return (bool)value ? 1 : 0;
    }
}
1
2
3
4
5
6
7
<Button Text="Search"
    HorizontalOptions="Center"
    VerticalOptions="Center"
    IsEnabled="{Binding Source={x:Reference entry1},
            Path=Text.Length,
            Converter={StaticResource intToBool}}" />
        

这个转换器两个方法用来双向转换,如果是单向绑定,则只需要实现一个,另一个返回null

相对绑定

自我绑定

RelativeSource Self

自己的两个不同属性之间绑定

父绑定

RelativeSource AncestorType={x:Type xxx:xxx}

模版父绑定

RelativeSource TemplatedParent

绑定失败

Text="{Binding xxx, FallbackValue='xxx'}"

在绑定解析失败后回退值。

Text="Binding xxx, TargetNullValue=''"

在返回Null值后设置回退值

多重绑定

可以设置多个绑定对象,但是要实现转换器,将多个绑定对象转换成一个对象。

命令 Command

可以再 ViewModel 中定义命令。

1
2
3
4
5
public interface ICommand{
  public void Execute (Object parameter);
  public bool CanExecute (Object parameter);
  public event EventHandler CanExecuteChanged;
}

命令可以带参数 Command<T>(T param)

编译绑定

通过配置绑定可以看出,使用反射来绑定源的属性名称,只有在运行时才能看出是否能绑定成功(没法做类型验证)

使用 x:DataType 可以在XAML编译时解析绑定。

静态资源与标记扩展

可以再每个页面组件的Resource中定义静态字段:

1
2
3
4
5
6
7
<LayoutOptions x:Key="vertOptions" Alignment="Center" />
<x:Double x:Key="borderWidth">3</x:Double>
<OnPlatform x:Key="textColor" x:TypeArguments="Color">
    <On Platform="iOS" Value="Red" />
    <On Platform="Android" Value="Aqua" />
    <On Platform="WinUI" Value="#80FF80" />
</OnPlatform>

即可在本页面中使用静态绑定。

这种方式便于统一修改。

对于一些全局使用的静态字段,可以使用标记扩展{x:Static xxx} 来访问。

1
2
3
4
5
6
// 定义全局静态字段
namespace GlobalStatic{
  static class AppConstant{
    public readonly static Color BlackColor = new Color();
  }
}
1
2
3
4
5
6
<!-- 导入命名空间,并使用 x:static 来绑定全局静态字段 -->
<ContentPage xmlns:local="clr-namespace:GlobalStatic">
  <Frame>
  	<Text Color="{x:Static local:AppConstant.BlackColor}" />
  </Frame>
</ContentPage>

附加行为

附加行为是一种为控件添加功能的好办法,可以省去在隐藏代码文件中重复的书写事件处理。

附加行为要实现的效果如下:

  1. 控件拥有一个附加属性(可以再xaml中配置的属性),来指示是否开启功能
  2. 当开启功能,该控件就具有了某个功能。
1
2
3
4
<ContentPage ...
             xmlns:local="clr-namespace:BehaviorsDemos">
    <Entry Placeholder="Enter a System.Double" local:AttachedNumericValidationBehavior.AttachBehavior="true" />
</ContentPage>

以上代码便是一种使用附加行为的例子,将一个附加属性设置为 True 来开启某个附加功能

// 等效的C#代码
Entry entry = new Entry { Placeholder = "Enter a System.Double" };
AttachedNumericValidationBehavior.SetAttachBehavior(entry, true);

为实现以上效果,附加行为的定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// * 静态类、静态方法、静态属性
// 1. 定义开关功能的属性,并设置访问器(与普通的绑定属性范式相同)
public static class AttachedNumbericValidateBehavior{
  public static readonly BindableProperty AttachedBehaviorProperty = 
    BindableProperty.CreateAttached("AttachedBehavior", typeof(bool),
    typeof(AttachedNumbericValidateBehavior), false, 
    propertyChanged: OnAttachedBehaviorChanged);
  public static bool GetAttachedBehavior(BindableObject view){
    return (bool)view.GetValue(AttachedBehaviorProperty);
  }
  public static void SetAttachedBehavior(BindableObject view, bool value){
    view.SetValue(AttachedBehaviorProperty, value);
  }
  public static void OnAttachBehaviorChanged(BindableObject view, 
                                             object oldValue, object newValue){
    Entry entry = view as Entry;
    if(entry == null){
      return;
    }
    var attachedBehavior = (bool)newValue;
    if(attachedBehavior){
      entry.TextChanged += OnEntryTextChanged;
    }else{
      entry.TextChanged -= OnEntryTextChanged;
    }
  }
  // 附加功能的事件处理逻辑
  static void OnEntryTextChanged(object sender, TextChangedEventArgs args)
  {
      double result;
      bool isValid = double.TryParse(args.NewTextValue, out result);
      ((Entry)sender).TextColor = isValid ? Colors.Black : Colors.Red;
  }
}

MAUI 行为

Maui 行为派生自 Behavior 类型,所有的控件都拥有 Behaviors 属性,来容纳配置的行为。

当 Maui 创建这些控件实例时,会执行配置的行为,以添加一些功能或样式。

定义一个Maui行为只需要继承 Behavior<T> 并实现方法即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class MyBehavior : Behavior<View>
{
    protected override void OnAttachedTo(View bindable)
    {
        base.OnAttachedTo(bindable);
        // Perform setup
    }

    protected override void OnDetachingFrom(View bindable)
    {
        base.OnDetachingFrom(bindable);
        // Perform clean up
    }

    // Behavior implementation
}

在控件的 Behaviros 中添加该自定义行为即可应用

1
2
3
4
5
<Entry>
	<Entry.Behaviors>
  	<local:MyBehavior></local:MyBehavior>
  </Entry.Behaviors>
</Entry>

将 maui 行为与附加属性结合

平台行为

通过创建分部(partral)的继承自PlatformBehavior 的平台行为类,并在不同的平台对应文件夹下实现该分部类的行为,来定义不同平台的控件的行为。

可绑定属性和附加属性

对比:

  1. 可绑定属性所在的类必须继承自ObservableObject;附加属性则无此强制要求。
  2. 可绑定属性使用 BindableProperty.Create 创建;附加属性使用 BindableProperty.CreateAttached 创建。
  3. 附加属性的访问器也得是 static 类型,并且要带有目标参数 Target
  4. 可绑定属性不可在样式中设置;附加属性则可以在样式中设置。
This post is licensed under CC BY 4.0 by the author.