You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 07:19:56