find properties that has an attribute in a control - c#

I have a page with a number of controls.
As the page is rendering I want to loop through all controls on the page and find any control that has a property with certain attribute. I am attempting to do this with c# - any ideas how I might achieve this?

I dont know how big is your control tree. This is what I would do. I'm not promissing the best performance.
Case 1. Looking for .NET Attributes
IEnumerable<Control> GetMarkedControls(ControlCollection controls)
{
foreach(Control c in controls)
{
var props = c.GetType().Properties();
if(props.Count(x => x.GetCustomAttributes(false).OfType<YourAttribute>().Count() > 0) > 0)
yield return c;
foreach (Control ic in GetMarkedControls(c.Controls))
yield return ic;
}
}
Case 2. Looking for HTML attributes
IEnumerable<WebControl> GetMarkedControls(ControlCollection controls)
{
foreach(Control c in controls)
{
if(c is WebControl)
{
var wc = c as WebControl;
if (wc.Attributes.FirstOrDeafult(x => x.Name == "yourAttribute") != null)
yield return c;
}
foreach (Control ic in GetMarkedControls(c.Controls))
yield return ic;
}
}
Now you can call it this way: var controlsWAttribute = GetMarkedControls(this.Controls); from your page or any control. This way you are not forced to call it at the page level.
With this method you explore the whole control tree in your page or control recursively.

You need to use Reflection
http://msdn.microsoft.com/en-us/library/system.reflection.aspx
using reflection you can get all the attributes of an object

Every control that is on the page has a "controls" property which contains all of its children controls. I have written recursive functions to loop through these before but do not have any on hand. Let me try to write one real quick:
public Collection<Control> findControlsWithAttributes(Control startingControl)
{
Collection<Control> toReturn = new Collection<Control>();
foreach (Control curControl in startingControl.controls)
{
if (DO COMPARISON HERE WITH CURCONTROL) toReturn.add(curControl);
if (curControl.Count() > 0) findControlsWithAttributes(curControl, toReturn);
}
return toReturn;
}
private void findControlsWithAttributes(Control startingControl, Collection<Control> inputCollection)
{
foreach (Control curControl in startingControl.controls)
{
if (DO COMPARISON HERE WITH CURCONTROL) inputCollection.add(curControl);
if (curControl.Count() > 0) findControlsWithAttributes(Control startingControl, Collection<Control> inputCollection);
}
}
Its been a little while since i've done this and I can't remember off the top of my head if Collection.Count is a method or property so make sure you check that first, but if you pass the page in then this will check against every server-visible control on your page and return a collection containing controls that match your comparison.
Lastly, Control.Attributes will return an AttributeCollection which you should be able to subsequently compare against.

Not sure what attributes you are after but if class attributes is what you are after look to #user751975 other wise you can do something like ...
page.Controls.Cast<System.Web.UI.WebControls.WebControl>().First().Attributes["class"]

Related

Finding control on pages that inherits from base page

In my project all of the .aspx pages inherit from a custom base class page which in turn inherits from System.Web.UI.Page.
I want to find the controls in current page. for that one I am using
foreach loop
foreach(control c in page.controls)
for this I am unable to cast my current page to system.web.ui.page.
how to cast the current page to system system.web.ui.page?
Please note that you are dealing with a control-tree, so you might want to have a recursion here. Following method should help (Linq with a recursive call):
private static IEnumerable<Control> FlattenControlTree<TFilterType>(Control control)
{
if (control is TFilterType)
{
yield return control;
}
foreach (Control contr in control.Controls.Cast<Control>().SelectMany((c) => FlattenControlTree<TFilterType>(c)))
{
if (contr is TFilterType)
{
yield return contr;
}
}
}
By the end of the day, you only need to call:
var controls = FlattenControlTree<YourBaseType>(this.Page);
Please note that this kind of recursion is not very effective when it comes to big trees.
You may try this;
foreach(Control c in ((System.Web.UI.Page)this.Page).Controls)

Loop through all controls of a Form, even those in GroupBoxes

