Wednesday, January 30, 2008

Adding Blank Rows to a DataGrid

In one application I had the requirement to add a blank row after every 10 rows in a DataGrid rather than use paging as shown in FIGURE 1. There is no in-built way to do this with the DataGrid, but it can be easily done by modifying the DataTable that the DataGrid is bound to and by writing some code in the DataGrid's ItemDataBound event. The rest of this article will describe how it is done.

Adding Blank Rows to the DataTable

To add a blank row every 10 rows seems simple enough to do using a for loop with a counter. I knew that I could not use a for each loop due to me adding new items to the collection within the loop. As it turns out, you cannot use a for loop with a counter as the upper bound of the loop is not re-evaluated on every iteration! This means that the only way to loop through the item collection is to use a while loop with a counter.

To keep track of when to add a blank row, another counter is used that is decremented. When this counter reaches 1, a new row is added to the DataTable and is then reset. At the same time, the upper bound is incremented to note the addition of the new row.

The code below shows a function that can be used to add blank rows to the first DataTable in any DataSet. As you can see, the row is not actually blank. The first column in the row is given the negative of the counter. When the DataRow is inspected in the ItemDataBound event, the fact that it is a negative value can be used to note that this should be displayed as a blank row in the DataGrid. In this case, the first column in the DataSet that was used was a Primary Key and could not be blank, and I knew that the values from the database would all be positive. When implementing this yourself you can use whatever identifier is suitable for your scenario.

Private Function addBlankLines(ByVal ds As DataSet) As DataSet

Dim dr, drBlank As DataRow
Dim count, repeatCount, upperBound As Integer

repeatCount = 10 'used to keep track of when to add a 'blank' row
upperBound = ds.Tables(0).Rows.Count

While count <= upperBound If repeatCount = 1 Then drBlank = ds.Tables(0).NewRow drBlank(0) = -count ds.Tables(0).Rows.InsertAt(drBlank, count + 1) count += 1 upperBound += 1 repeatCount = 10 Else repeatCount -= 1 End If count += 1 End While Return ds End Function
Rendering the Blank Rows to the DataGrid

After adding the blank rows to the DataTable, the next step is to render the blank rows in the DataGrid. To do this, a few lines in the DataGrid ItemDataBound event are required. The first thing to do is check to see if the item is an Item or Alternating item as this event fires for both the header and the footer of the DataGrid. The underlying DataRow that the ListItem is bound to can then be accessed to check the value in the first column to see if the value it contains denotes that it should be rendered as a blank row. The code below sets the text of the first cell to contain so as to make the blank row visible, and sets the BackColor to White (the rest of the rows are presented in a different color). This code can be easily adapted to allow you to render whatever format of blank row that you want.

Private Sub dgResults_ItemDataBound(ByVal sender As System.Object, ByVal e As System.Web.UI.WebControls.DataGridItemEventArgs) Handles dgResults.ItemDataBound
If e.Item.ItemType = ListItemType.Item Or e.Item.ItemType = ListItemType.AlternatingItem Then

If CType(CType(e.Item.DataItem, DataRowView).Row.Item(0), Integer) < text = " " backcolor =" System.Drawing.Color.White">
Alternative Use

One alternative use for the idea and code presented here is to create a running total row. As the DataTable is looped through, a running total can be kept and that value inserted into the correct column in the DataTable. Instead of rendering a blank row in the DataGrid, it can be rendered in another format.

What is the difference between a Web service and a Web application?

The best definition of Web service comes from the W3C: "Web services provide a standard means of interoperating between different software applications, running on a variety of platforms and/or frameworks." The key point in this definition is "interoperate". Web services are all about making heterogeneous applications interoperate. The values of broad interoperability are many: it promotes the network effect and encourages a strong tool and infrastructure marketplace -- both of which dramatically drive your costs down, while improving your time-to-market and productivity.

