MAUI的数据绑定
MAUI 的数据绑定
一些概念
- 在MAUI中,表达界面的类被称为 View 视图。这些类继承自 ContentView 。布局、控件都是一种视图。
- 数据绑定描述的是两种对象之间的“订阅-发布”关系。两个对象之间的属性可以互相操作,互相影响。
视图与视图的绑定
这种关系描述两个视图组件的关联,例如滑块视图控制了文本的值,或者控制某个组件的属性(颜色,位置等)
绑定方式1:
- 为绑定目标配置绑定上下文(源)
- 为绑定目标的某个属性设置绑定源属性
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}"/>
两种方式原理相同,等效。
指明绑定源
单独设定 BindingContext
在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>
附加行为
附加行为是一种为控件添加功能的好办法,可以省去在隐藏代码文件中重复的书写事件处理。
附加行为要实现的效果如下:
- 控件拥有一个附加属性(可以再xaml中配置的属性),来指示是否开启功能
- 当开启功能,该控件就具有了某个功能。
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
的平台行为类,并在不同的平台对应文件夹下实现该分部类的行为,来定义不同平台的控件的行为。
可绑定属性和附加属性
对比:
- 可绑定属性所在的类必须继承自
ObservableObject
;附加属性则无此强制要求。 - 可绑定属性使用 BindableProperty.Create 创建;附加属性使用 BindableProperty.CreateAttached 创建。
- 附加属性的访问器也得是 static 类型,并且要带有目标参数 Target
- 可绑定属性不可在样式中设置;附加属性则可以在样式中设置。