数据绑定 #
数据绑定概述 #
什么是数据绑定? #
数据绑定是一种将 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