Click here to Skip to main content
15,905,874 members
Articles / Programming Languages / C#
Article

DataPropertyGrid

Rate me:
Please Sign up or sign in to vote.
3.82/5 (6 votes)
1 May 20055 min read 64K   1.3K   42   3
A way to show and edit data (DataRow) in PropertyGrid control.

Image 1

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 Datarows, 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:

C#
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.

C#
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.

C#
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.

Image 2

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:

C#
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.

C#
this.dataPropertyGrid1.MuestraDatos
        (
        Row, //DataRow
        "OrderID,Active,CustomerID,EmployeeID,OrderDate," + 
         "ShippedDate,Freight,ShipCountry,ShipCity", //Campos
        "Código,Activa,Cliente,Empleado,Fecha,Entrega," +
         "Precio,Pais,Ciudad", //Nombre de campos
        "General,General,General,General,Tiempo,Tiempo," + 
         "General,Localizacion,Localizacion", //Categorías
        "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", //Descripciones
        "1,2,3,4,5,6,7,8,9", //Orden
        "1,0,0,0,0,0,0,0,0" //ReadOnly 1-true. 0-false
        );

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

  • 05/01/2005

    V1.0.0.1(initial version)

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
Costa Rica Costa Rica
Ing. Luis Alberto Ruiz Arauz
Software Engineering / Industrial Engineering

Comments and Discussions

 
GeneralWonderful... Pin
erceng24-Sep-08 3:54
erceng24-Sep-08 3:54 
GeneralAn easier solution Pin
Rob Lans2-May-05 1:44
Rob Lans2-May-05 1:44 
GeneralRe: An easier solution Pin
machocr2-May-05 7:22
machocr2-May-05 7:22 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.