WPF 数据绑定完全指南
目录
第一部分:Binding 绑定参数详解
1. 基础参数
Path(路径)
- 要绑定的属性名
- 可以省略,直接写属性名
- 支持嵌套属性:
Path=Address.City
<!-- 完整写法 -->
<TextBlock Text="{Binding Path=UserName}"/>
<!-- 简写(推荐) -->
<TextBlock Text="{Binding UserName}"/>
<!-- 嵌套属性 -->
<TextBlock Text="{Binding Address.City}"/>
Mode(模式)
控制数据流向:
| 模式 | 方向 | 说明 | 适用场景 |
|---|---|---|---|
OneWay | 后台 → UI | 单向绑定(默认) | 只读显示 |
TwoWay | 后台 ⇄ UI | 双向绑定 | 可编辑控件(TextBox、CheckBox等) |
OneWayToSource | UI → 后台 | 反向单向 | 特殊需求 |
OneTime | 只绑定一次 | 后续不更新 | 静态数据 |
<!-- OneWay: 后台 → UI(只读显示) -->
<TextBlock Text="{Binding UserName, Mode=OneWay}"/>
<!-- TwoWay: 后台 ⇄ UI(可编辑控件) -->
<TextBox Text="{Binding SearchText, Mode=TwoWay}"/>
<!-- OneTime: 只绑定一次 -->
<TextBlock Text="{Binding CreateTime, Mode=OneTime}"/>
UpdateSourceTrigger(更新时机)
控制什么时候同步到后台:
| 值 | 行为 | 场景 |
|---|---|---|
Default | 控件默认行为(TextBox 是失去焦点时) | 一般输入框 |
PropertyChanged | 每次属性变化立即同步 | 实时搜索、实时验证 |
LostFocus | 失去焦点时同步 | 表单提交 |
Explicit | 手动调用才同步 | 特殊需求 |
<!-- 实时搜索:每次输入立即同步 -->
<TextBox Text="{Binding SearchText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<!-- 表单提交:失去焦点时同步 -->
<TextBox Text="{Binding Email, UpdateSourceTrigger=LostFocus}"/>
2. 格式化参数
StringFormat(字符串格式)
格式化显示内容:
<!-- 货币格式 -->
<TextBlock Text="{Binding Price, StringFormat='价格: {0:C}'}"/>
<!-- 输出: 价格: ¥99.99 -->
<!-- 数字格式(保留2位小数) -->
<TextBlock Text="{Binding Value, StringFormat={}{0:N2}}"/>
<!-- 输出: 1,234.56 -->
<!-- 百分比格式 -->
<TextBlock Text="{Binding Progress, StringFormat={}{0:P0}}"/>
<!-- 输出: 75% -->
<!-- 日期格式 -->
<TextBlock Text="{Binding CreateTime, StringFormat='创建时间: {0:yyyy-MM-dd HH:mm:ss}'}"/>
<!-- 输出: 创建时间: 2024-01-15 14:30:25 -->
<!-- 自定义文本组合 -->
<TextBlock Text="{Binding UserName, StringFormat='欢迎, {0}!'}"/>
<!-- 输出: 欢迎, 张三! -->
<!-- 多字段组合 -->
<TextBlock Text="{Binding Price, StringFormat='单价: {0:C} × {1}件 = 总计: {2:C}'}"/>
Converter(转换器)
复杂的数据转换,需要实现 IValueConverter 接口:
// 创建转换器
public class BoolToColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is bool boolValue)
{
return boolValue ? Brushes.Green : Brushes.Red;
}
return Brushes.Gray;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
<!-- 在 App.xaml 注册 -->
<Application.Resources>
<local:BoolToColorConverter x:Key="BoolToColorConverter"/>
</Application.Resources>
<!-- 使用转换器 -->
<Ellipse Fill="{Binding IsOnline, Converter={StaticResource BoolToColorConverter}}"/>
<!-- 带参数的转换器 -->
<TextBlock Text="{Binding Temperature, Converter={StaticResource TempConverter}, ConverterParameter=Celsius}"/>
3. 空值和错误处理
FallbackValue(失败值)
绑定失败时显示的值(找不到属性、类型转换失败等):
<TextBlock Text="{Binding NotExistProperty, FallbackValue='属性不存在'}"/>
<TextBlock Text="{Binding InvalidData, FallbackValue='数据格式错误'}"/>
TargetNullValue(空值)
源数据为 null 时显示的值:
<TextBlock Text="{Binding Description, TargetNullValue='暂无描述'}"/>
<TextBlock Text="{Binding InstallDate, StringFormat='安装日期: {0:yyyy-MM-dd}', TargetNullValue='未设置'}"/>
组合使用
<!-- 同时处理 null 和错误 -->
<TextBlock Text="{Binding Data, TargetNullValue='无数据', FallbackValue='加载失败'}"/>
4. 绑定源指定
ElementName(元素名称)
绑定到同一个窗口的其他控件:
<!-- 滑块控制进度条 -->
<Slider x:Name="volumeSlider" Minimum="0" Maximum="100" Value="50"/>
<ProgressBar Value="{Binding Value, ElementName=volumeSlider}"/>
<TextBlock Text="{Binding Value, ElementName=volumeSlider, StringFormat='音量: {0:F0}'}"/>
<!-- 输入框实时显示 -->
<TextBox x:Name="inputBox" Text="测试文本"/>
<TextBlock Text="{Binding Text, ElementName=inputBox, StringFormat='你输入的是: {0}'}"/>
RelativeSource(相对源)
绑定到自身或父级:
<!-- 绑定到自身(显示自己的某个属性) -->
<TextBox Text="测试" x:Name="txtTest"/>
<TextBlock Text="{Binding Text, RelativeSource={RelativeSource Self}}"/>
<!-- 绑定到父窗口 -->
<TextBlock Text="{Binding Title, RelativeSource={RelativeSource AncestorType=Window}}"/>
<!-- 绑定到父级 StackPanel -->
<StackPanel Tag="父级数据">
<TextBlock Text="{Binding Tag, RelativeSource={RelativeSource AncestorType=StackPanel}}"/>
</StackPanel>
<!-- 绑定到父级的父级 -->
<Border>
<StackPanel>
<TextBlock Text="{Binding DataContext.Title, RelativeSource={RelativeSource AncestorType=Window}}"/>
</StackPanel>
</Border>
Source(显式源)
指定具体的数据源对象:
<!-- 绑定到静态资源 -->
<TextBlock Text="{Binding Source={StaticResource AppSettings}, Path=ThemeName}"/>
<!-- 绑定到单例 -->
<TextBlock Text="{Binding Source={x:Static local:ConfigService.Instance}, Path=Version}"/>
5. 数据验证
ValidatesOnDataErrors
启用数据验证,配合 IDataErrorInfo 接口:
<TextBox Text="{Binding Email, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}"/>
public class UserViewModel : ObservableObject, IDataErrorInfo
{
private string _email = "";
public string Email
{
get => _email;
set => SetProperty(ref _email, value);
}
public string Error => null;
public string this[string columnName]
{
get
{
if (columnName == nameof(Email))
{
if (string.IsNullOrEmpty(Email))
return "邮箱不能为空";
if (!Email.Contains("@"))
return "邮箱格式不正确";
}
return null;
}
}
}
ValidatesOnExceptions
属性设置抛出异常时验证:
<TextBox Text="{Binding Age, ValidatesOnExceptions=True}"/>
private int _age;
public int Age
{
get => _age;
set
{
if (value < 0 || value > 150)
throw new ArgumentException("年龄必须在 0-150 之间");
SetProperty(ref _age, value);
}
}
6. 其他参数
IsAsync(异步加载)
异步获取数据,不阻塞 UI:
<TextBlock Text="{Binding LargeData, IsAsync=True}"/>
NotifyOnSourceUpdated / NotifyOnTargetUpdated
触发更新事件:
<TextBox Text="{Binding UserName, NotifyOnTargetUpdated=True}"
TargetUpdated="OnTargetUpdated"/>
private void OnTargetUpdated(object sender, DataTransferEventArgs e)
{
// 目标属性更新后触发
}
StringFormat 高级用法
<!-- 条件格式 -->
<TextBlock Text="{Binding Temperature, StringFormat='{}{0:+0.0;-0.0;0}°C'}"/>
<!-- 正数显示 +25.0°C,负数显示 -10.5°C,零显示 0°C -->
<!-- 千分位分隔 -->
<TextBlock Text="{Binding Amount, StringFormat={}{0:#,##0.00}}"/>
<!-- 输出: 1,234,567.89 -->
<!-- 前导零 -->
<TextBlock Text="{Binding Code, StringFormat={}{0:D5}}"/>
<!-- 输出: 00123 -->
7. 常用组合示例
实时搜索框
<TextBox Text="{Binding SearchText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
只读显示
<TextBlock Text="{Binding StatusMessage}"/>
下拉框选中项
<ComboBox ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
滑块控制数值
<Slider Value="{Binding Volume, Mode=TwoWay}" Minimum="0" Maximum="100"/>
<TextBlock Text="{Binding Volume, StringFormat='音量: {0:F0}'}"/>
状态显示(在线/离线)
<Ellipse Fill="{Binding IsOnline, Converter={StaticResource BoolToColorConverter}}"/>
<TextBlock Text="{Binding IsOnline, Converter={StaticResource BoolToStringConverter}}"/>
价格、日期显示
<TextBlock Text="{Binding Price, StringFormat='价格: {0:C}'}"/>
<TextBlock Text="{Binding CreateTime, StringFormat={}{0:yyyy年MM月dd日 HH:mm}}"/>
表单输入(带验证)
<TextBox Text="{Binding Email, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"/>
<TextBlock Text="{Binding (Validation.Errors)[0].ErrorContent, ElementName=emailBox}"
Foreground="Red"/>
第二部分:常用控件绑定实战
1. TextBox(文本输入框)
用途: 用户输入文本
常用属性:
Text- 文本内容MaxLength- 最大长度IsReadOnly- 是否只读
绑定示例:
<!-- 双向绑定,实时读取 -->
<TextBox Text="{Binding UserName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<!-- 只读显示 -->
<TextBox Text="{Binding DisplayText}" IsReadOnly="True"/>
<!-- 限制长度 -->
<TextBox Text="{Binding Phone}" MaxLength="11"/>
后台读取:
// 自动同步到 ViewModel 属性
[ObservableProperty]
private string _userName = "";
// 用户输入会自动更新 _userName
2. TextBlock(文本显示)
用途: 只显示文本,不可编辑
常用属性:
Text- 显示内容FontSize- 字体大小Foreground- 字体颜色FontWeight- 字体粗细
绑定示例:
<!-- 简单绑定 -->
<TextBlock Text="{Binding StatusMessage}"/>
<!-- 格式化显示 -->
<TextBlock Text="{Binding Price, StringFormat='价格: {0:C}'}"/>
<!-- 多字段组合 -->
<TextBlock>
<Run Text="用户:"/>
<Run Text="{Binding UserName}"/>
<Run Text=", 年龄:"/>
<Run Text="{Binding Age}"/>
</TextBlock>
<!-- 带颜色 -->
<TextBlock Text="{Binding Status}" Foreground="{Binding StatusColor}"/>
后台读取:
// TextBlock 通常只用于显示,不需要读取
// 如果要获取显示的值,直接读取绑定的属性
string message = StatusMessage;
3. CheckBox(复选框)
用途: 布尔值选择(是/否)
常用属性:
IsChecked- 是否选中(true/false/null)Content- 显示文本IsThreeState- 是否支持三态
绑定示例:
<!-- 双向绑定 -->
<CheckBox Content="启用功能" IsChecked="{Binding IsEnabled, Mode=TwoWay}"/>
<!-- 三态复选框(true/false/null) -->
<CheckBox Content="不确定状态" IsThreeState="True"
IsChecked="{Binding Status, Mode=TwoWay}"/>
<!-- 多个复选框 -->
<StackPanel>
<CheckBox Content="选项A" IsChecked="{Binding OptionA}"/>
<CheckBox Content="选项B" IsChecked="{Binding OptionB}"/>
<CheckBox Content="选项C" IsChecked="{Binding OptionC}"/>
</StackPanel>
后台读取:
[ObservableProperty] private bool _isEnabled; // true=选中, false=未选中
[ObservableProperty] private bool? _status; // true/false/null(三态)
[ObservableProperty] private bool _optionA;
[ObservableProperty] private bool _optionB;
[ObservableProperty] private bool _optionC;
4. RadioButton(单选按钮)
用途: 从多个选项中选择一个
常用属性:
IsChecked- 是否选中GroupName- 分组名(同组只能选一个)Content- 显示文本
绑定示例:
<!-- 方式1:多个布尔属性 -->
<StackPanel>
<RadioButton Content="男" GroupName="Gender"
IsChecked="{Binding IsMale, Mode=TwoWay}"/>
<RadioButton Content="女" GroupName="Gender"
IsChecked="{Binding IsFemale, Mode=TwoWay}"/>
</StackPanel>
<!-- 方式2:枚举绑定(推荐) -->
<StackPanel>
<RadioButton Content="男" GroupName="Gender"
IsChecked="{Binding Path=SelectedGender, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static local:Gender.Male}}"/>
<RadioButton Content="女" GroupName="Gender"
IsChecked="{Binding Path=SelectedGender, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static local:Gender.Female}}"/>
</StackPanel>
后台读取:
// 方式1
[ObservableProperty] private bool _isMale;
[ObservableProperty] private bool _isFemale;
// 方式2(推荐)
public enum Gender { Male, Female }
[ObservableProperty] private Gender _selectedGender;
5. ComboBox(下拉框)
用途: 从列表中选择一项
常用属性:
ItemsSource- 数据源SelectedItem- 选中的对象SelectedIndex- 选中的索引DisplayMemberPath- 显示的属性名SelectedValuePath- 值的属性名SelectedValue- 选中的值
绑定示例:
<!-- 简单字符串列表 -->
<ComboBox ItemsSource="{Binding CityList}"
SelectedItem="{Binding SelectedCity, Mode=TwoWay}"/>
<!-- 对象列表(显示 Name,值为 Id) -->
<ComboBox ItemsSource="{Binding Users}"
DisplayMemberPath="Name"
SelectedValuePath="Id"
SelectedValue="{Binding SelectedUserId, Mode=TwoWay}"/>
<!-- 获取整个对象 -->
<ComboBox ItemsSource="{Binding Users}"
DisplayMemberPath="Name"
SelectedItem="{Binding SelectedUser, Mode=TwoWay}"/>
<!-- 自定义显示模板 -->
<ComboBox ItemsSource="{Binding Products}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="{Binding Price, StringFormat=' - {0:C}'}" Margin="10,0,0,0"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
后台读取:
// 简单列表
[ObservableProperty] private ObservableCollection<string> _cityList;
[ObservableProperty] private string _selectedCity;
// 对象列表
[ObservableProperty] private ObservableCollection<UserModel> _users;
[ObservableProperty] private int _selectedUserId; // 选中的 Id
[ObservableProperty] private UserModel _selectedUser; // 或整个对象
// 初始化数据
public void LoadData()
{
CityList = new ObservableCollection<string> { "北京", "上海", "广州", "深圳" };
SelectedCity = "北京";
Users = new ObservableCollection<UserModel>
{
new UserModel { Id = 1, Name = "张三" },
new UserModel { Id = 2, Name = "李四" }
};
}
6. ListBox(列表框)
用途: 显示列表,可选择一项或多项
常用属性:
ItemsSource- 数据源SelectedItem/SelectedItems- 选中项SelectionMode- 选择模式(Single/Multiple/Extended)
绑定示例:
<!-- 单选 -->
<ListBox ItemsSource="{Binding Products}"
SelectedItem="{Binding SelectedProduct, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="{Binding Price, StringFormat=' - {0:C}'}" Margin="10,0,0,0"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<!-- 多选 -->
<ListBox ItemsSource="{Binding Products}"
SelectionMode="Multiple">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Name}"
IsChecked="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=ListBoxItem}}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
后台读取:
[ObservableProperty] private ObservableCollection<ProductModel> _products;
[ObservableProperty] private ProductModel _selectedProduct;
// 多选需要特殊处理,通常用 IsSelected 属性
public class ProductModel : ObservableObject
{
public string Name { get; set; }
public decimal Price { get; set; }
[ObservableProperty]
private bool _isSelected;
}
// 获取所有选中项
var selectedProducts = Products.Where(p => p.IsSelected).ToList();
7. Slider(滑块)
用途: 数值范围选择
常用属性:
Value- 当前值Minimum/Maximum- 最小/最大值TickFrequency- 刻度间隔IsSnapToTickEnabled- 是否吸附到刻度
绑定示例:
<!-- 基础滑块 -->
<Slider Value="{Binding Volume, Mode=TwoWay}"
Minimum="0" Maximum="100"/>
<!-- 带刻度和吸附 -->
<Slider Value="{Binding Temperature, Mode=TwoWay}"
Minimum="0" Maximum="100"
TickFrequency="10"
IsSnapToTickEnabled="True"/>
<!-- 实时显示值 -->
<StackPanel>
<Slider Value="{Binding Brightness, Mode=TwoWay}" Minimum="0" Maximum="100"/>
<TextBlock Text="{Binding Brightness, StringFormat='亮度: {0:F0}%'}"/>
</StackPanel>
后台读取:
[ObservableProperty] private double _volume; // 0-100
[ObservableProperty] private double _temperature; // 带刻度
[ObservableProperty] private double _brightness; // 实时显示
8. ProgressBar(进度条)
用途: 显示进度
常用属性:
Value- 当前值Minimum/Maximum- 最小/最大值IsIndeterminate- 是否不确定进度(循环动画)
绑定示例:
<!-- 确定进度 -->
<ProgressBar Value="{Binding Progress}"
Minimum="0" Maximum="100"
Height="20"/>
<!-- 不确定进度(加载动画) -->
<ProgressBar IsIndeterminate="{Binding IsLoading}"
Height="20"/>
<!-- 带文本显示 -->
<Grid>
<ProgressBar Value="{Binding DownloadProgress}" Maximum="100"/>
<TextBlock Text="{Binding DownloadProgress, StringFormat={}{0:F0}%}"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Grid>
后台读取:
[ObservableProperty] private double _progress; // 0-100
[ObservableProperty] private bool _isLoading;
[ObservableProperty] private double _downloadProgress;
// 模拟进度更新
private async Task UpdateProgress()
{
for (int i = 0; i <= 100; i++)
{
Progress = i;
await Task.Delay(50);
}
}
9. DatePicker(日期选择器)
用途: 选择日期
常用属性:
SelectedDate- 选中的日期DisplayDate- 显示的日期DisplayDateStart/DisplayDateEnd- 可选范围
绑定示例:
<!-- 基础日期选择 -->
<DatePicker SelectedDate="{Binding BirthDate, Mode=TwoWay}"/>
<!-- 限制日期范围 -->
<DatePicker SelectedDate="{Binding AppointmentDate, Mode=TwoWay}"
DisplayDateStart="{Binding MinDate}"
DisplayDateEnd="{Binding MaxDate}"/>
<!-- 格式化显示 -->
<StackPanel>
<DatePicker SelectedDate="{Binding InstallDate, Mode=TwoWay}"/>
<TextBlock Text="{Binding InstallDate, StringFormat='安装日期: {0:yyyy-MM-dd}', TargetNullValue='未设置'}"/>
</StackPanel>
后台读取:
[ObservableProperty] private DateTime? _birthDate; // 可为 null
[ObservableProperty] private DateTime? _appointmentDate;
[ObservableProperty] private DateTime _minDate = DateTime.Today;
[ObservableProperty] private DateTime _maxDate = DateTime.Today.AddYears(1);
[ObservableProperty] private DateTime? _installDate;
10. Image(图片)
用途: 显示图片
常用属性:
Source- 图片源Stretch- 拉伸方式(None/Fill/Uniform/UniformToFill)Width/Height- 尺寸
绑定示例:
<!-- 从资源加载 -->
<Image Source="/Assets/icon/device.png" Width="64" Height="64"/>
<!-- 从路径绑定 -->
<Image Source="{Binding ImagePath}" Width="100"/>
<!-- 从 BitmapImage 绑定 -->
<Image Source="{Binding DeviceImage}" Width="100"/>
<!-- 带拉伸方式 -->
<Image Source="{Binding PhotoPath}"
Stretch="Uniform"
Width="200" Height="200"/>
<!-- 圆形图片(需要 Border 裁剪) -->
<Border Width="100" Height="100" CornerRadius="50" ClipToBounds="True">
<Image Source="{Binding AvatarPath}" Stretch="UniformToFill"/>
</Border>
后台读取:
// 方式1:字符串路径
[ObservableProperty] private string _imagePath = "/Assets/icon/device.png";
// 方式2:BitmapImage 对象
[ObservableProperty] private BitmapImage _deviceImage;
// 加载图片
public void LoadImage()
{
DeviceImage = new BitmapImage(new Uri("pack://application:,,,/Assets/device.png"));
}
11. 综合示例:设备信息编辑表单
XAML 界面
<StackPanel Margin="20">
<TextBlock Text="设备信息" FontSize="20" FontWeight="Bold" Margin="0,0,0,20"/>
<!-- 设备名称 -->
<StackPanel Orientation="Horizontal" Margin="0,5">
<TextBlock Text="名称:" Width="80" VerticalAlignment="Center"/>
<TextBox Text="{Binding DeviceName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Width="200" Padding="5"/>
</StackPanel>
<!-- 设备类型 -->
<StackPanel Orientation="Horizontal" Margin="0,5">
<TextBlock Text="类型:" Width="80" VerticalAlignment="Center"/>
<ComboBox ItemsSource="{Binding DeviceTypes}"
SelectedItem="{Binding SelectedType, Mode=TwoWay}"
Width="200" Padding="5"/>
</StackPanel>
<!-- 是否启用 -->
<StackPanel Orientation="Horizontal" Margin="0,5">
<TextBlock Text="状态:" Width="80" VerticalAlignment="Center"/>
<CheckBox IsChecked="{Binding IsEnabled, Mode=TwoWay}"/>
</StackPanel>
<!-- 温度阈值 -->
<StackPanel Orientation="Horizontal" Margin="0,5">
<TextBlock Text="阈值:" Width="80" VerticalAlignment="Center"/>
<Slider Value="{Binding Threshold, Mode=TwoWay}"
Minimum="0" Maximum="100" Width="150"/>
<TextBlock Text="{Binding Threshold, StringFormat={}{0:F0}°C}"
Margin="10,0,0,0" VerticalAlignment="Center"/>
</StackPanel>
<!-- 安装日期 -->
<StackPanel Orientation="Horizontal" Margin="0,5">
<TextBlock Text="日期:" Width="80" VerticalAlignment="Center"/>
<DatePicker SelectedDate="{Binding InstallDate, Mode=TwoWay}"
Width="200"/>
</StackPanel>
<!-- 预览 -->
<Border Background="#F0F0F0" Padding="15" Margin="0,20,0,0" CornerRadius="5">
<StackPanel>
<TextBlock Text="设备预览" FontWeight="Bold" Margin="0,0,0,10"/>
<TextBlock Text="{Binding DeviceName, StringFormat='名称: {0}'}"/>
<TextBlock Text="{Binding SelectedType, StringFormat='类型: {0}'}"/>
<TextBlock Text="{Binding IsEnabled, Converter={StaticResource BoolToStringConverter}, StringFormat='状态: {0}'}"/>
<TextBlock Text="{Binding Threshold, StringFormat='阈值: {0:F0}°C'}"/>
<TextBlock Text="{Binding InstallDate, StringFormat='安装: {0:yyyy-MM-dd}', TargetNullValue='未设置'}"/>
</StackPanel>
</Border>
<!-- 保存按钮 -->
<Button Content="保存" Command="{Binding SaveCommand}"
Margin="0,20,0,0" Padding="20,10"
Background="#3498DB" Foreground="White"/>
</StackPanel>
ViewModel
public partial class DeviceViewModel : ObservableObject
{
[ObservableProperty]
private string _deviceName = "";
[ObservableProperty]
private string _selectedType = "";
[ObservableProperty]
private bool _isEnabled = true;
[ObservableProperty]
private double _threshold = 50;
[ObservableProperty]
private DateTime? _installDate;
public ObservableCollection<string> DeviceTypes { get; } = new()
{
"温度传感器", "湿度传感器", "光照传感器", "压力传感器"
};
[RelayCommand]
private void Save()
{
// 这里可以直接读取所有绑定的属性
Console.WriteLine($"保存设备: {DeviceName}, 类型: {SelectedType}");
Console.WriteLine($"状态: {(IsEnabled ? "启用" : "禁用")}, 阈值: {Threshold}°C");
Console.WriteLine($"安装日期: {InstallDate?.ToString("yyyy-MM-dd") ?? "未设置"}");
// 实际项目中这里会调用数据库保存
// await _deviceService.SaveAsync(new DeviceModel { ... });
}
}
核心要点总结
赋值(后台 → UI)
- 直接修改 ViewModel 属性
- UI 自动更新(需要实现
INotifyPropertyChanged或用[ObservableProperty])
读取(UI → 后台)
- 使用
Mode=TwoWay双向绑定 - 用户操作会自动更新 ViewModel 属性
- 在命令或方法中直接读取属性值
常用绑定模式
| 控件类型 | 推荐 Mode | UpdateSourceTrigger |
|---|---|---|
| TextBox(输入) | TwoWay | PropertyChanged |
| TextBox(显示) | OneWay | Default |
| CheckBox | TwoWay | PropertyChanged |
| ComboBox | TwoWay | PropertyChanged |
| Slider | TwoWay | Default |
| DatePicker | TwoWay | PropertyChanged |
| TextBlock | OneWay | - |
| ProgressBar | OneWay | - |
| Image | OneWay | - |
实时更新
- 需要立即响应用户输入 →
UpdateSourceTrigger=PropertyChanged - 失去焦点时更新 →
UpdateSourceTrigger=Default(默认) - 只读显示 → 不需要设置(默认 OneWay)
最佳实践
- 输入控件用
TwoWay+PropertyChanged - 显示控件用
OneWay(默认) - 格式化用
StringFormat - 复杂转换用
Converter - 空值处理用
TargetNullValue和FallbackValue - 绑定其他控件用
ElementName - 绑定父级用
RelativeSource
掌握这些控件的绑定方式,你就能开发大部分 WPF 界面了!