views:

1165

answers:

7

I have a page with a table of stuff and I need to allow the user to select rows to process. I've figured out how to add a column of check boxes to the table but I can't seem to figure out how to test if they are checked when the form is submitted. If they were static elements, I'd be able to just check do this.theCheckBox but they are programaticly generated.

Also I'm not very happy with how I'm attaching my data to them (by stuffing it in there ID property).

I'm not sure if it's relevant but I'm looking at a bit of a catch-22 as I need to known which of the checkboxes that were created last time around were checked before I can re-run the code that created them.


Edit: I've found an almost solution. By setting the AutoPostBack property and the CheckedChanged event:

checkbox.AutoPostBack = false;
checkbox.CheckedChanged += new EventHandler(checkbox_CheckedChanged);

I can get code to be called on a post back for any check box that has changed. However this has two problems:

  • The call back is processed after (or during, I'm not sure) Page_Load where I need to use this information
  • The call back is not called for check boxes that were checked when the page loaded and still are.


Edit 2:

What I ended up doing was tagging all my ID's with a know prefix and stuffing this at the top of Form_Load:

foreach (string v in this.Request.Form.AllKeys)
{
    if (v.StartsWith(Prefix))
    {
        var data = v.Substring(Prefix.Length);
    }
}

everything else seems to run to late.

+1  A: 

First, make sure that each Checkbox has an ID and that it's got the 'runat="server"' in the tag.

then use the FindControl() function to find it.

For example, if you're looping through all rows in a GridView..

foreach(GridViewRow r in Gridview1.Rows)
{

    object cb = r.FindControl("MyCheckBoxId");
    if(r != null)
    {
      CheckBox chk = (CheckBox)cb;
      bool IsChecked = chk.Checked;
    }

}
David Stratton
I can't seem to find how to set `runat`. I can't just edit it into the tag as the checkboxes are all generated inside `Form_Load` via `new CheckBox()`
BCS
Since the control is dynamically added and it's an ASP control there won't be a need to set the runat attribute. Once you correctly setup all the attributes for the control and add eventhandlers to it, it would function as any other control added during design time. You could use @David's FindControl method by running a loop and building the IDs, provided you know the number of checkboxes added, but that is less efficient than looping through the parent container directly when dealing with multiple controls.
Ahmad Mageed
I haven't checked but I suspect that will suffer from the problem of it only finds stuff after I have regenerated it and I need it sooner than that.
BCS
@BCS: Then move your regeneration code OR move the code that depends on it to be later in the page lifecycle.
Chris Lively
@Chris: The problem with that option is that the code I need to run in response to the check-boxes can change what the regeneration code must produce. For that matter, the regeneration code my producing something different because of other sessions changing stuff.
BCS
+2  A: 

I'm going to assume you're using a DataList but this should work with and Control that can be templated. I'm also going to assume you're using DataBinding.

Code Front:

<asp:DataList ID="List" OnItemDataBound="List_ItemDataBound" runat="server">
    <ItemTemplate>
     <asp:CheckBox ID="DeleteMe" runat="server"/>
     <a href="<%# DataBinder.Eval(Container, "DataItem.Url")%>" target="_blank">
      <%# DataBinder.Eval(Container, "DataItem.Title")%></a>
    </ItemTemplate>
</asp:DataList>
<asp:Button ID="DeleteListItem" runat="server" OnClick="DeleteListItem_Click" ></asp:Button>

Code Behind:

public partial class Default : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
     if (!IsPostBack)
      LoadList();
    }

    protected void DeleteListItem_Click(object sender, EventArgs e)
    {
     foreach (DataListItem li in List.Items)
     {
      CheckBox delMe = (CheckBox)li.FindControl("DeleteMe");

      if (delMe != null && delMe.Checked)
        //Do Something
      }
     }

     LoadList();
    }

    protected void LoadList()
    {
     DataTable dt = //Something...
     List.DataSource = dt;
     List.DataBind();
    }

    protected void List_ItemDataBound(object sender, DataListItemEventArgs e)
    {
     if (e.Item.ItemType == ListItemType.AlternatingItem || e.Item.ItemType == ListItemType.Item)
     {
      string id = DataBinder.Eval(e.Item.DataItem, "ID").ToString();
      CheckBox delMe = (CheckBox)e.Item.FindControl("DeleteMe");

      if (delMe != null)
       delMe.Attributes.Add("value", id);                
     }
    }
}
CptSkippy
As it happens I'm not using either of those things. OTOH I'm going to have to look into what `ItemTemplate` is doing as it looks handy.
BCS
A: 

Your post is a little vague. It would help to see how you're adding controls to the table. Is it an ASP:Table or a regular HTML table (presumably with a runat="server" attribute since you've successfully added items to it)?

If you intend to let the user make a bunch of selections, then hit a "Submit" button, whereupon you'll process each row based on which row is checked, then you should not be handling the CheckChanged event. Otherwise, as you've noticed, you'll be causing a postback each time and it won't process any of the other checkboxes. So when you create the CheckBox do not set the eventhandler so it doesn't cause a postback.

