I need to enumerate over a collection of controls - regardless of their nesting level - that match a given predicate.
Originally the problem occurred, when I needed to set all textbox's in a grids row to ReadOnly, if a column in that row indicated that the record should not be editable.
Later I realized, that I already solved a problem in the past very much like this one, only with a different criteria (find a single control recursively by its ID).
After trying a few alternatives I came up with a general solution that works. But since I will use this method very often, I wanted to gather possible improvements.
This method will return all child controls matching a predicate:
public static IEnumerable<T> FindChildControls<T>(this Control parentControl,
Predicate<Control> predicate) where T : Control
{
foreach (Control item in parentControl.Controls) {
if (predicate(item))
yield return (T)item;
foreach (T child in item.FindChildControls<T>(predicate)) {
yield return child;
}
}
}
Using this method I can do the following:
var allTxt = Page.FindChildControls<TextBox>(c => c is TextBox);
var submit = Page.FindChildControls<Button>(c => c.ID == "btnSubmit").First();
You can use a queue to get rid of recursion if you want.
public static IEnumerable<T> FindChildControls<T>(Control parentControl,
Predicate<Control> predicate) where T : Control
{
Queue<Control> q = new Queue<Control>();
foreach (Control item in parentControl.Controls)
{
q.Enqueue(item);
}
while (q.Count > 0)
{
Control item = q.Dequeue();
if (predicate(item))
yield return (T)item;
foreach (Control child in item.Controls)
{
q.Enqueue(child);
}
}
}
Related
My aim is to loop through all fields in each row in a GridView. The fields are of type CheckBox, TextBox and DropDownList. If one of them are found unchecked/empty/selectedIndex=0, I'll add it to emptyControls list.
foreach (GridViewRow gvRow in gvProxyEntry.Rows)
{
List<object> emptyControls = new List<object>();
foreach (TableCell cell in gvRow.Cells)
{
Type controlType = cell.Controls[0].GetType();
if (controlType == typeof(CheckBox))
{
CheckBox chkBox = (CheckBox)cell.Controls[0];
if (chkBox.Checked == false)
{
emptyControls.Add(chkBox);
}
}
...
}
...
}
My problem is the if-else checking keeps failing to detect checkboxes (based on code snippet above).
I have a guess why this fails. Doing a debug, I found controlType variable is always of type System.Web.UI.LiteralControl.
How do I correctly get the correct type of all the fields without using field's ID? The reason I don't want to use the field's ID is to prevent code change in the future if new fields are being added to the row.
If a control is nested inside other control, you won't be able to find it easily. I believe you know the ID of the control at design time.
If so, you can use the following FindControlRecursive helper method.
Helper Method
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);
}
Usage
foreach (TableCell cell in gvRow.Cells)
{
var checkBox = FindControlRecursive(cell, "CheckBox1") as CheckBox;
if(checkBox != null)
{
// Do something
}
}
Using Linq you can get all the checkboxes using one line
var unCheckedCheckBoxes = gvProxyEntry.Rows.OfType<TableRow>()
.SelectMany(row => row.Cells.OfType<TableCell>()
.SelectMany(cell => cell.Controls.OfType<CheckBox>())).Where(c=>!c.Checked).ToList();
same way you can get other control types by changing type and the where condition
Solution 1
Apparently, I was targeting cell.Control[0], which is the wrong control type in cell.Controls. So I added another foreach loop to loop through cell.Controls to get the correct control type that I wanted.
foreach (GridViewRow gvRow in gvProxyEntry.Rows)
{
List<object> emptyControls = new List<object>();
foreach (TableCell cell in gvRow.Cells)
{
foreach(Control c in cell.Controls)//newly added foreach loop
{
Type controlType = c.GetType();
if (controlType == typeof(CheckBox))
{
CheckBox chkBox = (CheckBox)c;
if (chkBox.Checked == false)
{
emptyControls.Add(chkBox);
}
}
...
}
...
}
...
}
Solution 2
For LinQ users
Suppose I have three check boxes named chk1, chk2, and chk3 in ASP.NET. Is is possible to assign a property to each of them programmatically by appending the number to the variable name and using a for loop?
For example:
for (int x=1; x<=3; x++)
{
chk+[x].Checked = true;
}
If this is not possible, can you give me a better solution or approach to this problem?
Presuming winforms(ASP.NET tag was added later), you can use ControlCollection.Find, the second parameter specifies if all child controls should also be searched:
for (int x=1; x<=3; x++)
{
Control[] ctrl = this.Controls.Find("chk" + x, true);
foreach (CheckBox chk in ctrl.OfType<CheckBox>())
chk.Checked = true;
}
If it's actually ASP.NET you could use this recursive search extension-method:
public static class ControlExtensions
{
public static IEnumerable<Control> GetControlsRecursively(this Control parent)
{
foreach (Control c in parent.Controls)
{
yield return c;
if (c.HasControls())
{
foreach (Control control in c.GetControlsRecursively())
{
yield return control;
}
}
}
}
}
Now you get all checkboxes with these ID's in this way:
var allIDs = Enumerable.Range(1, 3).Select(i => "chk" + i).ToList();
var allCheckBoxes = this.GetControlsRecursively().OfType<CheckBox>()
.Where(chk => allIDs.Contains(chk.ID));
foreach(CheckBox chk in allCheckBoxes)
chk.Checked = true;
However, i would rarely use this recursive method. It's a little bit error-prone since other NamingContainers can contain the same ID again. Instead i would only use the right NamingContainer. For example, if you have a Panel where all releated CheckBoxes are sitting you can simply use myPanel.Controls.OfType<CheckBox>().
Also, you should use more meaningful ID's for your controls. Then you cannot use such loops anymore but you can start to write more robust and maintainable code like:
chkUserActive.Checked = true; // just an example
I need to set the selected item of my property grid. I'm getting an eventargs, which stores a string (this string tells me what property in my propertygrid the user wants to select).
The problem is i cannot find a collection of grid items, i can select one from. And also i dont know how to properly create a new GridItem object and set the SelectedGridItem property.
GridItem gridItem = ???;
detailsPropertyGrid.SelectedGridItem = gridItem;
thank you for your help.
Edit:
Its almost working now tahnk you VERY much.
GridItem gi = this.detailsPropertyGrid.EnumerateAllItems().First((item) =>
item.PropertyDescriptor != null &&
item.PropertyDescriptor.Name == colName);
this.detailsPropertyGrid.SelectedGridItem = gi;
this.detailsPropertyGrid.Select();
The only problem is: Now its selecting the Property Name field. Can I set the focus to the input field of the property?
Here are a couple of PropertyGrid extensions that can enumerate all items in a grid. This is how you can use it to select one item:
// get GridItem for a property named "Test"
GridItem gi = propertyGrid1.EnumerateAllItems().First((item) =>
item.PropertyDescriptor != null &&
item.PropertyDescriptor.Name == "Test");
// select it
propertyGrid1.Focus();
gi.Select();
// enter edit mode
SendKeys.SendWait("{F4}");
...
public static class PropertyGridExtensions
{
public static IEnumerable<GridItem> EnumerateAllItems(this PropertyGrid grid)
{
if (grid == null)
yield break;
// get to root item
GridItem start = grid.SelectedGridItem;
while (start.Parent != null)
{
start = start.Parent;
}
foreach (GridItem item in start.EnumerateAllItems())
{
yield return item;
}
}
public static IEnumerable<GridItem> EnumerateAllItems(this GridItem item)
{
if (item == null)
yield break;
yield return item;
foreach (GridItem child in item.GridItems)
{
foreach (GridItem gc in child.EnumerateAllItems())
{
yield return gc;
}
}
}
}
I have many labels as children of many different stack panels which are all children of a list box, and I need to reference one of these labels were the Content.toString() == "criteria". In other words, traversing the visual tree in WPF would be a ball ache because there are many parent/child methods to run. Is there a way of finding one of these labels on my window without it having a name and assuming I don't know how far 'down' it is in the tree? Maybe there's an item collection of everything in a window (without heirarchy) that I can run some LINQ against??
If you're wondering why I don't have a name for the labels - it's because they are generated by a data template.
Thanks a lot,
Dan
Seems like what you're looking for: Find DataTemplate-Generated Elements
I made a slight change to the code that #anatoliiG linked in order to return all the child controls of the specified type (instead of the first one):
private IEnumerable<childItem> FindVisualChildren<childItem>(DependencyObject obj)
where childItem : DependencyObject
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(obj, i);
if (child != null && child is childItem)
yield return (childItem)child;
foreach (var childOfChild in FindVisualChildren<childItem>(child))
yield return childOfChild;
}
}
With this function you could do something like this:
var criteriaLabels =
from cl in FindVisualChildren<Label>(myListBox)
where cl.Content.ToString() == "criteria"
select cl;
foreach (var criteriaLabel in criteriaLabels)
{
// do stuff...
}
i think this code might be usefull for you:
foreach (Control control in this.Controls)
{
if (control.GetType() == typeof(Label))
if (control.Text == "yourText")
{
// do your stuff
}
}
i used This question as my base
I don't know whether this will help or not:
If you are looking for a specific label in each stack panel in the listBox, then you could just look for that specific label with its specific name and compare the content.
I have an ASP.NET panel called pnlCategories. What I am trying to do is create a function that returns a List generic list of all Check Boxes that are checked inside this panel. There are other child controls (including other panels and tables) that this function will have to traverse through to find all the check boxes. Anyone have any ideas how to do this? This is C# by the way.
Simple, also untested. This could be adapted to only collect the controls IDs, but this is a little more reusable and is a great one to have in a common library.
public static void FindControlsRecursive(Control root, Type type, ref List<Control> list)
{
if(root.Controls.Count != 0)
{
foreach(Control c in root.Controls)
{
if(c.GetType() == type)
list.Add(c);
else if (c.HasControls())
FindControlsRecursive(c, type, ref list);
}
}
}
And usage:
var checkboxes = new List<Control>();
FindControlRecursive(pnlCategories, typeof(CheckBox), ref checkboxes);
var ids = checkboxes.Select(c => c.UniqueID).ToList(); // or however you'd like to get them.
I'd say something like this (adapted) might work. I haven't tested this, but it should get you somewhere close.
public List<CheckBox> FindAllCheckBoxControls(WebControl webControl)
{
if(webControl.Controls.Count == 0)
return new List<CheckBox>();
var checkBoxes = webControl.Controls
.Where(x => x.GetType() == typeof(CheckBox));
.Select(x => x as CheckBox)
.ToList();
webControl.Controls.ToList().ForEach(control =>
{
checkBoxes.AddRange(FindAllCheckBoxControls(control));
});
return checkBoxes.Distinct();
}