Introduction
DataPropertyGrid
, as its name indicates, is an inherit component of the PropertyGrid
control that allows to visualize and to edit data. Specifically, it's designed to show the content of a DataRow
with the possibility of showing its relations with associate tables and setting the way to visualize its properties.
The initial objective to create this control was to allow to show tables of database whose columns can change in time, for example, a table of application parameters, which has a single row with different columns for each one from the parameters according to its datatype. In this type of tables, to use a DataGrid
isn't an elegant solution; the other possibility is to create a form with TexBoxs, Combos, etc. and to modify it whenever a parameter is added, modified or eliminated in the database (in my experience, happens very frequently throughout the life utility of the applications).
Later, I decided to add functionality to show, in a customized way, the data with the purpose of obtaining an easy alternative to show and edit any row of the database, with no need to create a specific form for it. This custom way of visualization contemplates the name of the properties, the category to which they belong, its description, order [Ordering Items in the Property Grid]and readonly option.
Background
The PropertyGrid
control is an excellent tool to show, edit and validate objects properties, we only need to create an object and to associate it to a PropertyGrid
by its property SelectedObject
. If we add ComponentModel
namespace attributes to the object properties, we can get more control in the way that it is possible to visualize the object in this control.
The PropertyGrid
control, by nature is created to show objects, non Datarow
s, therefore, the form to show a Datarow
in the PropertyGrid
in the conventional way, it is necessary to create a class that allows to edit its fields by the object properties. For example:
public class myClass
{
public myClass(System.Data.DataRow row)
{
myRow = row;
if(myRow["Name"] != System.DBNull.Value)
{
this.Name = (string)myRow["Name"];
}
if(myRow["BirthDay"] != System.DBNull.Value)
{
this.BirthDay = (DateTime)myRow["BirthDay"];
}
}
private System.Data.DataRow myRow = null;
private string m_name;
public string Name
{
get { return m_name; }
set
{
m_name = value;
myRow["Name"] = value;
}
}
private DateTime m_birthDay;
public DateTime BirthDay
{
get { return m_birthDay; }
set
{
m_birthDay = value;
myRow["BirthDay"] = value;
}
}
}
............
private void Form1_Load(object sender, System.EventArgs e)
{
myClass obj = new myClass(dataRow);
this.propertyGrid1.SelectedObject = obj;
}
Nevertheless, if we wish to create a control that does this in a standard way, for any DataRow
, we need to make this process of dynamic...
Understanding the component
DataPropertyGrid
dynamically creates a class from the columns of the DataRow
that passed as parameter in the MuestraDatos
(System.Data.DataRow row
) method. The generated class code is compiled in memory, and from it an instance or object is created and is shown by the SelectedObject
property of the control.
In order to generate the class code that will represent the DataRow
, I have preferred to use template code (string) instead of CodeDom
, since it makes me easier and controlled to work with.
string codigo = @"
using System;
using System.Data;
using System.ComponentModel;
using System.Reflection;
namespace miNamespace {
[TypeConverter(typeof(PropertySorter))]
public class miClase {
private System.Data.DataRow m_Row = null;
";
foreach(System.Data.DataTable tabla in this.DS.Tables)
{
if(tabla != this.DS.Tables[0])
{
codigo +=
"public System.Collections.Hashtable Col"+ tabla.Columns[0].ColumnName +
" = new System.Collections.Hashtable();\n";
codigo +=
"public System.Collections.Hashtable _Col"+ tabla.Columns[0].ColumnName +
" = new System.Collections.Hashtable();\n";
}
}
codigo += @"public miClase(System.Data.DataRow row)
{
m_Row = row;
";
I make iterations with the columns and tables associated of the DataRow
in order to generate the fields, properties, attributes and enumerations. When the properties' values in the component are changed, immediately the respective fields in the DataRow
are also changed.
foreach(System.Data.DataColumn col in this.Tabla.Columns)
{
if(this.ColCampos.ContainsKey(col.ColumnName))
{
if(ColRelaciones.ContainsKey(col.ColumnName))
{
string campo =
col.ColumnName + "_Enum m_"
+ ColNombreCampos[col.ColumnName].ToString();
string propiedad = col.ColumnName + "_Enum " +
ColNombreCampos[col.ColumnName].ToString();
codigo += "private " + campo + ";\n";
codigo += "[Category(\"" + ColCategorias[col.ColumnName].ToString()
+ "\"),Description(\"" + ColDescripciones[col.ColumnName].ToString()
+ "\"),PropertyOrder(" + ColOrden[col.ColumnName].ToString()
+ "),ReadOnly(" + ColReadOnly[col.ColumnName].ToString().ToLower()
+ ")]\n";
codigo += "public " + propiedad + @"
{
get { return m_" + ColNombreCampos[col.ColumnName].ToString() + @"; }
set {
m_" + ColNombreCampos[col.ColumnName].ToString() + @" = value;";
codigo += "m_Row[\"" + col.ColumnName + "\"] = _Col" +
col.ColumnName + "[(int)value];";
codigo += @"
}
}
";
}
else
{
string campo = col.DataType.UnderlyingSystemType.ToString() + " m_" +
ColNombreCampos[col.ColumnName].ToString();
string propiedad = col.DataType.UnderlyingSystemType.ToString() + " " +
ColNombreCampos[col.ColumnName].ToString();
codigo += "private " + campo + ";\n";
codigo += "[Category(\"" + ColCategorias[col.ColumnName].ToString()
+ "\"),Description(\"" + ColDescripciones[col.ColumnName].ToString()
+ "\"),PropertyOrder(" + ColOrden[col.ColumnName].ToString()
+ "),ReadOnly(" + ColReadOnly[col.ColumnName].ToString().ToLower() +
")]\n";
codigo += @"
public " + propiedad + @"
{
get { return m_" + ColNombreCampos[col.ColumnName].ToString() + @"; }
set {
m_" +ColNombreCampos[col.ColumnName].ToString() + @" = value;";
codigo += "m_Row[\"" + col.ColumnName + "\"] = value;";
codigo += @"
}
}
";
}
}
}
foreach(System.Data.DataTable tabla in this.DS.Tables)
{
if(tabla != this.DS.Tables[0])
{
codigo += @"
public enum "; codigo += tabla.Columns[0].ColumnName + @"_Enum
{";
int i = 0;
foreach(System.Data.DataRow fila in tabla.Rows)
{
codigo += ObtieneEnumeracion(fila[1].ToString()) + " = " +
i + ",\n";
i ++;
}
codigo += "}";
}
}
codigo +="}}";
return codigo;
If the DataRow
has related tables, an enumeration is generated that allows to show the data in combobox way with understandable values for the end user, instead of key values used in database normalization. For example, the Customer_ID
field of the DataRow
would generate a Customer
property of type enumeration with the names of the different clients so that the user chooses names instead of the ID codes, in the case that there is a Dataset
with a table of clients with columns [Customer_Id
] representing the ID and [Customer
] the name of the client. This allows, in addition, to validate the input data.
If the DataRow
has an associated DataSet
, an enumeration by each one of the additional tables will be created that it has and whose columns are the code of a column of the DataRow
and another column with the description for this code. The name of the property no longer will be the name of the column of the DataRow
, but the name of the column description of the associate table.
Using the component
You can add DataPropertyGrid
to the ToolBox, so that it can be physically added to the forms or other visual components at design time and to edit its properties easily. Also you can add it by code in the way that you want.
In order to show a DataRow
, you only need to invoke MuestraDatos
method and to pass it as parameter in anyone of its two overloads:
this.dataPropertyGrid1.MuestraDatos(Row);
This is simpler one and it allows to show an unknown DataRow
. DataPropertyGrid
will show all columns and values of the DataRow
.
this.dataPropertyGrid1.MuestraDatos
(
Row,
"OrderID,Active,CustomerID,EmployeeID,OrderDate," +
"ShippedDate,Freight,ShipCountry,ShipCity",
"Código,Activa,Cliente,Empleado,Fecha,Entrega," +
"Precio,Pais,Ciudad",
"General,General,General,General,Tiempo,Tiempo," +
"General,Localizacion,Localizacion",
"el Código de la orden,Define si la orden está activa o no," +
"Cliente de la orden,Empleado que registró la orden,
Fecha de la orden,Fecha de entrega,Precio Total,País de" +
" recepcion,Ciudad de recepción",
"1,2,3,4,5,6,7,8,9",
"1,0,0,0,0,0,0,0,0"
);
This is the most complete one, which allows to show a DataRow
that we know ahead of time so that we will be able to set their properties.
Points of Interest
More than dynamic code generation and compilation, the key to do this work was DataType.UnderlyingSystemType
property of DataColum
in DataTable
. This property let us to obtain .NET System type of an database provider DataType
.
I had preferred to use a StringConverter
class instead of enumerations to show the related tables, because data could be shown with no need to eliminate nonalphanumeric characters and to replace spaces by underscores. This would improve the appearance and allows to show numeric and and datetime data, among others. Nevertheless, no matter how hard I tried, I did not obtain the object compiled in memory that do that with TypeConverter
. If I copy the same code string to a .cs file and compile it, it works fine. Why? Bug? I don't know. My greater interest was the object compiled in memory by reasons for performance, so I stopped to use TypeConverter
. If somebody can do that, please tell how.
History
Ing. Luis Alberto Ruiz Arauz
Software Engineering / Industrial Engineering