数据绑定 #

数据绑定概述 #

什么是数据绑定? #

数据绑定是一种将 UI 元素的属性与数据源对象的属性连接起来的技术,当数据源发生变化时,UI 会自动更新。

text
┌─────────────────────────────────────────────────────────────┐
│                    数据绑定原理                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   ┌─────────────────┐              ┌─────────────────┐     │
│   │   数据源         │              │   目标 (UI)      │     │
│   │   (Source)      │              │   (Target)      │     │
│   │                 │              │                 │     │
│   │  Property       │◄────────────►│  Property       │     │
│   │                 │   绑定关系    │                 │     │
│   └─────────────────┘              └─────────────────┘     │
│                                                             │
│   数据源:                                                    │
│   - ViewModel 中的属性                                       │
│   - 其他控件的属性                                           │
│   - 静态资源                                                 │
│                                                             │
│   目标:                                                      │
│   - 控件的依赖属性 (BindableProperty)                        │
│   - 如 Label.Text, Entry.Text, Button.Command 等            │
│                                                             │
└─────────────────────────────────────────────────────────────┘

绑定的基本要素 #

text
┌─────────────────────────────────────────────────────────────┐
│                    绑定要素                                  │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Binding 对象包含以下要素:                                   │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  Source     - 数据源对象                             │   │
│  │  Path       - 数据源属性的路径                       │   │
│  │  Mode       - 绑定模式                               │   │
│  │  Converter  - 值转换器                               │   │
│  │  StringFormat - 字符串格式化                         │   │
│  │  FallbackValue - 绑定失败时的默认值                  │   │
│  │  TargetNullValue - 数据源为 null 时的显示值          │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

绑定语法 #

XAML 绑定语法 #

xml
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MyApp.MainPage">
    
    <StackLayout>
        
        <Label Text="{Binding Title}" />
        
        <Entry Text="{Binding Username, Mode=TwoWay}" />
        
        <Label Text="{Binding Price, StringFormat='{}{0:C}'}" />
        
        <Label Text="{Binding Count, FallbackValue=0}" />
        
        <Label Text="{Binding Description, TargetNullValue='暂无描述'}" />
        
        <Button Command="{Binding SubmitCommand}"
                Text="提交" />
        
    </StackLayout>
    
</ContentPage>

C# 绑定语法 #

csharp
public partial class MainPage : ContentPage
{
    public MainPage()
    {
        InitializeComponent();
        
        var label = new Label();
        label.SetBinding(Label.TextProperty, "Title");
        
        var entry = new Entry();
        entry.SetBinding(Entry.TextProperty, new Binding("Username", BindingMode.TwoWay));
        
        var button = new Button();
        button.SetBinding(Button.CommandProperty, "SubmitCommand");
    }
}

绑定路径 #

xml
<StackLayout BindingContext="{Binding User}">
    
    <Label Text="{Binding Name}" />
    
    <Label Text="{Binding Address.City}" />
    
    <Label Text="{Binding Address.Street}" />
    
    <Label Text="{Binding Orders[0].ProductName}" />
    
</StackLayout>

绑定模式 #

绑定模式概述 #

text
┌─────────────────────────────────────────────────────────────┐
│                    绑定模式                                  │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Default      - 使用目标的默认模式                          │
│  OneWay       - 源 → 目标 (单向)                            │
│  TwoWay       - 源 ↔ 目标 (双向)                            │
│  OneWayToSource - 目标 → 源                                 │
│  OneTime      - 源 → 目标 (仅一次)                          │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                                                     │   │
│  │  OneWay:                                            │   │
│  │  ┌─────────┐                    ┌─────────┐        │   │
│  │  │  Source │ ─────────────────► │  Target │        │   │
│  │  └─────────┘                    └─────────┘        │   │
│  │                                                     │   │
│  │  TwoWay:                                            │   │
│  │  ┌─────────┐                    ┌─────────┐        │   │
│  │  │  Source │ ◄────────────────► │  Target │        │   │
│  │  └─────────┘                    └─────────┘        │   │
│  │                                                     │   │
│  │  OneWayToSource:                                    │   │
│  │  ┌─────────┐                    ┌─────────┐        │   │
│  │  │  Source │ ◄───────────────── │  Target │        │   │
│  │  └─────────┘                    └─────────┘        │   │
│  │                                                     │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

OneWay 模式 #

