I have a groupbox in my application which contains child controls.(As seen in the attchached pic). I want to enumerate through all the textboxes for performing some validation using a simple foreach loop.
This document outline would give a fair idea of the housing of the controls
foreach (Control control in grpBxTargetSensitivity.Controls)
{
if (control is FlowLayoutPanel && control.HasChildren)
{
foreach (Control ctrl in control.Controls)
{
if (ctrl is Panel && ctrl.HasChildren)
{
foreach (Control tbox in ctrl.Controls)
{
if (tbox is TextBox)
{
TextBox textbox = tbox as TextBox;
validData &= !string.IsNullOrWhiteSpace(textbox.Text);
}
}
}
}
}
}
My question is, Is there a better way (possibly through LINQ) to get all the controls including the textboxes housed inside the panels than the above method.?
I don't know that this is any better.. whether it's easier to read is a matter of opinion:
var validData
= grpBxTargetSensitivity.Controls.OfType<FlowLayoutPanel>()
.SelectMany(c => c.Controls.OfType<Panel>())
.SelectMany(c => c.Controls.OfType<TextBox>())
.All(textbox => !string.IsNullOrWhiteSpace(textbox.Text));
This'll grab all TextBoxes inside of all Panels in all FlowLayoutPanels in your GroupBox, and return true if all of those TextBoxes have a value in them.
A one liner slution,
IEnumerable<TextBox> collection = grpBxTargetSensitivity.Children.OfType<TextBox>(); //assuming you are looking for TextBox
or
You can try following generic method,
public static IEnumerable<T> FindVisualChildren<T>(DependencyObject depObj) where T : DependencyObject
{
if (depObj != null)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
if (child != null && child is T)
{
yield return (T)child;
}
foreach (T childOfChild in FindVisualChildren<T>(child))
{
yield return childOfChild;
}
}
}
}
then enumerate over the controls as follows,
foreach (TextBox tb in FindVisualChildren<TextBox>(grpBxTargetSensitivity)) //assuming you are looking for TextBox
{
// do something
}
I create one method by the ability to find any controls (or T type) and any inherited object from Control (or T type) for you:
public static List<T> GetSubControlsOf<T>(Control parent) where T : Control
{
var myCtrls = new List<T>();
foreach (Control ctrl in parent.Controls)
{
// if ctrl is type of T
if (ctrl.GetType() == typeof(T) ||
ctrl.GetType().IsInstanceOfType(typeof(T)))
{
myCtrls.Add(ctrl as T);
}
else if (ctrl.HasChildren)
{
var childs = GetSubControlsOf<T>(ctrl);
if (childs.Any())
myCtrls.AddRange(childs);
}
}
return myCtrls;
}
and use that this form for e.g:
foreach (var textbox in GetSubControlsOf<TextBox>(this))
{
validData &= !string.IsNullOrWhiteSpace(textbox.Text);
}
I found following links on StackOverflow itself:
How to get ALL child controls of a Windows Forms form of a specific type (Button/Textbox)?
Loop through all the user controls on a page
Give them try. Should work in your case.
Related
I have 2 listviews in tab page. however I am looking for a function to find the right control by name:
I have
foreach (Control c in form.Controls) // loop through form controls
{
if (c is TabControl)
{
TabControl f = (TabControl)c;
foreach (Control tab in f.Controls)
{
TabPage tabPage = (TabPage)tab;
foreach (Control control in tabPage.Controls)
{
MessageBox.Show(control.Name);
// code to go here
}
}
}
}
The Controls collection has a Find function that returns an array:
Control[] ctrls = this.Controls.Find("listView1", true);
if (ctrls.Length == 1) {
MessageBox.Show("Found " + ctrls[0].Name);
}
This will search for a control with the specified name in the controls of the specified control and all his sons.
public Control findControlbyName(String name, Control parent){
foreach (Control ctr in parent.Controls)
{
if (ctr.Name.Equals(name)) return ctr;
else return findControlbyName(name, ctr);
}
return null;
}
You just need to do:
findControlbyName("NameOfTheListView",this);
When I double click on my selected listview item I can easily get each value and set them in a textbox by using the following code.
ListViewItem item = listView1.SelectedItems[0];
entry_txtBox.Text = item.Text;
name_txtBox.Text = item.SubItems[1].Text;
But I'd rather loop through the selected item and compare each column name to each textbox tag. If they match set the column value to the textbox.
This is what I have so far.
foreach (Control c in this.Controls)
{
foreach (Control childc in c.Controls)
{
foreach (ColumnHeader header in listView1.Columns) // This should be the selected item.
{
if (childc is TextBox && header == childc.Tag)
{
// Fill Textboxes
}
}
}
}
Again my question is how can I loop through each column header and compare them to a textbox tag.
First you can use good old recursion to build a flat list of all your controls up by traversing the control tree:
var controls = GetControls(parent);
public IEnumerable<Control> GetControls(Control parent)
{
foreach(var control in parent.Controls)
{
yield return control;
var childControls = GetControls(control);
foreach(var child in childControls)
{
yield return child;
}
}
}
Then just do a LINQ to filter:
var textBoxControls = controls.Select(c => c is TextBox && header == TextBox.ID); //Or whatever condition you want to use.
I'm setting the TabStop property of all controls whose ReadOnly property is true to false with this code:
foreach (Control control in this.Controls)
{
if (control is TextBox)
{
//if (((TextBox)control).ReadOnly)
//{
// control.TabStop = false;
//}
control.TabStop = (!((TextBox)control).ReadOnly);
}
}
...but I reckon there's probably a more "modern" (elegant/fancy) way of doing this, probably with LINQ.
UPDATE
Okay, so I thought, based on the answers, that this would work:
private void FrmDelivery_Load(object sender, EventArgs e)
{
var q = Controls.SelectMany(c => Walk(c)).OfType<TextBox>().Where(c
=> c.ReadOnly);
foreach (var control in q)
{
control.TabStop = false;
}
}
IEnumerable<Control> Walk(Control control)
{
yield return control;
foreach (var child in control.Controls.SelectMany(c=> Walk(c)))
{
yield return c;
}
}
...but I get:
"'System.Windows.Forms.Control.ControlCollection' does not contain a definition for 'SelectMany' and no extension method 'SelectMany' accepting a first argument of type 'System.Windows.Forms.Control.ControlCollection' could be found (are you missing a using directive or an assembly reference?)"
-in both instances where SelectMany is used (and it doesn't appear to be resolvable); and, on the "yield return c" line:
"The name 'c' does not exist in the current context"
UPDATE 2
For my simple scenario (no containers / controls within controls), this works
foreach (Control control in this.Controls)
{
var txtbx = control as TextBox;
if (txtbx != null)
{
txtbx.TabStop = (!txtbx.ReadOnly);
}
}
To linqify your example:
foreach (var control in Controls.OfType<TextBox>().Where(c => c.ReadOnly)
{
control.TabStop = false;
}
However, your example does not process all controls because a Control can contain other control. You will need a recursive descent algorithm.
IEnumerable<Control> Walk(Control control)
{
yield return control;
foreach (var child in control.Controls.SelectMany(c=> Walk(c)))
{
yield return c;
}
}
var q = Controls
.SelectMany(c => Walk(c))
.OfType<TextBox>().
.Where(c => c.ReadOnly);
foreach (var control in q)
{
control.TabStop = false;
}
foreach (TextBox control in this.Controls.OfType<TextBox>())
Not the most performant, and not the most readable code, but here's how you could do it as a one-liner:
this.Controls.OfType<TextBox>().ToList().ForEach(t => t.TabStop = !t.ReadOnly);
...Just to prove that you can.
I have a C# form with some types of controls, i throught this code for loop all Labels and re-parent:
private void MakeAllLabelTrans(Control frm, Control parent)
{
foreach (Control ctl in frm.Controls)
{
if (ctl is Label)
{
ctl.Parent = parent;
// ctl.BackColor = Color.Transparent;
}
else if (ctl.HasChildren)
{
MakeAllLabelTrans(ctl, parent);
}
}
}
and call as: MakeAllLabelTrans(this, picBackground); in Form_Load event, but some label was missed (i have puted a messagebox in the loop body - it really not in the loop), but i don't know why?
You're changing the collection while enumerating it. This leads to some items being skipped.
You should rewrite this to first build a list of controls that will need to be reparented, and then reparent them in a second pass.
You should not modify the collection on which you are iterating.
private static IEnumerable<Labels> FindAllLabels(Control container)
{
foreach (Control ctl in container.Controls)
{
var lbl = ctl as Label;
if (lbl != null)
{
yield return ctl;
}
else if (ctl.HasChildren)
{
foreach (var innerResult in FindAllLabels(ctl))
yield return innerResult;
}
}
}
// now call the method like this if you want to change a property on the label
foreach (var label in FindAllLabels(theForm))
label.BackgroundColor = Color.White;
// or like this (note the .ToList()) if you want to move the labels around:
foreach (var label in FindAllLabels(theForm).ToList())
label.Parent = someOtherControl;
Im trying to figure out a way to build a somewhat clever version of the jQuery closest method in C#. Im using a generic method to find the desired control and then index the control chain
public static T FindControlRecursive<T>(Control control, string controlID, out List<Control> controlChain) where T : Control
{
controlChain = new List<Control>();
// Find the control.
if (control != null)
{
Control foundControl = control.FindControl(controlID);
if (foundControl != null)
{
// Add the control to the list
controlChain.Add(foundControl);
// Return the Control
return foundControl as T;
}
// Continue the search
foreach (Control c in control.Controls)
{
foundControl = FindControlRecursive<T>(c, controlID);
// Add the control to the list
controlChain.Add(foundControl);
if (foundControl != null)
{
// Return the Control
return foundControl as T;
}
}
}
return null;
}
To Call it
List<Control> controlChain;
var myControl = FindControls.FindControlRecursive<TextBox>(form, "theTextboxId"), out controlChain);
To find the closest element of id or type
// Reverse the list so we search from the "myControl" and "up"
controlChain.Reverse();
// To find by id
var closestById = controlChain.Where(x => x.ID.Equals("desiredControlId")).FirstOrDefault();
// To find by type
var closestByType = controlChain.Where(x => x.GetType().Equals(typeof(RadioButton))).FirstOrDefault();
Would this be a good approach or are there any other cool solutions out there creating this?
Whats your consideration?
Thanks!
Maybe something like this
public static IEnumerable<Control> GetControlHierarchy(Control parent, string controlID)
{
foreach (Control ctrl in parent.Controls)
{
if (ctrl.ID == controlID)
yield return ctrl;
else
{
var result = GetControlHierarchy(ctrl, controlID);
if (result != null)
yield return ctrl;
}
yield return null;
}
}