数据绑定允许我们将UI控件的属性与数据源(例如对象、集合、数据库等)的属性绑定起来。当数据源发生变化时,UI控件会自动更新,反之亦然。
优点:将控件的UI同数据本身的逻辑分离,怎么显示是UI的事情,而显示的内容我负责给出来就行了,通过数据绑定可以避免在代码中引用UI的
WPF提供了四种主要的绑定模式:
绑定模式
-
OneWay(单向绑定):
- 数据从源(source)流向目标(target),但反过来不会更新。默认的绑定方式
<TextBlock Text="{Binding Path=Name}" />
等效于
<TextBlock Text="{Binding Name}" />
-
TwoWay(双向绑定):
- 数据在源和目标之间双向流动,任何一方的变化都会更新另一方。
<TextBox Text="{Binding Path=Name, Mode=TwoWay}" />
-
OneWayToSource(单向到源):
- 数据从目标流向源,但源的变化不会影响目标。
<TextBox Text="{Binding Path=Name, Mode=OneWayToSource}" />
-
OneTime(一次性绑定):
- 仅在绑定时更新一次目标,之后源的变化不会再更新目标。
<TextBlock Text="{Binding Path=Name, Mode=OneTime}" />
注意的是,如果使用的绑定模式是OneWay或者TwoWay,还需要属性更改通知机制,一般是实现INotifyPropertyChanged或者INotifyCollectionChanged接口
绑定源
数据绑定的源可以是各种对象,包括CLR对象、集合、XML数据等。
-
CLR对象:
- 可以是任何普通的C#对象。(如果需要页面显示更新需要实现对应通知接口)
public class Person { public string Name { get; set; } }
-
集合:
- 绑定到集合时,常使用
ObservableCollection<T>
,它会在集合变化时通知UI。相对应的前端控件是itemsControl
public ObservableCollection<Person> People { get; set; }
- 绑定到集合时,常使用
-
XML数据:
- 也可以绑定到XML数据源。
<XmlDataProvider x:Key="xmlData" Source="data.xml" XPath="Books/Book"/>
属性更改通知
INotifyPropertyChanged
- 用于通知属性值更改。
public class Person : INotifyPropertyChanged
{
private string name;
public string Name
{
get { return name; }
set
{
name = value;
OnPropertyChanged(nameof(Name));
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
注意我们并没有手动订阅 PropertyChanged事件,因为WPF会自动监听这个事件,事件被触发时更新与该属性绑定的 UI 元素。所以要注意目标类要是public的,不然WPF可能无法访问到这个对象
下面是更加通用的设置写法
internal class Person:INotifyPropertyChanged
{
private string name;
public string Name
{
get => name;
set => SetField(ref name, value);
}
public event PropertyChangedEventHandler? PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetField<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;
}
}
INotifyCollectionChanged和ObservableCollection<T>
INotifyCollectionChanged
是集合版本的属性更改通知,当集合发生变化的时候也会通知页面更新数据,而ObservableCollection<T>
是前者的内置实现
注意ObservableCollection<T>
适用于通知集合本身的变化(如添加、删除项),但不包括项内部属性的变化。为了处理对象内部属性的变化,还需要确保这些对象实现 INotifyPropertyChanged
接口。
public partial class MainWindow : Window
{
public ObservableCollection<Person> People { get; set; }
public MainWindow()
{
InitializeComponent();
People = new ObservableCollection<Person>
{
new Person { Name = "Alice" },
new Person { Name = "Bob" },
new Person { Name = "Charlie" }
};
this.DataContext = this;
}
}
<Window x:Class="YourNamespace.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ListBox ItemsSource="{Binding People}" DisplayMemberPath="Name"/>
</Grid>
</Window>
集合视图:
绑定到集合以后,对原有的数据进行排序,筛选或分组,这些操作不会影响基础数据源本身,一个数据源可以有多个不同的视图来通过不同方式显示数据
如果不指定视图直接绑定到集合,那么WPF会绑定到默认视图,默认视图会共享,所以更改了默认视图所有绑定默认视图的控件都会改变
可以通过GetDefaultView获取默认视图
-
CollectionView: 它是对数据集合的一个视图,用于支持排序、筛选、分组和导航。所有的集合视图都派生自
CollectionView
类。 - ICollectionView: 一个接口,定义了集合视图的基本功能,如当前项、排序、筛选和分组等。
常用集合视图类
-
ListCollectionView: 适用于实现
IList
接口的集合,如List<T>
。最快的 -
BindingListCollectionView: 适用于实现
IBindingList
接口的集合,如BindingList<T>
。 - CollectionViewSource: 提供集合视图的 XAML 友好版本,可以用于资源字典和绑定。
指定视图
ListCollectionView:
public ObservableCollection<Person> people { get; set; }
public ListCollectionView PeopleView { get; set; }
public MainWindow()
{
people = new ObservableCollection<Person>
{
new Person { Name = "Alice" },
new Person { Name = "Bob" },
new Person { Name = "Charlie" }
};
PeopleView = new ListCollectionView(people);
InitializeComponent();
this.DataContext = this;
}
<ListBox Width="200" Height="100" ItemsSource="{Binding PeopleView}" DisplayMemberPath="Name">
<!--<ListBoxItem Content="Item 1" />
<ListBoxItem Content="Item 2" />
<ListBoxItem Content="Item 3" />-->
</ListBox>
CollectionViewSource:
<Window.Resources>
<CollectionViewSource
Source="{Binding Source={x:Static Application.Current}, Path=AuctionItems}"
x:Key="ListingDataView" />
</Window.Resources>
<ListBox Name="Master"
ItemsSource="{Binding Source={StaticResource ListingDataView}}">
<ListBox.GroupStyle>
<GroupStyle
HeaderTemplate="{StaticResource GroupingHeaderTemplate}" />
</ListBox.GroupStyle>
</ListBox>
private readonly CollectionViewSource _listingDataView;
public MainWindow()
{
InitializeComponent();
_listingDataView = (CollectionViewSource) (Resources["ListingDataView"]);
}
排序
可以通过 SortDescriptions
属性添加排序描述,排序可以按一个或多个属性进行。
ListCollectionView view = new ListCollectionView(data);
view.SortDescriptions.Add(new SortDescription("PropertyName", ListSortDirection.Ascending));
筛选
通过 Filter
委托实现筛选,过滤掉不符合条件的项。
view.Filter = item =>
{
var dataItem = item as YourDataType;
return dataItem != null && dataItem.PropertyName == "SomeValue";
};
private void RemoveFiltering(object sender, RoutedEventArgs args)
{
listingDataView.Filter -= ShowOnlyBargainsFilter;
}
private void AddFiltering(object sender, RoutedEventArgs args)
{
listingDataView.Filter += ShowOnlyBargainsFilter;
}
private void ShowOnlyBargainsFilter(object sender, FilterEventArgs e)
{
var product = e.Item as AuctionItem;
if (product != null)
{
// Filter out products with price 25 or above
e.Accepted = product.CurrentPrice < 25;
}
}
<CheckBox Name="Filtering" Grid.Row="1" Grid.Column="1"
Checked="AddFiltering" Unchecked="RemoveFiltering"
Margin="8" Style="{StaticResource CheckBoxStyle}">
Show only bargains
</CheckBox>
分组
使用 GroupDescriptions
属性进行分组。
view.GroupDescriptions.Add(new PropertyGroupDescription("CategoryProperty"));
private void AddSorting(object sender, RoutedEventArgs args)
{
// This sorts the items first by Category and within each Category,
// by StartDate. Notice that because Category is an enumeration,
// the order of the items is the same as in the enumeration declaration
listingDataView.SortDescriptions.Add(
new SortDescription("Category", ListSortDirection.Ascending));
listingDataView.SortDescriptions.Add(
new SortDescription("StartDate", ListSortDirection.Ascending));
}
private void RemoveSorting(object sender, RoutedEventArgs args)
{
listingDataView.SortDescriptions.Clear();
}
ICollectionView
提供了导航当前项的方法,如 MoveCurrentToNext
、MoveCurrentToPrevious
等。(当前项类似于集合里的遍历指针)
当数据集合较大时,使用排序、筛选和分组可能会导致性能问题,特别是在 UI 线程中执行时。
DependencyProperty:
- WPF控件使用依赖属性来支持高级功能(如数据绑定、动画等)。
public class MyControl : Control
{
public static readonly DependencyProperty MyProperty =
DependencyProperty.Register("My", typeof(string), typeof(MyControl));
public string My
{
get { return (string)GetValue(MyProperty); }
set { SetValue(MyProperty, value); }
}
}
DataContext
DataContext 定义了绑定源的数据对象,从而使得 XAML 中的绑定可以访问到这个数据对象的属性。DataContext
是你告诉 WPF "这里是数据的来源"。
绑定声明后,会在当前元素的数据上下文中解析绑定,如果没有找到,则往父节点的数据上下文查找,直到XAML的根节点
也支持指定特定对象进行解析而不从DataContext中查找
dataContext
设置可以在XAML中,也可以在后台的C#代码里面
<Window.DataContext>
<local:Person Name="John Doe" />
</Window.DataContext>
<TextBlock Text="{Binding Name}" />
local那一行是声明并实例化一个 Person 对象,local 是在 XAML 文件顶部定义的命名空间前缀,指向包含 Person 类的命名空间。
层级关系将其将其设置为 Window 的数据上下文。
public MainWindow()
{
InitializeComponent();
this.DataContext = new Person { Name = "John Doe" };
//this.DataContext = this;
}
对应的后台代码如上所示设置
也有直接将windows设成datacontext
的,这样直接绑定到 Window 或 UserControl 实例的公共属性。
也可以专门设置一个ViewModel,存放数据
public class MainViewModel
{
public string Title { get; set; }
public UserViewModel User { get; set; }
public ObservableCollection<ItemViewModel> Items { get; set; }
public MainViewModel()
{
Title = "My Application";
User = new UserViewModel { Name = "John Doe" };
Items = new ObservableCollection<ItemViewModel>
{
new ItemViewModel { Name = "Item 1" },
new ItemViewModel { Name = "Item 2" }
};
}
}
也支持viewModel的嵌套
<ListBox ItemsSource="{Binding ItemViewModelCollection.Items}">
如果不指定,直接写ItemsSource="{Binding}"则是直接绑定到dataContext对象上
将数据绑定到非dataContext
下面的例子假设myData的默认构造参数会将器ColorName设置成red
<DockPanel xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:SDKSample">
<DockPanel.Resources>
<c:MyData x:Key="myDataSource"/>
</DockPanel.Resources>
<Button Background="{Binding Source={StaticResource myDataSource}, Path=ColorName}"
Width="150" Height="30">
I am bound to be RED!
</Button>
</DockPanel>
<StackPanel>
<!-- 第一个按钮,背景色为蓝色 -->
<Button x:Name="Button1" Content="Button 1" Background="Blue" Width="100" Height="50" />
<!-- 第二个按钮,背景色绑定到第一个按钮的背景色 -->
<Button Content="Button 2" Width="100" Height="50"
Background="{Binding Path=Background, ElementName=Button1}" />
</StackPanel>
source
和ElementName
都可以用来指定绑定数据源
Source
更通用,可以绑定到静态资源、对象、静态属性等,适用于数据源与 UI 控件没有直接关系的情况。
ElementName
用于在 XAML 控件之间进行绑定,适用于需要访问特定控件的属性的情况。
// Make a new source
var myDataObject = new MyData();
var myBinding = new Binding("ColorName")
{
Source = myDataObject
};
// Bind the data source to the TextBox control's Text dependency property
myText.SetBinding(TextBlock.TextProperty, myBinding);
Binding.UpdateSourceTrigger
通过设置绑定的这个属性,可以设置什么时候更新源数据
如果 UpdateSourceTrigger
值为 UpdateSourceTrigger.PropertyChanged
,则目标属性更改后,TwoWay
或 OneWayToSource
绑定源数据值会立即更新。 但是,如果 UpdateSourceTrigger
值为 LostFocus
,则仅当目标属性失去焦点时才会使用新值更新该值。
类型转换器
如果绑定到的目标属性不是想要的格式,需要实现 IValueConverter来创建自定义转换器让绑定生效
下面的例子将color转换为SolidColorBrush,后者是BackGround可以接受的值
[ValueConversion(typeof(Color), typeof(SolidColorBrush))]
public class ColorBrushConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
Color color = (Color)value;
return new SolidColorBrush(color);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
}
public class BooleanToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (bool)value ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
<Window.Resources>
<local:BooleanToVisibilityConverter x:Key="BoolToVisConverter"/>
</Window.Resources>
<CheckBox IsChecked="{Binding IsVisible}" />
<TextBlock Text="Visible" Visibility="{Binding IsVisible, Converter={StaticResource BoolToVisConverter}}" />
数据模板
当直接绑定对象时,如果不指定模板,那么会显示集合里面对象的ToString方法,如果没有重写ToString,那么得到的会是无效的显示,可以通过指定DisplayMemberPath
来简单指定想要的属性
<ListBox Width="200" Height="100" ItemsSource="{Binding People}" DisplayMemberPath="Name">
而通过使用数据模板,可以自定义集合的显示方式
<ListBox ItemsSource="{Binding People}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding Age}" Margin="10,0,0,0"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
(绑定里面的,DataTemplate的DataContext会自动填充为people里面的元素)
(更多内容参考数据模板章节)
数据验证
数据验证(Validation)可以确保用户输入的数据满足应用程序的要求。验证规则可以通过实现ValidationRule
类来定义,并与数据绑定(Data Binding)结合使用。
- 定义验证规则
创建一个继承自ValidationRule
的类,并重写其Validate
方法。这个方法接收输入值并返回一个ValidationResult
对象。
using System.Globalization;
using System.Windows.Controls;
public class NotEmptyValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
if (value == null || string.IsNullOrWhiteSpace(value.ToString()))
{
return new ValidationResult(false, "Value cannot be empty.");
}
return ValidationResult.ValidResult;
}
}
- 在XAML中定义绑定和验证规则
在XAML中设置Binding
的ValidationRules
属性将验证规则关联到控件。
<Grid>
<TextBox Name="inputTextBox" Width="200" Height="30" VerticalAlignment="Center" HorizontalAlignment="Center">
<TextBox.Text>
<Binding Path="InputText" UpdateSourceTrigger="PropertyChanged" ValidatesOnDataErrors="True">
<Binding.ValidationRules>
<local:NotEmptyValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
</Grid>
- 定义数据模型和数据绑定
using System.ComponentModel;
public class ViewModel : INotifyPropertyChanged
{
private string _inputText;
public string InputText
{
get { return _inputText; }
set
{
if (_inputText != value)
{
_inputText = value;
OnPropertyChanged(nameof(InputText));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
在窗口的构造函数中设置数据上下文:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
}
- 提供视觉反馈
可以使用Validation.ErrorTemplate
属性来定义控件在验证失败时的外观。例如,显示红色边框和错误信息。
<Window.Resources>
<ControlTemplate x:Key="validationTemplate">
<DockPanel>
<TextBlock Foreground="Red" Margin="5,0,0,0" DockPanel.Dock="Right" Text="{Binding [0].ErrorContent}" />
<AdornedElementPlaceholder/>
</DockPanel>
</ControlTemplate>
</Window.Resources>
<Grid>
<TextBox Name="inputTextBox" Width="200" Height="30" VerticalAlignment="Center" HorizontalAlignment="Center">
<TextBox.Text>
<Binding Path="InputText" UpdateSourceTrigger="PropertyChanged" ValidatesOnDataErrors="True">
<Binding.ValidationRules>
<local:NotEmptyValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}"/>
<Setter Property="BorderBrush" Value="Red"/>
<Setter Property="BorderThickness" Value="2"/>
</Trigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
</Grid>
内置类:
WPF中提供了两个内置的ValidationRule
类:ExceptionValidationRule
和DataErrorValidationRule
。它们分别用于处理异常和数据错误。
- ExceptionValidationRule
ExceptionValidationRule
用于处理在数据绑定过程中抛出的异常。它会捕获绑定过程中发生的任何异常,并将其视为验证错误。
<TextBox Width="200" Height="30">
<TextBox.Text>
<Binding Path="Age" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<ExceptionValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
public class ViewModel : INotifyPropertyChanged
{
private int _age;
public int Age
{
get { return _age; }
set
{
if (_age != value)
{
if (value < 0)
{
throw new ArgumentException("Age cannot be negative");
}
_age = value;
OnPropertyChanged(nameof(Age));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
2. DataErrorValidationRule
DataErrorValidationRule
用于处理实现了IDataErrorInfo
接口的对象的验证。它会检查绑定目标对象是否实现了IDataErrorInfo
接口,并使用该接口提供的错误信息进行验证。
<TextBox Width="200" Height="30">
<TextBox.Text>
<Binding Path="Name" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<DataErrorValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
public class ViewModel : INotifyPropertyChanged, IDataErrorInfo
{
private string _name;
private int _age;
public string Name
{
get { return _name; }
set
{
if (_name != value)
{
_name = value;
OnPropertyChanged(nameof(Name));
}
}
}
public int Age
{
get { return _age; }
set
{
if (_age != value)
{
_age = value;
OnPropertyChanged(nameof(Age));
}
}
}
public string Error => null;
public string this[string columnName]
{
get
{
string result = null;
if (columnName == nameof(Name))
{
if (string.IsNullOrWhiteSpace(Name))
{
result = "Name cannot be empty.";
}
}
else if (columnName == nameof(Age))
{
if (Age < 0 || Age > 120)
{
result = "Age must be between 0 and 120.";
}
}
return result;
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
<Window.Resources>
<ControlTemplate x:Key="validationTemplate">
<DockPanel>
<TextBlock Foreground="Red" Margin="5,0,0,0" DockPanel.Dock="Right" Text="{Binding [0].ErrorContent}" />
<AdornedElementPlaceholder/>
</DockPanel>
</ControlTemplate>
</Window.Resources>
<Grid>
<TextBox Name="inputTextBox" Width="200" Height="30" VerticalAlignment="Center" HorizontalAlignment="Center">
<TextBox.Text>
<Binding Path="Age" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<ExceptionValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}"/>
<Setter Property="BorderBrush" Value="Red"/>
<Setter Property="BorderThickness" Value="2"/>
</Trigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
</Grid>
绑定到枚举
没有直接的方法将枚举用作数据绑定源,但是Enum.GetValues(Type)方法返回枚举的集合,可以用来作为数据源
<Window.Resources>
<ObjectDataProvider x:Key="EnumDataSource"
ObjectType="{x:Type sys:Enum}"
MethodName="GetValues">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="HorizontalAlignment" />
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</Window.Resources>
ObjectDataProvider里面的代码等效于
var enumDataSource = System.Enum.GetValues(typeof(System.Windows.HorizontalAlignment));
使用:
<ListBox Name="myComboBox" SelectedIndex="0"
ItemsSource="{Binding Source={StaticResource EnumDataSource}}"/>
综合使用:
假设你有一个按钮,你希望它的启用/禁用状态和颜色由后台(ViewModel)控制。
Step 1: 定义数据模型
首先,定义一个Person
类,包含一个属性IsEnabled
用于控制按钮的启用/禁用状态。
using System.ComponentModel;
public class Person : INotifyPropertyChanged
{
private string name;
private bool isEnabled;
public string Name
{
get { return name; }
set
{
name = value;
OnPropertyChanged(nameof(Name));
}
}
public bool IsEnabled
{
get { return isEnabled; }
set
{
isEnabled = value;
OnPropertyChanged(nameof(IsEnabled));
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Step 2: 定义ViewModel
然后,定义一个PeopleViewModel
类,该类包含一个Person
对象,并实现属性更改通知。
using System.Collections.ObjectModel;
using System.ComponentModel;
public class PeopleViewModel : INotifyPropertyChanged
{
private Person selectedPerson;
public Person SelectedPerson
{
get { return selectedPerson; }
set
{
selectedPerson = value;
OnPropertyChanged(nameof(SelectedPerson));
}
}
public ObservableCollection<Person> People { get; set; }
public PeopleViewModel()
{
People = new ObservableCollection<Person>
{
new Person { Name = "John Doe", IsEnabled = true },
new Person { Name = "Jane Smith", IsEnabled = false },
};
SelectedPerson = People[0];
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Step 3: 绑定到XAML
在XAML中,将DataContext
设置为PeopleViewModel
的实例,并绑定按钮的IsEnabled
属性和背景颜色属性。
<Window x:Class="WpfApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:PeopleViewModel />
</Window.DataContext>
<Grid>
<ListBox ItemsSource="{Binding People}" SelectedItem="{Binding SelectedPerson}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Content="Action"
IsEnabled="{Binding SelectedPerson.IsEnabled}"
Background="{Binding SelectedPerson.IsEnabled, Converter={StaticResource BoolToBrushConverter}}"
HorizontalAlignment="Center"
VerticalAlignment="Bottom"
Width="100"
Height="50" />
</Grid>
</Window>
Step 4: 值转换器
定义一个IValueConverter
来根据IsEnabled
属性转换按钮的背景颜色。
using System;
using System.Globalization;
using System.Windows.Data;
using System.Windows.Media;
public class BoolToBrushConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is bool isEnabled)
{
return isEnabled ? Brushes.LightGreen : Brushes.LightGray;
}
return Brushes.Transparent;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
在XAML中定义资源:
<Window.Resources>
<local:BoolToBrushConverter x:Key="BoolToBrushConverter"/>
</Window.Resources>
Step 5: 更新后台代码
在代码后面(代码隐藏文件MainWindow.xaml.cs
中),可以根据某些条件来更改SelectedPerson
的IsEnabled
属性,并观察UI的变化。
using System.Windows;
namespace WpfApp
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void ToggleButton_Click(object sender, RoutedEventArgs e)
{
var viewModel = (PeopleViewModel)DataContext;
if (viewModel.SelectedPerson != null)
{
viewModel.SelectedPerson.IsEnabled = !viewModel.SelectedPerson.IsEnabled;
}
}
}
}
在XAML中添加一个切换按钮以测试:
<Button Content="Toggle Enable"
Click="ToggleButton_Click"
HorizontalAlignment="Center"
VerticalAlignment="Bottom"
Margin="0,10,0,0" />
当你运行应用程序时,ListBox
中的项会显示Person
的名称。当选择不同的Person
时,按钮的启用/禁用状态和背景颜色会根据SelectedPerson.IsEnabled
属性进行更新。点击“Toggle Enable”按钮可以切换SelectedPerson.IsEnabled
属性,并实时更新UI。
绑定的属性
Path
指定绑定目标的属性路径。
Path=User.Name
绑定到 User
对象的 Name
属性。
Source
设置绑定的数据源对象。如果指定了 Source
,则 Binding
会直接从这个对象获取数据。
Source={StaticResource MyData}`
ElementName
绑定到 XAML 中指定名称的元素的属性。
ElementName=myTextBox, Path=Text
RelativeSource
指定定义绑定源的位置相对于当前元素的相对位置。这使得可以从当前元素的上下文中绑定数据,而不需要显式地指定源对象的路径。
<TextBlock Text="{Binding Path=DataContext.Title, RelativeSource={RelativeSource AncestorType=Window}}"/>
绑定到Windows的DataContext 的Title
绑定到自己
<TextBlock Text="{Binding Path=ActualWidth, RelativeSource={RelativeSource Self}}"/>
绑定到当前父元素
<Border Background="{Binding Path=Background, RelativeSource={RelativeSource TemplatedParent}}"/>
Mode
指定绑定的操作模式,决定数据如何在源和目标之间流动。
- OneWay
:源到目标的单向绑定,目标不可修改。
- TwoWay
:双向绑定,源和目标都可以更新。
- OneWayToSource
:目标到源的单向绑定。
- OneTime
:绑定只发生一次,适合初始化。
UpdateSourceTrigger
指定何时更新绑定源的值。
- `Default`:使用默认触发方式(通常为控件的事件)。
- `PropertyChanged`:每当目标属性更改时更新源。
- `LostFocus`:当控件失去焦点时更新源。
- `Explicit`:仅在调用 `UpdateSource` 时更新。
Converter
用于将绑定的值转换为适合目标的值,或将目标值转换为适合源的值。
Converter={StaticResource MyValueConverter}
ConverterParameter
传递给 Converter
的额外参数。
ConverterParameter="SomeParameter"`
ConverterCulture
指定用于转换的文化信息。
ConverterCulture="en-US"
FallbackValue
在绑定失败时使用的值。
FallbackValue="N/A"
TargetNullValue
绑定源值为 null
时使用的值。
TargetNullValue="No Value"
StringFormat
用于格式化绑定值的字符串格式。
StringFormat="{}{0:C}"
(格式化为货币)
BindingGroup
给绑定分组,对一组绑定进行整体验证和更新。例如可以在提交前,对所有表单里面的数据一起验证
BindingGroup="{Binding MyBindingGroup}"
UpdateSourceExceptionFilter
一个方法,用于处理绑定源更新时发生的异常。
UpdateSourceExceptionFilter="HandleException"`
ValidatesOnDataErrors
指定是否启用 IDataErrorInfo
验证。
ValidatesOnDataErrors=True
ValidatesOnExceptions
指定是否启用异常验证(INotifyDataErrorInfo
)。
ValidatesOnExceptions=True
NotifyOnSourceUpdated
指定是否在源更新时通知绑定目标。默认为Flase,改为True会触发Binding.SourceUpdated
事件
NotifyOnSourceUpdated=True
NotifyOnTargetUpdated
指定是否在目标更新时通知绑定源。默认为Flase,改为True会触发Binding.TargetUpdated
事件
NotifyOnTargetUpdated=True
NotifyOnValidationError
指定是否在验证失败时通知绑定。
NotifyOnValidationError=True