xml
<Label Text="{Binding Title, Mode=OneWay}" />

<Label Text="{Binding UserName}" />
csharp
public class MainViewModel
{
    private string _title;
    public string Title
    {
        get => _title;
        set
        {
            _title = value;
            OnPropertyChanged();
        }
    }
}

TwoWay 模式 #

xml
<Entry Text="{Binding Username, Mode=TwoWay}" />

<Switch IsToggled="{Binding IsEnabled, Mode=TwoWay}" />

<Slider Value="{Binding Volume, Mode=TwoWay}" />
csharp
public class SettingsViewModel : INotifyPropertyChanged
{
    private string _username;
    public string Username
    {
        get => _username;
        set
        {
            if (_username != value)
            {
                _username = value;
                OnPropertyChanged();
            }
        }
    }

    private bool _isEnabled = true;
    public bool IsEnabled
    {
        get => _isEnabled;
        set
        {
            if (_isEnabled != value)
            {
                _isEnabled = value;
                OnPropertyChanged();
            }
        }
    }
}

OneTime 模式 #

xml
<Label Text="{Binding AppVersion, Mode=OneTime}" />

控件默认绑定模式 #

text
┌─────────────────────────────────────────────────────────────┐
│                    控件默认绑定模式                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  属性                    │ 默认模式                         │
│  ────────────────────────┼─────────────────────────────────│
│  Label.Text              │ OneWay                           │
│  Entry.Text              │ TwoWay                           │
│  Switch.IsToggled        │ TwoWay                           │
│  Slider.Value            │ TwoWay                           │
│  CheckBox.IsChecked      │ TwoWay                           │
│  DatePicker.Date         │ TwoWay                           │
│  ListView.SelectedItem   │ OneWayToSource                   │
│  Button.Command          │ OneWay                           │
│                                                             │
└─────────────────────────────────────────────────────────────┘

绑定上下文 #

BindingContext 继承 #

text
┌─────────────────────────────────────────────────────────────┐
│                    BindingContext 继承                       │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ContentPage (BindingContext = ViewModel)                   │
│      │                                                      │
│      ├── StackLayout (继承 BindingContext)                  │
│      │       │                                              │
│      │       ├── Label (绑定到 ViewModel.Title)             │
│      │       │                                              │
│      │       └── Entry (绑定到 ViewModel.Name)              │
│      │                                                      │
│      └── Frame (BindingContext = ViewModel.User)            │
│              │                                              │
│              ├── Label (绑定到 User.Name)                   │
│              │                                              │
│              └── Label (绑定到 User.Email)                  │
│                                                             │
└─────────────────────────────────────────────────────────────┘

设置 BindingContext #

csharp
public partial class MainPage : ContentPage
{
    public MainPage()
    {
        InitializeComponent();
        BindingContext = new MainViewModel();
    }
}
xml
<ContentPage.BindingContext>
    <local:MainViewModel />
</ContentPage.BindingContext>

绑定到其他控件 #

xml
<StackLayout>
    <Slider x:Name="slider"
            Minimum="0"
            Maximum="100"
            Value="50" />
    
    <Label Text="{Binding Source={x:Reference slider}, Path=Value, StringFormat='值: {0:F0}'}" />
    
    <BoxView Color="Blue"
             WidthRequest="{Binding Source={x:Reference slider}, Path=Value}"
             HeightRequest="20" />
</StackLayout>

绑定到自身 #

xml
<Label Text="{Binding Source={RelativeSource Self}, Path=Text.Length}"
       FontSize="18" />

绑定到父级 #

xml
<StackLayout x:Name="parentStack"
             Orientation="Vertical"
             Spacing="10">
    
    <Label Text="{Binding Source={x:Reference parentStack}, Path=Orientation}" />
    
</StackLayout>

值转换器 #

什么是值转换器? #

text
┌─────────────────────────────────────────────────────────────┐
│                    值转换器                                  │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  当数据源类型与目标类型不匹配时,需要使用值转换器:            │
│                                                             │
│   ┌─────────────────┐              ┌─────────────────┐     │
│   │   数据源         │              │   目标 (UI)      │     │
│   │   bool          │              │   Color         │     │
│   └────────┬────────┘              └────────┬────────┘     │
│            │                                │               │
│            │         ┌──────────┐           │               │
│            └────────►│ 转换器    │───────────┘               │
│                      │ Convert  │                           │
│                      └──────────┘                           │
│                                                             │
│  示例:                                                      │
│  - bool → Color (true=Green, false=Red)                    │
│  - DateTime → string (格式化日期)                           │
│  - int → string (数字格式化)                                │
│  - List → int (计算数量)                                    │
│                                                             │
└─────────────────────────────────────────────────────────────┘

