The best performance improvement is the transition from the nonworking state to the working state. --John Ousterhout

Custom HyperLink Column for DataGrid

Summary

PROBLEM: I want to fill an ASP.NET DataGrid with two columns. The first is a simple product name. The second is a link. But I also want to color code the link so that discontinued products show up in red and others show up in green.

You'll find that you can't easily achieve this with the BoundColumn and HyperLinkColumn classes in .NET.

The first column, the product name can be implement easily with just a bound column. But the hyperlink column will have three pieces of information (corresponding to three fields from our database). The text to display (ProductName). The URL to navigate to (ProductID). And the color of the link (Discontinued).

The standard HyperLinkColumn will only allow you to specify a data field for the text and one for the URL, but no others.

So what do we do? Well there are a few approaches.

1) The quick and dirty approach is to do preprocessing on your DataTable before binding it to the DataGrid. For example, after filling the table from the database, add a column called LinkSpecial. Then loop over the rows and add your customized HTML to the LinkSpecial column for each one. The HTML column contents for each row would look something like the following with some row to row variation:

<a style="color:red" href='/MyApp/ProductDetails.aspx?id=3'>Yams Detail</a>

Then all you need to do is bind the LinkSpecial column to the datagrid in a simple BoundColumn. The HTML part of the field will render as a link.

2) The second approach is initially more complicated, but may save you a lot of time in the long run if its something you'll need repeatedly. Also, this same approach will work for more complicated template columns beyond just hyperlinks.

You can create a TemplateColumn in which you can work with any number of fields from your input table and create whatever kind of output you desire for you DataGrid column. In this article, we'll create a custom hyper link column which can take more than just two data fields. I recommend this method if you think you'll need to use the column again. It takes some time, but it would be worth it in the long run.

Hopefully, this will give you enough of an idea how to create your own template columns programmatically.

Begin by ensuring your have a DataGrid control on an ASPX page named DataGrid1. Then add the following method to your code behind file.

Snippet: Adding CRHyperLinkColumn to DataGrid

public void TestCRHyperLinkColumn()
{
   // First add a simple bound column with a product name
   BoundColumn nameColumn = new BoundColumn();
   nameColumn.DataField = "ProductName";
   nameColumn.DataFormatString = "{0}";
   nameColumn.HeaderText = "Product";

   // Now add the custom hyperlink column.
   // Note that you can add multiple data fields for the Link Text and
   // the link URL.

   CRHyperLinkColumn linkColumn = new CRHyperLinkColumn();
   linkColumn.InstanceID = 1;
   linkColumn.HeaderText = "Details";
   linkColumn.TextField.Clear();
   linkColumn.TextField.Add("Discontinued");
   linkColumn.TextField.Add("ProductName");

   // The class tag in the following refers to either DisTrue or 
   // DisFalse style objects defined in your style sheet 
   // or elsewhere on your page.

   // The link display text
   linkColumn.TextFormatString 
      = "Details for {1}"; 
   linkColumn.UrlField.Clear();
   linkColumn.UrlField.Add("ProductID");
   // the URL
   linkColumn.UrlFormatString 
      = "/AppName/ProductDetails.aspx?id={0}"; 
   linkColumn.SortExpression = "ProductName";
   linkColumn.LinkCssClass = "";
   linkColumn.LinkAlternateCssClass = "";

   DataGrid1.Columns.Add(nameColumn);
   DataGrid1.Columns.Add(linkColumn);
   DataGrid1.AutoGenerateColumns = false;

   DataTable dt = GetNorthwindProductTable();
   DataGrid1.DataSource = dt;
   DataGrid1.DataBind();

}

The above method refers to a couple of style classes. They would need to be defined in a style sheet (.css file) or in a style block in the head section of an html page as follows:

<style>
   .DisTrue
   {
      color: Red;
   }
   .DisFalse
   {
      color: Green;
   }
</style>

As you can see the CRHyperLinkColumn works very much like the HyperLinkColumn class. You just have a few more options to set.

There are still a couple pieces of code you'll need to implement the above method. I call a method called GetNorthwindProductTable() to create my DataTable. Obviously, you can use any data you want for this, but I pull it from the standard Northwind database in a standard SQL Server installation.

Snippet: Fill a DataSet from A Microsoft SQLServer Database using SqlDataAdapter and DataSet Classes

private DataTable GetNorthwindProductTable()
{
   string connectionString = "workstation id=SERVERMASTER;packet size=4096;"
      + "integrated security=SSPI;data source=SERVERMASTER;"
      + "persist security info=False;initial catalog=Northwind";
   string query = "select * from Products";
   SqlDataAdapter da = new SqlDataAdapter(query, connectionString);
   DataSet ds = new DataSet();
   da.Fill(ds);

   return ds.Tables["Table"];

}

