绑定的DataGrid ComboBox列设置SelectedItem后无法更新UI问题
问题描述
在DataGrid的模板列中使用绑定的ComboBox,其他功能正常,但通过代码设置SelectedItem时,UI和父行数据源均未更新,不过Customer对象确实已反映出该变更。另外发现ComboBox绑定的SelectedItem.FullName显示的是客户名称而非技术员名称,但ComboBox的项绑定工作正常。
XAML代码
<DataGrid x:Name="CustomerGrid" Grid.Row="2" Grid.ColumnSpan="3" Margin="5,10,5,5" ItemsSource="{Binding Customers}" SelectedItem="{Binding SelectedCustomer}"> <DataGrid.Columns> <DataGridTemplateColumn Header="Technician" MinWidth="180" IsReadOnly="True"> <DataGridTemplateColumn.CellTemplate> <DataTemplate> <ComboBox x:Name="cb1" VerticalContentAlignment="Center" ToolTip="{Binding Path=SelectedItem.FullName,ElementName=cb1}" ItemsSource="{Binding Technicians, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" SelectedItem="{Binding SelectedTechnician, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" SelectedValuePath="TechnicianID" ScrollViewer.HorizontalScrollBarVisibility="Disabled"> <ComboBox.Resources> <Style TargetType="Popup"> <Setter Property="Width" Value="{Binding Path=ActualWidth+10, ElementName=cb1}"/> </Style> </ComboBox.Resources> <ComboBox.ItemTemplate> <DataTemplate> <TextBlock Text="{Binding FullName}" ToolTipService.ToolTip="{Binding FullName}" TextWrapping="NoWrap"/> </DataTemplate> </ComboBox.ItemTemplate> </ComboBox> </DataTemplate> </DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn> </DataGrid.Columns> </DataGrid>
Customer类代码
//partial customer class internal class Customer : ViewModelBase { public delegate void SendReconnectHandler(ref Customer customer); public event SendReconnectHandler SendReconnect; public Customer(List<Technician> technicians) { Technicians = new ObservableCollection<Technician>(technicians); } #region Properties private ObservableCollection<Technician> _Technicians = new ObservableCollection<Technician>(); public ObservableCollection<Technician> Technicians { get { return _Technicians; } set { if (value != _Technicians) { _Technicians = value; NotifyPropertyChanged(); } } } private Technician _selectedTechnician = new Technician(); public Technician SelectedTechnician { get { return _selectedTechnician ?? new Technician(); } set { if (value != _selectedTechnician) { _selectedTechnician = value; NotifyPropertyChanged(); } if (!string.IsNullOrEmpty(_selectedTechnician.TechnicianID)) { TechSelected = true; } else { TechSelected = false; } } } public bool TechSelected { get { return _TechSelected; } set { if (value != _TechSelected) { _TechSelected = value; NotifyPropertyChanged(); } } }
赋值方法代码
//method for updating the technician, along with crazy things I have tried private void AssignTechs() { var routes = Customers.ToList().Select(c => c.Route).Distinct().ToList(); var vm = new AssignmentViewModel(routes); var vw = new AssignmentWindow(vm); vw.ShowDialog(); foreach (var va in vm.RouteAssignments) { foreach (var cust in Customers.Where(c => c.Route == va.RouteNumber)) { cust.SelectedTechnician = va.SelectedTechnician; } } //foreach (var va in vm.RouteAssignments) //{ // foreach (var cust in Customers.Where(c => c.Route == va.RouteNumber)) // { // cust.SelectedTechnician = va.SelectedTechnician; // CurrentMainWindow.CustomerGrid.Columns[12].SetCurrentValue(ComboBox.SelectedItemProperty, va.SelectedTechnician); // //CurrentMainWindow.CustomerGrid.SelectedItem = cust; // //((Customer)CurrentMainWindow.CustomerGrid.SelectedItem).SelectedTechnician = va.SelectedTechnician; // } //} //CurrentMainWindow.CustomerGrid.SelectedItem = Customers.FirstOrDefault(); //CurrentMainWindow.CustomerGrid.ScrollIntoView(CurrentMainWindow.CustomerGrid.SelectedItem); }
Technician类代码
internal class Technician : ViewModelBase//, IEditableObject { #region Properties private String _TechnicianID = string.Empty; public String TechnicianID { get { return _TechnicianID ?? string.Empty; } set { if (value != _TechnicianID) { _TechnicianID = value; NotifyPropertyChanged(); } } } private String _FirstName = string.Empty; public String FirstName { get { return _FirstName ?? string.Empty; } set { if (value != _FirstName) { _FirstName = value; NotifyPropertyChanged(); } } } private String _LastName = string.Empty; public String LastName { get { return _LastName ?? string.Empty; } set { if (value != _LastName) { _LastName = value; NotifyPropertyChanged(); } } } private String _FullName = string.Empty; public String FullName { get { return BuildFullName() ?? string.Empty; } set { if (value != _FullName) { _FullName = value; NotifyPropertyChanged(); } } } private String _EmailAddress = string.Empty; public String EmailAddress { get { return _EmailAddress ?? string.Empty; } set { if (value != _EmailAddress) { _EmailAddress = value; NotifyPropertyChanged(); } } } private ObservableCollection<Models.ComboBoxItem> _FridayDates = new ObservableCollection<Models.ComboBoxItem>(); public ObservableCollection<Models.ComboBoxItem> FridayDates { get { return _FridayDates; } set { if (value != _FridayDates) { _FridayDates = value; NotifyPropertyChanged(); } } } private Models.ComboBoxItem _SelectedFridayDate = null; public Models.ComboBoxItem SelectedFridayDate { get { return _SelectedFridayDate ?? new Models.ComboBoxItem(); } set { if (value != _SelectedFridayDate) { _SelectedFridayDate = value; NotifyPropertyChanged(); } } } private string _AssignedFriday = string.Empty; public String AssignedFriday { get { if ((SelectedFridayDate != null) && (!string.IsNullOrEmpty(SelectedFridayDate.Value))) { if (DateTime.TryParse(SelectedFridayDate.Value, out var date)) { return date.ToString("yyyy-MM-dd"); } } return SelectedFridayDate?.Value ?? string.Empty; } set { if (value != _AssignedFriday) { { _AssignedFriday = value; } } } } #endregion public Technician() { LoadFridayDates(); } private string BuildFullName() { string fullName = string.Empty; if (string.IsNullOrEmpty(_FullName)) { fullName = FirstName + " " + LastName; } else if (string.IsNullOrEmpty(FirstName) || string.IsNullOrEmpty(LastName)) { var names = _FullName.Split(' '); if (names.Count() == 2) { FirstName = names[0]; LastName = names[1]; } else { LastName = _FullName; } fullName = _FullName; } else { fullName = _FullName; } if (string.IsNullOrEmpty(fullName.Trim()) && !string.IsNullOrEmpty(EmailAddress)) { var s1 = EmailAddress.Split('@'); if (s1.Count() == 2) { fullName = s1[0]; } } return fullName.Trim(); } private void LoadFridayDates() { DateTime dt = DateTime.Now.AddDays(-1); var weekday = DayOfWeek.Friday; List<ComboBoxItem> list = new List<ComboBoxItem>() { new ComboBoxItem() }; while (dt.DayOfWeek != weekday) dt = dt.AddDays(1); list.Add(new ComboBoxItem() { Value = dt.ToLongDateString(), Key = "0" }); for (int i = 0; i < 20; i++) { dt = dt.AddDays(7); list.Add(new ComboBoxItem() { Value = dt.ToLongDateString(), Key = (i + 1).ToString() }); ; } FridayDates = new ObservableCollection<ComboBoxItem>(list); } }
解决方案
问题1:代码设置SelectedTechnician后UI不更新
核心问题是对象引用不匹配:从AssignmentViewModel获取的va.SelectedTechnician实例,和当前Customer.Technicians集合中的实例不是同一个对象。WPF的SelectedItem匹配基于对象引用,即使两个Technician的TechnicianID相同,只要不是同一实例,ComboBox就无法匹配到对应项。
修正代码:
foreach (var va in vm.RouteAssignments) { foreach (var cust in Customers.Where(c => c.Route == va.RouteNumber)) { // 从当前客户的Technicians集合中查找匹配ID的实例 var matchedTech = cust.Technicians.FirstOrDefault(t => t.TechnicianID == va.SelectedTechnician.TechnicianID); if (matchedTech != null) { cust.SelectedTechnician = matchedTech; } } }
问题2:ComboBox的SelectedItem.FullName显示客户名称
这是因为SelectedItem为null时,绑定会回退到父级DataContext(即Customer对象),如果Customer也有FullName属性,就会显示客户的名称。
修正ToolTip绑定:
ToolTip="{Binding Path=SelectedItem.FullName, ElementName=cb1, FallbackValue='未选择技术员'}"
同时修改Customer.SelectedTechnician的getter,避免返回不在集合中的新实例:
public Technician SelectedTechnician { get { // 直接返回null,让ComboBox显示空状态 return _selectedTechnician; } set { if (value != _selectedTechnician) { _selectedTechnician = value; NotifyPropertyChanged(); } TechSelected = !string.IsNullOrEmpty(_selectedTechnician?.TechnicianID); } }
内容的提问来源于stack exchange,提问作者Steve S
相关产品推荐
相关产品推荐

