Avoiding Infinite Loops While Traversing Children Controls - c#

I'm writing a simple extension method to perform an action on a control and all of its children, and I'm wondering if I have to worry about running into the same control twice.
Safe:
public static void Traverse(this Control control, Action<Control> action)
{
Traverse(control, action, new HashSet<control>());
}
private static void Traverse(this Control control, Action<Control> action, HashSet<Control> handled)
{
handled.Add(control);
foreach (Control child in control.Controls)
if (!handled.Contains(child))
Traverse(child, action, handled);
action.Invoke(control);
}
Possibly Unsafe:
public static void Traverse(this Control control, Action<Control> action)
{
foreach (Control child in control.Controls)
Traverse(child, action, handled);
action.Invoke(control);
}
Is the hash set necessary to keep this code safe? It needs to invoke the action on every control only once, and it can't enter an infinite loop. Is the structure of parent-child controls such that I don't need to worry about this?
Usage:
this.Traverse(o => o.SuspendLayout());
// Do lots of UI changes
this.Traverse(o => o.ResumeLayout());
The (possibly) comprehensive way to do this:
public static class ControlExtensions
{
public static void Traverse(this Control control, Action<Control> action)
{
Traverse(control, action, TraversalMethod.DepthFirst);
}
public static void Traverse(this Control control, Action<Control> action, TraversalMethod method)
{
switch (method)
{
case TraversalMethod.DepthFirst:
TraverseDepth(control, action);
break;
case TraversalMethod.BreadthFirst:
TraverseBreadth(control, action);
break;
case TraversalMethod.ReversedDepthFirst:
TraverseDepthReversed(control, action);
break;
case TraversalMethod.ReversedBreadthFirst:
TraverseBreadthReversed(control, action);
break;
}
}
private static void TraverseDepth(Control control, Action<Control> action)
{
Stack<Control> controls = new Stack<Control>();
Queue<Control> queue = new Queue<Control>();
controls.Push(control);
while (controls.Count != 0)
{
control = controls.Pop();
foreach (Control child in control.Controls)
controls.Push(child);
queue.Enqueue(control);
}
while (queue.Count != 0)
action.Invoke(queue.Dequeue());
}
private static void TraverseBreadth(Control control, Action<Control> action)
{
Queue<Control> controls = new Queue<Control>();
Queue<Control> queue = new Queue<Control>();
controls.Enqueue(control);
while (controls.Count != 0)
{
control = controls.Dequeue();
foreach (Control child in control.Controls)
controls.Enqueue(child);
queue.Enqueue(control);
}
while (queue.Count != 0)
action.Invoke(queue.Dequeue());
}
private static void TraverseDepthReversed(Control control, Action<Control> action)
{
Stack<Control> controls = new Stack<Control>();
Stack<Control> stack = new Stack<Control>();
controls.Push(control);
while (controls.Count != 0)
{
control = controls.Pop();
foreach (Control child in control.Controls)
controls.Push(child);
stack.Push(control);
}
while (stack.Count != 0)
action.Invoke(stack.Pop());
}
private static void TraverseBreadthReversed(Control control, Action<Control> action)
{
Queue<Control> controls = new Queue<Control>();
Stack<Control> stack = new Stack<Control>();
controls.Enqueue(control);
while (controls.Count != 0)
{
control = controls.Dequeue();
foreach (Control child in control.Controls)
controls.Enqueue(child);
stack.Push(control);
}
while (stack.Count != 0)
action.Invoke(stack.Pop());
}
}

Each child has one parent, so there's no need to worry.

Controls really can only have a single parent. There isn't really a reason to track "handled", as you'll only execute your method on the control a single time.
Now, if you're using a framework that allows controls to have multiple parents (I'm not aware of any .NET frameworks that allow this), then this might be required. If, however, you're using Windows Forms (which is what this appears to be) or WPF, you can just simplify this to:
private static void Traverse(this Control control, Action<Control> action)
{
foreach (Control child in control.Controls)
Traverse(child, action);
action(control);
}

You need to use recursion
public sub DoStuffToControlAndChildren(TargetControl as Control)
'Insert code to do stuff to TargetControl here
if TargetControl.Controls.count = 0 then
return
end if
For each ChildControl in TargetControl.Controls
DoStuffToControlAndChildren(ChildControl)
next
end sub

Related

Find Radio Group in Nested Controls in c# windows form

