Controls property and FindControl function C# - c#

I'm coding a calendar that displays some events. Each day has a button for morning, afternoon and night events, when there are events to show the button is enabled and its color is changed. I am displaying these buttons in an html table and when someone changes the month being displayed the program has to "cleanup" the buttons by disabling all of them and setting their colors to white again. Thing is I was able to enable them by using the FindControl method on the table containing the buttons this way:
string butControl = /* id of the button */
Button block = mainTable.FindControl(butControl) as Button;
block.BackColor = Color.Gray;
block.Enabled = true;
And it works fine. In my cleanup method I don't want to call all the names of the buttons because there are 105, instead I used this method:
private void CleanUp()
{
foreach (Control c in mainTable.Controls)
{
Button bot = c as Button;
if (bot != null)
{
bot.BackColor = Color.White;
bot.Enabled = false;
}
}
}
But this does not change the color or enabled property of any of the buttons. My question is: Are not the controls in the Controls property of the table the same that can be found via the FindControl method? Or am I doing something wrong when retrieving the controls?

Isn't the problem that in you're iterating a list of controls rather than the hierarchy? FindControl uses the hierarchy. You can loop the controls as follows:
public IEnumerable<T> EnumerateRecursive<T>(Control root) where T : Control
{
Stack<Control> st = new Stack<Control>();
st.Push(root);
while (st.Count > 0)
{
var control = st.Pop();
if (control is T)
{
yield return (T)control;
}
foreach (Control child in control.Controls)
{
st.Push(child);
}
}
}
public void Cleanup()
{
foreach (Button bot in EnumerateRecursive<Button>(this.mainTable))
{
bot.BackColor = Color.White;
bot.Enabled = false;
}
}
You can implement it using recursion as well, but I usually prefer a stack because it is much faster.

I assume that you're using an ASP table, as that would certainly not work. You could get around it in other ways, but if it doesn't matter to you to use some HTML, I would suggest that you restructure it to look like this:
<form id="form1" runat="server">
<asp:Panel ID="mainTable" runat="server">
<table>
<tr>
<td>
<asp:Button ID="Button1" runat="server" Text="Button" />
</td>
</tr>
</table>
</asp:Panel>
</form>
Note the use of only html controls inside the asp:Panel except for the actual buttons. Using ASP, you would have to recursively look for children.
EDIT:
Speaking of recursively looking for children, Stefan made that exact suggestion and provided code before I finished writing, and I would definitely recommend his method; he's evidently much less lazy than me.
==================================
Stefan's approach has a slight error in that you can't explicitly typecast without knowing a type, and you can't know a type if you use generics, as he has. Here is a lazy adaptation for use purely with buttons, as you are using it for.
Do not give this "answer" status. It is a corruption of someone else's work.
public IEnumerable<Button> EnumerateRecursive(Control root)
{
// Hook everything in Page.Controls
Stack<Control> st = new Stack<Control>();
st.Push(root);
while (st.Count > 0)
{
var control = st.Pop();
if (control is Button)
{
yield return (Button)control;
}
foreach (Control child in control.Controls)
{
st.Push(child);
}
}
}
public void Cleanup()
{
foreach (Button bot in EnumerateRecursive(this.mainTable))
{
bot.BackColor = Color.White;
bot.Enabled = false;
}
}

Related

Array of buttons, textboxes and labels with their properties

