Breadth first search of C# Control Collection - c#

I'm trying to write an extension method for System.Web.UI.Control which will search its ControlCollection for an instance of specific Type, and return the first instance found. Depth-first is simple, however I want to search breadth first so that higher collections take priority.
My current method is flawed and will exit early, returning null in certain cases where the entire search wasn't completed. I'm hoping I'm close to the right solution but need some fresh eyes on it. Any suggestions?
public static T FindFirstControlOfType<T>(this Control rootControl, bool searchRecursively) where T : Control
{
// list for container controls
List<Control> controlsWithChildren = new List<Control>();
// iterate the current control collection first
foreach (Control child in rootControl.Controls)
{
if (child.GetType().IsAssignableFrom(typeof(T)))
{
return (T)child;
}
// track those controls containing children
if (child.HasControls())
{
controlsWithChildren.Add(child);
}
}
// if recursion is enabled, search the child nodes
if (searchRecursively)
{
foreach (Control control in controlsWithChildren)
{
return FindFirstControlOfType<T>(control, true);
}
}
// if never found, return null
return null;
}
EDIT - Working Solution based on the marked answer, for anyone interested:
public static T FindFirstControlOfType<T>(this Control rootControl, bool searchNestedControls) where T : Control
{
Queue<Control> queue = new Queue<Control>();
EnqueueChildControls(queue, rootControl);
while (queue.Count > 0)
{
Control current = queue.Dequeue();
if (current.GetType() == typeof(T))
{
return (T)current;
}
if (searchNestedControls)
{
EnqueueChildControls(queue, current);
}
}
return null;
}
private static void EnqueueChildControls(Queue<Control> queue, Control control)
{
foreach (Control current in control.Controls)
{
queue.Enqueue(current);
}
}

Breadth first search (pseudo code)
Queue fringe
fringe.Enqueue(rootControl)
while(fringe.Count > 0)
{
current = fringe.Dequeue()
Visit(current)
fringe.EnqueueAll(current.Children)
}
There's no need for recursion for breadth first search.
Visit is whatever you want to do with the current object. If you're searching for something then Visit would mean: if(current matches criteria) return current
Update:
Working example: https://gist.github.com/1960108

Well the main problem is here:
foreach (Control control in controlsWithChildren)
{
return FindFirstControlOfType<T>(control, true);
}
You are returning the results of the recursive search on the very first child with children, regardless of whether that search succeeded. Breadth-first search is a widely-discussed topic, but it boils down to using a queue:
Queue<Control> controlsWithChildren = new Queue<Control>();
and (usually) a loop rather than recursion.

Related

How to find first editable control to focus on in a page

My current function only finds first Textbox which is not totally correct. Control class doesn't have IsEditable property.
private static Control FindFocusableControl(Control CurrentControl)
{
if (CurrentControl.Visible)
{
if (CurrentControl is TextBox)
{
return CurrentControl;
}
if (CurrentControl.HasControls())
{
foreach (Control CurrentChildControl in CurrentControl.Controls)
{
Control focusableControl = FindFocusableControl(CurrentChildControl);
if (focusableControl != null)
{
return focusableControl;
}
}
}
}
return null;
}
On the first recursion for the first child control, the child control is probably not visible so the routine exits.
if (CurrentControl.Visible)
You might need a different kind of check at this point, perhaps check for textbox first, then if visible.

Accessing unknown child

