做WPF的时间久了你会发现,数据绑定里面藏着的大多数坑,最后都能在下拉框这个控件上被集中引爆。ComboBox本身并不复杂,一个下拉列表加一个文本框而已,但一旦牵扯到双向绑定、联动刷新、异步加载,各种莫名其妙的问题就冒出来了。我在某公司维护过一个老旧的WPF客户端,光“省份-城市”这个联动下拉,前前后后就返工了三次,每次的原因还都不一样,最后一次才把双向绑定背后的机制真正吃透。
这篇文章专门聊聊WPF里ComboBox的双向绑定。从绑定模式的原理讲起,把ViewModel怎么写、XAML怎么配,以及我实际踩过的坑全部展开,争取让看完的人能直接写出健壮的下拉绑定代码,而不是在搜索引擎和问答社区之间来回折腾。文章内容不算难,适合刚接触WPF的初学者,也适合写了一两年但没系统梳理过绑定细节的开发者。
1. 双向绑定核心:ComboBox的Mode为什么重要
WPF里所有Binding都有一个Mode,用来控制数据在“界面”和“数据源”之间的流向。一共四种:OneWay(数据源到界面)、TwoWay(双向)、OneTime(只绑定一次)、OneWayToSource(界面到数据源)。大部分控件的默认模式是OneWay,因为大多数情况下你只需要把业务数据显示到界面上。但ComboBox的SelectedItem、SelectedValue这两个属性默认就是TwoWay,如果你在代码里显式写下Mode=TwoWay,更多是一种强调和可读性上的考虑,写不写行为都一样。
明白这个默认值之后,更重要的是理解“目标”和“源”的区分。在Binding的体系里,目标永远是控件端的依赖属性,源是你ViewModel里的属性。TwoWay表示:源改变时目标要跟着变,目标改变时源也要跟着变。对应到下拉框上,就是ViewModel里SelectedCity被赋值时下拉框要显示对应项;用户在下拉框里选了一个新项时,SelectedCity也要立刻变成新值。
| 绑定模式 | 数据流向 | 典型使用场景 |
|---|---|---|
| OneWay | 源到目标 | 展示字段、只读信息回显 |
| TwoWay | 源和目标双向同步 | 表单编辑、下拉选择、开关切换 |
| OneTime | 源到目标,仅绑定一次 | 初始化后永不变化的内容 |
| OneWayToSource | 目标到源 | 只收集界面输入,不关心回显 |
1.1 选中项的三个入口:SelectedItem、SelectedValue、SelectedIndex
ComboBox跟下拉选择相关的依赖属性有三个,很多人一开始分不清。我用一个简单表格对比:
| 属性 | 绑定对象 | 特点与适用场景 |
|---|---|---|
| SelectedItem | 数据源中的某个项对象 | 最灵活,适合绑定整个实体对象,联动场景首选 |
| SelectedValue | 选中项的某个属性值 | 配合SelectedValuePath使用,适合只关心编号/主键的轻量场景 |
| SelectedIndex | 选中项在集合中的下标 | 简单粗暴,但数据刷新后容易错位,慎用 |
我个人的习惯是:只要选项是实体对象(哪怕只是一个包含Id和Name的轻量类),一律用SelectedItem绑定整个对象,不要贪省事去用SelectedValue绑Id。原因后面再说,简单讲就是SelectedItem保留了整个对象的引用,后面做联动、做回显、做判断都方便,而且天然规避了“Id重复导致匹配错乱”的隐患。
1.2 INotifyPropertyChanged是一切的基石
双向绑定想要成立,ViewModel里的属性必须能通知界面“我变了”。这个能力来自INotifyPropertyChanged接口。一个没有实现这个接口的属性,无论你是从代码里改还是用户从界面上改,对面都不会有任何反应。常见的现象就是:下拉框选了,VM里的属性确实被赋值了,但界面一动不动——多半是源属性没发通知。
实现这个接口其实不难,核心就两件事:声明接口,然后在属性setter里调用PropertyChanged事件。现在的C#写法很成熟,直接用一个封装好的基类就行。
public class ViewModelBase : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } protected bool SetProperty<T>(ref T storage, T value, string propertyName) { if (EqualityComparer<T>.Default.Equals(storage, value)) return false; storage = value; OnPropertyChanged(propertyName); return true; } }SetProperty这个方法是我强烈推荐的写法。它内部先判断新旧值是否相等,相等就不触发通知,既能省掉不必要的UI刷新,也能防止某些情况下因为重复赋值导致的事件死循环。后面所有ViewModel的代码我都会基于这个基类来写。
2. ViewModel层准备:双向绑定的“源头”
2.1 写一个最简单的下拉绑定属性
假设我们有一个城市实体,包含Id和Name两个字段。ViewModel里需要两个属性:一个是给下拉框用的数据源CityList,一个是用户选中的结果SelectedCity。
public class CityModel { public int Id { get; set; } public string Name { get; set; } } public class MainViewModel : ViewModelBase { private List<CityModel> _cityList; public List<CityModel> CityList { get => _cityList; set => SetProperty(ref _cityList, value, nameof(CityList)); } private CityModel _selectedCity; public CityModel SelectedCity { get => _selectedCity; set => SetProperty(ref _selectedCity, value, nameof(SelectedCity)); } public MainViewModel() { CityList = new List<CityModel> { new CityModel { Id = 1, Name = "北京" }, new CityModel { Id = 2, Name = "上海" }, new CityModel { Id = 3, Name = "广州" } }; } }这里有两个细节值得注意。第一,CityList我用了List而不是ObservableCollection,因为整个集合是一次性替换的,List完全够用;如果你要在运行时向列表里增删单项,那才需要换成ObservableCollection,让集合的每次变化都能通知到UI。第二,属性SetProperty的写法没有写Mode=TwoWay,因为SelectedItem的默认模式已经是TwoWay了。
2.2 把ViewModel挂到DataContext上
光有ViewModel还不够,界面得拿到这个对象。最常见的做法是在窗口的构造函数里设置DataContext:
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); DataContext = new MainViewModel(); } }如果你用的是MVVM框架,通常会在App启动时通过容器解析出来。不管是哪种方式,记住一句话:任何Binding的DataContext解析都是从控件树向上找最近的DataContext。这意味着,只要最外层窗口设置了DataContext,里面所有控件的Binding默认都能访问到同一个ViewModel实例。
2.3 属性命名和大小写的坑
有个特别容易被忽略的点:XAML里的Binding是区分大小写的,而且它默认按“属性名”精确匹配。如果你的ViewModel里属性叫selectedCity,XAML里写SelectedCity,那绑定必失败,而且不会报编译错误,只会在输出窗口留一行无伤大雅的Debug提示。刚开始写WPF的人经常在这上面浪费一两个小时。
一个更隐蔽的坑是:当属性的类型是字符串时,XAML解析器偶尔会因为类型转换问题找错绑定路径。只要属性名写对,类型匹配,这个概率极低,但万一出现了莫名绑定不上,先检查输出窗口的BindingError提示,那里会给出具体的路径和错误原因。
3. XAML配置实操:从模板到绑定路径
3.1 最简配置:ItemsSource + DisplayMemberPath
假设我们不搞花活,下拉框只要显示CityModel的Name字段,XAML可以写成这样:
<ComboBox ItemsSource="{Binding CityList}" DisplayMemberPath="Name" SelectedItem="{Binding SelectedCity}" />DisplayMemberPath会告诉ComboBox:每一项拿CityModel的Name属性作为显示文本。它内部其实是生成了一层默认的显示模板,对于“只需要显示一个字段”的场景,一行属性搞定,比写ItemTemplate省事得多。
SelectedItem绑定到SelectedCity,用户选了哪项,SelectedCity就是哪项的CityModel实例。如果你在代码里执行selectedCity = cityList[2],下拉框也会自动把选中项切到第三个。这就是双向绑定的完整闭环。
3.2 用ItemTemplate自定义显示
业务上通常没这么简单。很多时候下拉里要显示两列,比如编码和名称并存,甚至还要带个状态图标。这时候DisplayMemberPath就不够了,需要用ItemTemplate。
<ComboBox ItemsSource="{Binding CityList}" SelectedItem="{Binding SelectedCity}"> <ComboBox.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal"> <TextBlock Text="{Binding Id}" Foreground="#888888" /> <TextBlock Text="{Binding Name}" Margin="8,0,0,0" /> </StackPanel> </DataTemplate> </ComboBox.ItemTemplate> </ComboBox>模板里每一项的DataContext就是CityModel本身,所以可以直接绑定Id和Name。这个写法在列表展开时看得很清楚:编码灰灰的显示在左,名称显示在右。ItemTemplate的好处是完全控制展示形式和布局,坏处是一旦数据实体的字段结构变了,模板也得跟着改,所以建议把模板拆成资源,别一股脑全堆在ComboBox内部。
3.3 选中框显示与下拉显示分离
很多人不知道ComboBox还有一个SelectionBoxItemTemplate属性。默认情况下,ComboBox关闭时用来展示当前选中项的模板,用的就是ItemTemplate。这会导致一个问题:如果下拉项是“编码+名称”两列,关闭时选中项也是“编码+名称”,看着非常拥挤。
解决办法就是单独给选中框一个模板。SelectionBoxItemTemplate只影响关闭状态下的显示,它的DataContext同样是选中的项对象:
<ComboBox ...> <ComboBox.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal"> <TextBlock Text="{Binding Id}" Foreground="#888888" /> <TextBlock Text="{Binding Name}" Margin="8,0,0,0" /> </StackPanel> </DataTemplate> </ComboBox.ItemTemplate> <ComboBox.SelectionBoxItemTemplate> <DataTemplate> <TextBlock Text="{Binding Name}" /> </DataTemplate> </ComboBox.SelectionBoxItemTemplate> </ComboBox>下拉展开时显示完整信息,选中后只在框里留下名称。这个技巧在实际项目里的使用频率极高,尤其在做订单、商品这类“有编号有名称”的数据时。
3.4 SelectedValuePath和SelectedValue的搭配
有些开发者偏爱轻量绑定,不愿意让ViewModel暴露整个对象,于是用SelectedValuePath配合SelectedValue来绑Id。
<ComboBox ItemsSource="{Binding CityList}" DisplayMemberPath="Name" SelectedValuePath="Id" SelectedValue="{Binding SelectedCityId}" />这个玩意的效果是:下拉显示Name,但SelectedValue绑的是选中项的Id。ViewModel里只需要一个int属性:
private int _selectedCityId; public int SelectedCityId { get => _selectedCityId; set => SetProperty(ref _selectedCityId, value, nameof(SelectedCityId)); }看起来很干练,但我建议你在使用前想清楚一个场景:如果这个下拉后面跟着第二个下拉(比如选了省要联动城市),当你拿到SelectedCityId准备去查城市列表时,你还需要省份Id、省名称、省编码等信息。这时候如果ViewModel里只存了一个Id,就要到处反查数据源列表,非常别扭。
而我用SelectedItem绑定整个省对象,ViewModel里直接就有省份的Id和Name,下一步逻辑顺手就写了。所以我的建议是:除非这个下拉框真的完全不需要除Id之外的任何信息,并且永远不会加联动,否则都优先用SelectedItem加DisplayMemberPath。
3.5 UpdateSourceTrigger什么时候才会用到
老想着TextBox.Text的开发者可能会习惯性给ComboBox写UpdateSourceTrigger=PropertyChanged。实际上ComboBox的SelectedItem本身就是依赖属性,值一变目标马上通知源,默认就是PropertyChanged级别的实时更新,不需要额外设置。
真正需要UpdateSourceTrigger的是ComboBox的Text属性。当IsEditable=true时,用户可以手动输入文本,默认情况下Text绑定的更新时机是LostFocus,也就是焦点离开才把文本写回源。想做到边输入边同步,就要显式设置:
<ComboBox IsEditable="True" Text="{Binding SearchText, UpdateSourceTrigger=PropertyChanged}" />这个场景多用于“可搜索下拉”或者“自由输入+选择”混合需求。有一点必须提醒:开启IsEditable之后,用户输入的值未必在ItemsSource里,SelectedItem可能变成null,你从SelectedItem拿不到用户输入的内容,得从Text拿。
4. 把它串起来:四个常见业务场景的绑定方案
4.1 场景一:最基本的下拉选择
回到前面最朴素的例子。一个窗体上放一个标签、一个ComboBox、一个显示结果的TextBlock。
<StackPanel> <ComboBox Width="220" Margin="10" ItemsSource="{Binding CityList}" DisplayMemberPath="Name" SelectedItem="{Binding SelectedCity}" /> <TextBlock Margin="10" Text="{Binding SelectedCity.Name, StringFormat=选中的城市:{0}}" /> </StackPanel>TextBlock直接绑定SelectedCity.Name,用StringFormat拼出文案。这个绑定的神奇之处在于,TextBlock的绑定路径会自动跟随SelectedCity的变化——因为路径是“SelectedCity.Name”,当SelectedCity这个对象换了之后,整个路径会重新求值。于是只要下拉框一换选项,TextBlock的文字立即刷新,完全不需要额外写事件。
4.2 场景二:省市级联
联动的核心逻辑是:当选中省发生变化时,城市列表重新加载,同时将已选城市清空。实现上就是在SelectedCity的setter里做后续处理。
public class ProvinceModel { public int Id { get; set; } public string Name { get; set; } } public class CityModel { public int Id { get; set; } public string Name { get; set; } public int ProvinceId { get; set; } } public class LinkageViewModel : ViewModelBase { private List<ProvinceModel> _provinceList; public List<ProvinceModel> ProvinceList { get => _provinceList; set => SetProperty(ref _provinceList, value, nameof(ProvinceList)); } private ProvinceModel _selectedProvince; public ProvinceModel SelectedProvince { get => _selectedProvince; set { if (SetProperty(ref _selectedProvince, value, nameof(SelectedProvince))) { LoadCities(); } } } private List<CityModel> _cityList; public List<CityModel> CityList { get => _cityList; set => SetProperty(ref _cityList, value, nameof(CityList)); } private CityModel _selectedCity; public CityModel SelectedCity { get => _selectedCity; set => SetProperty(ref _selectedCity, value, nameof(SelectedCity)); } private void LoadCities() { if (SelectedProvince == null) { CityList = null; SelectedCity = null; return; } CityList = _dataSource.Where(c => c.ProvinceId == SelectedProvince.Id).ToList(); SelectedCity = null; } }XAML只需要两个ComboBox按顺序排好:
<ComboBox ItemsSource="{Binding ProvinceList}" DisplayMemberPath="Name" SelectedItem="{Binding SelectedProvince}" /> <ComboBox Margin="0,8,0,0" ItemsSource="{Binding CityList}" DisplayMemberPath="Name" SelectedItem="{Binding SelectedCity}" />联动里最容易被忽视的是第一步:如果联动逻辑写在SelectedProvince的setter里,那么当窗口初始化、ViewModel给SelectedProvince赋默认值时,LoadCities也会被触发,这是好事。但如果给默认值的时候CityList还没准备好,就会出空引用或者清空异常。所以LoadCities里必须先判空。第二个容易忽视的点是:切换省份后要手动清空SelectedCity,否则上次城市还残留着,逻辑上就是不对的。
4.3 场景三:异步加载与进度反馈
真实项目的下拉数据不会像示例里那样写死在构造里,大部分是从数据库或者接口拿的。异步加载时要注意UI线程问题。
public class AsyncViewModel : ViewModelBase { private bool _isLoading; public bool IsLoading { get => _isLoading; set => SetProperty(ref _isLoading, value, nameof(IsLoading)); } private List<CityModel> _cityList; public List<CityModel> CityList { get => _cityList; set => SetProperty(ref _cityList, value, nameof(CityList)); } public async Task LoadAsync() { IsLoading = true; try { var data = await _service.GetCitiesAsync(); CityList = data; } finally { IsLoading = false; } } }XAML里可以用一个触发器控制加载状态的可见性:
<Grid> <ComboBox ItemsSource="{Binding CityList}" DisplayMemberPath="Name" SelectedItem="{Binding SelectedCity}" /> <TextBlock Text="加载中..." HorizontalAlignment="Center" VerticalAlignment="Center"> <TextBlock.Style> <Style TargetType="TextBlock"> <Setter Property="Visibility" Value="Collapsed" /> <Style.Triggers> <DataTrigger Binding="{Binding IsLoading}" Value="True"> <Setter Property="Visibility" Value="Visible" /> </DataTrigger> </Style.Triggers> </Style> </TextBlock.Style> </TextBlock> </Grid>注意,await _service.GetCitiesAsync()这个调用如果不是在UI线程完成的,async/await的机制会自动把后续代码调度回原始同步上下文。通常我们在窗口的Loaded事件里调用LoadAsync,那么await之后CityList的赋值会回到UI线程,所以不用手动Dispatcher。但如果你的服务层用了自定义的同步上下文配置,或者控制台环境里做了奇怪的调度,集合赋值后UI没更新,这时就要检查线程问题,用Application.Current.Dispatcher.Invoke强行切回UI线程。
4.4 场景四:刷新后恢复选中
列表刷新的场景很常见。比如管理员在另一个窗口里编辑了城市名称,回到主窗口点击刷新按钮,下拉列表更新后,之前选中的城市最好还在。这时候直接重新绑定ItemsSource会把SelectedCity重置为null,因为新集合里根本没有旧对象的引用。
解决办法是按唯一标识恢复选中:
public void Refresh() { var selectedId = SelectedCity?.Id; CityList = _dataSource.ToList(); if (selectedId.HasValue) { SelectedCity = CityList.FirstOrDefault(c => c.Id == selectedId.Value); } }这里有个潜在的坑:CityModel如果没有重写Equals方法,即使两条数据Id相同,也是两个不同的对象引用。但因为我们是用FirstOrDefault按Id去新集合里找对象,找到后再赋给SelectedCity,所以没问题。反过来,如果你没有重新定位,只把ItemsSource换成新列表,然后试图让SelectedCity保持旧对象的引用,那绑定就会失败或者显示空白——因为SelectedCity引用的旧对象已经不在新集合里了。
5. 踩坑实录:排查双向绑定失效的通用套路
5.1 绑定到底挂没挂上
遇到任何绑定不生效,我的第一动作永远是看输出窗口的BindingError提示。WPF会在调试模式下输出类似这样的错误:BindingExpression path error: 'CityList' property not found。路径名、类型、来源都会打印出来,绝大多数拼写类问题一眼就能定位。
第二步是检查DataContext。用Snoop或者直接在最外层元素的构造函数里断点,看DataContext是不是预期类型。很多时候问题不是绑定写错,而是ViewModel压根没赋成功。
第三步是排查属性名大小写。记住,XAML里的Binding是大小写敏感的,这点前面已经强调过。
5.2 对象引用不相等导致回选失败
这个坑在联动和刷新场景里很典型。A下拉框选中某个对象,B下拉框的ItemsSource依赖A,B的SelectedItem要初始化为某个匹配项。如果两个对象是从不同地方查出来的、引用不相等,B的SelectedItem赋值时,WPF内部会去集合里找这个引用,找不到就把值改成null。
解决办法有两个:一是保证赋值时用的是集合里的同一个实例,像我上面示例里那样用FirstOrDefault现找;二是给数据实体重写Equals和GetHashCode,让Contains判断依据业务主键:
public class CityModel { public int Id { get; set; } public string Name { get; set; } public override bool Equals(object obj) { return obj is CityModel other && other.Id == Id; } public override int GetHashCode() { return Id.GetHashCode(); } }重写完Equals之后,只要两条数据的Id相同,WPF就会认为它们相等,SelectedItem赋值时也能正确匹配上。这个技巧在大部分项目里都属于“救急神器”。
5.3 null值在双向绑定里的怪异行为
下拉框允许取消选中吗?默认不允许。用户点击下拉外面,选中项不会变成null。这在需要“非必填”选择的场景里会让人抓狂。想支持清空选择,可以把ComboBox设为可编辑模式,或者在顶部插入一个空选项。
我采用的做法是数据源第一行放一个null项:
CityList = new List<CityModel> { null, new CityModel { Id = 1, Name = "北京" }, new CityModel { Id = 2, Name = "上海" } };XAML里的ItemTemplate必须为null写一个空模板的坑,否则显示绑定会撞空引用。更稳妥的做法是给ComboBox设置一个自定义的显示逻辑,或者在模板里用DataTrigger判断:
<DataTemplate> <TextBlock> <TextBlock.Style> <Style TargetType="TextBlock"> <Setter Property="Text" Value="-请选择-" /> <Style.Triggers> <DataTrigger Binding="{Binding}" Value="{x:Null}"> <Setter Property="Text" Value="略过" /> </DataTrigger> </Style.Triggers> </Style> </TextBlock.Style> </TextBlock> </DataTemplate>当绑定的项为null时,DataTrigger把Text换成“略过”。这个写法能生效的前提是ComboBox的ItemTemplate能够处理null,通过x:Null触发确实可以。
5.4 List和ObservableCollection的混用误区
很多教程从一开始就无脑建议用ObservableCollection,导致不少开发者也给自己绑定的数据源一律用ObservableCollection。诚然,ObservableCollection在运行时增删单项时能自动通知UI,但它有一个隐藏的坑:它并不支持在后台线程直接做Add/Remove/Sort等操作,任何集合变动必须在UI线程执行。
当你的数据量很大(几百上千项),或者后台刷新频率很高时,ObservableCollection频繁的通知也会给UI线程带来额外压力。反观List,虽然集合内部变化不通知UI,但整个属性替换一次的代价很低,配合SetProperty也能达到刷新效果。
我的选型标准很朴素:运行时只整体替换数据源,不逐项增删的,用List;需要频繁增删单项的,用ObservableCollection;需要跨线程更新且追求高性能的,用BindingList自己控制,或者手动批量加到UI线程。
5.5 可编辑ComboBox的文本同步
把IsEditable设为true之后,Text和SelectedItem的行为开始分叉。用户输入新字符串时,SelectedItem可能还是null或者旧值,Text才是用户真正输入的内容。如果你绑定了SelectedItem去做逻辑判断,很容易拿到一个“没反映用户输入”的结果。
这个场景下最稳妥的方案是:绑定Text,用Text作为输入值;同时保留SelectedItem用于判断用户是否从列表里选中了精确匹配项。
<ComboBox IsEditable="True" ItemsSource="{Binding CityList}" DisplayMemberPath="Name" Text="{Binding InputText, UpdateSourceTrigger=PropertyChanged}" SelectedItem="{Binding SelectedCity}" />然后在ViewModel里做区分:InputText是搜索框里实实在在的字符串,SelectedCity是精确命中列表时的实体对象。这样既能自由输入,也能通过列表快捷选择,互不干扰。
5.6 性能:ComboBox为什么大列表会卡
ComboBox的默认ItemsPanel是StackPanel,不是虚拟化的。也就是说,当你给它塞了一万个项目时,它会把一万个可视化元素全部创建出来,哪怕用户根本不会下拉滚动。这是WPF里“列表级”控件和“项级”控件的重要区别——ListView/DataGrid默认会开虚拟化,ComboBox没有。
数据量超过几百项,最简单有效的处理是给ComboBox换一个虚拟化的ItemsPanel:
<ComboBox> <ComboBox.ItemsPanel> <ItemsPanelTemplate> <VirtualizingStackPanel /> </ItemsPanelTemplate> </ComboBox.ItemsPanel> </ComboBox>配合两个性能属性效果更明显:
<ComboBox VirtualizingStackPanel.IsVirtualizing="True" VirtualizingStackPanel.VirtualizationMode="Recycling">不过虚拟化下拉框也有副作用:下拉弹出后滚动时,选中项可能会从可视区域消失,高亮和选中状态偶尔异常。对性能敏感的真实业务,我更建议用“先模糊搜索再显示结果”的思路,从源头减少一次性显示的项目数量。毕竟用户在下拉框里从一万个城市里翻找,体验本身也是灾难。
写在最后
我自己的经验是,ComboBox的双向绑定一旦出问题,先别急着在XAML上改来改去,把三个问题按顺序问一遍:ViewModel有没有实现INotifyPropertyChanged、SelectedItem绑的是不是集合里的同一个对象、绑定路径有没有拼错。百分之八十的问题都能在这个排查环节里解决。
剩下百分之二十的疑难杂症,基本都出在联动逻辑和异步加载上,需要借助输出窗口的报错信息逐步debug。做WPF项目时,我习惯给每个ComboBox绑定都起一个明确的属性名,从来不用中文或拼音缩写,这样调试时能少走很多弯路。另外建议给ComboBox的绑定都用x:Name标记一个可识别的名称,配合Snoop工具查看可视树的绑定值,排查效率会高很多。
双向绑定这套东西,说穿了就是“属性通知”加“依赖属性”的组合拳。把原理吃透之后,不管以后是换DataGrid还是TreeView,遇到绑定的问题都能举一反三。希望这篇文章能帮你少踩几个我当年踩过的坑。