Is it possible to store in the same array - list of different type elements declaring in the same array different property value (bool)? For exampe list of those elements:
GV1_BTNEdit.Visible = false;
GV1_BTNCancel.Visible = true;
GV1.Columns[3].Visible = true;
GV1.Columns[5].Visible = true;
GV1.Columns[4].Visible = true;
Lbl1_GV1.Visible = true;
Edit: (pseudo-code)
This is how funcionality should look like:
Declares a list/array of elements with a simultaneous declaration of parameters / properties of these elements:
list/element array (list_name)
{
textbox1.visible = true;
label1.visible = false;
button1.visible = true;
}
Calling a list/array, i.e. simultaneous assignment to the elements of the list - the property resulting from their declaration in the list:
list_name[];
Calling the list with the same property change, i.e. simultaneous assigning to all elements of the list the property visible = false:
list_name[i].visible = false;
Calling the list with changing the property of visible elements to the opposite (i.e. what was true will change to false, and what was false - to true):
list_name[i].visible != list_name[i].visible;
If, after all, it cannot be done in this particular way, is there another similar possibility?
As a general rule, often it is better to place a group of controls say inside of div, and then simple hide/show the div to show/hide the group of controls.
so say this:
<div id="BillAddress" runat="sever">
Text address, and more controls here
</div>
Then in code, you can hide/show the whole mess with
BillAddress.Style("display") = "none";
Or to show that group of controls, then this:
BillAddress.Style("display") = "normal";
So, web land, and web markup lends itself to MUCH better using span's, or div's, or whatever to group controls.
And the beauty of above, is then you can also often wirte client side code in JavaScript to hide/show groups of controls inside that div 100% with client side code.
Having stated + suggested the above?
You can also say cook up your "own" attributes on a group of controls.
Say:
<asp:TextBox ID="TextBox1" runat="server"
MyGroup="Billing" >
</asp:TextBox>
<asp:TextBox ID="txtNewCity" runat="server"
MyGroup="Billing>
</asp:TextBox>
So, note how in above I just made up a attribute for the control.
Then, in code, can do this:
For Each c As System.Web.UI.Control In Page.Controls
If c.Attributes("MyBilling") IsNot Nothing Then
// hide/show the control
So, in web land, I don't really think it makes a whole lot of sense to attempt to group some controls in a array - just drop all those controls inside of some div, and hide/show that div. But, you can use "tag" or even cook up + add your own attributes to controls, and then loop the controls in that page, or div or whatever.
For example, I cooked up my own "data binder" routine.
I simple will place a group of controls say inside of a "div" with a "id" and runat server.
Then, to load data into that "div" and set of controls?
I place controls in a div, and then I can do this:
int intPK = (int)ViewState[this.ID + "_MyPk"];
string strSQL = "SELECT * FROM " + this.MyTable + " WHERE ID = " + intPK;
DataTable rstData = General.MyRst(strSQL);
General.FLoader(this.EditRecord, rstData.Rows[0]);
In above, EditRecord is my "div"
The markup looks like:
<asp:TextBox ID="txtHotel" runat="server" f="HotelName" width="280"></asp:TextBox> <br />
<asp:TextBox ID="tFN" runat="server" f="FirstName" Width="140"></asp:TextBox> <br />
etc. etc.
So, I use a made up attribute called "f", where f="database column name"
So, with that simple idea, then I can load up a whole data form with about 3 lines of code.
So, say I click edit on a grid.
I pop this form:
but, the form data is done with my fLoader routine. All it does is look for "f" attributes, and fills out the control from the database.
And then for save, I have Fwriter. Does the same thing, but in reverse.
So, save button code for above is this:
protected void cmdSave_ServerClick(object sender, EventArgs e)
{
int intPK = (int)ViewState[this.ID + "_MyPk"];
string strSQL = "SELECT * FROM tblHotels WHERE ID = " + intPK;
DataTable rstData = General.MyRst(strSQL);
General.FWriterUpdate(this.EditRecord, rstData.Rows[0]);
}
So, Fwriter again just loops the controls inside of that div, and transfers back from the controls their values into the table.
So, in above, we see two examples of grouping controls, and then being able to "operate" on that group of controls.
No arrays required here. so, I used these concepts for my own data binding system, and thus I NEVER have to write code to load up each individual control from the database. Nor do I have to write code to send + save the data back to the database.
I means I quite much have ms-access like drag + drop of controls, and the shuffle from database to the form is done for me. And this all works due to the requiring to "group" and identify controls on the page, and what I want to do with that group of controls.
Edit: Processing conrols in an array or list
In all of the above "div" and control processing routines, we REALLY are doing the same as if we placed the controls into an array, or list.
For example, the floader routine does this: (we can pass it the current web page, or as more often a "div".
So, it looks like this:
foreach (System.Web.UI.Control c in F.Controls)
{
if (c.GetType() == typeof(TextBox))
{
TextBox ctlC = c as TextBox;
ctlC.Text = "zoo";
ctlC.Visible = true;
}
else if (c.GetType() == typeof(Label))
{
Label ctlC = c as Label;
}
else if (c.GetType() == typeof(DropDownList))
{
DropDownList ctlC = c as DropDownList;
}
else if (c.GetType() == typeof(CheckBox))
{
CheckBox ctlC = c as CheckBox;
}
I removed most of the code, but above shows how you can pass/have a bunch of controls in a page, or div or whatever.
So, my WHOLE post and narrative been based on the above approach - which I assumed was obvious here.
However, lets code up a collection, and thus make this even more obvious.
so this markup:
<asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
<br />
<asp:Label ID="Label1" runat="server" Text="Label"></asp:Label>
<br />
<asp:CheckBox ID="CheckBox2" runat="server" />
So, our code behind can then do this:
List<Control> mycontrols = new List<Control>();
mycontrols.Add(TextBox1);
mycontrols.Add(CheckBox2);
mycontrols.Add(Label1);
foreach (Control myC in mycontrols)
{
myC.Visible = false;
}
// set text value of controls
foreach (Control myC in mycontrols)
{
myC.Visible = false;
if (myC.GetType() == typeof(TextBox))
{
TextBox ctlC = myC as TextBox;
ctlC.Text = "zoo";
ctlC.Visible = true;
}
else if (myC.GetType() == typeof(Label))
{
Label ctlC = c as Label;
ctlC.Text = "zoo zoo";
}
else if (myC.GetType() == typeof(CheckBox))
{
CheckBox ctlC = c as CheckBox;
ctlC.Checked = true;
}
}
So, for visible, we can use visible property. but, for other features of the control, say like Text or whatever? We cast the control into its type, and thus can use say checked of the checkbox, and it does not even have a .text property.

How to loop through the div to reset the delegate of the links in?

If i have method like this to Draw my side Menu Dynamically :
private void DrawSideMenu()
{
LinkButton x;
TaskDTO TaskList = new TaskDTO();
List<TaskDTO> List = TaskList.DrawMenu(int.Parse(Session["emp"].ToString()));
HtmlGenericControl myDIV = new HtmlGenericControl("div");
myDIV.ID = "menu8";
HtmlGenericControl myOrderedList = new HtmlGenericControl("ul");//css clss for <ul>
myOrderedList.ID = "orderedList";
myOrderedList.Attributes.Add("class", "task");
HtmlGenericControl listItem1;
string count = "";
foreach (TaskDTO i in List)
{
count = AdjustMenuCount1(i.TaskCode);
x = new LinkButton();
x.ID = i.TaskCode.ToString();
x.Text = i.TaskName + " " + count;
x.Click += new EventHandler(TaskC);
x.Style["FONT-FAMILY"] = "tahoma";
listItem1 = new HtmlGenericControl("li");
listItem1.Attributes.Add("class", "normal");
if (count != "0")
{
listItem1.Controls.Add(x);
myOrderedList.Controls.Add(listItem1);
}
}
myDIV.Controls.Add(myOrderedList);
MenuTD.Controls.Add(myDIV);
Session["SideMenu"] = myDIV;//Save to redraw when page postbacks
}
This Method takes long time to draw my menu.so i call it one time in (!IsPostBack) and save it in session so that i could redraw it like that :
MenuTD.Controls.Add( ((System.Web.UI.Control)(Session["SideMenu"])));
It redraws it successfully but when i click on any link it doesn't hit the event because i thought it's not possible to save the x.Click += new EventHandler(TaskC); in the session ,so i want to know how to loop through my session content to resetting the delegate of my link ?
That idea won't work because if you're not wiring up the Event Handler every time the page is loaded, it won't run.
If we come back to the original issue, you said it's slow. Creating controls at runtime cannot be slow and it's most likely the way you create your list of items:
List<TaskDTO> List = TaskList.DrawMenu(int.Parse(Session["emp"].ToString()));
Instead of storing complete menu, try to store in the Session only List and create all controls as usual. If menu is required on one page only, then use ViewState instead of Session.
Also it makes sense to change the entire code as currently you hardcode all style and layout settings in the code. Create all layout (div, ul, li) in aspx, move all styles in css (for example, you use "task" class but still set "tahoma" in the code). This would simplify the code and bring more flexibility.
List<TaskDTO> List = null;
void Page_Load(object sender, EventArgs e)
{
if (ViewState["List"] != null) {
List = (List<TaskDTO>)ViewState["List"];
} else {
// ArrayList isn't in view state, so we need to load it from scratch.
List = TaskList.DrawMenu(int.Parse(Session["emp"].ToString()));
}
// Code to create menu, e.g.
if (!Page.IsPosBack) {
Repeater1.DataSource = List;
Repeater1.DataBind();
}
}
void Page_PreRender(object sender, EventArgs e)
{
// Save PageArrayList before the page is rendered.
ViewState.Add("List", List);
}
...
<ul id="orderedList">
<asp:Repeater ID="Repeater1" runat="server">
<ItemTemplate>
<li><%# Eval("TaskName") %></li>
</ItemTemplate>
</asp:Repeater>
</ul>
Maybe save it in application level so it only gets built once, then just put the menu into an object and loop through it to re-add the clicks.
I'm afraid that in order for it to work you are going to have to rebind the Click handler on every Page_Load.
Based on your code, and assuming your TaskC is available, you can make this method:
private void RebindMenuHandlers() {
if(Session["SideMenu"] == null)
return; // Your menu has not been built yet
var menu = ((System.Web.UI.Control)(Session["SideMenu"]));
var orderedList = menu.Controls[0];
foreach(var listItem in orderedList){
foreach(var control in listItem){
var linkButton = control as LinkButton;
if(linkButton != null){
linkButton.Click += new EventHandler(TaskC);
}
}
}
}
Then call it on your Page_Load event:
void Page_Load(object sender, EventArgs e)
{
RebindMenuHandlers();
// .... etc
}
I just typed this directly here, so please forgive any silly compilation mistakes, this should be enough to give you the general idea. Hope that helps.

How to access span class in codebehind?

Is it possible display/hide spans in the codebehind based on their class?
I've been able to do this with a span's id but not with classes.
markup:
<span runat='server' id='myId' class='myClass'>some text</span>
codebehind:
protected override void OnPreRender(EventArgs e)
{
// This works
myId.Visible = false;
// This doesn't work
myClass.Visible = false;
}
I get the error "The name myClass does not exist in current context".
But the codebehind has no trouble with the id.
No, there is nothing that exists that lets you refer to something by class; only the ID actually works in the code-behind. You can find the object by ID, and then check it's class, or you can define a container control around something:
<asp:Panel ID="X" runat="server">
.
.
</asp:Panel>
And using this, you can loop through the controls in the panel, check the class, and process it, like:
foreach (var c in X.Controls)
{
if (c is WebControl && ((WebControl)c).CssClass == "myClass")
//Do something
else if (c is HtmlControl && ((HtmlControl)c).Attributes.ContainsKey("class") && ((HtmlControl)c).Attributes["class"] == "myClass")
//Do something
}
That's possible to do. You could also do it at the page level, but would have to do it recursively, and it may affect your apps performance.
You could try something like this:
<span runat='server' id='myId' class='myClass'>some text</span>
...
</span>
myId.Attributes.Add("style", "display:none;");
Of course it will not. You are using class name as an Id.
You can select the control using Contols Collection on Page object.
foreach (Control c in Page.Controls)
{
if (c.CssClass == "myClass")
{
c.Visible=false;
}
}

How can, or can you access a group of controls or elements with code behind?

Is there a way to access a group of controls in ASP.NET somehow?
In jQuery you can access multiple elements using a class="somegroup" like
$('.somegroup')...
In ASP.NET I understand I can access an element or control using the ID, but is there a way to access multiple controls or elements at once?
For example, let's say I have this in design view:
<asp:Label ID="label1" CssClass="someclass"></asp:Label>
<asp:Label ID="lbl" CssClass="someclass"></asp:Label>
<asp:Label ID="lb2" CssClass="someclass"></asp:Label>
Now I want to turn the visibility off on all of them.
Instead of doing this:
label1.Visible = false;
lbl.Visible = false;
lb2.Visible = false;
Is there an equivalent to this?
someclass.Visible = false;
Is there possibly a different tag property I could be using?
using asp.net and C#
You may write your own function, pass a string class into it (and optionally a parent control or form) then loop thru Controls collection checking for the CssClass property and making needed modifications for matched controls.
Something like
void hide(Control el, string cssClass) {
foreach (WebControl c in el.Controls)
{
if (c.CssClass == cssClass)
{
c.Visible = false;
}
}
}
and call
hide(this, "someclass");
public void Apply(string selector, Control parent, Action<WebControl> a)
{
if (selector.StartsWith("."))
{
foreach(WebControl wc in parent.Controls)
{
if (wc.CssClass == selector.Substring(1))
{
a(wc);
if (wc.HasControls())
{
Apply(selector,wc,a);
}
}
}
}
if (selector.StartsWith("#"))
{
foreach (WebControl wc in parent.Controls)
{
if (wc.ID == selector.Substring(1))
{
a(wc);
return;//no need to search any further.
}else
{
if (wc.HasControls())
{
Apply(selector, wc, a);
}
}
}
}
}
Maybe this will help?
then you can do this:
Apply(".SomeClass", this, a => a.CssClass="SomethingElse");
There is no baked-in syntax for doing a selector-type operation in C#.
You can however write your own loop to do this, like this:
private void SetAllLabelsWithCssClassValueToInvisible(Control parentControl,
string className)
{
foreach(Control childControl in parentControls.Controls)
{
// Try to cast control to a label, null if it fails
var label = childControl as Label;
// Check to see if we successfully cast to label or not
if(label != null)
{
// Yes, it is a label
// Does it have the correct CssClass property value?
if(label.CssClass == className)
{
// Update the Visible property to false
label.Visible = false;
}
}
}
}
Note: Obviously you can expand/improve upon this notion and make it fit your needs, just a proof of concept that is very specific for Label controls with a particular CssClass value and the Visible property.
Internally, the jQuery Sizzle engine is doing this looping for you.

Iterating through textbox controls in a panel C#

I have seen many others with similar problems but I cannot find the flaw in my logic here. Any help would be greatly appreciated.
I have a Panel which I have added numerous label and textbox controls to, ie:
myPanel.Controls.Add(txtBox);
These controls are created and added in a method called previous to the iteration method.
I want to iterate through each textbox and use its Text property as a parameter in another method but I am not having any luck. Here is my attempt to iterate:
public void updateQuestions()
{
try
{
foreach (Control c in editQuestionsPanel.Controls)
{
if (c is TextBox)
{
TextBox questionTextBox = (TextBox)c;
string question = questionTextBox.Text;
writeNewQuestionToTblQuestions(question);
}
}
}
catch (Exception err)
{
Console.WriteLine(err.Message);
}
}
The problem I am having is that the controls are not in the Panel when I arrive at this updateQuestions() method. Here is the process involved:
A commandButton is clicked and the questions are read from a DB, for each question a method is called which adds 2 labels and a textbox to editQuestionsPanel.Controls. This panel is inside a PlaceHolder which is then made visible.
When a button inside the PlaceHolder is clicked, the updateQuestions() method is called and the editQuestionsPanel.Controls.Count = 1. As there are approx 12 questions in the DB it should be around 36. The one control inside the Panel is of type:
System.Web.UI.LiteralControl
It contains no controls.
I am sure that somwhere in the lifecycle the Panel's controls are being cleared but I do not know how to step thru the life cycle. I have a Page_load method which is called as soon as a button is clicked but once the button which calls updateQuestions() is clicked the editQuestionsPanel.Controls.Count is already back to 1 so it must be cleared before this but I do not know how to correct this...
Any help you can give to help me solve this would be greatly appreciated - its killing me!
This selects from collection controls only that which are of type TextBox.
(the same as control is TextBox or (control as TextBox) != null)
If controls are contained in editQuestionsPanel.Controls:
using System.Linq;
IEnumerable<TextBox> textBoxes = editQuestionsPanel.Controls.OfType<TextBox>();
foreach (TextBox textBox in textBoxes)
{
// do stuff
}
To select all child controls use next extension method:
public static IEnumerable<T> GetChildControls<T>(this Control control) where T : Control
{
var children = control.Controls.OfType<T>();
return children.SelectMany(c => GetChildControls<T>(c)).Concat(children);
}
Using:
IEnumerable<TextBox> textBoxes = editQuestionsPanel.GetChildControls<TextBox>();
When you add controls dynamically, you need to do that on every request - asp.net doesn't do that for you!
Add the controls in the Init or Load phase, then they will get populated with the postback values.
A frequently made mistake: Container.Controls only contains the first level child controls in this container. That is: TextBox1 in PanelA, PanelA in PanelB, you can't get TextBox1 in PanelB.Controls.
My solution is to write an extension method:
public static IEnumerable<Control> AllControls(this Control ctl)
{
List<Control> collection = new List<Control>();
if (ctl.HasControls())
{
foreach (Control c in ctl.Controls)
{
collection.Add(c);
collection = collection.Concat(c.AllControls()).ToList();
}
}
return collection;
}
Now TextBox1 is in PanelB.AllControls(). To filter all controls with type, using PanelB.AllControls().OfType<TextBox>()
If the other answers don't help, try doing your code but add recursivity. Your code would not work if it's editQuestionsPanel => Panel => Textbox
You can do something like this instead:
var questions = from tb in editQuestionsPanel.Controls.OfType<TextBox>()
select tb.Text;
foreach(var question in questions)
{
writeNewQuestionToTblQuestions(question);
}
Try this
int Count = 0;
foreach (Control ctr in Panel1.Controls)
{
if (ctr is TextBox)
Count++;
}

Categories