In my code, I need to find a child of a child of a child.
Only the first child will always be the same child but the child of the child is randomized between 4 different possible prefabs.
on that last child I want to disable it's image.
here would be the ideal code if the child wouldn't be randomized between others:
protected void SetUnpickedPrizesVisibility(bool isVisible)
{
foreach (var element in list)
{
if (!element.IsPicked)
{ element.transform.FindChild("FirstChild").FindChild("SecondChild").FindChild("ThirdChild").GetComponent<Image>().enabled = false;
}
}
}
But since, the SecondChild varies, I can't simply do that.
Is my only option to do something like this?
protected void SetUnpickedPrizesVisibility(bool isVisible)
{
foreach (var element in list)
{
if (!element.IsPicked)
{
if(element.transform.FindChild("FirstChild").FindChild("SecondChild1"))
element.transform.FindChild("FirstChild").FindChild("SecondChild1").FindChild("ThirdChild").GetComponent<Image>().enabled = false;
if(element.transform.FindChild("FirstChild").FindChild("SecondChild2"))
element.transform.FindChild("FirstChild").FindChild("SecondChild2").FindChild("ThirdChild").GetComponent<Image>().enabled = false;
if(element.transform.FindChild("FirstChild").FindChild("SecondChild3"))
element.transform.FindChild("FirstChild").FindChild("SecondChild3").FindChild("ThirdChild").GetComponent<Image>().enabled = false;
}
}
}
Or does someone have a different idea?
I know this is a very confusing to read question but I tried my best to simplify it.
Thanks,
So why not add a script to the prefab of the last child that disables the image. That way you only need to keep a reference to the script when you instantiate it and not go looking for the children.
You can just search through, something like this:
foreach (var element in list)
{
if (!element.IsPicked)
{
foreach(var grandChild in element)
{
foreach(var greatGrandChild in grandChild)
{
greatGrandChild.GetComponent<Image>().enabled = false;
}
}
}
}
It's easier to use child, grandChild and greatGrandChild rather than child of the child of child :-)

Find all TextBox controls in UWP Page

I need to find all TextBox(es) that are on a UWP Page but having no luck. I thought it would be a simple foreach on Page.Controls but this does not exist.
Using DEBUG I am able to see, for example, a Grid. But I have to first cast the Page.Content to Grid before I can see the Children collection. I do not want to do this as it may not be a Grid at the root of the page.
Thank you in advance.
UPDATE: This is not the same as 'Find all controls in WPF Window by type'. That is WPF. This is UWP. They are different.
You're almost there! Cast the Page.Content to UIElementCollection, that way you can get the Children collection and be generic.
You'll have to make your method recurse and look either for Content property if element is a UIElement or Children if element is UIElementCollection.
Here's an example:
void FindTextBoxex(object uiElement, IList<TextBox> foundOnes)
{
if (uiElement is TextBox)
{
foundOnes.Add((TextBox)uiElement);
}
else if (uiElement is Panel)
{
var uiElementAsCollection = (Panel)uiElement;
foreach (var element in uiElementAsCollection.Children)
{
FindTextBoxex(element, foundOnes);
}
}
else if (uiElement is UserControl)
{
var uiElementAsUserControl = (UserControl)uiElement;
FindTextBoxex(uiElementAsUserControl.Content, foundOnes);
}
else if (uiElement is ContentControl)
{
var uiElementAsContentControl = (ContentControl)uiElement;
FindTextBoxex(uiElementAsContentControl.Content, foundOnes);
}
else if (uiElement is Decorator)
{
var uiElementAsBorder = (Decorator)uiElement;
FindTextBoxex(uiElementAsBorder.Child, foundOnes);
}
}
Then you call that method with:
var tb = new List<TextBox>();
FindTextBoxex(this, tb);
// now you got your textboxes in tb!
You can also use the following generic method from the VisualTreeHelper documentation to get all your child controls of a given type:
internal static void FindChildren<T>(List<T> results, DependencyObject startNode)
where T : DependencyObject
{
int count = VisualTreeHelper.GetChildrenCount(startNode);
for (int i = 0; i < count; i++)
{
DependencyObject current = VisualTreeHelper.GetChild(startNode, i);
if ((current.GetType()).Equals(typeof(T)) || (current.GetType().GetTypeInfo().IsSubclassOf(typeof(T))))
{
T asType = (T)current;
results.Add(asType);
}
FindChildren<T>(results, current);
}
}
It basically recursively get the children for the current item and add any item matching the requested type to the provided list.
Then, you just have to do the following somewhere to get your elements:
var allTextBoxes = new List<TextBox>();
FindChildren(allTextBoxes, this);
To my mind, you could do it in the same way as in WPF. Because UWP uses mostly the same XAML that WPF.
So, please check out answer for the same question about WPF

