views:

48

answers:

3

How does asp.net parse the GridView? Suppose I have defined both EditItemTemplate and ItemTemplate. It seems that I can't do any data bound at the loading time to the controls like dropdownlist in the EditItemTemplate.

And is it true that the data will be bound to the controls in the EditItemTemplate when the Edit mode is activated? If not, how to bind all controls at the loading time in C#?

Thanks, this is actually driving me crazy. I can't find anything about how asp.net execute or evaluate the GridView online.

+1  A: 

You need to bind at when it is put into edit mode. Remember the edit controls do not exist until the it is set to edit mode for a specific row. At that time the controls are available to be bound.

Normally if you have common lookup type data you want to bind at edit mode, I load it into Cache, Session or ViewState (depending on content and situation) the first time it is needed and then bind it from this 'cached' location to save DB calls. I normally implement the OnDataBinding method for each control within the EditItemTemplate that need special binding like a DropDownList.

In your aspx:

<EditItemTemplate>
    <asp:DropDownList ID="yourDropDownList" runat="server" 
        OnDataBinding="yourDropDownList_DataBinding"></asp:DropDownList>
</EditItemTemplate>

Then in your codebehind:

protected void yourDropDownList_DataBinding(object sender, System.EventArgs e) 
{ 
    DropDownList ddl = (DropDownList)(sender);
    // do you databinding code here

    // this is an example
    ddl.DataSource = GetMyDropDownListData();
    ddl.DataBind();
    ddl.SelectedValue = Eval("FieldFromGridData");
}

I prefer to bind this way as it will localizes the code to specific controls and your don't have to go looking for them using FindControl on rows etc...

The yourDropDownList_DataBinding will only fire for the row in edit mode. On your initial bind where nothing is in edit mode, the databinding will not fire but each time you put a row into edit mode it will execute, that is why I say to somehow cache the data you want to bind to a DropDownList the first time you get it.

Kelsey
A: 

Try set your grid up using Eval to get the data from your gridview's datasource.

    <asp:GridView ID="gv" runat="server" SkinID="gridviewSkin" AutoGenerateColumns="False" DataKeyNames="Id"
                AutoGenerateEditButton="false"
                OnRowEditing="GvItems_RowEditing"
                OnRowCancelingEdit="GvItems_RowCancelingEdit"
                OnRowUpdating="GvItems_RowUpdating"
                OnPageIndexChanging="Gv_PageIndexChanging"
                AllowPaging="true"
                PageSize="20"
        >
    <Columns>
        <asp:CommandField ShowEditButton="True" CausesValidation="true" />
        <asp:TemplateField HeaderText="Name" ItemStyle-Width="200" ItemStyle-VerticalAlign="Top">
            <ItemTemplate>
                <%# Eval("Name")%>
            </ItemTemplate>
            <EditItemTemplate>        
                <asp:TextBox ID="txtName" runat="server" Width="200" Text='<%# Eval("Name") %>' MaxLength="50" />
                <asp:RequiredFieldValidator ID="rfv_txtName" runat="server" ControlToValidate="txtName" Display="Dynamic" ErrorMessage="(Required)" />
            </EditItemTemplate>
        </asp:TemplateField>    
        <asp:TemplateField HeaderText="Active" ItemStyle-Width="100" ItemStyle-HorizontalAlign="Center">
            <ItemTemplate>
                <asp:CheckBox ID="chkActive_Item" runat="server" Checked='<%# (bool)Eval("IsActive")%>' Enabled="false" />
            </ItemTemplate>
            <EditItemTemplate>
                <asp:CheckBox ID="chkActive_Edit" runat="server" Checked='<%# (bool)Eval("IsActive")%>' />
            </EditItemTemplate>
        </asp:TemplateField>        
    </Columns>
    <EmptyDataTemplate>No items exist</EmptyDataTemplate>
</asp:GridView>

To edit, then

protected void GvItems_RowEditing(object sender, GridViewEditEventArgs e)
{
   gv.EditIndex = e.NewEditIndex;

   //also reload and bind the items

}

For loading of dropdown datasources:

        <asp:TemplateField HeaderText="Channel Type" ItemStyle-Width="200" ItemStyle-VerticalAlign="Top">
            <ItemTemplate>
                <%# Eval("ChannelType.Name") %>
            </ItemTemplate>
            <EditItemTemplate>
                <asp:DropDownList ID="ddlChannelType" runat="server" DataSource="<%# GetChannelTypeDropdownBoxDataSource() %>" DataTextField="Name" DataValueField="Id" AppendDataBoundItems="true" SelectedValue='<%# Eval("ChannelId") %>' />
            </EditItemTemplate>
        </asp:TemplateField>

