Let's say I have two classes:
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
}
I want to show those data in the datagridview - first column is day number, and then there are columns for each hour. I've used this solution:
WinForms DataGridView - databind to an object with a list property (variable number of columns)
Everything is working fine until I change the value programatically - I can change the number of day and the value is refreshed automatically inside the datagridview but when I change hour, datagridview is not refreshed and the new value is refreshed when I click on the specific row. I change it like this:
days[0].Number = 55;
days[0].Hours[0].Value = 55;
Could someone tell me if it's even possible what I am trying to achieve and if so, how can my goal be achieved.
I've tried to look inside PropertyDescriptor and datagridview to see, how is it working internally but I am still stuck on this problem.
EDIT:
This is my current solution:
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;
}
}
Then I create DayList and set is as datasource for datagridview.
This is the result:
As I said when I change the number, everything is ok (datagridview cell is refreshed) "days[0].Number = 55;" but when I set the value of an hour "days[0].Hours[0].Value = 55;", the cell is refreshed when I click on the specific row.
public Day() {
Hours.ListChanged += Hours_ListChanged;
}
private void Hours_ListChanged(object sender, ListChangedEventArgs e) {
OnPropertyChanged("Value");
}
OnPropertChanged has to have the Value string in it that way DataGridView will update the column.