I have a form MainForm which is a Windows Forms form that contains many child controls. I want to call one function on MainForm that notifies all of its children. Does the Windows Forms form provide a means to do this? I played with update, refresh and invalidate with no success.
foreach (Control ctrl in this.Controls)
{
// call whatever you want on ctrl
}
If you want access to all controls on the form, and also all the controls on each control on the form (and so on, recursively), use a function like this:
public void DoSomething(Control.ControlCollection controls)
{
foreach (Control ctrl in controls)
{
// do something to ctrl
MessageBox.Show(ctrl.Name);
// recurse through all child controls
DoSomething(ctrl.Controls);
}
}
... which you call by initially passing in the form's Controls collection, like this:
DoSomething(this.Controls);
The answer from MusiGenesis is elegant, (typical in a good way), nice and clean.
But just to offer an alternative using lambda expressions and an 'Action' for a different type of recursion:
Action<Control> traverse = null;
//in a function:
traverse = (ctrl) =>
{
ctrl.Enabled = false; //or whatever action you're performing
traverse = (ctrl2) => ctrl.Controls.GetEnumerator();
};
//kick off the recursion:
traverse(rootControl);
No, there isn't. You must roll out your own.
On a side note - WPF has "routed events" which is exactly this and more.
You are going to need a recursive method to do this (as below), because controls can have children.
void NotifyChildren( control parent )
{
if ( parent == null ) return;
parent.notify();
foreach( control child in parent.children )
{
NotifyChildren( child );
}
}
Related
I have different objects: 6 buttons, Listbox, and 5 labels, those names are like lblHum, lblCo2...
I need to show or hide them together with one button click.
Is it possible (without putting objects in a panel or group boxes) to apply, for example, Hide action, within 1 method for all objects without specifying hiding for each?
Like private void(List<objects>,action)
You can write something like this:
public void HideAll(params Control[] controls)
{
foreach(var control in controls) control.Hide();
}
Call it like this:
HideAll(lblHum, lblCo2, etc);
Or for something more flexible:
public void GroupAction(Action<Control> action, params Control[] controls)
{
foreach(var control in controls) action(control);
}
And then call it like this:
GroupAction(c => c.Hide(), lblHum, lblCo2, etc);
In both cases, though, the collection is still there. You just hid it with a method. And since the method is fairly simple, I'm not sure you get much value above putting the collection directly in your code. That is, you can make a list as a member of the form class and add the controls when the form is initialized. Then you can loop over the list whenever you want.
Since all controls inherit from the same base class (i.e. Control), you can do this with a generic method and passing in an Action. For example:
public void ApplyToAll<T>(IEnumerable<T> items, Action<T> action)
{
foreach (var item in items)
{
action(item);
}
}
And call the method like this:
var items = new List<Control> { lblHum, lblCo2... etc };
ApplyToAll(items, x => x.Visible = false);
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 want to iterate through various controls of update panel. While iterating using ID for control I want to delete some of the controls.
But I don't know how to iterate through controls of update panel using GetEnumerator method?
If we can do iteration by some other way, please let me know.
Couldn't you loop over the Controls collection of the updatepanel:
foreach(var control in myUpdatePanel.Controls) {
...
}
You can loop the ControlCollection.
Just remember that these controls can be nested, if they are in panels eg.
private void RecusiceControls(ControlCollection controls)
{
foreach (Control control in controls)
{
RecusiceControls((ControlCollection)control.Controls);
if (control is Button) // whatever the control is you are looking for
{
}
}
}
Is it possible to know who got the focus in a lost focus event?
Compact Framework does not have an ActiveControl, so I don't know how to tell who got the focus.
This is the solution that ended up working:
public System.Windows.Forms.Control FindFocusedControl()
{
return FindFocusedControl(this);
}
public static System.Windows.Forms.Control FindFocusedControl(System.Windows.Forms.Control container)
{
foreach (System.Windows.Forms.Control childControl in container.Controls)
{
if (childControl.Focused)
{
return childControl;
}
}
foreach (System.Windows.Forms.Control childControl in container.Controls)
{
System.Windows.Forms.Control maybeFocusedControl = FindFocusedControl(childControl);
if (maybeFocusedControl != null)
{
return maybeFocusedControl;
}
}
return null; // Couldn't find any, darn!
}
One option would be to interop the GetFocus API
[DllImport("coredll.dll, EntryPoint="GetFocus")]
public extern static IntPtr GetFocus();
This will give you the handle to the window that currently has input focus, you can then recursively iterate the control tree to find the control with that handle.
Using the corell.dll looks like a good idea.
Another possible way is to create GotFocus event handlers for all the controls on your form Then create a class level variable that updates with the name of the control that has the current focus.
No. first comes the LostFocus-event of one control then comes the GotFocus-event of the next control. as long as you can not figure out which control the user uses in the next moment, it is not possible.
whereas if the compact framework control does have a TabIndex-property it could be predicted only if the user uses the tab-key.
Edit:
OK You posted the solution and it works fine I must admit: the simple "No" is wrong
+1
This is a shorter code for the Vaccano's answer, using Linq
private static Control FindFocusedControl(Control container)
{
foreach (Control childControl in container.Controls.Cast<Control>().Where(childControl => childControl.Focused)) return childControl;
return (from Control childControl in container.Controls select FindFocusedControl(childControl)).FirstOrDefault(maybeFocusedControl => maybeFocusedControl != null);
}
Exactly the same (in high-level, abstraction).
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.