I wrote this recursive function to find name of radio button in each tab page:
private static DevExpress.XtraEditors.RadioGroup FindRadioGroupInTabPage(System.Windows.Forms.Control parentControl)
{
if (!parentControl.HasChildren)
{
return null;
}
foreach (var ct in parentControl.Controls.OfType<System.Windows.Forms.Control>())
{
if (ct is DevExpress.XtraEditors.RadioGroup rdg)
{
return rdg;
}
else
{
FindRadioGroupInTabPage(ct);
return null;
}
}
return null;
}
But it always returns Null. What's wrong with my code?
Use the following and in RadioButtonList change from RadioButton to DevExpress.XtraEditors.RadioGroup.
public static class ControlExtensions
{
public static IEnumerable<T> Descendants<T>(this Control control) where T : class
{
foreach (Control child in control.Controls)
{
if (child is T thisControl)
{
yield return (T)thisControl;
}
if (child.HasChildren)
{
foreach (T descendant in Descendants<T>(child))
{
yield return descendant;
}
}
}
}
public static List<RadioButton> RadioButtonList(this Control control)
=> control.Descendants<RadioButton>().ToList();
}
Usage where this is the current form
List<RadioButton> radioButtons = this.RadioButtonList();
Can be used on say a panel
List<RadioButton> radioButtonInPanel = panel1.RadioButtonList();

Removing Controls from asp.net page at runtime

I have an asp.net dashboard site that allows a user to load HTML templates from a dropdownlist. There are multiple types of DevExpress components on the page, including the ASPxDockPanel. If a user changes templates I get an error that the dockpanel already exists, I would like to include a recursive function like the one below that checks to see if any ASPxDockPanels are present on the page, and if they are present remove them. This works for only the first dock panel then bombs out. I think this is because an enumerable set of controls cannot be modified while looping through it. How can I loop though the controls and remove the dock panels at runtime?
protected void LoadTableTemplate(string selectedTemplate, int currentMode)
{
FindAllDockPanels(this);
}
public void FindAllDockPanels(Control ctrl)
{
if (ctrl != null)
{
foreach (Control control in ctrl.Controls)
{
if (control is ASPxDockPanel)
{
ctrl.Controls.Remove(control);
control.Dispose();
}
FindAllDockPanels(control);
}
}
}
Use a temporary collection, like so:
public void FindAllDockPanels(Control ctrl) {
if (ctrl != null) {
List<Control> remove = new List<Control>();
foreach (Control control in ctrl.Controls) {
if (control is ASPxDockPanel) {
remove.Add( control );
}
}
foreach(Control control in remove) {
control.Controls.Remove( control );
control.Dispose(); // do you really need to dispose of them?
}
FindAllDockPanels(control);
}
}
If you find yourself doing this often, it might be worth moving these "DelayedDelete" actions to an extension method, like so:
public static void DelayedRemove<T>(this IEnumerable<T item> collection, T itemToRemove) {
// add it to a private static dictionary bound to the `collection` instance.
}
public static void DelayedRemoveFinish(this IEnumerable<T item> collection) {
// empty the private static dictionary in here
}
then you'd use it like so:
public void FindAllDockPanels(Control ctrl) {
if (ctrl != null) {
foreach (Control control in ctrl.Controls) {
if (control is ASPxDockPanel) control.Controls.DelayedRemove( control );
}
control.Controls.DelayedRemoveFinish();
FindAllDockPanels(control);
}
}
Much cleaner, no? :)

C# Lost Focus without using control event

There is a way to get who lost his focus in a c# form without using the LostFocus event each component?
[edit]
I need for a On Screen Keyboard.
I need to store last focussed control to fire keypress, but i need to do it to all in the window.
Also the main project is wpf, than i have some component nested as itemsTemplate and so on...
I finally used this:
foreach (Control uie in FindInLogicalTreeDown(this, typeof(TextBox))) AssignEvents(uie);
private static IEnumerable<DependencyObject> FindInLogicalTreeDown(DependencyObject obj, Type type)
{
if (obj != null)
{
if (obj.GetType() == type) { yield return obj; }
foreach (object child in LogicalTreeHelper.GetChildren(obj))
if (typeof(DependencyObject).IsAssignableFrom(child.GetType()))
foreach (var nobj in FindInLogicalTreeDown((DependencyObject)child, type)) yield return nobj;
}
yield break;
}
void AssignEvents(Control element)
{
element.GotMouseCapture += new MouseEventHandler(Component_GotFocus);
}
public Control LastFocus { get; set; }
public void Component_GotFocus(object sender, RoutedEventArgs e)
{
LastFocus = (Control)sender;
if (LastFocus.GetType() == typeof(TextBox)) { KeyboardVisible = true; }
}
i don't think there is any way until unless you subscribe events and keep track which lost focus event has fired last

Make all Controls on a Form read-only at once

