maui-data-binding

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

.NET MAUI Data Binding

.NET MAUI 数据绑定

Binding Modes

绑定模式

ModeDirectionUse case
OneWay
Source → TargetDisplay-only (default for most properties)
TwoWay
Source ↔ TargetEditable controls (
Entry.Text
,
Switch.IsToggled
)
OneWayToSource
Target → SourceRead user input without pushing back to UI
OneTime
Source → Target (once)Static values; no change tracking overhead
Set explicitly when the property default doesn't match your intent:
xml
<Entry Text="{Binding UserName, Mode=TwoWay}" />
<Label Text="{Binding Title, Mode=OneTime}" />
模式方向适用场景
OneWay
源 → 目标仅显示(大多数属性的默认模式)
TwoWay
源 ↔ 目标可编辑控件(
Entry.Text
Switch.IsToggled
OneWayToSource
目标 → 源读取用户输入但不回推至UI
OneTime
源 → 目标(仅一次)静态值;无变更追踪开销
当属性默认模式不符合需求时,需显式设置:
xml
<Entry Text="{Binding UserName, Mode=TwoWay}" />
<Label Text="{Binding Title, Mode=OneTime}" />

BindingContext and Property Paths

BindingContext与属性路径

  • Every
    BindableObject
    inherits
    BindingContext
    from its parent unless explicitly set.
  • Property paths support dot notation and indexers:
xml
<Label Text="{Binding Address.City}" />
<Label Text="{Binding Items[0].Name}" />
  • Set
    BindingContext
    in XAML or code-behind:
xml
<ContentPage xmlns:vm="clr-namespace:MyApp.ViewModels"
             x:DataType="vm:MainViewModel">
    <ContentPage.BindingContext>
        <vm:MainViewModel />
    </ContentPage.BindingContext>
</ContentPage>
  • 所有
    BindableObject
    会从父元素继承
    BindingContext
    ,除非显式设置。
  • 属性路径支持点标记和索引器:
xml
<Label Text="{Binding Address.City}" />
<Label Text="{Binding Items[0].Name}" />
  • 可在XAML或代码后置中设置
    BindingContext
xml
<ContentPage xmlns:vm="clr-namespace:MyApp.ViewModels"
             x:DataType="vm:MainViewModel">
    <ContentPage.BindingContext>
        <vm:MainViewModel />
    </ContentPage.BindingContext>
</ContentPage>

Compiled Bindings

编译绑定

Compiled bindings resolve at build time, delivering 8–20× faster binding resolution than reflection-based bindings.
编译绑定在构建时解析路径,比基于反射的绑定快8-20倍

Enabling compiled bindings

启用编译绑定

Declare
x:DataType
on the element or an ancestor:
xml
<ContentPage x:DataType="vm:MainViewModel">
    <Label Text="{Binding UserName}" />
</ContentPage>
在元素或其祖先上声明
x:DataType
xml
<ContentPage x:DataType="vm:MainViewModel">
    <Label Text="{Binding UserName}" />
</ContentPage>

DataTemplate requires its own x:DataType

DataTemplate需单独声明x:DataType

DataTemplate
creates a new binding scope. Always redeclare:
xml
<CollectionView ItemsSource="{Binding People}">
    <CollectionView.ItemTemplate>
        <DataTemplate x:DataType="model:Person">
            <Label Text="{Binding FullName}" />
        </DataTemplate>
    </CollectionView.ItemTemplate>
</CollectionView>
DataTemplate
会创建新的绑定作用域,必须重新声明:
xml
<CollectionView ItemsSource="{Binding People}">
    <CollectionView.ItemTemplate>
        <DataTemplate x:DataType="model:Person">
            <Label Text="{Binding FullName}" />
        </DataTemplate>
    </CollectionView.ItemTemplate>
</CollectionView>

Compiler warnings

编译器警告

WarningMeaning
XC0022Binding path not found on the declared
x:DataType
XC0023Property is not bindable
XC0024
x:DataType
type not found
XC0025Binding used without
x:DataType
(non-compiled fallback)
Treat these as errors in CI:
<WarningsAsErrors>XC0022;XC0025</WarningsAsErrors>
.
警告代码含义
XC0022绑定路径在声明的
x:DataType
中不存在
XC0023属性不可绑定
XC0024
x:DataType
类型未找到
XC0025使用绑定时未声明
x:DataType
(回退至非编译绑定)
在CI中将这些警告视为错误:
<WarningsAsErrors>XC0022;XC0025</WarningsAsErrors>

.NET 9+ compiled code bindings (SetBinding with lambda)

.NET 9+ 编译代码绑定(使用lambda的SetBinding)

csharp
// Fully AOT-safe, no reflection
label.SetBinding(Label.TextProperty,
    static (PersonViewModel vm) => vm.FullName);

// With mode and converter
entry.SetBinding(Entry.TextProperty,
    static (PersonViewModel vm) => vm.Age,
    mode: BindingMode.TwoWay,
    converter: new IntToStringConverter());
csharp
// 完全支持NativeAOT,无反射
label.SetBinding(Label.TextProperty,
    static (PersonViewModel vm) => vm.FullName);

// 带模式和转换器
entry.SetBinding(Entry.TextProperty,
    static (PersonViewModel vm) => vm.Age,
    mode: BindingMode.TwoWay,
    converter: new IntToStringConverter());

IValueConverter

IValueConverter

Implement
IValueConverter
with
Convert
(source → target) and
ConvertBack
(target → source):
csharp
public class IntToBoolConverter : IValueConverter
{
    public object? Convert(object? value, Type targetType,
        object? parameter, CultureInfo culture)
        => value is int i && i != 0;

    public object? ConvertBack(object? value, Type targetType,
        object? parameter, CultureInfo culture)
        => value is true ? 1 : 0;
}
实现
IValueConverter
接口,包含
Convert
(源→目标)和
ConvertBack
(目标→源)方法:
csharp
public class IntToBoolConverter : IValueConverter
{
    public object? Convert(object? value, Type targetType,
        object? parameter, CultureInfo culture)
        => value is int i && i != 0;

    public object? ConvertBack(object? value, Type targetType,
        object? parameter, CultureInfo culture)
        => value is true ? 1 : 0;
}

Declaring converters in XAML resources

在XAML资源中声明转换器

xml
<ContentPage.Resources>
    <local:IntToBoolConverter x:Key="IntToBool" />
</ContentPage.Resources>

<Switch IsToggled="{Binding Count, Converter={StaticResource IntToBool}}" />
xml
<ContentPage.Resources>
    <local:IntToBoolConverter x:Key="IntToBool" />
</ContentPage.Resources>

<Switch IsToggled="{Binding Count, Converter={StaticResource IntToBool}}" />

ConverterParameter

ConverterParameter

ConverterParameter
is always passed as a string. Parse it inside
Convert
:
xml
<Label Text="{Binding Score, Converter={StaticResource ThresholdConverter},
              ConverterParameter=50}" />
