The way DataGrid
Auto Generates Columns is pretty robust, but unfortunately it does not work the way you want it to. One way or another, you need to tell it what columns to expect. If you give it an object
type, it isn't going to reflect over what type is assigned to the 'object', it's just going to reflect over 'System.object', which will net you 0 columns auto generated. I'm not smart enough to explain this whole debacle. I just want to jump into the solution I came up with, which should work well for your purposes.
I actually surprised myself by making a working example. There's a few things going on here that need some 'splaining.
I left your XAML alone, it's still
<DataGrid CanUserAddRows="False"
ItemsSource="{Binding ProductData, UpdateSourceTrigger=PropertyChanged}"
IsReadOnly="True"
AutoGenerateColumns="True"
Margin="0 2 0 0" />
However, in the ViewModel, I've made a decision that hopefully you can work with, to changed ProducData
from an ObservableCollection<>
to a DataTable
. The binding will still work the same, and the UI will update appropriately.
Your original intent was to make the ObservableCollection
of type object
to make things more generic, but here I've implemented the Factory Pattern
to show a way to create multiple types
without making things too complicated. You can take it or leave it for what it's worth.
ViewModel (I'm using Prism boilerplate, replace BindableBase
with your implementation of INotifyPropertyChanged
public class ViewAViewModel : BindableBase
{
private DataTable _productData;
private IDataFactory _dataFactory;
public ViewAViewModel(IDataFactory dataFactory)
{
_dataFactory = dataFactory;
}
public DataTable ProductData
{
get { return _productData; }
set { _productData = value; OnPropertyChanged(); }
}
public void Load()
{
ProductData = _dataFactory.Create(typeof(FooData));
}
}
DataFactory
public interface IDataFactory
{
DataTable Create(Type t);
}
public class DataFactory : IDataFactory
{
public DataTable Create(Type t)
{
if (t == typeof(FooData))
{
return new List<FooData>()
{
new FooData() {Id = 0, AlbumName = "Greatest Hits", IsPlatinum = true},
new FooData() {Id = 1, AlbumName = "Worst Hits", IsPlatinum = false}
}.ToDataTable();
}
if (t == typeof(BarData))
{
return new List<BarData>()
{
new BarData() {Id = 1, PenPointSize = 0.7m, InkColor = "Blue"},
new BarData() {Id = 2, PenPointSize = 0.5m, InkColor = "Red"}
}.ToDataTable();
}
return new List<dynamic>().ToDataTable();
}
}
Base class and Data Models
public abstract class ProductData
{
public int Id { get; set; }
}
public class FooData : ProductData
{
public string AlbumName { get; set; }
public bool IsPlatinum { get; set; }
}
public class BarData : ProductData
{
public decimal PenPointSize { get; set; }
public string InkColor { get; set; }
}
So for usage, you can swap FooData
with BarData
, or any type derived of ProductData
public void LoadFooData()
{
ProductData = _dataFactory.Create(typeof(FooData));
}
Last but not least, I found this little gem somewhere on SO (If I find it again, will credit the author) This is an extension method to generate the DataTable
from an IList<T>
with column names based on the properties of T
.
public static class DataFactoryExtensions
{
public static DataTable ToDataTable<T>(this IList<T> data)
{
PropertyDescriptorCollection properties =
TypeDescriptor.GetProperties(typeof(T));
DataTable table = new DataTable();
foreach (PropertyDescriptor prop in properties)
table.Columns.Add(prop.Name, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType);
foreach (T item in data)
{
DataRow row = table.NewRow();
foreach (PropertyDescriptor prop in properties)
row[prop.Name] = prop.GetValue(item) ?? DBNull.Value;
table.Rows.Add(row);
}
return table;
}
}
public void LoadBarData()
{
ProductData = _dataFactory.Create(typeof(BarData));
}
And in either case, INPC
will update your UI.
Update based on your addition to the post
To implement this using a method that returns a list like your example; SomethingThatReturnsClassAList()
Just update factory like so. (Notice how easy it is to change your code to meet new requirements when you use the Factory Pattern
? =)
public class DataFactory : IDataFactory
{
public DataTable Create(Type t)
{
if (t == typeof(FooData))
{
return SomethingThatReturnsClassAList().ToDataTable();
}
if (t == typeof(BarData))
{
return SomethingThatReturnsClassBList().ToDataTable();
}
return new List<dynamic>().ToDataTable();
}
}