WinForms DataGridView绑定可变列:修改Hour值后视图不自动刷新
WinForms DataGridView 自动刷新Hour.Value的解决方案
问题背景
我定义了两个实现INotifyPropertyChanged的类Hour和Day,Day包含Number属性与BindingList<Hour>集合。需要在WinForms DataGridView中展示:首列为Day的Number,后续列为每个Hour的Value。采用继承BindingList<Day>并实现ITypedList的自定义DayList作为数据源,目前修改Day.Number时视图能自动刷新,但通过代码修改Hour.Value(如days[0].Hours[0].Value = 55)时,视图不会自动更新,仅点击对应行后才刷新。
解决方案
这个需求完全可以实现,核心问题是自定义的ListItemDescriptor没有监听Hour的PropertyChanged事件,导致Hour.Value变化时无法通知DataGridView更新。需要通过以下步骤修复:
- 在
ListItemDescriptor中订阅对应Hour的PropertyChanged事件,当Value变化时触发属性值变更通知 - 监听
Day.Hours集合的ListChanged事件,确保集合元素变化时重新订阅Hour的事件 - 修正
ListItemDescriptor的PropertyType为int(原代码错误设置为typeof(Hour)),确保DataGridView正确识别列类型
修改后的完整代码
Hour类(无需修改)
public class Hour : INotifyPropertyChanged { private int _value; public int Value { get => _value; set { SetField(ref _value, value, "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; } }
Day类(无需修改)
public class Day : INotifyPropertyChanged { private int _number; public int Number { get => _number; set { SetField(ref _number, value, "Number"); } } public BindingList<Hour> Hours { get; set; } = new BindingList<Hour>(); 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; } }
DayList类(修正列生成逻辑)
class DayList : BindingList<Day>, ITypedList { public PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] listAccessors) { var origProps = TypeDescriptor.GetProperties(typeof(Day)); List<PropertyDescriptor> newProps = new List<PropertyDescriptor>(origProps.Count); PropertyDescriptor doThisLast = null; foreach (PropertyDescriptor prop in origProps) { if (prop.Name == "Hours") doThisLast = prop; else newProps.Add(prop); } if (doThisLast != null && this.Count > 0) { // 按Hour的索引生成列,确保每个Hour对应一列 int hourCount = this[0].Hours.Count; Type propType = typeof(int); for (var i = 0; i < hourCount; i++) { // 列名格式为Hours[1], Hours[2]... var item = new ListItemDescriptor(doThisLast, i, i + 1, propType); newProps.Add(item); } } return new PropertyDescriptorCollection(newProps.ToArray()); } public string GetListName(PropertyDescriptor[] listAccessors) { return ""; } }
ListItemDescriptor类(核心修改)
class ListItemDescriptor : PropertyDescriptor, INotifyPropertyChanged { private static readonly Attribute[] nix = new Attribute[0]; private readonly PropertyDescriptor tail; private readonly Type type; private readonly int index; private Day currentDay; private Hour currentHour; public event PropertyChangedEventHandler PropertyChanged; public ListItemDescriptor(PropertyDescriptor tail, int index, int value, Type type) : base(tail.Name + "[" + value + "]", nix) { this.tail = tail; this.type = type; this.index = index; } private void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } private void Hour_PropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == nameof(Hour.Value)) { // 通知DataGridView当前属性值已变化 OnValueChanged(currentDay, EventArgs.Empty); } } public override object GetValue(object component) { var day = component as Day; if (day != null && day != currentDay) { // 取消订阅旧Day的相关事件 if (currentDay != null) { currentDay.Hours.ListChanged -= Hours_ListChanged; UnsubscribeHourEvent(); } currentDay = day; // 订阅新Day的Hours集合变化事件 currentDay.Hours.ListChanged += Hours_ListChanged; // 订阅对应Hour的PropertyChanged事件 SubscribeHourEvent(); } IList list = tail.GetValue(component) as IList; return (list == null || list.Count <= index) ? 0 : ((Hour)list[index]).Value; } private void Hours_ListChanged(object sender, ListChangedEventArgs e) { // 集合变化时重新订阅事件并通知视图更新 UnsubscribeHourEvent(); SubscribeHourEvent(); OnValueChanged(currentDay, EventArgs.Empty); } private void SubscribeHourEvent() { var list = tail.GetValue(currentDay) as IList; if (list != null && list.Count > index) { currentHour = list[index] as Hour; currentHour.PropertyChanged += Hour_PropertyChanged; } } private void UnsubscribeHourEvent() { if (currentHour != null) { currentHour.PropertyChanged -= Hour_PropertyChanged; currentHour = null; } } protected override void OnValueChanged(object component, EventArgs e) { base.OnValueChanged(component, e); } public override Type PropertyType { get { return typeof(int); } // 修正为Hour.Value的类型 } public override bool IsReadOnly { get { return false; } // 支持编辑可改为false } public override void SetValue(object component, object value) { IList list = tail.GetValue(component) as IList; if (list != null && list.Count > index) { var hour = list[index] as Hour; if (hour != null) { hour.Value = Convert.ToInt32(value); } } } public override void ResetValue(object component) { throw new NotSupportedException(); } public override bool CanResetValue(object component) { return false; } public override Type ComponentType { get { return tail.ComponentType; } } public override bool ShouldSerializeValue(object component) { return false; } }
关键说明
- 修正了
ListItemDescriptor的PropertyType为int,确保DataGridView正确渲染列内容 - 通过订阅
Hour.PropertyChanged和BindingList<ListChanged>事件,实现了Hour.Value变化时的实时通知 - 修改了
DayList的列生成逻辑,改为按Hour的索引生成列,避免原逻辑中因Hour.Value不连续导致的列缺失问题
内容的提问来源于stack exchange,提问作者mnaauuu
相关产品推荐
相关产品推荐