Does anyone have a piece of code to make all Controls (or even all TextBoxes) in a Form which is read-only at once without having to set every Control to read-only individually?
Write an extension method which gathers controls and child controls of specified type:
public static IEnumerable<T> GetChildControls<T>(this Control control) where T : Control
{
var children = control.Controls.OfType<T>();
return children.SelectMany(c => GetChildControls<T>(c)).Concat(children);
}
Gather TextBoxes on the form (use TextBoxBase to affect RichTextBox, etc - #Timwi's solution):
IEnumerable<TextBoxBase> textBoxes = this.GetChildControls<TextBoxBase>();
Iterate thru collection and set read-only:
private void AreTextBoxesReadOnly(IEnumerable<TextBoxBase> textBoxes, bool value)
{
foreach (TextBoxBase tb in textBoxes) tb.ReadOnly = value;
}
If want - use caching - #igor's solution
In Form:
if (_cached == null)
{
_cached = new List<TextBox>();
foreach(var control in Controls)
{
TextBox textEdit = control as TextBox;
if (textEdit != null)
{
textEdit.ReadOnly = false;
_cached.Add(textEdit);
}
}
}
else
{
foreach(var control in _cached)
{
control .ReadOnly = false;
}
}
Add recursion also (Controls can be placed into other controls (panels)).
You should be able to write yourself a utility function to do this. You can iterate over the form’s controls and then each control’s child controls recursively. For example:
public static void SetEnableOnAllControls(Control parentControl, bool enable)
{
parentControl.Enabled = enable;
foreach (Control control in parentControl.Controls)
SetEnableOnAllControls(control, enable);
}
[...]
// inside your form:
SetEnableOnAllControls(this, false);
This doesn’t take care of ToolStrips, which aren’t controls. You could write a separate, similar method for those.
Notice that the above disables the form itself too. If you don’t want that, try this:
public static void SetEnableOnAllChildControls(Control parentControl, bool enable)
{
foreach (Control control in parentControl.Controls)
{
control.Enabled = enable;
SetEnableOnAllChildControls(control, enable);
}
}
If you really meant the ReadOnly property, which is only relevant for TextBoxes, try this:
public static void SetReadOnlyOnAllControls(Control parentControl, bool readOnly)
{
if (parentControl is TextBoxBase)
((TextBoxBase) parentControl).ReadOnly = readOnly;
foreach (Control control in parentControl.Controls)
SetReadOnlyOnAllControls(control, readOnly);
}
I would use reflection to check to see if the generic Control object has an Enabled property.
private static void DisableControl(Control control)
{
PropertyInfo enProp = control.GetType().GetProperty("Enabled");
if (enProp != null)
{
enProp.SetValue(control, false, null);
}
foreach (Control ctrl in control.Controls)
{
DisableControl(ctrl);
}
}
this.Enabled = false;
Depends on what you doing really, you might want to consider putting the control within a panel and disabling that.
I haven't tested this, but it should work:
foreach (var textBox in this.Controls.OfType<TextBox>())
textBox.ReadOnly = true;
Edit: This is not such a good solution it seems: see Timwi's comment.
I just developed a recursive solution that handles any kind of Web Control, using a simple static method and ASP.NET polymorphysm.
/// <summary>
/// Handle "Enabled" property of a set of Controls (and all of the included child controls through recursivity)
/// By default it disable all, making all read-only, but it can also be uset to re-enable everything, using the "enable" parameter
/// </summary>
/// <param name="controls">Set of controls to be handled. It could be the entire Page.Controls set or all of the childs of a given control (property "Controls" of any Control-derived class)</param>
/// <param name="enable">Desired value of the property "enable" of each control. </param>
public static void DisableControls(ControlCollection controls, bool enable = false)
{
foreach (Control control in controls)
{
var wCtrl = control as WebControl;
if (wCtrl != null)
{
wCtrl.Enabled = enable;
}
if (control.Controls.Count > 0)
DisableControls(control.Controls, enable);
}
}
/// <summary>
/// Enable a set of Controls (and all of the included child controls through recursivity).
/// Friendly name for DisableControls(controls, true), that achieve the same result.
/// </summary>
/// <param name="Controls">Set of controls to be handled. It could be the entire Page.Controls set or all of the childs of a given control (property "Controls" of any Control-derived class)</param>
public static void EnableControls(ControlCollection controls)
{
DisableControls(controls, true);
}
This is tested and looks fairly fast (less than a millisecond in a web form with 25+ controls to be disabled).
If you prefer an extension method, i think it should be enough to change the solution as follows:
public static void DisableControls(this Control control, bool enable = false)
{
foreach (Control ctrl in control.Controls)
{
var wCtrl = ctrl as WebControl;
if (wCtrl != null)
{
wCtrl.Enabled = enable;
}
if (ctrl.Controls.Count > 0)
ctrl.DisableControls(enable);
}
}
public static void EnableControls(this Control control)
{
control.DisableControls(true);
}

Get available controls from a Form

How do I get available controls from a Windows Forms form using C#?
Or, ProfK's solution in enumerable syntax:
public static IEnumerable<Control> GetControls(Control form) {
foreach (Control childControl in form.Controls) { // Recurse child controls.
foreach (Control grandChild in GetControls(childControl)) {
yield return grandChild;
}
yield return childControl;
}
}
Try this method in your form. It will recursively get all controls on your form, and their children:
public static List<Control> GetControls(Control form)
{
var controlList = new List<Control>();
foreach (Control childControl in form.Controls)
{
// Recurse child controls.
controlList.AddRange(GetControls(childControl));
controlList.Add(childControl);
}
return controlList;
}
Then call it with a:
List<Control> availControls = GetControls(this);
I think you mean all controls on the form.
So simply you can use Controls property inside your form object.
foreach(Control c in this.Controls)
{
//TODO:
}

Categories