July 22, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

Advanced Data Reporting in ASP.NET

  • October 25, 2002
  • By Dino Esposito
  • Send Email »
  • More Articles »

Selecting by Condition

In all the techniques discussed so far, the selection of a control is manual and interactive, occurring only when the user clicks it. What about enhancing the control a little bit to make it support selection by condition?

When the selection feature is enabled in a DataGrid control, your ultimate goal is to retrieve one or more DataRow objects. In the normal process of selection, you use the grid to provide a friendly user interface. When you want to pick up all the rows that match a certain condition, you don't need the grid to help with the selection process, although it is helpful in providing visual feedback about the matching rows.

When you need to process all the rows that meet certain criteria, you can use the Select method on the DataTable object. Select returns an array of DataRow objects without the involvement of the DataGrid control. I'm going to show you how to force a DataGrid control to draw all the rows that match a condition with a unique background color to indicate selection. The grid will not expose these rows through a collection because you can retrieve them by using the DataTable object.

The ability to highlight rows by condition is governed by two more custom properties on the SuperGrid control that we haven't discussed yet. They are RowSelectFilter and SelectBackColor. I'll add these properties to the programming interface of the SuperGrid control.

public String RowSelectFilter {
get { return (String) ViewState["RowSelectFilter"]; }
set { ViewState["RowSelectFilter"] = value; }
}
private Color m_SelectBackColor = Color.SkyBlue;
public Color SelectBackColor {
get { return m_SelectBackColor; }
set { m_SelectBackColor = value;}
}

I made the RowSelectFilter property persistent across page requests by using the control's ViewState collection. RowSelectFilter holds the string that represents the condition. The role of SelectBackColor is quite self-explanatory—it represents the color to use for the background of the row.

Changing the Background Color

Implementing the select-by-condition feature requires two important steps. First, you evaluate the condition for the row being drawn. Second, you change the background color for the row. The second step is the easiest, so I'll explain it first.

You can override the background color of a DataGrid control item by hooking into the ItemCreated event. You make sure that the item type is either Item or AlternatingItem, and then you set the BackColor property of the item. (This code snippet must then be expanded to include the code that actually evaluates the condition.)

if (itemType == ListItemType.Item || 
itemType == ListItemType.AlternatingItem) 
{
DataRowView drv = (DataRowView) e.Item.DataItem;
if (drv != null && RowSelectFilter != "")
{
if (bMeetSomeCriteria)
e.Item.BackColor = SelectBackColor;
// Can set other style properties here...
}
}

Evaluating the Condition

If the condition is not subject to dynamic changes, evaluating it is straightforward. A generic and variable condition is a bit more complicated to evaluate. What will the expression look like? The easiest approach you can take is to support any expression that is acceptable for data columns and for the DataTable object's Select method. This is a great approach for two reasons: you already have the tools you need, and a rather powerful language, in data-binding expressions. The following are typical expressions you can evaluate:

country = USA'
hiredate < #1/1/1994#

These expressions can be enriched with Boolean and arithmetic operators, some functions (Len, Substring, Convert, Iif), and ad-hoc operators such as LIKE and IN. (See the MSDN documentation for more information about data-binding expressions.) How do you evaluate these expressions?

A very promising tool seems to be the DataBinder.Eval method. Unfortunately, Eval understands only those expressions that evaluate to properties or column fields. Therefore, to use Eval, you must add a new expression-based column to the data source. After you add a new column, however, you have no further reason to stick to Eval. Also, Eval is not a particularly lightweight method.

An alternative approach that does not require the creation of a column is based on the DataTable object's Select method. As the code following demonstrates, you select all the rows that meet the criteria, then compare each of them to the current data item until a match is found:

if (itemType == ListItemType.Item || 
itemType == ListItemType.AlternatingItem) 
{
DataRowView drv = (DataRowView) e.Item.DataItem;
if (drv != null && RowSelectFilter != "")
{
DataTable dt = drv.Row.Table;
DataRow [] a = dt.Select(RowSelectFilter);
foreach(DataRow dr in a)
if (dr == drv.Row)
{
e.Item.BackColor = SelectBackColor;
// Can set other style properties here...
break;
}
}
}