I'd like to add an event to all TextBoxes on my Form:
foreach (Control C in this.Controls)
{
if (C.GetType() == typeof(System.Windows.Forms.TextBox))
{
C.TextChanged += new EventHandler(C_TextChanged);
}
}
The problem is that they are stored in several GroupBoxes and my loop doesn't see them. I could loop through controls of each GroupBox individually but is it possible to do it all in a simple way in one loop?
The Controls collection of Forms and container controls contains only the immediate children. In order to get all the controls, you need to traverse the controls tree and to apply this operation recursively
private void AddTextChangedHandler(Control parent)
{
foreach (Control c in parent.Controls)
{
if (c.GetType() == typeof(TextBox)) {
c.TextChanged += new EventHandler(C_TextChanged);
} else {
AddTextChangedHandler(c);
}
}
}
Note: The form derives (indirectly) from Control as well and all controls have a Controls collection. So you can call the method like this in your form:
AddTextChangedHandler(this);
A more general solution would be to create an extension method that applies an action recursively to all controls. In a static class (e.g. WinFormsExtensions) add this method:
public static void ForAllControls(this Control parent, Action<Control> action)
{
foreach (Control c in parent.Controls) {
action(c);
ForAllControls(c, action);
}
}
The static classes namespace must be "visible", i.e., add an appropriate using declaration if it is in another namespace.
Then you can call it like this, where this is the form; you can also replace this by a form or control variable whose nested controls have to be affected:
this.ForAllControls(c =>
{
if (c.GetType() == typeof(TextBox)) {
c.TextChanged += C_TextChanged;
}
});
A few simple, general purpose tools make this problem very straightforward. We can create a simple method that will traverse an entire control's tree, returning a sequence of all of it's children, all of their children, and so on, covering all controls, not just to a fixed depth. We could use recursion, but by avoiding recursion it will perform better.
public static IEnumerable<Control> GetAllChildren(this Control root)
{
var stack = new Stack<Control>();
stack.Push(root);
while (stack.Any())
{
var next = stack.Pop();
foreach (Control child in next.Controls)
stack.Push(child);
yield return next;
}
}
Using this we can get all of the children, filter out those of the type we need, and then attach the handler very easily:
foreach(var textbox in GetAllChildren().OfType<Textbox>())
textbox.TextChanged += C_TextChanged;
Try this
AllSubControls(this).OfType<TextBox>().ToList()
.ForEach(o => o.TextChanged += C_TextChanged);
where AllSubControls is
private static IEnumerable<Control> AllSubControls(Control control)
=> Enumerable.Repeat(control, 1)
.Union(control.Controls.OfType<Control>()
.SelectMany(AllSubControls)
);
LINQ is great!
Haven't seen anyone using linq and/or yield so here goes:
public static class UtilitiesX {
public static IEnumerable<Control> GetEntireControlsTree(this Control rootControl)
{
yield return rootControl;
foreach (var childControl in rootControl.Controls.Cast<Control>().SelectMany(x => x.GetEntireControlsTree()))
{
yield return childControl;
}
}
public static void ForEach<T>(this IEnumerable<T> en, Action<T> action)
{
foreach (var obj in en) action(obj);
}
}
You may then use it to your heart's desire:
someControl.GetEntireControlsTree().OfType<TextBox>().ForEach(x => x.Click += someHandler);
As you have stated, you will have to go deeper than just cycling over each element in your form. This, unfortunately, implies the use of a nested loop.
In the first loop, cycle through each element. IF the element is of type GroupBox, then you know you'll need to cycle through each element inside the groupbox, before continuing; else add the event as normal.
You seem to have a decent grasp of C# so I won't give you any code; purely to ensure you develop all the important concepts that are involved in problem solving :)
you can only loop through open forms in windows forms using form collection for example to set windows start position for all open forms:
public static void setStartPosition()
{
FormCollection fc = Application.OpenForms;
foreach(Form f in fc)
{
f.StartPosition = FormStartPosition.CenterScreen;
}
}
I know that this is an older topic, but would say the code snippet from http://backstreet.ch/coding/code-snippets/mit-c-rekursiv-durch-form-controls-loopen/ is a clever solution for this problem.
It uses an extension method for ControlCollection.
public static void ApplyToAll<T>(this Control.ControlCollection controlCollection, string tagFilter, Action action)
{
foreach (Control control in controlCollection)
{
if (!string.IsNullOrEmpty(tagFilter))
{
if (control.Tag == null)
{
control.Tag = "";
}
if (!string.IsNullOrEmpty(tagFilter) && control.Tag.ToString() == tagFilter && control is T)
{
action(control);
}
}
else
{
if (control is T)
{
action(control);
}
}
if (control.Controls != null && control.Controls.Count > 0)
{
ApplyToAll(control.Controls, tagFilter, action);
}
}
}
Now, to assign an event to all the TextBox controls you can write a statement like (where 'this' is the form):
this.Controls.ApplyToAll<TextBox>("", control =>
{
control.TextChanged += SomeEvent
});
Optionally you can filter the controls by their tags.
Since the Question regarding "Adding an Event to your TextBoxes"; was already answered; I'm providing some explanation and adding an iteration alternative using a for loop instead.
Problem:
Being Unable to Get Controls Inside a Container.
Solution:
In order to retrieve the Controls inside a Container you have to specify the Container that Contains the Controls you wish to access to.
Therefore your loop must check the Controls inside a Container.
Otherwise your loop will not find the Controls inside a Container.
i.e:
foreach (Control control in myContainer.Controls)
{
if (control is TextBox) { /* Do Something */ }
}
In case you have several Containers:
Initially iterate the Containers.
Then iterate over the controls inside the container (the container found in the initial iteration).
Pseudo Code Example on How to use a for Loop Instead:
/// <summary> Iterate Controls Inside a Container using a for Loop. </summary>
public void IterateOverControlsIncontainer()
{
// Iterate Controls Inside a Container (i.e: a Panel Container)
for (int i = 0; i < myContainer.Controls.Count; i++)
{
// Get Container Control by Current Iteration Index
// Note:
// You don't need to dispose or set a variable to null.
// The ".NET" GabageCollector (GC); will clear up any unreferenced classes when a method ends in it's own time.
Control control = myContainer.Controls[i];
// Perform your Comparison
if (control is TextBox)
{
// Control Iteration Test.
// Shall Display a MessageBox for Each Matching Control in Specified Container.
MessageBox.Show("Control Name: " + control.Name);
}
}
}
Updated answer:
I needed to disable all the controls in a form, including groupboxes. This code worked:
private void AlterControlsEnable(bool ControlEnabled)
{
foreach (Control i in Controls)
i.Enabled = ControlEnabled;
}