csharp
int threshold = int.Parse((string)parameter);
ConverterParameter
始终以字符串形式传递,需在
Convert
方法内解析:
xml
<Label Text="{Binding Score, Converter={StaticResource ThresholdConverter},
              ConverterParameter=50}" />
csharp
int threshold = int.Parse((string)parameter);

StringFormat

StringFormat

Use
Binding.StringFormat
for simple display formatting without a converter:
xml
<Label Text="{Binding Price, StringFormat='Total: {0:C2}'}" />
<Label Text="{Binding DueDate, StringFormat='{0:MMM dd, yyyy}'}" />
Note: Wrap the format string in single quotes when it contains commas or braces.
对于简单的显示格式化,可使用
Binding.StringFormat
替代转换器:
xml
<Label Text="{Binding Price, StringFormat='Total: {0:C2}'}" />
<Label Text="{Binding DueDate, StringFormat='{0:MMM dd, yyyy}'}" />
注意: 当格式字符串包含逗号或大括号时,需用单引号包裹。

Multi-Binding

多绑定

Combine multiple source values with
IMultiValueConverter
:
xml
<Label>
    <Label.Text>
        <MultiBinding Converter="{StaticResource FullNameConverter}"
                      StringFormat="{}{0}">
            <Binding Path="FirstName" />
            <Binding Path="LastName" />
        </MultiBinding>
    </Label.Text>
</Label>
csharp
public class FullNameConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType,
        object parameter, CultureInfo culture)
    {
        if (values.Length == 2 && values[0] is string first && values[1] is string last)
            return $"{first} {last}";
        return string.Empty;
    }

    public object[] ConvertBack(object value, Type[] targetTypes,
        object parameter, CultureInfo culture)
        => throw new NotSupportedException();
}
通过
IMultiValueConverter
组合多个源值:
xml
<Label>
    <Label.Text>
        <MultiBinding Converter="{StaticResource FullNameConverter}"
                      StringFormat="{}{0}">
            <Binding Path="FirstName" />
            <Binding Path="LastName" />
        </MultiBinding>
    </Label.Text>