创建值转换器 #

csharp
public class BoolToColorConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value is bool boolValue)
        {
            return boolValue ? Color.Green : Color.Red;
        }
        return Color.Gray;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value is Color color)
        {
            return color == Color.Green;
        }
        return false;
    }
}

使用值转换器 #

xml
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:MyApp.Converters"
             x:Class="MyApp.MainPage">
    
    <ContentPage.Resources>
        <ResourceDictionary>
            <local:BoolToColorConverter x:Key="BoolToColor" />
        </ResourceDictionary>
    </ContentPage.Resources>
    
    <StackLayout>
        <BoxView Color="{Binding IsActive, Converter={StaticResource BoolToColor}}"
                 WidthRequest="50"
                 HeightRequest="50" />
    </StackLayout>
    
</ContentPage>

常用值转换器示例 #

布尔反转转换器 #

csharp
public class InvertedBoolConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return value is bool b && !b;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return value is bool b && !b;
    }
}

日期格式转换器 #

csharp
public class DateFormatConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value is DateTime date)
        {
            string format = parameter as string ?? "yyyy-MM-dd";
            return date.ToString(format);
        }
        return string.Empty;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value is string str && DateTime.TryParse(str, out var date))
        {
            return date;
        }
        return DateTime.MinValue;
    }
}

多参数转换器 #

csharp
public class MultiBoolConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        foreach (var value in values)
        {
            if (value is bool b && !b)
                return false;
        }
        return true;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}
xml
<Label.Text>
    <MultiBinding Converter="{StaticResource MultiBoolConverter}">
        <Binding Path="IsLoggedIn" />
        <Binding Path="HasPermission" />
        <Binding Path="IsVerified" />
    </MultiBinding>
</Label.Text>

字符串格式化 #

StringFormat 用法 #

xml
<StackLayout>
    
    <Label Text="{Binding Price, StringFormat='价格: {0:C}'}" />
    
    <Label Text="{Binding Count, StringFormat='数量: {0:N0}'}" />
    
    <Label Text="{Binding Date, StringFormat='日期: {0:yyyy-MM-dd}'}" />
    
    <Label Text="{Binding Percentage, StringFormat='进度: {0:P1}'}" />
    
    <Label Text="{Binding Name, StringFormat='你好, {0}!'}" />
    
</StackLayout>

常用格式说明符 #

text
┌─────────────────────────────────────────────────────────────┐
│                    格式说明符                                │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  数字格式:                                                  │
│  {0:C}  - 货币格式     $1,234.56                            │
│  {0:N}  - 数字格式     1,234.56                             │
│  {0:N0} - 整数格式     1,234                                │
│  {0:P}  - 百分比格式   12.34%                               │
│  {0:F2} - 固定小数位   1234.56                              │
│                                                             │
│  日期格式:                                                  │
│  {0:d}     - 短日期   03/29/2026                            │
│  {0:D}     - 长日期   Saturday, March 29, 2026              │
│  {0:yyyy-MM-dd}        2026-03-29                           │
│  {0:HH:mm:ss}          14:30:45                             │
│  {0:yyyy年MM月dd日}     2026年03月29日                       │
│                                                             │
└─────────────────────────────────────────────────────────────┘

绑定集合 #

ListView 绑定 #

xml
<ListView ItemsSource="{Binding Items}"
          SelectedItem="{Binding SelectedItem}"
          HasUnevenRows="True">
    
    <ListView.ItemTemplate>
        <DataTemplate>
            <ViewCell>
                <StackLayout Padding="15">
                    <Label Text="{Binding Title}"
                           FontSize="16"
                           FontAttributes="Bold" />
                    <Label Text="{Binding Subtitle}"
                           TextColor="Gray"
                           FontSize="12" />
                </StackLayout>
            </ViewCell>
        </DataTemplate>
    </ListView.ItemTemplate>
    
</ListView>

CollectionView 绑定 #

