I Have a DataGridView that's working just fine displaying data as follows:
private DataGridView dgv;
private List<Item> Items = new();
Items.Add(new() {ID = 1, Service = new() { Name = "Item 1"}, Quantity = 2});
dgv.DataSource = Items;
// [...]
Public Class Item
{
public int ID {get; set;}
public Service Service {get; set;}
public int Quantity {get; set;}
}
Public Class Service
{
public string Name {get; set;}
Public overrides string To string => Name;
}
Everything was working just fine until I introduced
public interface IService
{
public string Name {get; set;}
}
And then changed:
public Service Service {get; set;}
To:
public IService Service {get; set;}
And then have the Service
class implements the interface:
public class Service: IService...
Everything works still except that the Service
column in the DataGridView shows nothing, although when I hover over it with mouse I can see the value
When the DataGridView needs to format the value of a Cell, for presentation, it performs a (long) series of tests on the underlying Type.
It then calls the default formatter for the Type, so you can override ToString()
in a class object, to return a formatted value.
When the underlying Type is an Interface, it verifies whether IFormattable is implemented. If it's not, then the FormattedValue
is null
.
See also the notes here:
DataGridView HeaderCell doesn't show value when it's a numeric type
Given the current structure:
public class Item {
public int ID { get; set; }
public IService? Service { get; set; } = null;
public int Quantity { get; set; }
}
public class Service : IService {
public string Name { get; set; } = string.Empty;
public override string ToString() => Name;
}
public interface IService {
string Name { get; set; }
}
You can do a couple of things:
If you need to keep the structure of the classes as it is, then handle the CellFormatting event and set the Cell's Value to the current e.Value
(a Service
object), cast to string, i.e., e.Value.ToString()
:
private void dgv_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e) {
var dgv = sender as DataGridView;
if (dgv?.Columns[e.ColumnIndex].Name == nameof(Service)) {
e.Value = e.Value?.ToString();
}
// Or...
if (e.Value?.GetType() == typeof(Service)) {
e.Value = e.Value?.ToString();
}
}
If you instead can change the Service class, then implement IFormattable. You can make its ToString()
implementation just call the existing ToString()
override if no other formatting is needed (e.g., you need to consider a specific Culture or Format):
public class Service : IService, IFormattable {
public string Name { get; set; } = string.Empty;
public override string ToString() => Name;
public string ToString(string? format, IFormatProvider? provider) => ToString();
}
In this case, you don't need to handle CellFormatting
.
Of course, to make the DataGridView use the IFormattable methods, you need to provide a format. Hence, after you have set the DataSource of the DataGridView, set the Format Property of the Service
Column to a generic format, e.g.,:
dgv.Columns["Service"].DefaultCellStyle.Format = "G";
The DataGridView has a check to verify whether the class object implements IFormattable, and calls its methods if it does.