</Label>
csharp
public class FullNameConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType,
        object parameter, CultureInfo culture)
    {
        if (values.Length == 2 && values[0] is string first && values[1] is string last)
            return $"{first} {last}";
        return string.Empty;
    }

    public object[] ConvertBack(object value, Type[] targetTypes,
        object parameter, CultureInfo culture)
        => throw new NotSupportedException();
}

Relative Bindings

相对绑定

SourceSyntaxUse case
Self
{Binding Source={RelativeSource Self}, Path=Width}
Bind to own properties
Ancestor
{Binding Source={RelativeSource AncestorType={x:Type vm:ParentVM}}, Path=Title}
Reach parent BindingContext
TemplatedParent
{Binding Source={RelativeSource TemplatedParent}, Path=Padding}
Inside ControlTemplate
xml
<!-- Square box: Height = Width -->
<BoxView WidthRequest="100"
         HeightRequest="{Binding Source={RelativeSource Self}, Path=WidthRequest}" />
源对象语法适用场景
自身
{Binding Source={RelativeSource Self}, Path=Width}
绑定至自身属性
祖先
{Binding Source={RelativeSource AncestorType={x:Type vm:ParentVM}}, Path=Title}
访问父级BindingContext
模板父级
{Binding Source={RelativeSource TemplatedParent}, Path=Padding}
在ControlTemplate内部使用
xml
<!-- 正方形框:高度 = 宽度 -->
<BoxView WidthRequest="100"
         HeightRequest="{Binding Source={RelativeSource Self}, Path=WidthRequest}" />

Binding Fallbacks

绑定回退值

  • FallbackValue – used when the binding path cannot be resolved or the converter throws.
  • TargetNullValue – used when the bound value is
    null
    .
xml
<Label Text="{Binding MiddleName, TargetNullValue='(none)',
              FallbackValue='unavailable'}" />
<Image Source="{Binding AvatarUrl, TargetNullValue='default_avatar.png'}" />
  • FallbackValue:当绑定路径无法解析或转换器抛出异常时使用。
  • TargetNullValue:当绑定值为
    null
    时使用。
xml
<Label Text="{Binding MiddleName, TargetNullValue='(none)',
              FallbackValue='unavailable'}" />
<Image Source="{Binding AvatarUrl, TargetNullValue='default_avatar.png'}" />

Threading

线程处理

MAUI automatically marshals property-change notifications to the UI thread. You can raise
PropertyChanged
from any thread; the binding engine dispatches the update to the main thread.
csharp
// Safe from a background thread
await Task.Run(() =>
{
    Items = LoadData();          // Raises PropertyChanged
    OnPropertyChanged(nameof(Items));
});
Caveat: Direct
ObservableCollection
mutations (Add/Remove) from background threads may still require
MainThread.BeginInvokeOnMainThread
.
MAUI会自动将属性变更通知调度至UI线程。你可以在任意线程触发
PropertyChanged
事件,绑定引擎会将更新调度至主线程。
csharp
// 在后台线程中执行安全
await Task.Run(() =>
{
    Items = LoadData();          // 触发PropertyChanged
    OnPropertyChanged(nameof(Items));
});
注意: 从后台线程直接修改
ObservableCollection
(如Add/Remove)可能仍需使用
MainThread.BeginInvokeOnMainThread

Performance

性能优化

  • Reflection overhead: Non-compiled bindings use reflection to resolve paths at runtime—measurably slower on large lists and startup.
  • Compiled bindings eliminate reflection; always prefer them.
  • NativeAOT / trimming: Reflection-based bindings may break under trimming. Compiled bindings (XAML
    x:DataType
    or code
    SetBinding
    with lambdas) are trimmer- and AOT-safe.
  • Avoid complex converter chains in hot paths; pre-compute values in the ViewModel instead.
  • Use
    OneTime
    mode for truly static data to skip change-tracking registration.
  • 反射开销:非编译绑定在运行时使用反射解析路径——在大型列表和启动阶段会明显变慢。
  • 编译绑定:消除反射开销;始终优先使用。
  • NativeAOT/裁剪:基于反射的绑定在裁剪环境下可能失效。编译绑定(XAML的
    x:DataType
    或代码中使用lambda的
    SetBinding
    )支持裁剪和AOT。
  • 避免在热点路径中使用复杂转换器链;建议在ViewModel中预先计算值。
  • 对于真正的静态数据,使用
    OneTime
    模式以跳过变更追踪注册。