Navigate through Controls and Components

I need to traverse all controls and components inside a UserControl I'm developing.
I tried:
public void Traverse(Control cnt)
{
foreach (Control c in cnt.Controls)
{
if (c.HasChildren) Traverse(c);
Debug.Print(c.Name); // For debugging purpose only
// My code goes here
}
}
Problem raises when functions meets a ToolStrip: it has no children, but Items (ToolStripItemCollection: IList, ICollection, IEnumerable).
I don't care of type: using Reflection I need to set some property, so I feel good having objects as result.
How can I get the name of every component that is inside my UserControl?
Thanks
I've written a version that goes through the control's properties and looks for IComponents that have ICollections on them:
Method:
private void GetControls(ICollection controls, IList<string> names)
{
foreach (var ctl in controls)
{
if (ctl is IComponent)
{
var name = ctl.GetType().GetProperty("Name");
if (name != null)
names.Add((string) name.GetValue(ctl, null));
foreach (var property in ctl.GetType().GetProperties())
{
var prop = property.GetValue(ctl, null);
if (prop is ICollection)
GetControls((ICollection)prop, names);
}
}
}
}
Called:
var ctlNames = new List<string>();
GetControls(Controls, ctlNames);
I've tested this and it seems to find every control on the form. I haven't tested it for every kind of control and I can't vouch for how efficient it is.

How to reduce this IF-Else ladder in c#