Finally, you need the CRHyperLinkColumn class. The class actually uses another class which implements the ITemplate interface. I will list them separately below. Copy them into separate (.cs) files in the same folder. And don't forget to add a using directive to the code-behind file of you aspx page such as 'using Cambia.Web.CoreLib.Controls'.

These classes actually have some nice features not demonstrated above. Hopefully, I can write some more articles soon showing how to use the additional features. If you add more than one CRHyperLinkColumn to a single grid be sure to set the InstanceID's to different values.

Class: CRHyperLinkColumn

using System;
using System.Collections.Specialized;
using System.Web.UI.WebControls;

namespace Cambia.Web.CoreLib.Controls
{
   /// <summary>
   /// CRHyperLinkColumn.
   /// </summary>
   public class CRHyperLinkColumn: TemplateColumn
   {
      CRLinkColumnTemplate linkTemplate;

      #region -- Construction/Initialization --
      public CRHyperLinkColumn(): base()
      {
         Reset();
      }
      public void Reset()
      {
         linkTemplate = new CRLinkColumnTemplate();
         this.ItemTemplate = linkTemplate;
         HeaderStyle.HorizontalAlign = HorizontalAlign.Center;
      }
      #endregion

      #region -- Methods --
      public HyperLink GetLinkControl(DataGridCommandEventArgs ea)
      {
         if (ea.Item.ItemType == ListItemType.Item
            || ea.Item.ItemType == ListItemType.AlternatingItem)
         {
            return (HyperLink)ea.Item.FindControl(LinkControlID);
         }
         return null;
      }
      public string GetReferenceValue(DataGridCommandEventArgs ea)
      {
         if (ea.Item.ItemType == ListItemType.Item
            || ea.Item.ItemType == ListItemType.AlternatingItem)
         {
            return ((TextBox)ea.Item.FindControl(ReferenceControlID)).Text;
         }
         return "";
      }
      #endregion

      #region -- Properties --
      public StringCollection UrlField
      {
         get
         {
            return linkTemplate.UrlField;
         }
         set
         {
            linkTemplate.UrlField = value;
         }
      }
      public StringCollection TextField
      {
         get
         {
            return linkTemplate.TextField;
         }
         set
         {
            linkTemplate.TextField = value;
         }
      }
      public StringCollection ReferenceField
      {
         get
         {
            return linkTemplate.ReferenceField;
         }
         set
         {
            linkTemplate.ReferenceField = value;
         }
      }

      public string UrlFormatString
      {
         get
         {
            return linkTemplate.UrlFormatString;
         }
         set
         {
            linkTemplate.UrlFormatString= value;
         }
      }
      public string TextFormatString
      {
         get
         {
            return linkTemplate.TextFormatString;
         }
         set
         {
            linkTemplate.TextFormatString = value;
         }
      }
      public string ReferenceFormatString
      {
         get
         {
            return linkTemplate.ReferenceFormatString;
         }
         set
         {
            linkTemplate.ReferenceFormatString = value;
         }
      }

      public string LinkCssClass
      {
         get
         {
            return linkTemplate.LinkCssClass;
         }
         set
         {
            linkTemplate.LinkCssClass = value;
         }
      }
      public string LinkAlternateCssClass
      {
         get
         {
            return linkTemplate.LinkAlternateCssClass;
         }
         set
         {
            linkTemplate.LinkAlternateCssClass = value;
         }
      }
      public Type LinkControlType
      {
         get
         {
            return linkTemplate.LinkControlType;
         }
      }
      public Type ReferenceControlType
      {
         get
         {
            return linkTemplate.ReferenceControlType;
         }
      }
      public string LinkControlID
      {
         get
         {
            return linkTemplate.LinkControlID;
         }
      }
      public string ReferenceControlID
      {
         get
         {
            return linkTemplate.ReferenceControlID;
         }
      }
      public int InstanceID
      {
         get
         {
            return linkTemplate.InstanceID;
         }
         set
         {
            linkTemplate.InstanceID = value;
         }
      }

      #endregion

   }
}

Class: CRLinkColumnTemplate. Used by CRHyperLinkColumn