how to check if a control of a certain type?

When I use th below code, it works. All the controls are hidden.
foreach (Control ctr in eItem.Controls)
{
ctr.visible = false;
}
However, I want to hide only labels and dropdownlists. That why I'm trying to use the below code without success
foreach (Control ctr in eItem.Controls)
{
if(ctr is Label | ctr is DropDownList)
{
ctr.visible = false;
}
}
EDIT
Here's the whole method
private void HideLabelAndDDLOnPageLoad()
{
foreach (ListViewItem eItem in lsvTSEntry.Items)
{
foreach (Control ctr in eItem.Controls)
{
if (ctr is Label || ctr is DropDownList)
{
ctr.Visible = false;
}
}
}
}
When I remove the if, all the controls get hidden. When I put it back, nothing happens.
Thanks for helping
I think what you are after is || change it to ||...that is the logical or operator.
foreach (Control ctr in eItem.Controls)
{
if(ctr is Label || ctr is DropDownList)
{
ctr.Visible = false;
}
}
| = bitwise operator
|| = logical or operator
Based on your edit
It appears your controls are inside an updatepanel, if that is the case you want to loop for all controls within the updatepanel's content template container.
Here you go:
foreach (Control ctr in UpdatePanel1.ContentTemplateContainer.Controls)
{
// rest of code
if(ctr is Label || ctr is DropDownList)
{
ctr.Visible = false;
}
}
The | is the bitwise or operator.
You are looking for ||, the logical or operator.
if(ctr is Label || ctr is DropDownList)
Without your exact markup we can only guess the solution here.
You must be using another container to wrap your controls inside your ItemTemplate in the ListView, something like a Panel or other containers. When you get the Controls on the list view item you actually get the warping container and not its children(labels, dropdowns etc.)
One solution to this is something like:
foreach (ListViewItem item in lsvTSEntry.Items)
{
item.FindControl("myLabel").Visible = false;
item.FindControl("myDropdownList").Visible = false;
}
Basically you try to find the controls by id and hide them. Notice there is no error checking there so you could get a NullReferenceException if FindControl returns null.
In case you have nested containers in your ItemTemplate and you want to hide all the labels and dropdowns regardless of where they are you can implement your own recursive FindControl that will look like:
private Control FindControlRecursive(Control rootControl, string controlId)
{
if (rootControl.ID == controlId)
{
return rootControl;
}
foreach (Control controlToSearch in rootControl.Controls)
{
Control controlToReturn = FindControlRecursive(controlToSearch, controlId);
if (controlToReturn != null)
{
return controlToReturn;
}
}
return null;
}
Not the most elegant but.... You can change this to take an array of Id's of course for speed purposes.
Based on this of course you can implement the search by control type which instead of taking a controlId as a parameter will take the types of controls to find.

Iterating Through All DropDownList In an ASP.NET WebForm C#