This is the IF -Else ladder which I have created to focus first visible control on my form.According to the requirement any control can be hidden on the form.So i had to find first visible control and focus it.
if (ddlTranscriptionMethod.Visible)
{
ddlTranscriptionMethod.Focus();
}
else if (ddlSpeechRecognition.Visible)
{
ddlSpeechRecognition.Focus();
}
else if (!SliderControl1.SliderDisable)
{
SliderControl1.Focus();
}
else if (ddlESignature.Visible)
{
ddlESignature.Focus();
}
else
{
if (tblDistributionMethods.Visible)
{
if (chkViaFax.Visible)
{
chkViaFax.Focus();
}
else if (chkViaInterface.Visible)
{
chkViaInterface.Focus();
}
else if (chkViaPrint.Visible)
{
chkViaPrint.Focus();
}
else
{
chkViaSelfService.Focus();
}
}
}
Is there any other way of doing this. I thought using LINQ will hog the performance as i have to tranverse the whole page collection. I am deep on page which has masterpages.Please suggest.
I think your tree is good. This certainly looks like a logic tree that can be simplified, and you have a good sense of smell to be suspicious of it. However, it seems to be that the logic tree reflects what you need. The logic really is this convoluted, and this is the conditional framework that C# gives you to handle this situation. I don't think it can be improved.
If you had a simple list of controls that should have the focus, and you wanted to give focus to the first visible control in the list, you could do this:
(From c in ListOfControls
Where c.visible = true
Select c).First.Focus();
But, it appears you have some additional criteria, so that wouldn't work.
Two approaches:
Iterate controls and set focus if visible
Use TabIndex and set focus to first. Then focus should fall to first visible control
If you're just trying to focus the first visible control on the form, then I would replace the entire ladder with a single loop:
foreach (Control c in Controls)
{
if (c.Visible)
{
c.Focus();
break;
}
}
If you need to focus an inner control, use a recursive method:
bool FocusFirst(ControlCollection controls)
{
foreach (Control c in controls)
{
if (c.Visible)
{
c.Focus();
FocusFirst(c.Controls);
break;
}
}
}
You could return after you meet your criteria, for example:
if (ddlTranscriptionMethod.Visible)
{
ddlTranscriptionMethod.Focus();
return;
}
if (ddlSpeechRecognition.Visible)
{
ddlSpeechRecognition.Focus();
return;
}
etc..
You can iterate controls and set focus if visible. But I would suggest you to use state pattern for better code readability.
All your doing is setting the focus which is client-side functionality. I would personally do this in javascript (using jQuery). ASP.NET controls that aren't set to visible aren't rendered in the HTML, so you could look for the existence of those elements or there might be an easier way if you're just looking for the first visible, enabled control.
What about a jumpto seems like you could use that here.
When the list of items to evaluate is large (and sometimes it's very large), I try to separate the order of evaluation from the conditional logic, something like this:
List<WebControl> wcBasics = new List<WebControl>();
wcBasics.Add(ddlTranscriptionMethod);
wcBasics.Add(ddlSpeechRecognition);
wcBasics.Add(ddlESignature);
List<CheckBox> checks = new List<CheckBox>();
checks.Add(chkViaFax);
checks.Add(chkViaInterface);
checks.Add(chkViaPrint);
private void Focus()
{
foreach (WebControl c in wcBasics)
if (c.Visible) {
c.Focus();
return;
}
if (!tblDistributionMethods.Visible) return;
foreach (CheckBox chk in checks)
if (chk.Visible) {
chk.Focus();
return;
}
}
chkViaSelfService.Focus();
}
Here is a slightly different take on the problem. First, define an interface to represent controls which may or may not be focused:
public interface IFormControl
{
bool Focus();
}
Then create an implementation which handles the easy cases:
public class FormControl : IFormControl
{
private readonly Control _control;
public FormControl(Control control)
{
_control = control;
}
public bool Focus()
{
if(_control.Visible)
{
_control.Focus();
}
return _control.Visible;
}
}
And create another which handles the more difficult cases:
public class DependentFormControl : IFormControl
{
private readonly Control _control;
private readonly Func<bool> _prerequisite;
public DependentFormControl(Control control, Func<bool> prerequisite)
{
_control = control;
_prerequisite = prerequisite;
}
public bool Focus()
{
var focused = _prerequisite() && _control.Visible;
if(focused)
{
_control.Focus();
}
return focused;
}
}
Then, create an extension method which sets the focus on the first control in a sequence:
public static void FocusFirst(this IEnumerable<IFormControl> formControls)
{
var focused = false;
foreach(var formControl in formControls)
{
if(formControl.Focus())
{
break;
}
}
}
And finally, create the set of controls to be focused:
var controls = new FormControl[]
{
new FormControl(ddlTranscriptionMethod),
new FormControl(ddlSpeechRecognition),
new DependentFormControl(SliderControl1, () => !SliderControl1.SliderDisable),
new FormControl(ddlESignature),
new DependentFormControl(chkViaFax, () => tblDistributionMethods.Visible),
new DependentFormControl(chkViaInterface, () => tblDistributionMethods.Visible),
new DependentFormControl(chkViaPrint, () => tblDistributionMethods.Visible),
new DependentFormControl(chkViaSelfService, () => tblDistributionMethods.Visible)
};
controls.FocusFirst();
You can create the set of controls when your page loads and just call .FocusFirst() whenever necessary.
This is a classic state machine. By setting up a machine it can make the code easier to understand and maintain, though it may add to the total lines.
I won't get into the specifics of your code. By determining what state the user is operating in, you can programmatically change the differing values in an understandable specific state fashion. (Pseudo code below)
enum FormStates
{
Initial_View
Working_View
Edit_View
Shutdown_View
};
{ // Somewhere in code
switch (theCurrentState)
{
case Initial_View :
Control1.Enabled = true;
Control2.Enabled = true;
theCurrentState = Working_View;
break;
case Working_View
if (string.empty(Contro1.Text) == false)
{
Control2.Enabled = false;
Speachcontrol.Focus();
theCurrentState = Edit_view;
}
else // Control 2 is operational
{
Control1.Enabled = false;
SliderControl.Focus();
}
case Edit_View:
...
break;
break;
}
By organizing the code in logical steps, it makes it easier to add more states without jeopardizing an huge if/else.

Categories

Resources