Background
On my current assignment at an aluminum company, I was working on a .NET/SQL Windows Forms application. I was asked to dynamically add columns to a DataGridView like control (ComponentOne’s C1FlexGrid control).
Before assigned this task, I was populating the grid with a generic list of objects: List<PricePageGrid>.
List<PricePageGrid> pricePageGridList = new List<PricePageGrid>();
public class PricePageGrid
{
public string Alloy { get; set; }
public string Shape { get; set; }
public string Size { get; set; }
public decimal BasePrice { get; set; }
public string Weight { get; set; }
public decimal PriceAdder { get; set; }
public decimal PercentageAdjustment { get; set; }
public decimal QuantityDiscountPrice { get; set; }
}
// Set the DataSource property of the grid to the pricePageGridList
PricePageGrid.DataSource = pricePageGridList;
I was then told that my PricePageGrid class now needed to accommodate a variable number of properties, which needed to be displayed in the grid. More specifically, the class needed to accommodate a variable number of Temper Adders from a TemperAdderList, and for each item in the list, add a new property along with its value to the PricePageGrid object. Below is some pseudo code that may help clarify what needed to be done.
public List<TemperAdder> TemperAdderList { get; set;}
public class PricePageGrid
{
public string Alloy { get; set; }
public string Shape { get; set; }
public string Size { get; set; }
public decimal BasePrice { get; set; }
public string Weight { get; set; }
public decimal PriceAdder { get; set; }
public decimal PercentageAdjustment { get; set; }
public decimal QuantityDiscountPrice { get; set; }
// For each instance of the class, add a Temper Adder property along with its value for each TemperAdder item in the TemperAdderList.
public object_type TemperAdder_1 {get; set; } // Temper Adders 1 to N
...
public object_type TemperAdder_N {get; set; }
}
Solution
After doing some research, I found the ExpandoObject class (System.Dynamic.ExpandoObject) in the .NET 4.0 Framework. It represents an object that allows you to dynamically add and remove members at run time. It was what I needed. However, I quickly had a problem. The example ExpandoObject code that I was copying into my application would not compile. The fix: I needed to add a reference to the Microsoft.CSharp.dll to my project.
To make the ExpandoObject code readable, I found some code on the web from Jonathon Sullinger that neatly wraps some of the functionality of the ExpandoObject class. Here is the code that I ended up with.
public class DynamicPricePage
{
public dynamic Instance = new ExpandoObject();
public void AddProperty(string name, object value)
{
((IDictionary<string, object>)this.Instance).Add(name, value);
}
public dynamic GetProperty(string name)
{
if (((IDictionary<string, object>)this.Instance).ContainsKey(name))
return ((IDictionary<string, object>)this.Instance)[name];
else
return null;
}
public void AddMethod(Action methodBody, string methodName)
{
this.AddProperty(methodName, methodBody);
}
}
Using the AddProperty() method in the DynamicPricePage class, I could easily add properties to the DynamicPricePage instance at runtime. The next section will show you how.
Creating the Dynamic Object and Populating the List
1) The code to create the dynamic object and set the “static” properties.
Note: When assigning a value to a property, the property type becomes the type of the value. So for instance, the type of the Weight property will be a string, because it was assigned a string value “0.0”.
DynamicPricePage ppg = new DynamicPricePage();
ppg.Instance.Alloy = AlloyDescription;
ppg.Instance.Shape = ShapeDescription;
ppg.Instance.Size = DimensionDescription
ppg.Instance.BasePrice = BasePricePerUOM;
ppg.Instance.Weight = “0.0”;
ppg.Instance.PriceAdder = 0.0m;
ppg.Instance.PercentageAdjustment = 0.0m;
ppg.Instance.QuantityDiscountPrice = ppg.Instance.BasePrice + PriceAdder;
2) The code to dynamically add the variable number of properties along with their values from the TempeAdderList to the DynamicPricePage object.
foreach (TemperAdder ta in TemperAdderList)
{
ppg.AddProperty(ta.Description, ta.PriceAdder) // property name and value
}
3) Add the dynamic object to a list of DyamicPricePage objects.
List<DynamicPricePage> pricePageGridList = new List<DynamicPricePage>();
pricePageGridList.Add(ppg);
Converting the List of Dynamic Objects to a Table
Now that I had the data that I wanted in a pricePageGridList, I assigned the pricePageGridList to the data source property of the grid: PricePageGrid.DataSource = pricePageGridList. When I ran the application, no data appeared in the grid. Why? In short, the grid did not like the DynamicPricePage type of the pricePageGridList.
What next? The pricePageGridList was not of much use if I could not display its contents in the grid. Could I convert the pricePageGridList to a table? The answer, YES!
It was a three step process to convert the pricePageGridList to a table. Here is the link where I found the code to convert the list of dynamic objects to a table.
1) Cast a dynamic object to dictionary so we get the properties from it.
var dynamicPricePageProperties = pricePageGridList[0].Instance as IDictionary<string, object>;
2) Create a DataTable and add columns to the table using the dynamicPricePageProperties acquired in step one. Also, set the data type when adding a column. In a table, if a column's data type is a string, you cannot apply a Format. For example, pricePageGrid.Cols["BasePrice"].Format = "####0.00" will not work.
var table = new System.Data.DataTable();
foreach (var column in dynamicPricePageProperties)
{
if (Utility.IsDecimal(column.Value.ToString())) // Utility.IsDecimal tries to parse the string: decimal.Parse(input);
table.Columns.Add(column.Key, typeof(decimal));
else
table.Columns.Add(column.Key, typeof(string));
}
3) Populate the table created in step two using the pricePageGridList
foreach (DynamicPricePage dynamicPricePage in pricePageGridList)
{
DataRow row = table.NewRow();
System.Collections.Generic.IDictionary<string, object> dictionaryCollection = dynamicPricePage.Instance as IDictionary<string, object>;
foreach (var key in dictionaryCollection.Keys)
{
row[key] = dictionaryCollection[key]; // Uses the key to retrieve the Value of the item in the dictionaryCollection
}
table.Rows.Add(row);
}
Once I converted the list of dynamic objects into a table, I assigned the table to the DataSource property of the grid (PricePageGrid.DataSource = table;), and the data now appeared in the grid when I ran the application.