# WPF 数据绑定完全指南

## 目录
- [第一部分：Binding 绑定参数详解](#第一部分binding-绑定参数详解)
- [第二部分：常用控件绑定实战](#第二部分常用控件绑定实战)

---

# 第一部分：Binding 绑定参数详解

## 1. 基础参数

### Path（路径）
- 要绑定的属性名
- 可以省略，直接写属性名
- 支持嵌套属性：`Path=Address.City`

```xml
<!-- 完整写法 -->
<TextBlock Text="{Binding Path=UserName}"/>
<!-- 简写（推荐） -->
<TextBlock Text="{Binding UserName}"/>
<!-- 嵌套属性 -->
<TextBlock Text="{Binding Address.City}"/>
```

### Mode（模式）
控制数据流向：

| 模式 | 方向 | 说明 | 适用场景 |
|------|------|------|----------|
| `OneWay` | 后台 → UI | 单向绑定（默认） | 只读显示 |
| `TwoWay` | 后台 ⇄ UI | 双向绑定 | 可编辑控件（TextBox、CheckBox等） |
| `OneWayToSource` | UI → 后台 | 反向单向 | 特殊需求 |
| `OneTime` | 只绑定一次 | 后续不更新 | 静态数据 |

```xml
<!-- 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` | 手动调用才同步 | 特殊需求 |

```xml
<!-- 实时搜索：每次输入立即同步 -->
<TextBox Text="{Binding SearchText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>

<!-- 表单提交：失去焦点时同步 -->
<TextBox Text="{Binding Email, UpdateSourceTrigger=LostFocus}"/>
```

---

## 2. 格式化参数

### StringFormat（字符串格式）
格式化显示内容：

```xml
<!-- 货币格式 -->
<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` 接口：

```csharp
// 创建转换器
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();
    }
}
```

```xml
<!-- 在 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（失败值）
绑定失败时显示的值（找不到属性、类型转换失败等）：

```xml
<TextBlock Text="{Binding NotExistProperty, FallbackValue='属性不存在'}"/>
<TextBlock Text="{Binding InvalidData, FallbackValue='数据格式错误'}"/>
```

### TargetNullValue（空值）
源数据为 null 时显示的值：

```xml
<TextBlock Text="{Binding Description, TargetNullValue='暂无描述'}"/>
<TextBlock Text="{Binding InstallDate, StringFormat='安装日期: {0:yyyy-MM-dd}', TargetNullValue='未设置'}"/>
```

### 组合使用
```xml
<!-- 同时处理 null 和错误 -->
<TextBlock Text="{Binding Data, TargetNullValue='无数据', FallbackValue='加载失败'}"/>
```

---

## 4. 绑定源指定

### ElementName（元素名称）
绑定到同一个窗口的其他控件：

```xml
<!-- 滑块控制进度条 -->
<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（相对源）
绑定到自身或父级：

```xml
<!-- 绑定到自身（显示自己的某个属性） -->
<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（显式源）
指定具体的数据源对象：

```xml
<!-- 绑定到静态资源 -->
<TextBlock Text="{Binding Source={StaticResource AppSettings}, Path=ThemeName}"/>

<!-- 绑定到单例 -->
<TextBlock Text="{Binding Source={x:Static local:ConfigService.Instance}, Path=Version}"/>
```

---

## 5. 数据验证

### ValidatesOnDataErrors
启用数据验证，配合 `IDataErrorInfo` 接口：

```xml
<TextBox Text="{Binding Email, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}"/>
```

```csharp
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
属性设置抛出异常时验证：

```xml
<TextBox Text="{Binding Age, ValidatesOnExceptions=True}"/>
```

```csharp
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：

```xml
<TextBlock Text="{Binding LargeData, IsAsync=True}"/>
```

### NotifyOnSourceUpdated / NotifyOnTargetUpdated
触发更新事件：

```xml
<TextBox Text="{Binding UserName, NotifyOnTargetUpdated=True}" 
         TargetUpdated="OnTargetUpdated"/>
```

```csharp
private void OnTargetUpdated(object sender, DataTransferEventArgs e)
{
    // 目标属性更新后触发
}
```

### StringFormat 高级用法
```xml
<!-- 条件格式 -->
<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. 常用组合示例

### 实时搜索框
```xml
<TextBox Text="{Binding SearchText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
```

### 只读显示
```xml
<TextBlock Text="{Binding StatusMessage}"/>
```

### 下拉框选中项
```xml
<ComboBox ItemsSource="{Binding Items}" 
          SelectedItem="{Binding SelectedItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
```

### 滑块控制数值
```xml
<Slider Value="{Binding Volume, Mode=TwoWay}" Minimum="0" Maximum="100"/>
<TextBlock Text="{Binding Volume, StringFormat='音量: {0:F0}'}"/>
```

### 状态显示（在线/离线）
```xml
<Ellipse Fill="{Binding IsOnline, Converter={StaticResource BoolToColorConverter}}"/>
<TextBlock Text="{Binding IsOnline, Converter={StaticResource BoolToStringConverter}}"/>
```

### 价格、日期显示
```xml
<TextBlock Text="{Binding Price, StringFormat='价格: {0:C}'}"/>
<TextBlock Text="{Binding CreateTime, StringFormat={}{0:yyyy年MM月dd日 HH:mm}}"/>
```

### 表单输入（带验证）
```xml
<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` - 是否只读

**绑定示例：**
```xml
<!-- 双向绑定，实时读取 -->
<TextBox Text="{Binding UserName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>

<!-- 只读显示 -->
<TextBox Text="{Binding DisplayText}" IsReadOnly="True"/>

<!-- 限制长度 -->
<TextBox Text="{Binding Phone}" MaxLength="11"/>
```

**后台读取：**
```csharp
// 自动同步到 ViewModel 属性
[ObservableProperty]
private string _userName = "";

// 用户输入会自动更新 _userName
```

---

## 2. TextBlock（文本显示）

**用途：** 只显示文本，不可编辑

**常用属性：**
- `Text` - 显示内容
- `FontSize` - 字体大小
- `Foreground` - 字体颜色
- `FontWeight` - 字体粗细

**绑定示例：**
```xml
<!-- 简单绑定 -->
<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}"/>
```

**后台读取：**
```csharp
// TextBlock 通常只用于显示，不需要读取
// 如果要获取显示的值，直接读取绑定的属性
string message = StatusMessage;
```

---

## 3. CheckBox（复选框）

**用途：** 布尔值选择（是/否）

**常用属性：**
- `IsChecked` - 是否选中（true/false/null）
- `Content` - 显示文本
- `IsThreeState` - 是否支持三态

**绑定示例：**
```xml
<!-- 双向绑定 -->
<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>
```

**后台读取：**
```csharp
[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` - 显示文本

**绑定示例：**
```xml
<!-- 方式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>
```

**后台读取：**
```csharp
// 方式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` - 选中的值

**绑定示例：**
```xml
<!-- 简单字符串列表 -->
<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>
```

**后台读取：**
```csharp
// 简单列表
[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）

**绑定示例：**
```xml
<!-- 单选 -->
<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>
```

**后台读取：**
```csharp
[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` - 是否吸附到刻度

**绑定示例：**
```xml
<!-- 基础滑块 -->
<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>
```

**后台读取：**
```csharp
[ObservableProperty] private double _volume;        // 0-100
[ObservableProperty] private double _temperature;   // 带刻度
[ObservableProperty] private double _brightness;    // 实时显示
```

---

## 8. ProgressBar（进度条）

**用途：** 显示进度

**常用属性：**
- `Value` - 当前值
- `Minimum` / `Maximum` - 最小/最大值
- `IsIndeterminate` - 是否不确定进度（循环动画）

**绑定示例：**
```xml
<!-- 确定进度 -->
<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>
```

**后台读取：**
```csharp
[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` - 可选范围

**绑定示例：**
```xml
<!-- 基础日期选择 -->
<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>
```

**后台读取：**
```csharp
[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` - 尺寸

**绑定示例：**
```xml
<!-- 从资源加载 -->
<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>
```

**后台读取：**
```csharp
// 方式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 界面
```xml
<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
```csharp
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）

### 最佳实践
1. **输入控件**用 `TwoWay` + `PropertyChanged`
2. **显示控件**用 `OneWay`（默认）
3. **格式化**用 `StringFormat`
4. **复杂转换**用 `Converter`
5. **空值处理**用 `TargetNullValue` 和 `FallbackValue`
6. **绑定其他控件**用 `ElementName`
7. **绑定父级**用 `RelativeSource`

掌握这些控件的绑定方式，你就能开发大部分 WPF 界面了！
