假设我有两节课:
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;”时,当我单击特定行时,单元格会刷新。
根据您评论中的同意,这里有一个片段可以部分回答您的问题。一个好的起点可能是完成
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呢?除了在想要动态添加和删除列的位置隐藏或显示列之外,您还有其他原因吗?