Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

.FindControl always returns null

I have two methods. The first creates one table dynamically, and I add that table into a PlaceHolder.

private void generateData(){
    Table tbl = new Table();
    tbl.ID = "table1";
    holder_info.Controls.Add(tbl);
    // ...adding tr's and td's....
    // ...adding CheckBox in tds....
}

If I do .FindControl("...") inside this method I can find the control using:

CheckBox check = (CheckBox)holder_info.FindControl("checkbox1");

It's OK, but not what I pretend.

In the second method, I want to check whether the user checked checkBox and did something, but I can't find the control (it always returns null).

protected void saveInfo_Click(object sender, ImageClickEventArgs e)
{
  CheckBox check = (CheckBox)holder_info.FindControl("checkbox1");
  if(check.checked){ ... }
}

Also, if I try to find the control "table1", I get null.

Why does this happen?

like image 310
oteal Avatar asked Aug 22 '12 15:08

oteal


3 Answers

It's because you are adding a control dynamically to page, and when you click on the button page get a postback and remove the dynamically added controls. That is why it's not able to find the checkbox control in the button click event.

For dynamic controls, check the article Retaining State for Dynamically Created Controls in ASP.NET applications (The Code Project).

like image 101
Pranay Rana Avatar answered Oct 19 '22 06:10

Pranay Rana


FindControl() only finds the immediate children, and not the children of those children.

To do what you want, you'll need to write a routine to recursively find child controls.

like image 37
Jonathan Wood Avatar answered Oct 19 '22 05:10

Jonathan Wood


As Pranay explained, it's because you are adding controls dynamically. You need to rebuild the page as it was before the postback. Furthermore, the Table control is not a good webcontrol when it comes to remembering what information was displayed earlier. You might have better luck with a Repeater, a DataList, or a DataGrid. A comparison is available in Deciding When to Use the DataGrid, DataList or Repeater (MSDN).

To demonstrate, I can show a short example of how you could go about implementing the Repeater, as that is the simple one of the controls IMO. So, instead of adding a table in a postback (such as a button click) just add the repeater in the markup, and set it to visible when you want to display the data. This way, you don't even have to bother about dynamic controls, it's there when you need it.

So, a basic markup for a repeater with a checkbox is as follows.

<asp:Repeater ID="myTable" runat="server">
    <HeaderTemplate>
        <table cellpadding="4" cellspacing="0">
        <thead>
            <tr>
                <th></th>
                <th>Name</th>
                <th>Company</th>
            </tr>
        </thead>
        <tbody>
    </HeaderTemplate>
    <ItemTemplate>
    <tr>
        <td>
            <asp:HiddenField ID="uidfield" Value='<%# Eval("Uid") %>' runat="server" />
            <asp:CheckBox ID="valuefield" Checked='<%# Eval("IsChecked") %>' runat="server" />
        </td>
        <td>
            <asp:Label Text='<%# Eval("Name") %>' runat="server" />
        </td>
        <td>
            <asp:Label Text='<%# Eval("Company") %>' runat="server" />
        </td>
    </tr>
    </ItemTemplate>
    <FooterTemplate>
    </tbody> </table>
    </FooterTemplate>
</asp:Repeater>

As you can see, the table is built manually (you can use DataGrid instead if you don't want to do that). Now you can have a class which is your model, something like:

public class Person {
  public Person() { }

  public Person(Guid id, string name, string company) {
    this.Uid = id;
    this.Name = name;
    this.Company = company;
  }

  public Guid Uid { get; set; }

  public string Name { get; set; }

  public string Company { get; set; }
}

And a viewmodel class that "accommodates the needs" of the view (as in your web page control). Note the extra property IsChecked, it shouldn't be a part of the model (how is a person checked?), but it fits well in the viewmodel class.

public class PersonViewModel {
  private Person model;
  public PersonViewModel(Person model) {
      this.model = model;
  }

  public Guid Uid { get { return model.Uid; } }

  public string Name { get { return model.Name; } }

  public string Company { get { return model.Company; } }

  public bool IsChecked { get; set; }
}

Okay, so on to the code behind. When using the repeater control you need to bind it to a list. An instance of something that inherits from IEnumerable will be just fine, so you need to hook it up.

In your code behind for your page, add these methods.

protected override void OnInit(EventArgs e) {
    base.OnInit(e);
    mailinglists.DataBinding += bindMyTable;
}

protected override void OnLoad(EventArgs e) {
    base.OnLoad(e);
    if (!Page.IsPostBack) {
        DataBind();
    }
}

private void bindMyTable(object sender, EventArgs e) {
    // Are the conditions set for adding data to the table?
    if (conditionsMet()) {
        myTable.DataSource = getDataSource();
    }
}

private IEnumerable<PersonViewModel> getDataSource() {
     List<PersonViewModel> view = new List<PersonViewModel>();
     view.Add(new PersonViewModel(new Person() { Uid = Guid.NewGuid(), Name = "Example", Company = "Co" }));
     return view;
}

Now you just call myTable.DataBind() whenever the conditions are met, say when you click a button. When you click a button and need to check which checkboxes are checked, you can use the repeater.Items property to iterate them, something like this:

private void onValidatePage() {
    List<Guid> checks = new List<Guid>();
    foreach (RepeaterItem repeatitem in myTable.Items) {
        string uid = ((HiddenField)repeatitem.FindControl("uidfield")).Value;
        bool value = ((CheckBox)repeatitem.FindControl("valuefield")).Checked;
        if (value) {
            checks.Add(new Guid(uid));
        }
    }
    // Now "checks" hold all the id's of the checked items
}
like image 1
Patrick Avatar answered Oct 19 '22 06:10

Patrick