xml
<CollectionView ItemsSource="{Binding Items}"
                SelectedItem="{Binding SelectedItem}"
                SelectionMode="Single">
    
    <CollectionView.ItemTemplate>
        <DataTemplate>
            <Frame Padding="10" Margin="5" CornerRadius="8">
                <StackLayout>
                    <Label Text="{Binding Title}" FontSize="16" />
                    <Label Text="{Binding Description}" TextColor="Gray" />
                </StackLayout>
            </Frame>
        </DataTemplate>
    </CollectionView.ItemTemplate>
    
</CollectionView>

分组绑定 #

csharp
public class Grouping<K, T> : ObservableCollection<T>
{
    public K Key { get; }

    public Grouping(K key, IEnumerable<T> items)
    {
        Key = key;
        foreach (var item in items)
        {
            Items.Add(item);
        }
    }
}

public class MainViewModel
{
    public ObservableCollection<Grouping<string, Item>> GroupedItems { get; set; }

    public MainViewModel()
    {
        var items = GetItems();
        GroupedItems = new ObservableCollection<Grouping<string, Item>>(
            items.GroupBy(i => i.Category)
                 .Select(g => new Grouping<string, Item>(g.Key, g))
        );
    }
}
xml
<ListView ItemsSource="{Binding GroupedItems}"
          IsGroupingEnabled="True"
          GroupDisplayBinding="{Binding Key}"
          GroupShortNameBinding="{Binding Key}">
    
    <ListView.ItemTemplate>
        <DataTemplate>
            <TextCell Text="{Binding Title}"
                      Detail="{Binding Subtitle}" />
        </DataTemplate>
    </ListView.ItemTemplate>
    
</ListView>

绑定调试 #

绑定错误排查 #

text
┌─────────────────────────────────────────────────────────────┐
│                    常见绑定问题                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  问题 1:绑定不更新                                         │
│  原因:未实现 INotifyPropertyChanged                        │
│  解决:在属性 setter 中触发 PropertyChanged 事件            │
│                                                             │
│  问题 2:绑定显示空白                                       │
│  原因:BindingContext 未设置或路径错误                      │
│  解决:检查 BindingContext 和属性路径                       │
│                                                             │
│  问题 3:双向绑定不工作                                     │
│  原因:绑定模式设置错误或属性只读                            │
│  解决:确保使用 TwoWay 模式且属性可写                       │
│                                                             │
│  问题 4:转换器不生效                                       │
│  原因:转换器未注册或类型不匹配                              │
│  解决:检查 ResourceDictionary 和类型转换                   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

调试技巧 #

xml
<Label Text="{Binding Title, FallbackValue='绑定失败'}"
       TextColor="Red" />

<Label Text="{Binding Name, TargetNullValue='值为空'}" />
csharp
public class MainViewModel
{
    private string _title;
    public string Title
    {
        get => _title;
        set
        {
            if (_title != value)
            {
                _title = value;
                OnPropertyChanged();
                Debug.WriteLine($"Title changed to: {value}");
            }
        }
    }
}

绑定最佳实践 #

1. 使用 INotifyPropertyChanged #

csharp
public class ViewModelBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    protected bool SetProperty<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
    {
        if (EqualityComparer<T>.Default.Equals(field, value))
            return false;

        field = value;
        OnPropertyChanged(propertyName);
        return true;
    }
}

public class MainViewModel : ViewModelBase
{
    private string _name;
    public string Name
    {
        get => _name;
        set => SetProperty(ref _name, value);
    }
}

2. 使用 ObservableCollection #

csharp
public class MainViewModel
{
    public ObservableCollection<Item> Items { get; set; }

    public MainViewModel()
    {
        Items = new ObservableCollection<Item>();
    }

    public void AddItem(Item item)
    {
        Items.Add(item);
    }

    public void RemoveItem(Item item)
    {
        Items.Remove(item);
    }
}

3. 避免在构造函数中绑定 #

csharp
public partial class MainPage : ContentPage
{
    private MainViewModel _viewModel;

    public MainPage()
    {
        InitializeComponent();
        _viewModel = new MainViewModel();
        BindingContext = _viewModel;
    }

    protected override void OnAppearing()
    {
        base.OnAppearing();
        _viewModel.LoadData();
    }
}

下一步 #

现在你已经掌握了 Xamarin.Forms 的数据绑定,接下来学习 MVVM 模式,了解如何构建清晰的架构!

最后更新:2026-03-29