This question already has answers here:
Better way to find control in ASP.NET
(9 answers)
Closed 4 years ago.
I'm sorry, but I can't understand why this doesn't work. After compile, I receive a "Null reference exception". Please help.
public partial class labs_test : System.Web.UI.Page
{
protected void Button1_Click(object sender, EventArgs e)
{
if (TextBox1.Text != "")
{
Label Label1 = (Label)Master.FindControl("Label1");
Label1.Text = "<b>The text you entered was: " + TextBox1.Text + ".</b>";
}
}
protected void DropDownList1_SelectedIndexChanged(object sender, EventArgs e)
{
Label Label1 = (Label)Master.FindControl("Label1");
Label1.Text = "<b>You chose <u>" + DropDownList1.SelectedValue + "</u> from the dropdown menu.</b>";
}
}
and UI:
<%# Page Language="C#" MasterPageFile="~/MasterPage.master" AutoEventWireup="true" CodeFile="test.aspx.cs" Inherits="labs_test" Title="Untitled Page" %>
<asp:Content ID="Content1" ContentPlaceHolderID="head" Runat="Server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1" Runat="Server">
Type in text and then click button to display text in a Label that is in the MasterPage.<br />
This is done using FindControl.<br />
<asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
<asp:Button ID="Button1" runat="server" OnClick="Button1_Click" Text="Submit" /><br />
<br />
Choose an item from the below list and it will be displayed in the Label that is
in the MasterPage.<br />
This is done using FindControl.<br />
<asp:DropDownList ID="DropDownList1" runat="server" AutoPostBack="True" OnSelectedIndexChanged="DropDownList1_SelectedIndexChanged">
<asp:ListItem>Item 1</asp:ListItem>
<asp:ListItem>Item 2</asp:ListItem>
<asp:ListItem>Item 3</asp:ListItem>
</asp:DropDownList>
<asp:Label ID="Label1" runat="server" Text="Label"></asp:Label>
</asp:Content>
Courtesy of Mr. Atwood himself, here's a recursive version of the method. I would also recommend testing for null on the control and I included how you can change the code to do that as well.
protected void Button1_Click(object sender, EventArgs e)
{
if (TextBox1.Text != "")
{
Label Label1 = FindControlRecursive(Page, "Label1") as Label;
if(Label1 != null)
Label1.Text = "<b>The text you entered was: " + TextBox1.Text + ".</b>";
}
}
protected void DropDownList1_SelectedIndexChanged(object sender, EventArgs e)
{
Label Label1 = FindControlRecursive(Page, "Label1") as Label;
if (Label1 != null)
Label1.Text = "<b>You chose <u>" + DropDownList1.SelectedValue + "</u> from the dropdown menu.</b>";
}
private Control FindControlRecursive(Control root, string id)
{
if (root.ID == id) return root;
foreach (Control c in root.Controls)
{
Control t = FindControlRecursive(c, id);
if (t != null) return t;
}
return null;
}
When Label1 exists on the master page:
How about telling the content page where your master page is
<%# MasterType VirtualPath="~/MasterPages/PublicUI.Master" %>
Then making a method in the master like
public void SetMessage(string message)
{
Label1.Text = message;
}
And call it in page's code behind.
Master.SetMessage("<b>You chose <u>" + DropDownList1.SelectedValue + "</u> from the dropdown menu.</b>");
When Label1 exists on the content page
If it is simply on the same page, just call Label1.Text = someString;
or if you for some reason need to use FindControl, change your Master.FindControl to FindControl
FindControl only searches in the immediate children (technically to the next NamingContainer), not the entire control tree. Since Label1 is not an immediate child of Master, Master.FindControl won't locate it. Instead, you either need to do FindControl on the immediate parent control, or do a recursive control search:
private Control FindControlRecursive(Control ctrl, string id)
{
if(ctrl.ID == id)
{
return ctrl;
}
foreach (Control child in ctrl.Controls)
{
Control t = FindControlRecursive(child, id);
if (t != null)
{
return t;
}
}
return null;
}
(Note this is convenient as an extension method).
Related
I create textbox dynamically, but can't retrieve a value from created textbox. Anyone can explain to me what am I doing wrong?
HtmlGenericControl testes = new HtmlGenericControl("DIV");
testes.ID = "Div_Cabos_Rede";
testes.Attributes.Add("class", "col-md-12 letra");
testes.InnerHtml = "Cabos de rede";
TextBox Cabos_de_rede = new TextBox();
Cabos_de_rede.ID = "Txt_Cabos_Rede";
Cabos_de_rede.Attributes.Add("class", "col-md-12 form-control");
testes.InnerHtml = "Cabos de rede";
Body.Controls.Add(testes);
Body.Controls.Add(Cabos_de_rede);
This works fine almost fine (minor unrelated css problems), but when later I try to retrieve data from dynamically created textbox I get NULL value.
Here is my code to retrieve value:
TextBox testar = (TextBox)Body.FindControl("Txt_Cabos_Rede");
ScriptManager.RegisterStartupScript(this, GetType(), "alert", "alert('" + testar + "');", true);
The main problem dealing with dynamically created control is you need to reload them back in either Page Init or Page Load event.
FYI: We normally use either Panel or PlaceHolder to load controls instead of Body tag, so that we can style them easily.
ASPX
<%# Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="DemoWebForm.Default" %>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<asp:PlaceHolder runat="server" ID="PlaceHolder1" />
<asp:Button runat="server" ID="SubmitButton" Text="Submit" OnClick="SubmitButton_Click" />
<br />
Posted Value:
<asp:Label runat="server" ID="ResultLabel" />
</form>
</body>
</html>
Code Behind
public partial class Default : System.Web.UI.Page
{
protected void Page_Init(object sender, EventArgs e)
{
HtmlGenericControl testes = new HtmlGenericControl("DIV");
testes.ID = "Div_Cabos_Rede";
testes.Attributes.Add("class", "col-md-12 letra");
testes.InnerHtml = "Cabos de rede";
TextBox Cabos_de_rede = new TextBox();
Cabos_de_rede.ID = "Txt_Cabos_Rede";
Cabos_de_rede.Attributes.Add("class", "col-md-12 form-control");
testes.InnerHtml = "Cabos de rede";
PlaceHolder1.Controls.Add(testes);
PlaceHolder1.Controls.Add(Cabos_de_rede);
}
protected void SubmitButton_Click(object sender, EventArgs e)
{
TextBox testar = FindControlRecursive(PlaceHolder1, "Txt_Cabos_Rede") as TextBox;
ResultLabel.Text = testar.Text;
}
// Custom method to search a control recursively
// in case it is nested inside other control.
// You can create it as an extension method if you would like.
public static Control FindControlRecursive(Control root, string id)
{
if (root.ID == id)
return root;
return root.Controls.Cast<Control>()
.Select(c => FindControlRecursive(c, id))
.FirstOrDefault(c => c != null);
}
}
I know you have a lot of questions. Before commenting on this question, please create a new project, and make this very simple code to work.
Very simple but I can't figure out why it wont works.
I got five TextBox and one Button, click to count the number of TextBox.
<%# Page Title="Home Page" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="Testing._Default" %>
<asp:Content ID="BodyContent" ContentPlaceHolderID="MainContent" runat="server">
<asp:Label ID="Label1" runat="server" Text="Test"></asp:Label> </br>
<asp:TextBox ID="TextBox1" runat="server" Width="40px"></asp:TextBox>
<asp:TextBox ID="TextBox2" runat="server" Width="40px"></asp:TextBox>
<asp:TextBox ID="TextBox3" runat="server" Width="40px"></asp:TextBox>
<asp:TextBox ID="TextBox4" runat="server" Width="40px"></asp:TextBox>
<asp:TextBox ID="TextBox5" runat="server" Width="40px"></asp:TextBox>
</br>
<asp:Button ID="Button1" runat="server" Text="Generate" OnClick="Button1_Click" />
</asp:Content>
Code behind
protected void Button1_Click(object sender, EventArgs e)
{
var List = this.Controls.OfType<TextBox>();
Label1.Text = List.Count().ToString();
}
But the result return me 0.
Since your TextBoxes are inside a ContentPlaceHolder so you need to replace the this keyword with your ContentPlaceHolder(MainContent). This should works as you want:
var List = (Page.Master.FindControl("MainContent") as ContentPlaceHolder)
.Controls.OfType<TextBox>();
Label1.Text = List.Count().ToString();
Most solutions will not work if text boxes are in tables or divs. The only way is to recursively look for them in all controls. Paste the following function in a class somewhere.
public static List<Control> GetAllControls(List<Control> controls, Type t, Control parent) //first call pass this.Page as the 'Parent' parameter
{
foreach (Control c in parent.Controls)
{
if (c.GetType() == t)
controls.Add(c);
if (c.HasControls())
controls = GetAllControls(controls, t, c);
}
return controls;
}
Then from your asp page you call it as follows.
List<Control> list = new List<Control>();
list = GetAllControls(list, typeof(TextBox), this.Page);
This gets all controls of the type you passed (in my example it was TextBox)
Then you can iterate through the list of Textbox controls.
foreach (Control ctl in list)
{
if (ctl.GetType() == typeof(TextBox)) //this should always test true but you i left it here for clarity
{
//do something
((TextBox)ctl).Attributes.Add("onfocus", "this.select()");
}
}
Here is my implementation to add the select() attribute to all text boxes.
//my asp page
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
Utils.SetTextBoxFocusSelect(this.Page);
}
//in a utilities class
public class Utils
{
public static void SetTextBoxFocusSelect(Page page)
{
List<Control> list = new List<Control>();
list = GetAllControls(list, typeof(TextBox), page);
foreach (Control ctl in list)
{
if (ctl.GetType() == typeof(TextBox))
{
((TextBox)ctl).Attributes.Add("onfocus", "this.select()");
}
}
}
public static List<Control> GetAllControls(List<Control> controls, Type t, Control parent /* can be Page */)
{
foreach (Control c in parent.Controls)
{
if (c.GetType() == t)
controls.Add(c);
if (c.HasControls())
controls = GetAllControls(controls, t, c);
}
return controls;
}
}
When finding a control that caused the postback, <asp:ImageButton> & <asp:Button> are exceptions as they do not use __doPostBack function. This fact is also supported by this Article.
So, as the article pointed above uses a hiddenField, Javascript codes for workaround, Is there a more elegant way of doing this??
What I want is when using a Button/ImageButton control, i still want to use Request.Form["__EVENTTARGET"] to somehow get the control name. Is there any setting that i need to know??
OR
Any property of Button / ImageButton that will make it use the __doPostBack function ??
Below is code I was trying ::
EventTargets.aspx
<%# Page Title="" Language="C#" MasterPageFile="~/Site.master" AutoEventWireup="true"
CodeFile="EventTargets.aspx.cs" Inherits="EventTargets" %>
<asp:Content ID="Content1" ContentPlaceHolderID="HeadContent" Runat="Server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" Runat="Server">
<asp:Button ID="btnAdd" runat="server" Text="Add"
ClientIDMode="Static"/>
</asp:Content>
And the complete code of my cs file:
EventTargets.aspx.cs
public partial class EventTargets: System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (IsPostBack)
{
string CtrlID = string.Empty;
// for Button Controls, this is always string.Empty
// and therefore it doesn't goes inside IF statement
if (Request.Form["__EVENTTARGET"] != null &&
Request.Form["__EVENTTARGET"] != string.Empty)
{
CtrlID = Request.Form["__EVENTTARGET"];
}
}
}
}
I don't know its ELEGANT WAY for you or not :) but it is easy..see..
protected void Page_Load(object sender, EventArgs e)
{
if (IsPostBack)
{
Control c = GetPostBackControl(this.Page);
string ctrlId = c.ID;
}
}
private Control GetPostBackControl(Page page)
{
Control control = null;
string postBackControlName = Request.Params.Get("__EVENTTARGET");
string eventArgument = Request.Params.Get("__EVENTARGUMENT");
if (postBackControlName != null && postBackControlName.Length > 0)
{
control = Page.FindControl(postBackControlName);
}
else
{
foreach (string str in Request.Form)
{
Control c = Page.FindControl(str);
if (c is Button)
{
control = c;
break;
}
}
}
return control;
}
I write one example to create own control on ASP.NET Froms. The controls very simple- combobox and button. User need choose value and when after he submit the button, the value from combobox need display in label.
So. Code of my Control:
public class MyControl:Control,IPostBackEventHandler
{
protected override void Render(HtmlTextWriter writer)
{
writer.AddAttribute("size","1");
writer.AddAttribute("ID","List2");
writer.AddAttribute("name", "ListYear");
writer.RenderBeginTag(HtmlTextWriterTag.Select);
for (int i = 1950; i < DateTime.Now.Year; i++)
{
writer.RenderBeginTag(HtmlTextWriterTag.Option);
writer.WriteEncodedText(i.ToString());
writer.RenderEndTag();
}
writer.RenderEndTag();
writer.AddAttribute("type","submit");
writer.AddAttribute("value","ClickMe");
writer.AddAttribute("name","BtnChange");
writer.RenderBeginTag(HtmlTextWriterTag.Input);
writer.RenderEndTag();
base.Render(writer);
}
public delegate void OnClickEventHandler(object sender, EventArgs args);
public event OnClickEventHandler Click;
public void RaisePostBackEvent(string eventArgument)
{
Click(this, new EventArgs());
}
}
The Page ASP:
<%# Page Language="C#" AutoEventWireup="true" CodeBehind="TestMyControl.aspx.cs" Inherits="Hello.TestMyControl" %>
<%# Register assembly="Hello" namespace="Hello" tagPrefix="MyContrl" %>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:Label ID="Label1" runat="server" Text="Label" Visible="False"></asp:Label>
<br />
<MyContrl:MyControl runat="server" OnClick="Unnamed1_OnClick" ID="Control1"></MyContrl:MyControl>
</div>
</form>
</body>
</html>
And in the end Event function:
protected void Unnamed1_OnClick(object sender, EventArgs args)
{
Label1.Visible = true;
Label1.Text="You choose "+Control1.????+" year";
}
What substitute for a question mark that take the value from the list?
P.S. Something strange is going on. Because when I click the button, the handler is not called, and I can not get into Unnamed1_OnClick
Since you have set the value on an attribute, to retrieve it you need to access Attributes property
Make your control inherit from HtmlControl
public class MyControl : HtmlControl, IPostBackEventHandler
{
...
On your page
<MyContrl:MyControl runat="server" OnClick="Unnamed1_OnClick" ID="Control1"></MyContrl:MyControl>
On your code
Label1.Text = Control1.Attributes["value"];
You can debug this line to see all available attributes
You would need to pass the name of the combobox and its the value of the text that is selected.
Like this:
protected void Unnamed1_OnClick(object sender, EventArgs args)
{
Label1.Visible = true;
Label1.Text="You choose "+ myCustomControl.SelectedItem.Value.ToString()
+ " year";
}
(Sorry. I misread the initial post and edited my code accordingly once I realized my mistake.)
Add select list to your user control with name="YourSelectList"
then in the click event handler
protected void Unnamed1_OnClick(object sender, EventArgs args)
{
Label1.Visible = true;
Label1.Text="You choose "+Control1.YourSelectList.SelectedValue.ToString()+" year";
}
My code generates an TextBox on the fly in C# (page_load function). Can I access it in the code later? It does give me compilation error and does not seem to work. Can someone verify ?
Code for additonal problem
aContent += "<table>";
aContent += "<tr><td>lablel </td><td style='bla blah'><input type='textbox' id='col-1' name='col-1'/></td></tr> ... 10 such rows here
</table>"
spanMap.InnerHtml = aContent;
The contents are rendered OK but recusrive iteration does not return the textbox. I am calling it like this
TextBox txt = (TextBox)this.FindControlRecursive(spanMap, "col-1");
// txt = (TextBox) spanMapping.FindControl("col-1"); this does not work too
if (txt != null)
{
txt.Text = "A";
}
Assuming that you're persisting it correctly, you should be able to access it in code-behind using the FindControl method. Depending on where the control is, you may have to search recursively through the control hierarchy:
private Control FindControlRecursive(Control root, string id)
{
if (root.ID == id)
{
return root;
}
foreach (Control c in root.Controls)
{
Control t = FindControlRecursive(c, id);
if (t != null)
{
return t;
}
}
return null;
}
Using FindControlRecursive:
TextBox txt = this.FindControlRecursive(Page.Form, "TextBox1") as TextBox;
if (txt != null)
{
string text = txt.Text;
}
If you still can't find it using the above method, make sure that you're creating the control during after every postback, somwhere before Page_Load, like OnInit.
EDIT
I think you need to change the way you're adding content to the container. Instead of using a <span>, I would use a Panel, and instead of building markup, simply add controls to the panel in code-behind:
TextBox txt = new TextBox();
txt.ID = String.Format("txt_{0}", Panel1.Controls.Count);
Panel1.Controls.Add(txt);
Here's an example:
<%# Page Language="C#" %>
<script type="text/C#" runat="server">
protected void Page_Load(object sender, EventArgs e)
{
var textBox = new TextBox();
textBox.ID = "myTextBox";
textBox.Text = "hello";
Form1.Controls.Add(textBox);
}
protected void BtnTestClick(object sender, EventArgs e)
{
var textBox = (TextBox)Form1.FindControl("myTextBox");
lblTest.Text = textBox.Text;
}
</script>
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<form id="Form1" runat="server">
<asp:LinkButton ID="btnTest" runat="server" Text="Click me" OnClick="BtnTestClick" />
<asp:Label ID="lblTest" runat="server" />
</form>
</body>
</html>