With the best of intentions, some developers "work around" the Web service specifications. For example, because of perceived performance issues, they might use a virtually empty SOAP message body and pass their real message content in a SOAP attachment. While this message might be sent using SOAP, since you need to manually process the attachment yourself in custom code it certainly is not broadly interoperable and thus can't be considered a Web service -- no 3rd party tools or infrastructure will directly support it so you lose all of the benefits Web services had to offer in the first place. Don't let your application fall into this trap. Follow both the intent and the specifics of the standards (not only SOAP and WSDL, but also WS-I) and you will benefit significantly from the broad interoperability that is the true value of Web services.

Dynamic User Interface in ASP.NET Web Applications

Dynamic User Interface in ASP.NET Web Applications

Dynamic User Interface in ASP.NET Web Applications


What is that?

Every developer knows well that to be able to design a proper user interface then we have to have a solid knowledge about every aspect of this user interface before we actually start designing it. Unfortunately, this is not always 100% possible. Situations arise from time to time in which we have no or little idea about what will be the proper user interface for a given application. In web applications things becomes worth as these situations arises more frequently.

The tutorial you are reading now is intended to address this particular problem. This type of user interface construction technique is typically referred to as 'Dynamic Control Creation'. The technique is not that new in desktop applications and is already employed in several development framework many years ago. What we are going to present you in this tutorial is the web based implementation of this technique.

To make your user interface dynamic and responsive to the various situations and modes, several techniques are typically employed. Making irrelevant controls invisible, disabling unused menu items, and moving the frequently used controls to the focus of your user interface are all techniques from that class. Dynamic control creation is another story. The unmatched flexibility and innovation that you can experience with dynamic control creation exceeds in robustness and features any other technique that can be proposed in this class.

City Hall: The 'PlaceHolder' control

At the heart of the process of dynamic control creation in web applications is the PlaceHolder control. This control is a container control in that it can contain other controls inside it. These controls can be dynamically created at run time. It's important here to note that the PlaceHolder control is developed with the intention to host (i.e., to contain) server side controls. Inside the PlcaeHolder control is the Control.Controls collection. This is the collection inside which you add your dynamically created controls. Let's illustrate this by an example:

Simple dynamic control creation. Example 1 (Download)

In this example we will show you how to create a text box inside a PlaceHolder control dynamically. Because this is dynamic control creation you will not draw this text box form the traditional tool box or using the traditional Visual Studio 2005 user interface / form designer.

Create a new web site and on the 'Default.aspx' draw a PlaceHolder control from the tool box. Also add a button so we can write and trigger the code we need. Your form will be similar to figure 1.


Figure 1

Add the following code fragment to the Click event of our button:

Protected Sub Button1_Click( ByVal sender As Object , ByVal e As System.EventArgs) _
Handles Button1.Click
Dim t As TextBox
t = New TextBox
PlaceHolder1.Controls.Add(t)
End Sub

This code defines and creates an instance of the text box we are attempting to create dynamically. PlaceHolder1.Controls.Add(t) metod actually adds this newly created text box to the Controls collection of the PlaceHolder causing the text box to actually appear on the form. The PlaceHolder itself will not be seen at run time.

Save and run our web site and click the button to see how the dynamically created text box will show regardless that fact it has no existence on the form at design time! See figures 2 and 3 for before and after running respectively.


Figure 2


Figure 3

Let's examine a more sophisticated example in which the dynamically created controls are a direct representation of an underlying data.

Creation as a data representation technique. Example 2 (Download)

It's frequently desired to have a set of controls dynamically created to represent a given structure of data already available in your web application. The typical case is when you have an SQL or XML data source and you need to have a visual representation of such data. To more focus our tutorial over the problem we are presenting you now (i.e., Dynamic Control Creation) we will skip the database connectivity and data retrieval issues and will assume that all of these issues are already implemented and that the resulting data is loaded and available in a simple array.

We will assume that every record to be dynamically represent in the user interface is represented by an instance of the Record class. Let the Record class be defined as follows:

Class Record
Public ProductName As String ' The product name
Public Desc1 As String ' The label of field 1
Public Value1 As String ' The value of field 1
Public Desc2 As String ' The label of field 2
Public Value2 As String ' The value of field 2
End Class

