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();
}
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 have the following property for getting and setting a Session variable...
protected List<Item> SearchResults
{
get
{
if (Session["SearchResults"] == null)
Session["SearchResults"] = new List<string>();
return ((List<string>) Session["SearchResults"]).Select(s => new Item(s)).ToList();
}
set { Session["SearchResults"] = value.Select(p => p.Serialize()).ToList(); }
}
As you can see, I am storing the items as serialized strings.
When the user checks a few checkboxes in a GridView and then clicks submit, I would like to modify the value of the Selected property of the Items that he has selected. Here is my code for doing that and it is working great...
private void RecordSelections()
{
var searchResults = SearchResults; //localize session list
foreach (var checkBox in AssignGrid.Rows.Cast<GridViewRow>().Select(r => (CheckBox)r.FindControl("chkSelect")))
{
var result = searchResults.FirstOrDefault(r => r.Key == long.Parse(checkBox.Attributes["ItemKey"].ToString()));
if (result != null) result.Selected = checkBox.Checked;
SearchResults = searchResults; //store session list
}
}
My question then is this. I have found it necessary to localize the session list (see commented line) and then to store that session list (also commented), but what is it precisely that is happening when I access this Session variable through the getter? I would like to do something like the following to get an Item out of the list and modify it's value. I am not able to answer exactly why it is that this doesn't work. Would it work if I were not serializing the object (the reason for serializing the item is another post altogether)?
private void RecordSelections()
{
foreach (var checkBox in AssignGrid.Rows.Cast<GridViewRow>().Select(r => (CheckBox)r.FindControl("chkSelect")))
{
var result = SearchResults.FirstOrDefault(r => r.Key == long.Parse(checkBox.Attributes["ItemKey"].ToString()));
if (result != null) result.Selected = checkBox.Checked;
}
}
Thanks in advance for your help.
I have put all of my form controls in a hashtable thus :-
foreach (Control c in this.Controls)
{
myhash.Add(c.Name, c);
}
amongst which are two radio buttons. I would like to get the value of the buttons, ie checked or unchecked, and assign them to a variable. How can I do that please. Thanks for all and any help.
foreach (Control c in hashtable.Values)
{
if(c is RadioButton)
{
string name = x.Name;
bool isChecked = (c as RadioButton).Checked;
}
}
or if you know the name
(hashtable["name"] as RadioButton).Checked;
You can retrieve a value by a key associated with it, basically control Name is a key in hashtable you've created. So if you know a name of controls you need to access:
var control = hash[radioButtonControlName] as RadioButton;
Otherwise using LINQ OfType() and List.ForEach():
// OfType() does check whether each item in hash.Values is of RadioButton type
// and return only matchings
hash.Values.OfType<RadioButton>()
.ToList()
.ForEach(rb => { bool isChecked = rb.Checked } );
OR using foreach loop:
(there is a nice overview of misconception of the List.ForEach() usage)
var radioButtons = hash.Values.OfType<RadioButton>();
foreach(var button in radioButons)
{
bool isChecked = rb.Checked;
}
Cast the control that is the radio button to a RadioButton Class instance and then look at the checked property. At least that would be how I've done this many times over in WebForms using similar classes.
Assuming the hashtable in your code is an instance of Hashtable:
Hashtable myhash= new Hashtable();
foreach (Control c in this.Controls)
{
myhash.Add(c.Name, c);
}
You can do this:
foreach (DictionaryEntry entry in myhash)
{
RadioButton rb = entry.Value as RadioButton;
if (rb != null)
bool checked = rb.Checked;
}
Also you can see the key of the hashmap entry with:
foreach (DictionaryEntry entry in myhash)
{
var componentName = entry.Key;
}
That will correspond with the name of the component that you put in the hashmap (c.Name).
Hope this help you.
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);
}
}
}