What is the object that I would have to reference in order to iterate through all the DropDownList on a web page. I have a web page with several drop down list on it. I want a piece of code that will do the following:
foreach (DropDownList d in xxx)
{
someFunction(d, d.ID);
}
Thanks.
If you don't need to worry about nested controls in which case you would need recursion, something like below should work.
foreach(DropDownList list in Controls.OfType<DropDownList>())
{
//TODO: Something with list
}
If recursion is required you could make a method like below..
public static IEnumerable<Control> GetAllControls(Control parent)
{
if(null == parent) return null;
return new Control[] { parent }.Union(parent.Controls.OfType<Control>().SelectMany(child => GetAllControls(child));
}
And then modify your loop...
foreach(DropDownList list in GetAllControls(this).OfType<DropDownList>())
{
//TODO: Something with list
}
foreach (var dropDownList in Page.Controls.OfType<DropDownList>())
{
}
There is no magical all control container. You're going to have to recursively traverse your control tree and find all the drop downs.
public void DoSomethingForAllControlsOf<T>(Control thisControl, Action<T> method)
where T: class
{
if(thisControl.Controls == null)
return;
foreach(var control in thisControl.Controls)
{
if(control is T)
method(control as T);
DoSomethingForAllControlsOf<T>(control, method);
}
}
That should recursively walk down the control tree and invoke the method on all elements of type T. Example:
DoSomethingForAllControlsOf<DropDownList>(this, someFunction);
You can't run a foreach loop on that because although you have numerous DropDownLists, they are not part of an iterable collection. You could, however, store each DropDownList into an array, and iterate through that array.
To get all of the dropdown controls, you'll probably need to loop through recursively. You can use this function to do it:
public Control DisableDropDowns(Control root)
{
foreach (Control ctrl in root.Controls)
{
if (ctrl is DropDownList)
((DropDownList)ctrl).Enabled = false;
DisableDropDowns(ctrl);
}
}
The LINQ way:
First you need an extension method to grab all the controls of the type you're interested in:
//Recursively get all the formControls
public static IEnumerable<Control> GetAllControls(this Control parent)
{
foreach (Control control in parent.Controls)
{
yield return control;
foreach (Control descendant in control.GetAllControls())
{
yield return descendant;
}
}
}`
Then you can iterate as you wanted:
var formCtls = this.GetAllControls().OfType<DropDownList>();`
foreach(DropDownList ddl in formCtls){
//do what you gotta do ;)
}
while(dropdownlist1.SelectedIndex++ < dropdownlist1.Items.Count)
{
if (dropdownlist1.SelectedValue == textBox1.text)
{
// do stuff here.
}
}
//resetting to 0th index(optional)
dropdownlist1.SelectedIndex = 0;

foreach Control ctrl in SomePanel.Controls does not get all controls

I have a panel with a bunch of labeles and textboxes inside of it.
The code:
foreach (Control ctrl in this.pnlSolutions.Controls)
Seems to only be finding html table inside the panel and 2 liternals.
But it does not get the textboxes that are in the html table.
Is there a simple way to get all the controls inside of a panel regardless of the nesting?
thanks!
Here's a lazy solution:
public IEnumerable<Control> GetAllControls(Control root) {
foreach (Control control in root.Controls) {
foreach (Control child in GetAllControls(control)) {
yield return child;
}
}
yield return root;
}
Remember also that some controls keep an internal collection of items (like the ToolStrip) and this will not enumerate those.
You would need to recursively "treewalk" through the controls, think about it like walking through a folder structure.
there is a sample Here
As far as I know your have to implement the recursion yourself, but its not really difficult.
A sketch (untested):
void AllControls(Control root, List<Control> accumulator)
{
accumulator.Add(root);
foreach(Control ctrl in root.Controls)
{
AllControls(ctrl, accumulator);
}
}
I had exactly the problem stated in the question so this may help somebody. I was attempting to clear a control collection prior to rewriting it.
private void clearCollection(Control.ControlCollection target)
{
foreach (Control Actrl in target)
{
if (Actrl is Label || Actrl is Button)
{
target.Remove(Actrl);
}
}
}
By removing the control inside the foreach loop it must mess with the internal pointers and the result is controls in the collection are missed.
My solution was to find all the controls then remove in a separate loop.
private void clearCollection(Control.ControlCollection target)
{
List<Control> accumulator = new List<Control>();
foreach (Control Actrl in target)
{
if (Actrl is Label || Actrl is Button)
{
accumulator.Add(Actrl); // find all controls first.
}
}
for (int i = 0; i < accumulator.Count; i++)
{
target.Remove(accumulator[i]);
}
}
The reason is because the only controls that are direct children of your panel are the table and the literals you mention, and it is only these that this.pnlSolutions.Controls returns.
The text boxes an labels are child controls of the table, making them grandchildren of the panel.
As #Yoda points out you need to recursively walk the controls to find them all.

Categories