Here's the code that will initialize the data array. Please give little attention to such code because this case is over simplified for the illustrative purpose. In a real world web application, this array is initialized from a data source like an SQL server or an XML file. Since this is an over simplified scenario, we will not dig deep in this code.

Dim products(0 To 9) As Record
Dim i As Integer 18
For i = 0 To 9 19
products(i) = New Record
products(i).ProductName = "Product " & (i + 1)
products(i).Desc1 = "Weight of product " & (i + 1)
products(i).Desc2 = "Volume of product " & (i + 1)
products(i).Value1 = (i + 1) * 10
products(i).Value2 = (i + 1) * 100 25
Next

Now to the most important part of our example: The dynamic creation of controls. Here's the code that performs the dynamic creation of controls:

For i = 0 To 9
'----------
Dim ProductName As New Label
Dim Desc1 As New Label
Dim Desc2 As New Label
Dim Value1 As New TextBox
Dim Value2 As New TextBox
'----------
ProductName.Text = products(i).ProductName
ProductName.Font.Bold = True
Desc1.Text = products(i).Desc1
Desc2.Text = products(i).Desc2
Value1.Text = products(i).Value1
Value2.Text = products(i).Value2
'----------
PlaceHolder1.Controls.Add(ProductName)
PlaceHolder1.Controls.Add(Desc1)
PlaceHolder1.Controls.Add(Value1)
PlaceHolder1.Controls.Add(Desc2)
PlaceHolder1.Controls.Add(Value2)
'----------
Next

As always, we create an instance from the controls we need to dynamically add to our form. The new practice here is that we are treating them just like any controls created in design time and set their properties with the standard syntax. Finally we add them to the PlaceHolder control as we mentioned previously.

If you are to run our example now, you will get the result illustrated in figure 4 below:


Figure 4

Not that user friendly ..... huh? This is of course an unacceptable user interface by any standard!

Because you are dynamically creating your controls, it's necessary to visualize the resulting output yourself! No more WYSIWYG visual designers! You need to add some formatting tags so that the above messy output is more acceptable.

To solve this dynamic output mess we will opt to use the HTML tag "
" to make sure that every label and every text box is in it's isolated line and that they all will not merge. As well we will use the HTML tag "


" to insert a horizontal line before every record (i.e., every product in our particular case). This all can be achieved by using the Literal control which can represent the "
" and the "
" HTML tags. All you have to do is to add a Literal control to the controls collection of your PlaceHolder every time you need to add a "
" or a "
".

A common mistake is to use the same Literal control every time you need to add a "
" or a "


". If you are to do so, only the first addition will be successful and the rest will be simply ignored as you are adding a control to a collection of controls already containing the control you are adding!

The proper practice is to define a function that returns a new and distinct Literal control every time. See the following function for example:

Function GetLiteral( ByVal text As String )
Dim rv As Literal
rv = New Literal
rv.Text = text
GetLiteral = rv
End Function

And now every time we need to add a Literal control we call that function specifying the HTML tag we need to add. After utilizing this technique, then dynamic control creation code should be now like this:

'----------
PlaceHolder1.Controls.Add(GetLiteral( "


" ))
PlaceHolder1.Controls.Add(ProductName)
PlaceHolder1.Controls.Add(GetLiteral( "
" ))
PlaceHolder1.Controls.Add(Desc1)
PlaceHolder1.Controls.Add(GetLiteral( "
" ))
PlaceHolder1.Controls.Add(Value1)
PlaceHolder1.Controls.Add(GetLiteral( "
" ))
PlaceHolder1.Controls.Add(Desc2)
PlaceHolder1.Controls.Add(GetLiteral( "
" ))
PlaceHolder1.Controls.Add(Value2)
PlaceHolder1.Controls.Add(GetLiteral( "
" ))
'----------

And when you run the web application you will see this tidy output:


Figure 5

For further information

Refer to the online copy of Microsoft Developers Network at http://msdn.microsoft.com or use your own local copy of MSDN.