Winforms datagridview - 可变列数 - INotifyPropertyChanged

问题描述 投票:0回答:1

假设我有两节课:

public class Hour : INotifyPropertyChanged
{
    public int Value
    {
        // ...
    }

    // INotifyPropertyChanged implementation
}

public class Day
{
    public int Number
    {
        // ...
    }
    public BindingList<Hour> Hours { get; set; } = new BindingList<Hour>();

    // INotifyPropertyChanged implementation
}

我想在 datagridview 中显示这些数据 - 第一列是天数,然后是每小时的列。我用过这个解决方案:

WinForms DataGridView - 数据绑定到具有列表属性的对象(可变的列数)

一切工作正常,直到我以编程方式更改值 - 我可以更改天数并且该值会在 datagridview 内自动刷新,但是当我更改小时时,datagridview 不会刷新,并且当我单击特定时会刷新新值排。我这样改:

days[0].Number = 55;

days[0].Hours[0].Value = 55;

有人可以告诉我我想要实现的目标是否可能吗?如果可以,我的目标如何实现。

我尝试查看 PropertyDescriptor 和 datagridview 内部,看看它内部是如何工作的,但我仍然陷入这个问题。

编辑:

这是我当前的解决方案:

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;
    }
}

    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;
        }
}

    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;
            PropertyDescriptor doThisFirst = null;
            foreach (PropertyDescriptor prop in origProps)
            {
                if (prop.Name == "Hours") doThisLast = prop;
                else newProps.Add(prop);
            }
            if (doThisLast != null)
            {
                var min = this.Min(f => f.Hours.Min(h => h.Value));
                var max = this.Max(f => f.Hours.Max(h => h.Value));

                if (max > 0)
                {
                    Type propType = typeof(Hour);

                    for (var i = min; i <= max; i++)
                    {
                        var item = new ListItemDescriptor(doThisLast, (int)(i - min), i, propType);
                        newProps.Add(item);
                    }
                }
            }

            return new PropertyDescriptorCollection(newProps.ToArray());
        }

        public string GetListName(PropertyDescriptor[] listAccessors)
        {
            return "";
        }
    }

    class ListItemDescriptor : PropertyDescriptor
    {
        private static readonly Attribute[] nix = new Attribute[0];
        private readonly PropertyDescriptor tail;
        private readonly Type type;
        private readonly int index;
        public ListItemDescriptor(PropertyDescriptor tail, int index, int value, Type type) : base(tail.Name + "[" + value + "]", nix)
        {
            this.tail = tail;
            this.type = type;
            this.index = index;
        }
        public override object GetValue(object component)
        {
            IList list = tail.GetValue(component) as IList;
            return (list == null || list.Count <= index) ? 0 : ((Hour)list[index]).Value;
        }
        public override Type PropertyType
        {
            get { return type; }
        }
        public override bool IsReadOnly
        {
            get { return true; }
        }
        public override void SetValue(object component, object value)
        {
            throw new NotSupportedException();
        }
        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;
        }
    }

然后我创建 DayList 并将其设置为 datagridview 的数据源。

这是结果:

正如我所说,当我更改数字时,一切正常(datagridview 单元格刷新)“days[0].Number = 55;”但是当我设置一个小时的值“days[0].Hours[0].Value = 55;”时,当我单击特定行时,单元格会刷新。

c# winforms data-binding datagridview inotifypropertychanged
1个回答
0
投票

根据您评论中的同意,这里有一个片段可以部分回答您的问题。一个好的起点可能是完成

ListItemDescriptor
类的实现,以便它将诸如“Hour[00]”之类的列名称映射到读取和写入方向上的数组对象
Day.Hour[0]

它可能看起来像这样:


class ListItemDescriptor : PropertyDescriptor
{
    public ListItemDescriptor(string name) : base(name, new Attribute[0]) { }
    public override Type ComponentType => typeof(Day);
    public override bool IsReadOnly => false;
    public override Type PropertyType => typeof(int);
    public override bool CanResetValue(object component) => true;
    public override void ResetValue(object component)
    {
        if(component is Day day)
        {
            switch (Name)
            {
                case nameof(Day.Number): 
                    day.Number = default; 
                    break;
                default:
                    var index =
                        Convert.ToInt32(_getIndex.Match(Name).Groups[1].Value);
                    day.Hours[index].Value = default; 
                    break;
            }
        }
    }
    Regex _getIndex = new Regex(@"\[(\d+)\]");
    public override object? GetValue(object? component)
    {
        if(component is Day day)
        {
            switch (Name)
            {
                case nameof(Day.Number): return day.Number;
                default:
                    var index = 
                        Convert.ToInt32(_getIndex.Match(Name).Groups[1].Value);
                    return day.Hours[index].Value;
            }
        }
        return default;
    }
    public override void SetValue(object? component, object? value)
    {
        if(component is Day day)
        {
            switch (Name)
            {
                case nameof(Day.Number): day.Number = Convert.ToInt32(value); break;
                default:
                    var index =
                        Convert.ToInt32(_getIndex.Match(Name).Groups[1].Value);
                    day.Hours[index].Value = Convert.ToInt32(value);
                    break;
            }
        }
    }
    public override bool ShouldSerializeValue(object component) => true;
}

稍后,我将发布一个指向我的 POC 的克隆链接,我在其中尝试了两个测试用例,但如果列要来来去去,则需要更多时间。我想知道的一件事是,既然一天总是有24小时,为什么不让DGV拥有一套完整的列并修改列的visibility呢?除了在想要动态添加和删除列的位置隐藏或显示列之外,您还有其他原因吗?

© www.soinside.com 2019 - 2024. All rights reserved.