This code works fine, but consider that Select can take a while to complete and might return a bunch of rows. In addition, this code calls Select for each item and alternating item. There has to be a better way.

Adding an Extra Column

As discussed in previous chapters, an expression-based column is not very expensive and does not result in the storage of new data. Its only cost is evaluating the expression when you attempt to read the value, so it is ideal for our purposes. The approach we'll take is to pad the data source with a newly added, sneaky column, defined as follows:

DataColumn dc;
dc = new DataColumn("RowSelectFilter", 
typeof(bool), 
RowSelectFilter);

The column will be a Boolean column with a hard-coded name—say, RowSelectFilter. Its contents are dynamically determined by evaluating the expression set through the RowSelectFilter property. Whenever you access a row of the RowSelectFilter column, the expression is evaluated and results in a Boolean value. Because the column is added internally to the SuperGrid control code, it is invisible to the user. Assuming that you have such a column, the code that highlights a grid item changes as follows:

if (itemType == ListItemType.Item || 
itemType == ListItemType.AlternatingItem) 
{
DataRowView drv = (DataRowView) e.Item.DataItem;
if (drv != null && RowSelectFilter != "")
{
if ((bool) drv["RowSelectFilter"])
e.Item.BackColor = SelectBackColor;

}
}

One problem left to solve, but it is the trickiest. How and when do you create the RowSelectFilter column? The RowSelectFilter column must be up and running when the DataGrid control is rendered and, of course, cannot be created prior to setting up the data source. To create the column, you need to access the contents of the DataSource property, extract the collection of columns, and add the new one. If you perform this task before the grid is rendered, you end up executing the task too many times. On the other hand, creating the column when the RowSelectFilter property is set is risky because nothing can guarantee that at that time the DataSource property points to a valid and non-null object. A better time to create the RowSelectFilter column is when the DataSource property is set. But this solution also isn't free of a little drawback: you must always set the RowSelectFilter property before you bind the grid to the data source and order the refresh.

Detecting when a given property is assigned—DataSource in this case—is not that difficult from within a control. You simply override the property, as shown in the following code snippet:

public override object DataSource {
get {return base.DataSource;}
set {base.DataSource = value;}
}

This code defines an override for the DataSource property that is identical to the original property. Now customizing the set accessor is as easy as adding some lines of code:

set { base.DataSource = value;
 
// Custom code
DataTable dt = null;
if (DataSource is DataTable)
dt = (DataTable) DataSource;
else if (DataSource is DataView)
dt = ((DataView)DataSource).Table;
 
DataColumn dc;
try {
if (dt.Columns.Contains("RowSelectFilter"))
dt.Columns["RowSelectFilter"].Expression = RowSelectFilter;
else {
dc = new DataColumn("RowSelectFilter", 
typeof(bool), RowSelectFilter);
dt.Columns.Add(dc);
}
}
catch {RowSelectFilter="";}
}

The code first ascertains the type of data source. (As mentioned earlier in this chapter, the sample code supports only data sources that are DataView or DataTable objects. You can easily enhance the code to make it support the DataViewManager class. Supporting DataReader and collection classes is much trickier.)

After you hold the living instance of the DataTable object that populates the DataGrid control, you add a new column named RowSelectFilter. If the column already exists—for example, because you already added it—you only change the value of its Expression property.

Figure 6-9 shows the SuperGrid control in the context of our sample application. You can declaratively set all the properties pertinent to the select-by-condition feature. The full source code for the SuperGrid.cs, ByCondition.aspx, and ByCondition.cs applications is available on the companion CD.

<expo:SuperGrid id="grid" runat="server" 

RowSelectFilter="hiredate > #1/1/1994#"
SelectBackColor="yellow">

Figure 6-9 A version of the SuperGrid control that selects rows by condition.





Page 3 of 4



Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Sitemap | Contact Us

Rocket Fuel