I've been trying to follow a few tutorials and code examples but something isn't working as I'd expect.
I have a series of textboxes on a webpage and I need to loop through each one and save it's value to the database. The number of textboxes on the page will vary. I load them from a database. They are all added to a table object.
TableCell cellb = new TableCell();
TextBox txtAnswer = new TextBox();
txtAnswer.TextMode = TextBoxMode.MultiLine;
txtAnswer.Rows = 2;
txtAnswer.ID = "field_" + dataRow["fieldID"].ToString();
txtAnswer.Text = "answer"; //this will be got from the database
cellb.Controls.Add(txtAnswer);
so that adds the textbox to the table row. I then have a save button which has the following code
foreach (Control c in Page.Controls)
{
foreach (Control childc in c.Controls)
{
if (childc is TextBox)
{
TextBox tmpText = (TextBox)childc;
tmpField = tmpText.ID.Split('_');
fieldID = Convert.ToInt32(tmpField[1]);
//save value to the database (eventually)
debug.InnerHtml += tmpText.Text; //this just outputs the values for now
}
}
}
So the above should loop though all the page controls and find the textfields as added on the page_load. However, I am now wondering if it's because they don't exist. So when I save the page, it doesn't know of the controls. I can see the table control, but nothing inside it.... any ideas?!
Dynamic controls must be added on each page request. Preferably during the Init event. It sounds like they have not been added yet (again) by the time you iterate through your controls.
Also, if you know your TextBoxes are within a specific control you should probably find that control first and then, using the same approach you are using, iterate over the controls. Two reasons for this are: efficiency and also, on your code, you are only searching two levels down from the page control. This may be ok but it includes other controls that will not contain any of those textboxes.
Why dont you use a Gridview Control and Then Make custom template with Texbox. Then ON Page Load you can add no of Textboxes you want and then loop the gridview and save the data.
First thing. Make sure you are not creating your controls inside an if (!isPostBack){} - as they need recreating on each postback.
Secondly, I don't believe your loop will find all controls as it will only really travel through the first level.
Ideally, you should search for the controls recursivley.
Here is a recursive method that I use - this will help find all controls of a given ID.
/// <summary>
/// Finds a Control recursively. Note finds the first match that exists
/// </summary>
/// <param name="ContainerCtl">Should be the lowest container in the heirarchy, for eg dont choose Master page if you can pick the specific panel</param>
/// <param name="IdToFind">ID of the control you are looking for</param>
/// <returns>the control if found else null</returns>
public static Control FindControlRecursive(Control Root, string Id)
{
if (Root.ID == Id) { return Root; }
foreach (Control Ctl in Root.Controls)
{
Control FoundCtl = FindControlRecursive(Ctl, Id);
if (FoundCtl != null) { return FoundCtl; }
}
return null;
}
Now, what I would do is:
When you create your TextBox's, store all the ID's in an Array. Then when you need to access them, loop through the Array and for each entry, call the above method. this will then return the TextBox you need.
Related
I have a windows form that just exists to take input from user, for all intents and purposes it is just a label and a corresponding input box (textbox, checkbox, masket textbox etc).
I have programatically placed all the input fields in a TabIndex order that is optimal for cycling through them in a way that fits with their locations (tab down each column of inputs, then to the top of the next column).
The person that I am building this project for has stipulated that only like each textbox to come available one at a time, as the previous one has been filled out. This is a crude way of doing it with no validation, but essentially something like this...
if (String.IsNullOrEmpty(textbox1.Text))
{
textbox2.Enabled = true
}
So this is fine to do with two textboxes in this example, but the form has 28 different inputs on it, so an absurd series of if statements will only be a last resort.
My thoughts has been to put all the inputs in a List, ideally in the same order as is their TabIndexes. I tried to do this using a foreach loop...
List<Control> inputsList = new List<Control>();
public void initialiseControls()
{
//control position to insert control into list at specified index
int cntrlpos = 0;
//for every control in form
foreach (Control cntrl in this.Controls)
{
//not counting labels (not input fields)
if (!(cntrl is Label))
{
//set list position to equal control's TabIndex
cntrlpos = cntrl.TabIndex;
//insert the control into the list at the position reflecting TabIndex
inputsList.Insert(cntrlpos, cntrl); //<---- Error Occurs
//ASK TEXTBOX TO OUTPUT LIST POSITION AS TEST
//foreach (var txtbx in this.Controls.OfType<TextBox>())
//{
// txtbx.Text = Convert.ToString(cntrlpos);
//}
}
}
As soon as the function is called, an exception is thrown stating that "Index must be within the bounds of the list".
When I put a breakpoint into the code, it showed cntrlpos to equal 29, which is more than the 28 total input controls there are on the form.
I don't know where to go from here, if anyone can offer some advice on the code above to place the Controls into the list in the correct order (or point me in the direction of another method to do something like this), then I would really appreciate it.
Thanks,
Mark
To make your list, try this:
List<Control> inputList =
(from Control c in getAllControls(this)
where c.TabStop
orderby c.TabIndex
select c).ToList();
Define the method getAllControls elsewhere in your form class:
IEnumerable<Control> getAllControls(Control parent)
{
foreach (Control control in parent.Controls)
{
yield return control;
foreach (Control descendant in getAllControls(control))
yield return descendant;
}
}
(Taken and modified slightly from Recursive control search with Linq)
This will make it so that you get even nested controls (such as those in panels or groupboxes).
You can't just use the TabIndex as an index into your list, because even stuff like labels have tab indices, which will mess up your indices.
I think you've over complicated it...
Just use Control.GetNextControl:
Retrieves the next control forward or back in the tab order of child
controls.
For example, with just TextBoxes:
private void textBoxes_TextChanged(object sender, EventArgs e)
{
Control ctl = (Control)sender;
if (!String.IsNullOrEmpty(ctl.Text))
{
Control next = this.GetNextControl(ctl, true);
if (next != null)
{
next.Enabled = true;
}
}
}
Obviously you might need a slightly more complicated check for some other types of controls in a different handler, but you'd still just grab the next control to enable using GetNextControl().
I'm trying to perform an operation on every control on a page that is inherited from a masterpage. I don't know how to access the child pages controls. I have tried recursively getting to my controls like this:
private void checkControls(ControlCollection controlcollection)
{
foreach (Control control in controlcollection)
{
if (control.Controls.Count > 0)
{
Debug.WriteLine(control.GetType().ToString());
checkControls(control.Controls);
}
else
{
Debug.WriteLine(control.GetType().ToString());
}
}
The method is called like this:
protected void resettodefault()
{
checkControls(this.Page.Controls);
}
However, the only controls that are printed from this execution are:
ASP.site_master
System.Web.UI.LiteralControl
I would prefer to access my controls directly (without recursion). Otherwise, how can I modify my recursion to get to the desired page's controls?
I would suggest using a base page instead of a master page, this way your logic for iterating over controls (and whatever you will do with that afterwards) is not tied to which master page a page is using.
As far as getting all the controls on the page, because the controls are hierarchical, as is the HTML they represent, so iterating over them recursively makes sense. However if you are dead set on not recursively getting controls something like this should work:
IEnumerable<Control> GetAllControls()
{
var allControls = new List<Control>();
var currentControls = new Queue<Control>();
currentControls.Enqueue(this.Page);
while (currentControls.Count >0)
{
var c = currentControls.Dequeue();
if (!allControls.Contains(c))
{
allControls.Add(c);
if (c.Controls != null && c.Controls.Count > 0)
{
foreach (Control e in c.Controls)
{
currentControls.Enqueue(e);
}
}
}
}
return allControls;
}
The last thing to consider is the lifecycle of the page, and when you iterate over the controls. If you try to walk to control tree too early not all controls may exist.
EDIT: Updated code.
Update
For validation purposes I would highly recommend using the built in validation controls of asp.net. You can read more about them here. This has the added benefit of providing validation on the client, providing faster UI responses and easing the load off the servers.
For resetting all the textboxes. I would recommend creating a new class for this purpose, then calling upon that class when needed instead of messing with the master page:
public class UIControlsHelper
{
public static void ClearTextboxes(Page page)
{
GetAllControls(page)
.Where(x => typeof(TextBox).IsAssignableFrom(x.GetType())
.ToList()
.ForEach(x => (TextBox)x.Text = string.Empty);
}
IEnumerable<Control> GetAllControls(Page page)
// Same as above, but with the page parameter replaced.
}
}
And in any of your pages:
UIControlsHelper.ClearTextboxes(this);
To access the controls in your child page do the following steps:
1-declare a variable of the type you want to access. For example if you want to access a Label in your child page use:
Label lbl_child=this.ContentPlaceHolder1.findcontrol("your label id in child page") as Label;
Now you have your label and you are free to make changes on it. Every change on this control will be reflected on the child control.
ContentPlaceHolder1 is your contentplace holder id so change it with your content id.
public void ClearTextboxes(Page page) {
GetAllControls(page)
.Where(x => typeof(TextBox).IsAssignableFrom(x.GetType()))
.ToList()
.ForEach(x => ((TextBox)x).Enabled=false);
}
I am working on a solution in C# and ASP.NET 4.0 I am trying to get the value of a radiobutton from my page that was dynamically created based on some database information.
Here is what gets generated in the page source:
<td>
<input id="masterMain_3Answer_0" type="radio" name="ctl00$masterMain$3Answer"
value="Y" onclick="return answeredyes(3);" />
<label for="masterMain_3Answer_0">Y</label>
</td>
<td>
<input id="masterMain_3Answer_1" type="radio" name="ctl00$masterMain$3Answer"
value="N" onclick="return answeredno(3,'desc');" />
<label for="masterMain_3Answer_1">N</label>
</td>
Inside the OnClick function of my submit button I want to gather wether Y or N has been selected based on the user's input.
Here is what I have written so far:
RadioButton _rbAnswer = new RadioButton();
RadioButtonList _rbList = new RadioButtonList();
ContentPlaceHolder cp = (ContentPlaceHolder)Master.FindControl("masterMain");
_rbAnswer = (RadioButton)Master.FindControl("masterMain_3Answer_0");
HtmlInputRadioButton rb = (HtmlInputRadioButton)Master.FindControl("masterMain_3Answer_0");
_rbAnswer = (RadioButton)cp.FindControl("masterMain_3Answer_0");
_rbList = (RadioButtonList)cp.FindControl("masterMain_3Answer_0");
I am able to get the ContentPlaceHolder without any issues but the rest of the objects are null after it attempts to get the . I have also attempted removing the "masterMain_" but still doesn't want to find the controls.
Here is the code in which the individual radiobuttonlists are added
TableRow _tempRow = new TableRow();
TableCell _cellOK = new TableCell();
RadioButtonList _rbList = new RadioButtonList();
_rbList.ID = r[0].ToString()+"Answer";
_rbList.RepeatDirection = RepeatDirection.Horizontal;
//add options for yes or no
ListItem _liOk = new ListItem();
_liOk.Value = "Y";
ListItem _linotOk = new ListItem();
_linotOk.Value = "N";
_rbList.Items.Add(_linotOk);
//add cell to row
_rbList.Items.Add(_liOk);
_cellOK.Controls.Add(_rbList);
_tempRow.Cells.Add(_cellOK);
//add the row to the table
stdtable.Rows.Add(_tempRow);
To be able to quickly find dynamically created controls, add a dictionary to your page class:
private Dictionary<string, Control> fDynamicControls = new Dictionary<string, Control>();
then when a new control is created in code and its ID is assigned:
fDynamicControls.Add(newControl.ID, newControl);
and when you need control's reference:
Control c = fDynamicControls["controlIdThatYouKnow"];
When using FindControl don't use the id that's generated by the page. Use the ID that you specified inthe aspx.
If this is inside a Repeateror another DataBound control, you have to first find the current record. (GridViewRow or RepeaterItem) first, an use that item's .FindControl function.
See this (different - not duplicate) question to see a code example of how to do it: How to find control with in repeater on button click event and repeater is placed with in gridview in asp.net C#
When you create dynamic controller give specific ids for them. This facilitate to generate controls with our own id. therefore then we can access the controls with this id.
And also use OnInit life cycle event to generate dynamic controllers, this is the best place to generate them.
RadioButton _rbAnswer = new RadioButton();
_rbAnswer.ID="ranswerid";
Given your update, you'll find that your control heirarchy is fairly deep. You have a RadioButtonList inside a cell inside a row inside a table ...
FindControl is a method that needs to be called on a specific object and can only find objects that are actual children of that object. In this case, you either need to build a recursive method or go directly to the control in question. Since so many of these controls are generated dynamically, you'll have no real way of accessing them directly so building the recursive function may be simplest. However, on very large pages this method can be very resource consuming:
public static WebUserControl FindControlRecursive(this WebUserControl source, string name)
{
if (source.ID.Equals(name, StringComparison.Ordinal))
return source;
if (!source.Controls.Any()) return null;
if (source.Controls.Any(x => x.ID.Equals(name, StringComparison.Ordinal))
return source.FindControl(name);
WebUserControl result = null;
// If it falls through to this point then it
// didn't find it at the current level
foreach(WebUserControl ctrl in source.Controls)
{
result = ctrl.FindControlRecursive(name);
if (result != null)
return result;
}
// If it falls through to this point it didn't find it
return null;
}
This is an extension method that would allow you to call this on your ContentPlaceHolder control:
var _cp = (ContentPlaceHolder)Master.FindControl("masterMain");
RadioButtonList _rbList = _cp.FindControlRecursive("3Answer");
if (_rbList != null)
// ... Found it
Note: Treat the above as psuedo-code. It has not be implemented by me anywhere so may (likely) require tweaking to behave exactly right.
HEllo, I need to dynamically activate fields in a page according to the service that is going to be executed...
Let me explain:
There's a page with all the possible fields and a ListBox with all the selected services to be executed, then when the user selects which service to execute (change a car plate, for example), then I need to activate only the field(s) that the service require... (The realationship between Services and Fields are stored in a database).
public void CheckAll(int pService_Id, Control pPage)
{
foreach (Control control in pPage.Controls)
{
busExecutaServico vExecuta = new busExecutaServico();
if (vExecuta.EnableField(control.ID.ToString(), Convert.ToInt32(listBoxServices.SelectedValue)))
{
switch (control.GetType().ToString())
{
case "TextBox":
TextBox controleText = (TextBox)Page.FindControl(control.ID.ToString());
controleText.Enabled = true;
break;
Note that busExecutaServico is the class which contains the method (EnableField) for checking if the selected item matches any field on the database..
I can't seem to get the control.ID.ToString() to work properly (the ID always comes as NULL)
If anyone can help me solve this, or if there's another way (even if it's completely different from what i'm trying), it would be of great help. thanks
I like to use a recursive function for locating controls by either type or ID.
public Control FindControlRecursive(Control rootControl, string controlId)
{
if (rootControl.ID == controlId)
return rootControl;
foreach (Control control in rootControl.Controls)
{
Control foundControl = FindControlRecursive(control, controlId);
if (foundControl != null)
{
return foundControl;
}
}
return null;
}
public Control FindControlRecursive(Control rootControl, Type type)
{
if (rootControl.GetType().Equals(type))
return rootControl;
foreach (Control control in rootControl.Controls)
{
Control foundControl = FindControlRecursive(control, type);
if (foundControl != null)
{
return foundControl;
}
}
return null;
}
You can adapt these to first return a collection of controls, then process them later. Might be easier to keep track of what's happening.
I learned this technique here: http://www.west-wind.com/Weblog/posts/5127.aspx
Be aware that FindControl only searches the current naming container so Page.FindControl will only find controls that are added directly to Page. For example, if you had a repeater control that had the controls you were looking for and it was added to Page, you could find your repeater control via Page.FindControl but it wouldn't find child controls within your repeater, you'd have to recursively perform the FindControl on all container controls in the page.
This might seem a bit strange but it allows you to have controls with the same ID existing on the same page. For example, if you had 10 instances of a user control with textboxes within them called "MyName", you'd really want them to not being over-writing each other's 'MyName' fields!
Your code will come across a null for an ID unless every control has been given an ID.
Also why use:-
TextBox controleText = (TextBox)Page.FindControl(control.ID.ToString());
at all instead of:-
TextBox controleText = (TextBox)control;
and indeed since you only want to change the Enabled property consider:-
((WebControl)control).Enabled = False;
That I suspect will eliminate many case statements.
In your code you don't need to search any control - you already have it in 'control' variable. You even don't need to cast it to TextBox, just to a WebControl, just do this:
...
if (vExecuta.EnableField(control.ID.ToString(), Convert.ToInt32(listBoxServices.SelectedValue)))
((WebControl)control).Enabled = true;
P.S. control.ID is already string, so you should remove any ID.ToString() also.
I have a master page which is nested 2 levels. It has a master page, and that master page has a master page.
When I stick controls in a ContentPlaceHolder with the name "bcr" - I have to find the controls like so:
Label lblName =(Label)Master.Master.FindControl("bcr").FindControl("bcr").FindControl("Conditional1").FindControl("ctl03").FindControl("lblName");
Am I totally lost? Or is this how it needs to be done?
I am about to work with a MultiView, which is inside of a conditional content control. So if I want to change the view I have to get a reference to that control right? Getting that reference is going to be even nastier! Is there a better way?
Thanks
Finding controls is a pain, and I've been using this method which I got from the CodingHorror blog quite a while ago, with a single modification that returns null if an empty id is passed in.
/// <summary>
/// Recursive FindControl method, to search a control and all child
/// controls for a control with the specified ID.
/// </summary>
/// <returns>Control if found or null</returns>
public static Control FindControlRecursive(Control root, string id)
{
if (id == string.Empty)
return null;
if (root.ID == id)
return root;
foreach (Control c in root.Controls)
{
Control t = FindControlRecursive(c, id);
if (t != null)
{
return t;
}
}
return null;
}
In your case, I think you'd need the following:
Label lblName = (Label) FindControlRecursive(Page, "lblName");
Using this method is generally much more convenient, as you don't need to know exactly where the control resides to find it (assuming you know the ID, of course), though if you have nested controls with the same name, you'll probably get some strange behavior, so that might be something to watch out for.
Firstly, you should know that MasterPages actually sit inside Pages. So much so that a MasterPage's Load event is actually called after your ASPX's Load event.
This means, the Page object is actually the highest control in the control hierarchy.
So, knowing this, the best way to find any control in such a nested environment, is to write a recursive function that loops through every control and child controls until it finds the one you're looking for. in this case, your MasterPages are actually child controls of the main Page control.
You get to the main Page object from inside any control like this:
C#:
this.Page;
VB.NET
Me.Page
I find that usually, the Control's class FindControl() method is pretty useless, as the enviroment is always nested.
Because if this, I've decided to use .NET's 3.5 new Extension features to extend the Control class.
By using the code below (VB.NET), in say, your AppCode folder, all your controls will now peform a recursive find by calling FindByControlID()
Public Module ControlExtensions
<System.Runtime.CompilerServices.Extension()> _
Public Function FindControlByID(ByRef SourceControl As Control, ByRef ControlID As String) As Control
If Not String.IsNullOrEmpty(ControlID) Then
Return FindControlHelper(Of Control)(SourceControl.Controls, ControlID)
Else
Return Nothing
End If
End Function
Private Function FindControlHelper(Of GenericControlType)(ByVal ConCol As ControlCollection, ByRef ControlID As String) As Control
Dim RetControl As Control
For Each Con As Control In ConCol
If ControlID IsNot Nothing Then
If Con.ID = ControlID Then
Return Con
End If
Else
If TypeOf Con Is GenericControlType Then
Return Con
End If
End If
If Con.HasControls Then
If ControlID IsNot Nothing Then
RetControl = FindControlByID(Con, ControlID)
Else
RetControl = FindControlByType(Of GenericControlType)(Con)
End If
If RetControl IsNot Nothing Then
Return RetControl
End If
End If
Next
Return Nothing
End Function
End Module
Although I love recursion, and agree with andy and Mun, one other approach you may want to consider is to have a strongly typed Master page. All you have to do is add one directive in your aspx page.
Instead of accessing a page's control from your master page, consider accessing a control in your master page from the page itself. This approach makes a lot of sense when you have a header label on your master page, and want to set its value from each page that uses the master.
I'm not 100% sure, but I think this would be simpler technique with nested master pages, as you would just point the VirtualPath to the master containing the control you wish to access. It might prove to be tricky though if you want to access two controls, one in each respective master page.
Here is a code that is more generic and works with a custom condition (that can be a lambda expression!)
Call:
Control founded = parent.FindControl(c => c.ID == "youdId", true);
Control extension
public static class ControlExtensions
{
public static Control FindControl(this Control parent, Func<Control, bool> condition, bool recurse)
{
Control founded = null;
Func<Control, bool> search = null;
search = c => c != parent && condition(c) ? (founded = c) != null :
recurse ? c.Controls.FirstOrDefault(search) != null :
(founded = c.Controls.FirstOrDefault(condition)) != null;
search(parent);
return founded;
}
}
I have used the <%# MasterType VirtualPath="~/MyMaster.master" %> method. I have a property in the main master page then in the detail master page other property with the same name calling the main master property and it works fine.
I have this in the main master page
public string MensajeErrorString
{
set
{
if (value != string.Empty)
{
MensajeError.Visible = true;
MensajeError.InnerHtml = value;
}
else
MensajeError.Visible = false;
}
}
this is just a div element that have to show an error message. I would like to use this same property in the pages with the detail master page(this is nested with the main master).
Then in the detail master I have this
public string MensajeErrorString
{
set
{
Master.MensajeErrorString = value;
}
}
Im calling the main master property from the detail master to create the same behavior.
I just got it working perfectly.
In contentpage.aspx, I wrote the following:
If Master.Master.connectsession.IsConnected Then
my coded comes in here
End If