views:

44

answers:

1

I wanted to get the total row count in a GridView's databound event handler, so I tried the following:

   protected void grid_DataBound(object sender, EventArgs e)
   {
       GridView grid = (GridView)sender;
       DataSet data = grid.DataSource as DataSet;
       if (data == null)
         return;
       int totalRows = data.Tables[0].Rows.Count;
   }

The problem is that the grid.DataSource property is null. The DataSourceID property is not null, so does that mean that I can't access the DataSource object in the DataBound event handler unless I assign the DataSource property directly?

Edit

Here is the code in my GridHelper class for adding rowcount to the BottomPagerRow. I would like to get rid of the requirement to pass in the ObjectDataSource, but can't because I need to use the Selected event to get total row count. Hence the reason for the question.

I also think this might be better in a custom control where I can access ViewState, and/or create child controls during the Init event (I still have a problem to resolve with the way the pager renders with an extra cell), but then I'd still have the problem of how to get to the total row count when the DataSource itself doesn't appear to be available in any GridView events.

Edit 2 Not really relevant to the specific problem, but I resolved the problem I was having with the pager rendering so i've updated the code posted. I found the trick here: http://michaelmerrell.com/2010/01/dynamically-modifying-the-asp-net-gridview-paging-control/

#region Fields
private int totalRows = -1;
#endregion

#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="GridHelper"/> class.
/// Adds EventHandlers to the GridView to display results from the ObjectDataSource in the footer.
/// Marked as obsolete because AddResultsToFooter method provides a static access to the same functionality
/// An instance of GridHelper is required by the passed in GridView to store the totalRows value between the two event handlers
/// </summary>
/// <param name="grid">The grid.</param>
/// <param name="source">The ObjectDataSource linked to the GridView.</param>
[Obsolete("Use AddResultsToFooter instead.")]
[EditorBrowsable(EditorBrowsableState.Never)]
public GridHelper(GridView grid, ObjectDataSource source)
{
    source.Selected += source_Selected;
    grid.PreRender += grid_PreRender;
}
#endregion

#region Event Handlers

private void grid_PreRender(object sender, EventArgs e)
{
    GridView grid = (GridView)sender;

    if (grid.HeaderRow != null)
        grid.HeaderRow.TableSection = TableRowSection.TableHeader;

    //Add a cell to the bottom pager row to display the total results
    if (grid.BottomPagerRow == null || !grid.BottomPagerRow.Visible)
        return;

    //Get the control used to render the pager
    //http://michaelmerrell.com/2010/01/dynamically-modifying-the-asp-net-gridview-paging-control/
    Table tblPager = grid.BottomPagerRow.Cells[0].Controls[0] as Table;
    if (tblPager == null)
        return;

    if (totalRows < 0)
    {
        //The DataSource has not been refreshed so get totalRows from round trip to client
        addResultsToPagerTable(tblPager, grid.Attributes["results"]);
        return;
    }

    int firstRow = grid.PageIndex * grid.PageSize + 1;
    int lastRow = firstRow + grid.Rows.Count - 1;

    string results;
    if (totalRows <= grid.PageSize)
        results = string.Format("<span class='grid-pager'>{0} Results</span>", totalRows);
    else
        results = string.Format("Results <b>{0}</b> to <b>{1}</b> of <b>{2}</b>", firstRow, lastRow, totalRows);

    addResultsToPagerTable(tblPager, results);

    //Need to store the information somewhere that is persisted via ViewState, and we don't have access to ViewState here
    grid.Attributes.Add("results", results);
}

/// <summary>
/// Handles the Selected event of the source control. Gets the total rows, since it is not possible to access them from the GridView.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.Web.UI.WebControls.ObjectDataSourceStatusEventArgs"/> instance containing the event data.</param>
private void source_Selected(object sender, ObjectDataSourceStatusEventArgs e)
{
    if (e.ReturnValue is DataView)
        totalRows = ((DataView)e.ReturnValue).Count;
    else if (e.ReturnValue is EntitySet)
        totalRows = ((EntitySet)e.ReturnValue).Count;
}
#endregion

#region Private Methods
private static void addResultsToPagerTable(Table tblPager, string results)
{
    //http://michaelmerrell.com/2010/01/dynamically-modifying-the-asp-net-gridview-paging-control/

    //Get a handle to the original pager row
    TableRow pagesTableRow = tblPager.Rows[0];

    //Add enough cells to make the pager row bigger than the label we're adding
    while (pagesTableRow.Cells.Count < 10)
        pagesTableRow.Cells.Add(new TableCell { BorderStyle = BorderStyle.None });

    //Add a new cell in a new row to the table
    TableRow newTableRow = new TableRow();
    newTableRow.Cells.AddAt(0, new TableCell
    {
        Text = results,
        BorderStyle = BorderStyle.None,
        ColumnSpan = pagesTableRow.Cells.Count
    });

    tblPager.Rows.AddAt(0, newTableRow);

}
#endregion

#region Public Methods
/// <summary>
/// Adds EventHandlers to the GridView to display results from the ObjectDataSource in the footer.
/// </summary>
/// <param name="grid">The GridView.</param>
/// <param name="source">The ObjectDataSource linked to the GridView.</param>
public static void AddResultsToFooter(GridView grid, ObjectDataSource source)
{
    if (grid == null)
        throw new ArgumentNullException("grid", "grid is null.");
    if (source == null)
        throw new ArgumentNullException("source", "source is null.");

    new GridHelper(grid, source);
}
#endregion
A: 

Hey,

In VS, you can debug this by putting a breakpoint there and mousing over DataSource, but I think it's because the GridView extracts a DataView from the DataTable that it binds to, so it isn't looking at the DataSet...

Debugging will find out.

HTH.

Brian
That's exactly how I know that the DataSource property is null. The problem is something to do with DataSource and DataSourceID being different ways of assigning the data source. I thought that ASP.Net would assign the DataSource based on the DataSourceID during DataBinding, but you get an error if you assign both. So now I need to know if it is possible to get to the total rows from inside the DataBinding event when DataSourceID has been used. I want to create a generic handler that will allow the client code to use either technique.
Colin
OK, that would make sense too. DataSourceID and DataSource are always kept separate yes, and to get information about the binding, I think you need to look at tapping into the DataSource control that's supplying the data to figure any extra statistics out. The Selected event fires when the results have been pulled from the backend, and depending on the type of datasource control, you can access row counts from there.
Brian
Yes, I can get the row count from the Selected event on the DataSource, and that is what I do in a GridHelper class that is used by about 100 GridViews in our application, but the helper class needs a separate reference to the ObjectDataSource, and I'd rather access the DataSource from the GridView - because client code could potentially pass in the wrong source. I will post the code in an edit to the original question
Colin
OK; the Grid doesn't directly expose the data source control I believe though... the workaround could be to use a recursive findcontrol (http://stevesmithblog.com/blog/recursive-findcontrol/) to find the related data source, based on the data source ID.
Brian
Yeah, I did think about that, but I'm not sure whether it is wise to put a page level recursion into a generic handler (for efficiency reasons).
Colin
I understand your concern... don't see any other way around it unless the GridView does expose the data source control through another property.
Brian