In your submit button's eventhandler you would loop through each table row, cell, then determine whether the cell's children control contained a checkbox.

I would suggest not using a table. From what you're describing perhaps a GridView or DataList is a better option.


EDIT: here's a simple example to demonstrate. You should be able to get this working in a new project to test out.

Markup

    <form id="form1" runat="server">
    <div>
    <table id="tbl" runat="server"></table>
    <asp:Button ID="btnSubmit" runat="server" Text="Submit"
      onclick="btnSubmit_Click" />
    </div>
    </form>

Code-behind

protected void Page_Load(object sender, EventArgs e)
{
    for (int i = 0; i < 10; i++)
    {
        var row = new HtmlTableRow();
        var cell = new HtmlTableCell();
        cell.InnerText = "Row: " + i.ToString();
        row.Cells.Add(cell);
        cell = new HtmlTableCell();
        CheckBox chk = new CheckBox() { ID = "chk" + i.ToString() };
        cell.Controls.Add(chk);
        row.Cells.Add(cell);
        tbl.Rows.Add(row);
    }
}

protected void btnSubmit_Click(object sender, EventArgs e)
{
    foreach (HtmlTableRow row in tbl.Rows)
    {
        foreach (HtmlTableCell cell in row.Cells)
        {
            foreach (Control c in cell.Controls)
            {
                if (c is CheckBox)
                {
                    // do your processing here
                    CheckBox chk = c as CheckBox;
                    if (chk.Checked)
                    {
                        Response.Write(chk.ID + " was checked <br />");
                    }
                }
            }
        }
    }
}
Ahmad Mageed
It is an HTML table. I only want to process checked rows but CheckChanged doesn't work for that either. after adding an asp:Button with a `Click` handler, I found that it also runs to late (after Form_Load) Maybe I'm going stuff in the wrong place.
BCS
CheckChanged won't be triggered since you've set AutoPostBack to false. It must be set to true. If you're okay with posting back on each check change, then you could do that. Otherwise you would process them all at once upon a submit button click. In that case you would process them in a loop similar to what I posted.
Ahmad Mageed
I've added a sample to demo how this would work given your HTML table scenario.
Ahmad Mageed
When I tried it, `CheckChanged` runs on submit if `AutoPostBack=false`.
BCS
When AutoPostBack is true the checkbox will post back when you change its state. CheckChanged will be called if the checked state change between postbacks. It doesn't matter if the postback was generated by the AutoPostBack setting or a button click or anything else that generates a postback.
drs9222
+1  A: 

Postback data is restored between the InitComplete event and the PreLoad event. If your checkboxes are not created until later then the checkboxes will play "catch up" with their events and the data will be loaded into the control shortly after it is created.
If this is to late for you then you will have to do something like what you are already doing. That is you will have to access the post data before it is given to the control.
If you can save the UniqueId of each CheckBox that you create then can directly access the post data without having to given them a special prefix. You could do this by creating a list of strings which you save the ids in as you generate them and then saving them in the view state. Of course that requires the view state to be enabled and takes up more space in the viewstate.

foreach (string uniqueId in UniqueIds)
{
    bool data = Convert.ToBoolean(Request.Form[uniqueId]);
    //...
}
drs9222
the first bit is good to know. As for the `UniqueId` bit, I'd rather remain stateless.
BCS
A: 

What about using the CheckBoxList control? I have no Visual Studio open now, but as far as I remember it is a DataBound control, providing DataSource and DataBind() where you can provide a list at runtime. When the page does a postback you can traverse the list by calling something like myCheckBoxList.Items and check whether the current item is selected by calling ListItem.Selected method. This should work.

Juri
I've never used Date Binding so that's a downside. Another concern: do you happen to know if it can process stuff *before* it loads data from the `DateSource`? That is based on the data that was used for the previous page load.
BCS
I guess I interpreted your question in the wrong way. The CheckBoxList is not suited for what you want to achieve. However the DataBinding mechanism could help you. "Process stuff before"...well in theory you just load your control in the "!Page.isPostBack" case, so the ViewState will again map your previous items back on your Databound control (Repeater, Grid or whatever). Unfortunately I cannot check it at the moment, but there should be a way to get the checkboxes (also by using the FindControl(...) method).
Juri
A: 

Add them in an override of the CreateChildControls method of the Page. Be sure to give them an ID! This way they get added to the control tree at the correct time.

IMHO The best way would be to use DataBound Templated Control though, i.e. something like a ListView (in .NET 3.5). then in pageload after postback traverse all items in the databound control and use item.FindControl to get at the actual checkbox.

Colin
The first option won't work as I need to do the processing before I create the checkboxes because the processing will effect what checkboxes are created. OTOH I'm going to have to look into this DataBound stuff and see how it does.
BCS
A: 

What I ended up doing was tagging all my ID's with a know prefix and stuffing this at the top of Form_Load:

foreach (string v in this.Request.Form.AllKeys)
{
    if (v.StartsWith(Prefix))
    {
        var data = v.Substring(Prefix.Length);
    }
}

everything else seems to run to late.

BCS