And get your code to return a datasource:

    protected ChannelType[] GetChannelTypeDropdownBoxDataSource()
    {
        return _channelTypeRepository.GetAll();
    }
CRice
A: 

There are a number of options for databinding nested controls in the GridView templates.

The easiest, and the one I use when I can, is to use an ObjectDataSource and bind your dropdownlist to that.

If this isn't an option, then you can bind in the RowDataBound event. The example on MSDN is lacking, but if you follow that example (C#), and where it says:

 // Display the company name in italics.
      e.Row.Cells[1].Text = "<i>" + e.Row.Cells[1].Text + "</i>";

You would have something like:

DropDownList ddlWhatever = e.Row.FindControl("ddlWhatever") as DropDownList;
if(ddlWhatever != null) { /* bind data to it and call ddlWhatever.DataBind(); */ }

Since the EditItemTemplate and the InsertItemTemplate aren't rendered at the same time, I usually keep the control names the same in each template to simplify the databound event in the code behind. But, there's nothing stopping you from having a ddlEditItems and a ddlInsertItems and binding those differently in the databound event.

Another trick I've used before is binding to the DropDownList with the OnInit event of the dropdown. For instance:

// web form
<asp:DropDownList id="ddlWhatever" AutoPostBack="True" 
     runat="server" OnInit="ddlWhatever_Init">

// Code behind
protected void ddlWhatever_Init(object s, EventArgs a)
{       
   object[] years = {
     new { Year = 2009 }, new { Year = 2010 }
   };
   ddlWhatever.DataSource = years;
   ddlWhatever.DataTextField = "Year";
   ddlWhatever.DataValueField = "Year";
   ddlWhatever.DataBind();
}

I've had some people tell me not to do it this last way (i.e. a control shouldn't be responsible for binding itself). I disagree, and don't recall seeing anything related to that claim in Microsoft's Framework Design Guidelines. When it all comes down to it, I like the last way when I can't use an ObjectDataSource, but I do have to also code to the acceptance level of fellow developers. :D

The rules I usually stick with:

  1. Use ObjectDataSource when it's quick and easy
  2. Use GridView RowDataBound when the nested control's items are dependent on other values in the GridView
  3. Use the OnInit method if you're just binding data, keeping in mind that you can't access other controls when this method is fired (e.g. GridView may not have been initialized yet)

I hope that helps, I went through similar frustration with the MSDN's lack of GridView examples.

Jim Schubert
@Jim Schubert see my answer above, this is exactly what the `OnDataBinding` method is for, I wouldn't recommend using OnInit. Using `RowDataBound` is not good in my opinion because it doesn't localize the code to the control itself. I think most people do it with `RowDataBound` because so few people realize how to use `OnDataBinding`.
Kelsey
@Kelsey, that's a fair argument, but OnDataBinding assumes that a datasource has already been provided and `DataBind` has already been called. By doing your binding in this method, doesn't it *shift* the control's flow of events? From MSDN: `This method notifies a server control to perform any logic for binding data that is associated with it.` Binding data in this event encapsulates the association, binding, and logic for the control.
Jim Schubert
Also, from MSDN regarding Control.Init: `Server controls should perform any initialization steps that are required to create and set up an instance.` I think the ultimate point is that you can bind the control almost anywhere.
Jim Schubert
@Jim Schubert Remember when you are putting the grid into edit mode, you need to rebind the data. The data is then bound to each control which triggers the `OnDataBinding` method for each in the template.
Kelsey
@Jim Schubert If you did it on the ONInit, what if I wanted to bind it based on some other data or control info? You can't because as per the part of MSDN you left out: `In this stage of the server control's lifecycle, the control's view state has yet to be populated. Additionally, you can not access other server controls when this method is called either, regardless of whether it is a child or parent to this control. Other server controls are not certain to be created and ready for access.`
Kelsey
@Jim Schubert Yes, you can do it other ways. In this case I don't think `OnInit` was intended for databinding or you would do all your page's `DataBinding` in the page's `OnInit` as well. (Sorry for taking up so much comments but there wasn't enough room :))
Kelsey
@Kelsey: I agree with all your points. Some would argue that databinding values which aren't related to selections in other controls belongs in the `OnInit` event. As I mentioned in my answer, you'll have to use the `RowDataBound` event or some event after `OnInit` if the data depends on selections in other controls. I wasn't trying to suggest a correct way to do something, only addressing the question `how to bind all controls at the loading time in C#` as was asked.
Jim Schubert