using System;
using System.Data;
using System.Collections.Specialized;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace Cambia.Web.CoreLib.Controls
{
   /// <summary>
   /// Creates a data grid column that displays a link.
   /// </summary>
   public class CRLinkColumnTemplate : System.Web.UI.ITemplate
   {

      #region -- Data Members --
      private StringCollection m_UrlField;
      private StringCollection m_TextField;
      private StringCollection m_ReferenceField;
      private string m_UrlFormatString;
      private string m_TextFormatString;
      private string m_ReferenceFormatString;
      private string m_LinkCssClass;
      private string m_LinkAlternateCssClass;
      private string m_LinkID = "lnkLink";
      private string m_ReferenceID = "txtReference";
      private int m_InstanceID;
      #endregion

      public CRLinkColumnTemplate()
      {
         Reset();
      }
      public void Reset()
      {
         m_UrlField = new StringCollection();
         m_TextField = new StringCollection();
         m_ReferenceField = new StringCollection();
         m_UrlFormatString = "{0}";
         m_TextFormatString = "{0}";
         m_ReferenceFormatString = "{0}";
         m_LinkCssClass = "";
         m_LinkAlternateCssClass = "";
         m_InstanceID = 1;
      }

      public void InstantiateIn(Control container)
      {
         // add a hyperlink to the cell
         HyperLink link = new HyperLink();
         link.ID = LinkID;
         link.DataBinding += new EventHandler(this.BindLink); 
         container.Controls.Add(link);

         // hidden label reference value
         Label lblHiddenReference = new Label();
         //An id is required, don't delete
         lblHiddenReference.ID = RefID; 
         lblHiddenReference.Visible = false;
         lblHiddenReference.DataBinding 
            += new EventHandler(this.BindReferenceValue);
         container.Controls.Add(lblHiddenReference);
      }

      public void BindLink(object sender, EventArgs e)
      {
         HyperLink link = (HyperLink) sender;
         DataGridItem container = (DataGridItem)link.NamingContainer;
         if (container.ItemType == ListItemType.Item)
            link.CssClass = m_LinkCssClass;
         else
            link.CssClass = m_LinkAlternateCssClass;

         DataRowView drv;
         drv = ((DataRowView) container.DataItem);

         // link url
         if (m_UrlField.Count > 0)
         {
            object[] formatArgs = new object[m_UrlField.Count];
            int i=0;
            foreach (string colName in m_UrlField)
            {
               formatArgs[i] = drv[colName];
               i++;
            }
            link.NavigateUrl = String.Format(m_UrlFormatString, formatArgs);
         }
         else
            link.NavigateUrl = "";

         // link text
         if (m_TextField.Count > 0)
         {
            object[] formatArgs = new object[m_TextField.Count];
            int i=0;
            foreach (string colName in m_TextField)
            {
               formatArgs[i] = drv[colName];
               i++;
            }
            link.Text = String.Format(m_TextFormatString, formatArgs);
            }
         else
            link.Text = "";

      }

      public void BindReferenceValue(object sender, EventArgs e)
      {

         Label lblHiddenReference = (Label) sender;
         DataGridItem container = 
         (DataGridItem) lblHiddenReference.NamingContainer;

         DataRowView drv;
         drv = ((DataRowView) container.DataItem);
         if (m_ReferenceField.Count > 0)
         {
            object[] formatArgs = new object[m_ReferenceField.Count];
            int i=0;
            foreach (string colName in m_ReferenceField)
            {
               formatArgs[i] = drv[colName];
               i++;
            }
            lblHiddenReference.Text = 
            String.Format(m_ReferenceFormatString, formatArgs);
         }
         else
            lblHiddenReference.Text = "";

      }

      public Type LinkControlType
      {
         get 
         {
            return typeof(HyperLink);
         }
      }
      public string LinkControlID
      {
         get
         {
            return LinkID;
         }
      }
      public StringCollection UrlField
      {
         get
         {
            return m_UrlField;
         }
         set
         {
            m_UrlField = value;
         }
      }
      public StringCollection TextField
      {
         get
         {
            return m_TextField;
         }
         set
         {
            m_TextField = value;
         }
      }
      public Type ReferenceControlType
      {
         get
         {
            return typeof(System.Web.UI.WebControls.TextBox);
         }
      }
      public string ReferenceControlID
      {
         get
         {
            return RefID;
         }
      }
      public StringCollection ReferenceField
      {
         get
         {
            return m_ReferenceField;
         }
         set
         {
            m_ReferenceField = value;
         }
      }

      public string UrlFormatString
      {
         get
         {
            return m_UrlFormatString;
         }
         set
         {
            m_UrlFormatString = value;
         }
      }
      public string TextFormatString
      {
         get
         {
            return m_TextFormatString;
         }
         set
         {
            m_TextFormatString = value;
         }
      }
      public string ReferenceFormatString
      {
         get
         {
            return m_ReferenceFormatString;
         }
         set
         {
            m_ReferenceFormatString = value;
         }
      }

      public string LinkCssClass
      {
         get
         {
            return m_LinkCssClass;
         }
         set
         {
            m_LinkCssClass = value;
         }
      }
      public string LinkAlternateCssClass
      {
         get
         {
            return m_LinkAlternateCssClass;
         }
         set
         {
            m_LinkAlternateCssClass = value;
         }
      }
      public int InstanceID
      {
         get
         {
            return m_InstanceID;
         }
         set
         {
            m_InstanceID = value;
         }
      }
      private string LinkID
      {
         get
         {
            return this.m_LinkID + m_InstanceID.ToString();
         }
      }
      private string RefID
      {
         get
         {
            return this.m_ReferenceID + m_InstanceID.ToString();
         }
      }
   }
}