Hello everyone. This article will show you how to dynamically create TemplateFields for controls such as GridViews, Repeaters, etc. It will allow you to Bind a datasource to your control with our knowing the schema definition of the datasource (except for the Datasource's name). There are many versions of this article out on the web. The problem is 98% of them are in C++ or C#. This (of course) would frustrate any visual J# or VB programmer not fluent in C... so I decided to at least throw out a version of this same article in VB...I'll do my best :) Also, I'm a "visual developer", very anal about the way my code looks, and so I tried my very best to indent my code properly, but alas, the text editor resets my indents for me. I was somewhat frustrating. I apologize for the arrangement of my code. (The best way to read it is to copy the snippets and throw them into a Visual Studio Code behind page; That way VS will peform the indentation for you ;). I have also uploaded the source for this project. Note however that the source only contains code for the class and initial binding of the GridView. I have not implemented handlers to handle the editing, Updating and deleting portion of the GridView yet; I will in the near future) To fully understand the methods used in this article, I recommend strongly reviewing the concepts of inheritance, interfaces, Databinding and Implementation. They are key ideologies that are utilized here, and understanding them will make reading this article a synch. I have tried my possible best to make my code as Modular and Compact as possible, as well as my explanations. This doesn't mean that key concepts are not explained properly here. I will definately take time to explain key concepts and abstract conventions if necessary. Also, I'm not used to writing articles...(this is my 1st)...so constructive criticism is highly encouraged. Now let us Begin....first we will analyze the problem. The issue is that we want to display data (from a database for instance) in some sort of data displaying control (e.g the dreaded GridView). There are numerous wways of accomplishing this. One of the more common methods is to declare a Datasource object (using the datasourceobject control...note however that I will not go further into this area...for more information, please review Datasourceobjects) and use that to execute your binding. Another more common method is to, through the ASP markup, create template columns for each of the binding columns of your datasource and through code, call your datasource and bind it to your GridView (If the later concept is familiar to you then read on, if not, I strongly suggest putting reading this article on hold and review the GridView Control and TemplateFields) i.e. described below... <ASP:TEMPLATEFIELD HeaderText="Employee ID"> <EDITITEMTEMPLATE> <ASP:TEXTBOX ID="tbxEmployeeID" CssClass="InputFields" runat="server" Width="60px" Text=''>ASP:TEXTBOX> <EDITITEMTEMPLATE> <ITEMTEMPLATE> <ASP:LABEL ID="lblEmployeeIDPrompt" runat="server" Text=''>ASP:LABEL> <ITEMTEMPLATE> </ASP:TEMPLATEFIELD> The problem with using this method is that we must already know, or at least have an Idea of how many fields are in the datasource (maybe a table) and the names of each of them. This leads to the idea of creating a class that would take care of this for us. All we would need to worry about is pulling the Datasource, databinding it andthen worrying about the Edit, Update & Delete Functionality.So, what we need to do is make a class that would create templatefields for each ofthe columns that exist in our datasource. I have an example of this posted below. Public Class TemplateClass End Class This is the declaration of the class. Since we want this Class to work with templates, we need to get the features and "behaviors" of a Template/TemplateField class, i.e we need to implement the methods, properties and events of the Template class. Even more simpler, we need to basically Implement the Template class. So, we would definately have to Implement the ITemplate interface by inputing the next line of code shown below.. Public Class TemplateClass Implements System.Web.UI.ITemplate End Class If you are using Visual Studio 2008, you will notice that as soon as you type the Implements statement and press enter, Visual Studio will create a Method definition called "InstantiateIn". The purpose of an Interface (denoted with an "I" by convention) is to provide all the necessary tools of a class object you would need in order to implement the class object. I know this sounds funny, but think of it as a list of things you need to build a house. The list only shows you what you need, it doesn't build the house for you. You would have to build the house yourself. If you do not see the InstantiateIn method however, just put the definition below the Implementation statement as shown below... Public Class TemplateClass Implements System.Web.UI.ITemplate Public Sub InstantiateIn(ByVal container As System.Web.UI.Control) Implements System.Web.UI.ITemplate.InstantiateIn End Sub End Class For ease or understanding, I will divide the Class into 3 regions. Region (1): Properties. When we create an instance of this class object, we would need to ascertain three things. The first being what type of template it is (i.e EditItemTemplate, HeaderTemplate, etc). Second, we need to get the name of the field that would contain the data needing to be bound to the template. Third, we need to determine what form this data would take (would it be a button, or a checkbox , etc). The properties code region is shown below... #Region "Properties" Private lstTemplateType As ListItemType Private strFieldName As String Private strInfoType As String #End Region The first property is declared as a ListItemType object. This is basically an enumerated type which would keep record of what type of template we have. the second is the FieldName (column name) as a string and third is the form the data would take. Region (2): Constructor I am going on the assumption that if you are reading this article, you know what a constructor is, if not, please read the concept of constructors for classes from any tutorial or textbook. In this case, we are going to make a parameterized constructor. (Note however that you do not need to use a parameterized constructor, this is just a type safe method retrieving the properties we need). The constructor would take 3 arguments.... #Region "Constructor" Sub New(ByVal Template As ListItemType, ByVal FieldName As String, ByVal FieldValueType As String) ' ' Sets the Class Properties ' strInfoType = FieldValueType strFieldName = FieldName ' ' ItemType Value ' Select Case Template Case ListItemType.AlternatingItem lstTemplateType = ListItemType.AlternatingItem Case ListItemType.EditItem lstTemplateType = ListItemType.EditItem Case ListItemType.Footer lstTemplateType = ListItemType.Footer Case ListItemType.Header lstTemplateType = ListItemType.Header Case ListItemType.Item lstTemplateType = ListItemType.Item Case ListItemType.Pager lstTemplateType = ListItemType.Pager Case ListItemType.SelectedItem lstTemplateType = ListItemType.SelectedItem Case ListItemType.Separator lstTemplateType = ListItemType.Separator Case Else lstTemplateType = Nothing End Select End Sub #End Region These areguments would represent the 3 properties that were declared in the properties region section. Note that we are assigning the properties of the class values (initializing them). We use a select case to determine which ListItemtype we are creating. The third regionis the Engine of the class, the Methdods region. I will break this region further into two parts (according to the number of methods it contains) The first method in this region is the Instantiate( ) Method. This metod is responsible for creating the necessary objects needed for the class. It basically creates the Buttons, Labels, textBoxes, etc for the respective templateFields that we need them for... Public Sub InstantiateIn(ByVal container As System.Web.UI.Control) Implements System.Web.UI.ITemplate.InstantiateIn Select Case lstTemplateType Case ListItemType.EditItem Select Case strInfoType.Trim.ToUpper Case "COMMAND" Dim ibtnCancel As New ImageButton ibtnCancel.ID = "ibtnCancel" ibtnCancel.ImageUrl = "Common/Images/16_Delete.gif" ibtnCancel.CommandName = "CancelEditRecord" ibtnCancel.ToolTip = "Cancel" container.Controls.Add(ibtnCancel) Dim ibtnUpdate As New ImageButton ibtnUpdate.ID = "ibtnUpdate" ibtnUpdate.ImageUrl = "Common/Images/objects_007.gif" ibtnUpdate.CommandName = "UpdateRecord" ibtnUpdate.ToolTip = "Update" container.Controls.Add(ibtnUpdate) Case "TEXT" Dim tbxFieldValue As New TextBox tbxFieldValue.ID = strFieldName tbxFieldValue.Text = String.Empty AddHandler tbxFieldValue.DataBinding, New EventHandler(AddressOf OnDataBinding) container.Controls.Add(tbxFieldValue) Case Else End Select Case ListItemType.Item Select Case strInfoType.Trim.ToUpper Case "COMMAND" Dim ibtnEdit As New ImageButton ibtnEdit.ID = "ibtnEdit" ibtnEdit.ImageUrl = "Common/Images/Pencil.gif" ibtnEdit.CommandName = "Edit" ibtnEdit.ToolTip = "Edit" container.Controls.Add(ibtnEdit) Dim ibtnDelete As New ImageButton ibtnDelete.ID = "ibtnDelete" ibtnDelete.ImageUrl = "Common/Images/ComputerFile106.gif" ibtnDelete.CommandName = "DeleteRecord" ibtnDelete.ToolTip = "Delete" container.Controls.Add(ibtnDelete) Case "TEXT" Dim lblFieldValue As New Label lblFieldValue.ID = strFieldName lblFieldValue.Text = String.Empty AddHandler lblFieldValue.DataBinding, New EventHandler(AddressOf OnDataBinding) container.Controls.Add(lblFieldValue) Case Else End Select Case ListItemType.Header Dim ltrlHeader As New Literal ltrlHeader.Text = "<B>" + strFieldName + "</B>" container.Controls.Add(ltrlHeader) Case Else End Select End Sub Let me explain the above code. Remember that we are implementing the ITemplate interface. Instantiate in is a metod that belongs to that interface, and so we MUST implement it (hence the "Implements System.Web....." statement in the Method definition heading. The Method takes an argument called "container" of a System.Web.UI.Control type. This would represent any data control which we would bind the Data to. In the Implementation, we start first by identifying what type of ListItemTemplate we are creating. This is implemented using a select case decision statement. If ListItemType chosen is an EditItemType/Template, then we have to further determine if we are making buttons, or textboxes (remember: if you have ever used the Template feature in a GridView, you'll notice that once you click on the Edit button, the data in the selected row gets embedded in TextBoxes). We use the Infotype string to determine this. Selecting "COMMAND" indicates the creation of a button (in this code, you can specify what ever you want) and creates both a cancel button & an Update button. You would then have to specify the properties of the dynamically created button. The ID, The ImageUrl & ToolTip (in the case of ImageButtons) and the CommandName. We then add the controls to the container object's control collection using the statements container.Controls.Add(ibtnCancel) and container.Controls.Add(ibtnUpdate) Now if the InfoType os a "TEXT" then we would create a Textbox and assign it some properties. One of these properties is a Databinding EventHandler called "OnDataBinding". We accomplish this with the "AddHandler" syntax. (In order to use the AddHandler sysntax, a method defined as an Event Handler must exist already i.e. you must have created the Hanlder which you are assigning already). Note also that the ID of the TextBox created is set to the name of the field. This will come in handy later on This EventHandler (OnDataBinding) is responsible for binding the data to our GridView. For the ItemTemplate type, we do the same thing. Giving it Edit and delete image buttons, but instead of a textbox, we declare a label and also assign it this Handler. The Header Template doesn't need much just a Literal object for holding the text, formating it with HTML syntax (like Bold in this case or underline) We add all the created controls to the GridView container and thats it for InstantiateIn. The second method is the DataBinding EventHandler called OnDataBining( ). Here, we are actually performing the Databinding of the Templates created. the syntax is shown below... Public Sub OnDataBinding(ByVal sender As Object, ByVal e As System.EventArgs) ' ' Databinding Event responsible for binding the values from the datasource to the Data Container of the ASP Control ' ' Get the Control to which we want to databind the Values ' Dim ctrlDataItemObject As Control = CType(sender, Control) ' ' We can get it's Container by referencing the naming container property of the control ' Dim objDataItemContainer As IDataItemContainer = CType(ctrlDataItemObject.NamingContainer, IDataItemContainer) ' ' Get the Item that we are binding (Probably data from a database ' Dim objBoundItem As Object = DataBinder.Eval(objDataItemContainer.DataItem, strFieldName.Trim) ' ' Since we can Template Command Fields, as well as Item Fields, we need to ascertain which we binding currently ' Select Case lstTemplateType Case ListItemType.EditItem Dim tbxTextData As TextBox = CType(sender, TextBox) tbxTextData.Text = objBoundItem.ToString Case ListItemType.Item Dim lblFieldValue As Label = CType(sender, Label) lblFieldValue.Text = objBoundItem.ToString Case Else End Select End Sub So, we first get te control object we are binding to. The sender Object passed into an Event handler usually contains this information (or more accurately 'IS' this information) so we can get the control by using the Ctype syntax . We also need to capture this controls DataItem property. (for information about the DataItem property, please visit the msdn.com) we achieve this using the NamingContainer property of the control object. now the next statement extracts the databound value using the all too familiar DataBinder.Eval() statement. Since the only time we need to databind any template is if it is either an EditItem or an Item template, we now determine which of these templates we are binding using our selectcase statement with the ListItemType object. If it's a nEditItemTemplate, we must be binding textbox templated to an EditItemType, we have to directly reference that textbox set it's text property to the Data we are retrievinf from our datasource. same for the Item template, only we are bindng a Label.and with that, the OnDataBinding Method is complete. So b4 we move on, the full view of the TemplateClass looks something like this... Public Class TemplateClass Implements System.Web.UI.ITemplate ' NOTES =============================================================================== ' ' CREATOR: Rammone4 ' DATE: 04/09/2008 ' DESCRIPTION: This Class Dynamically Templates any Generic Container Control with the specified TemplateFields ' ' ======================================================================================= #Region "Properties" Private lstTemplateType As ListItemType Private strFieldName As String Private strInfoType As String #End Region #Region "Methods" Public Sub InstantiateIn(ByVal container As System.Web.UI.Control) Implements System.Web.UI.ITemplate.InstantiateIn Select Case lstTemplateType Case ListItemType.EditItem Select Case strInfoType.Trim.ToUpper Case "COMMAND" Dim ibtnCancel As New ImageButton ibtnCancel.ID = "ibtnCancel" ibtnCancel.ImageUrl = "Common/Images/16_Delete.gif" ibtnCancel.CommandName = "CancelEditRecord" ibtnCancel.ToolTip = "Cancel" container.Controls.Add(ibtnCancel) Dim ibtnUpdate As New ImageButton ibtnUpdate.ID = "ibtnUpdate" ibtnUpdate.ImageUrl = "Common/Images/objects_007.gif" ibtnUpdate.CommandName = "UpdateRecord" ibtnUpdate.ToolTip = "Update" container.Controls.Add(ibtnUpdate) Case "TEXT" Dim tbxFieldValue As New TextBox tbxFieldValue.ID = strFieldName tbxFieldValue.Text = String.Empty AddHandler tbxFieldValue.DataBinding, New EventHandler(AddressOf OnDataBinding) container.Controls.Add(tbxFieldValue) Case Else End Select Case ListItemType.Item Select Case strInfoType.Trim.ToUpper Case "COMMAND" Dim ibtnEdit As New ImageButton ibtnEdit.ID = "ibtnEdit" ibtnEdit.ImageUrl = "Common/Images/Pencil.gif" ibtnEdit.CommandName = "Edit" ibtnEdit.ToolTip = "Edit" container.Controls.Add(ibtnEdit) Dim ibtnDelete As New ImageButton ibtnDelete.ID = "ibtnDelete" ibtnDelete.ImageUrl = "Common/Images/ComputerFile106.gif" ibtnDelete.CommandName = "DeleteRecord" ibtnDelete.ToolTip = "Delete" container.Controls.Add(ibtnDelete) Case "TEXT" Dim lblFieldValue As New Label lblFieldValue.ID = strFieldName lblFieldValue.Text = String.Empty AddHandler lblFieldValue.DataBinding, New EventHandler(AddressOf OnDataBinding) container.Controls.Add(lblFieldValue) Case Else End Select Case ListItemType.Header Dim ltrlHeader As New Literal ltrlHeader.Text = "<B>" + strFieldName + "</B>" container.Controls.Add(ltrlHeader) Case Else End Select End Sub Public Sub OnDataBinding(ByVal sender As Object, ByVal e As System.EventArgs) ' ' Databinding Event responsible for binding the values from the datasource to the Data Container of the ASP Control ' ' Get the Control to which we want to databind the Values ' Dim ctrlDataItemObject As Control = CType(sender, Control) ' ' We can get it's Container by referencing the naming container property of the control ' Dim objDataItemContainer As IDataItemContainer = CType(ctrlDataItemObject.NamingContainer, IDataItemContainer) ' ' Get the Item that we are binding (Probably data from a database ' Dim objBoundItem As Object = DataBinder.Eval(objDataItemContainer.DataItem, strFieldName.Trim) ' ' Since we can Template Command Fields, as well as Item Fields, we need to ascertain which we binding currently ' Select Case lstTemplateType Case ListItemType.EditItem Dim tbxTextData As TextBox = CType(sender, TextBox) tbxTextData.Text = objBoundItem.ToString Case ListItemType.Item Dim lblFieldValue As Label = CType(sender, Label) lblFieldValue.Text = objBoundItem.ToString Case Else End Select End Sub #End Region #Region "Constructor" Sub New(ByVal Template As ListItemType, ByVal FieldName As String, ByVal FieldValueType As String) ' ' Sets the Class Properties ' strInfoType = FieldValueType strFieldName = FieldName ' ' ItemType Value ' Select Case Template Case ListItemType.AlternatingItem lstTemplateType = ListItemType.AlternatingItem Case ListItemType.EditItem lstTemplateType = ListItemType.EditItem Case ListItemType.Footer lstTemplateType = ListItemType.Footer Case ListItemType.Header lstTemplateType = ListItemType.Header Case ListItemType.Item lstTemplateType = ListItemType.Item Case ListItemType.Pager lstTemplateType = ListItemType.Pager Case ListItemType.SelectedItem lstTemplateType = ListItemType.SelectedItem Case ListItemType.Separator lstTemplateType = ListItemType.Separator Case Else lstTemplateType = Nothing End Select End Sub #End Region End Class Using the Class: Now we are going to use this class. First create your GridView in your aspx page as shown below. DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> <title>Untitled Pagetitle> head> <body> <form id="form1" runat="server"> <ASP:GRIDVIEW ID=GridView1 runat="server" BackColor="White" Width="100%" AutoGenerateColumns ="False" BorderColor="#DEDFDE" BorderWidth="1px" CellPadding="4" ForeColor="Black" GridLines="Vertical" BorderStyle="None"> <FOOTERSTYLE BackColor="#CCCC99" /> <ROWSTYLE BackColor="#F7F7DE" /> <PAGERSTYLE BackColor="PaleGoldenrod" ForeColor="DarkSlateBlue" HorizontalAlign="Center" /> <SELECTEDROWSTYLE BackColor="#CE5D5A" ForeColor="White" Font-Bold="True" /> <HEADERSTYLE BackColor="#6B696B" Font-Bold="True" ForeColor="White" /> <ALTERNATINGROWSTYLE BackColor="White" /> ASP:GRIDVIEW> form> body> html> Next, in your codebehind page, you'll have to get your datasource. (In this example, I'm just using a sqldataconnection...in practice you should never have your connectionstring in your .vb file, a good way of getting external data would be to use a DataAccessLayer) The Method looks something like this... Private Function GetDataSource() As DataTable Dim sqlConnectionObject As SqlConnection = New SqlConnection("Data source=**;database=**;User id=**;password=**") Dim tblData As New DataTable Dim dstDataset As New DataSet Dim dbCommand As SqlDataAdapter = New SqlDataAdapter("SELECT * FROM ***", sqlConnectionObject) dbCommand.Fill(dstDataset) Return dstDataset.Tables(0).Copy() End Function Now the usage of the class is very easy. I have another method which takes care of this. It is named Create GridView Template. It looks something like this Private Sub CreateGridViewTemplate() Dim tblDataTable As DataTable = GetDataSource() Dim btnTemplateField As New TemplateField Dim objDataColumn As DataColumn With btnTemplateField .ItemTemplate = New TemplateClass(ListItemType.Item, "...", "COMMAND") .HeaderTemplate = New TemplateClass(ListItemType.Header, "...", "") .EditItemTemplate = New TemplateClass(ListItemType.EditItem, "", "COMMAND") End With ' ' Add the Templated Command/Button Column to the GridView ' GridView1.Columns.Add(btnTemplateField) For Each objDataColumn In tblDataTable.Columns Dim objNewTemplateField As New TemplateField ' ' Now based on the number of Columns in our Datatable from our database, we are going to make Templated Columns. ' i.e If there are 9 columns in our DataTable, we'll make 9 templated Columns in our GridView (using a For Each Loop) ' objNewTemplateField.HeaderTemplate = New TemplateClass(ListItemType.Header, objDataColumn.ColumnName, "") objNewTemplateField.ItemTemplate = New TemplateClass(ListItemType.Item, objDataColumn.ColumnName, "TEXT") objNewTemplateField.EditItemTemplate = New TemplateClass(ListItemType.EditItem, objDataColumn.ColumnName, "TEXT") GridView1.Columns.Add(objNewTemplateField) Next ' ' Bind the source ' GridView1.DataSource = tblDataTable GridView1.DataBind() End Sub Now we have completed the Binding Process. Your code behind file should look something like this (as of the moment) Imports System.Data Imports System.Data.SqlClient Partial Public Class Index Inherits System.Web.UI.Page #Region "Methods" Private Function GetDataSource() As DataTable Dim sqlConnectionObject As SqlConnection = New SqlConnection("Data source=**;database=**;User id=**;password=**") Dim tblData As New DataTable Dim dstDataset As New DataSet Dim dbCommand As SqlDataAdapter = New SqlDataAdapter("SELECT * FROM ****", sqlConnectionObject) dbCommand.Fill(dstDataset) Return dstDataset.Tables(0).Copy() End Function Private Sub CreateGridViewTemplate() Dim tblDataTable As DataTable = GetDataSource() Dim btnTemplateField As New TemplateField Dim objDataColumn As DataColumn With btnTemplateField .ItemTemplate = New TemplateClass(ListItemType.Item, "...", "COMMAND") .HeaderTemplate = New TemplateClass(ListItemType.Header, "...", "") .EditItemTemplate = New TemplateClass(ListItemType.EditItem, "", "COMMAND") End With ' ' Add the Templated Command/Button Column to the GridView ' GridView1.Columns.Add(btnTemplateField) For Each objDataColumn In tblDataTable.Columns Dim objNewTemplateField As New TemplateField ' ' Now based on the number of Columns in our Datatable from our database, we are going to make Templated Columns. ' i.e If there are 9 columns in our DataTable, we'll make 9 templated Columns in our GridView (using a For Each Loop) ' objNewTemplateField.HeaderTemplate = New TemplateClass(ListItemType.Header, objDataColumn.ColumnName, "") objNewTemplateField.ItemTemplate = New TemplateClass(ListItemType.Item, objDataColumn.ColumnName, "TEXT") objNewTemplateField.EditItemTemplate = New TemplateClass(ListItemType.EditItem, objDataColumn.ColumnName, "TEXT") GridView1.Columns.Add(objNewTemplateField) Next ' ' Bind the source ' GridView1.DataSource = tblDataTable GridView1.DataBind() End Sub #End Region #Region "EventHandlers" Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load CreateGridViewTemplate() End Sub #End Region End Class Now we are done. Unfortunately, I since you can post snap shots, I can't show you the fruit of our labor.... But if you follow my instructions and your datasource declarations are accurate, you will see the results in the GridView guaranteed... I will update the post further by showing you how to Edit, Delete and Update later. For now, I must take my leave...
|