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.
Related
I have this code and it seems like it should work but for some reason it doesnt.
foreach(Control c in Controls.OfType<CheckBox>()) {
Check_checked += 1;
}
** Check edit: When I step through the code the Control c is picking up all of the textboxes but nothing else.
Any help would be appreciated.
**I realise that ofType should not be picking up text/labels/watever.
Im getting text/labels/watever.
I moved my code to another computer also running visual studio. It doesnt pickup checkboxes and it doesnt seem to be bugging about picking up text/label/watever on that system.
So I think one of the issues is my VS is bugger-up. Will re-install now.
look at following answer
Get all controls of a specific type
Code check if the control is a ContainerControl and then iterates over all the children of that control to find the controls of type.
public static IEnumerable<T> GetControlsOfType<T>(Control root)
where T : Control
{
var t = root as T;
if (t != null)
yield return t;
var container = root as ContainerControl;
if (container != null)
foreach (Control c in container.Controls)
foreach (var i in GetControlsOfType<T>(c))
yield return i;
}
Then you could do something like this:
foreach (var pictureBox in GetControlsOfType<CheckBox>(form)) {
}
This code is a simple solution for counting checkboxes on a form that are checked.
private int CountChecks(IEnumerable controls)
{
var result = 0;
foreach (Control xControl in controls)
{
if (xControl.HasChildren) result += CountChecks(xControl.Controls);
if (!(xControl is CheckBox)) continue;
if (!(xControl as CheckBox).Checked) continue;
result++;
}
return result;
}
you might use this in this manner:
var howManyAreChecked = CountChecks(Controls);
This would have to be in a form to use this syntax. You must pass a forms Controls into the method in order to work correctly.
I am working on a windows phone app. I want to copy children of one canvas to other canvas. I can do it with the following code but the problem is I have to remove it from one canvas first. Code is:
private void add_template_Click(object sender, RoutedEventArgs e)
{
var childrenList = Template_canvas1.Children.Cast<UIElement>().ToArray();
root.Children.Clear();
foreach (var c in childrenList)
{
Template_canvas1.Children.Remove(c);
root.Children.Add(c);
}
}
I want to keep these elements on both the canvas. Is there another way?
Instead of trying to add the same Template_canvas1.Children to the root canvas, first make a copy of those Children and then add the copy to the root canvas.
public static T CloneXaml<T>(T source)
{
string xaml = XamlWriter.Save(source);
StringReader sr = new StringReader(xaml);
XmlReader xr = XmlReader.Create(sr);
return (T)XamlReader.Load(xr);
}
Then change your loop to:
foreach (var c in childrenList)
{
var copy = CloneXaml(c);
root.Children.Add(copy);
}
I haven't tested this code, so you may have to modify it a bit, but it should put you in the right direction.
Alternatively, you can probably use the code below which is copied from Dr Herbie's answer:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.UI.Xaml;
using System.Reflection;
using Windows.UI.Xaml.Controls;
namespace UIElementClone {
public static class UIElementExtensions {
public static T DeepClone<T>(this T source) where T : UIElement {
T result; // Get the type
Type type = source.GetType(); // Create an instance
result = Activator.CreateInstance(type) as T;
CopyProperties<T>(source, result, type);
DeepCopyChildren<T>(source, result);
return result;
}
private static void DeepCopyChildren<T>(T source, T result) where T : UIElement {
// Deep copy children.
Panel sourcePanel = source as Panel;
if (sourcePanel != null) {
Panel resultPanel = result as Panel;
if (resultPanel != null) {
foreach (UIElement child in sourcePanel.Children) {
// RECURSION!
UIElement childClone = DeepClone(child);
resultPanel.Children.Add(childClone);
}
}
}
}
private static void CopyProperties<T>(T source, T result, Type type) where T : UIElement {
// Copy all properties.
IEnumerable<PropertyInfo> properties = type.GetRuntimeProperties();
foreach (var property in properties) {
if (property.Name != "Name") { // do not copy names or we cannot add the clone to the same parent as the original.
if ((property.CanWrite) && (property.CanRead)) {
object sourceProperty = property.GetValue(source);
UIElement element = sourceProperty as UIElement;
if (element != null) {
UIElement propertyClone = element.DeepClone();
property.SetValue(result, propertyClone);
}
else {
try {
property.SetValue(result, sourceProperty);
}
catch (Exception ex) {
System.Diagnostics.Debug.WriteLine(ex);
}
}
}
}
}
}
}
}
If none of these worked for you, I'm afraid you'd have to implement your own serializer. It looks like David Poll implemented a decent serlizer, so have a look. Using his serlizer is as simple as using the XamlWriter, then you can use the XamlReader:
public static T CloneXaml<T>(T source)
{
UiXamlSerializer uxs = new UiXamlSerializer();
string xaml = uxs.Serialize(source);
StringReader sr = new StringReader(xaml);
XmlReader xr = XmlReader.Create(sr);
return (T)XamlReader.Load(xr);
}
To get this functionality, download his Slab library, go to the "Binaries" folder and copy all the dlls that start with "SLaB.Utilities.Xaml.Serializer" to your project. There might be some other dlls required as dependency. He has example solution in the library if you like to look at the code an learn.
Without a good Minimal, Complete, and Verifiable example that shows clearly what you've tried, with a precise description of what exactly you're trying to achieve, it's impossible to know for sure what the best answer would be.
That said, given that WPF already knows how to "clone" elements in a sense, through the use of data templates, your question really sounds a lot like an XY Problem to me. That is, you only think you need to literally clone the elements already in your visual tree, when in fact what you should be doing is defining a view model that represents the data to be displayed for the element(s) to be "cloned", define a single data template that uses XAML to describe the visual elements that will display the data in the view model, and then simply apply the template as necessary wherever you want the visual elements to be "cloned".
I.e. they won't really be cloned. Instead, WPF will automatically populate a whole new sub-tree of visual elements exactly as you want them to be. Since the template allows you to completely define all aspects, there is no issue related to e.g. trying to get the event subscriptions hooked up, setting up bindings correctly, etc.
In your specific example (vague though it is), it sounds like you most likely want to use an ItemsControl element, in which the ItemsPanel is a Canvas object. You would then define a DataTemplate that represents a single item in the ItemsPanel; this template would be referenced either implicitly by setting its DataType property, or explicitly by setting the ItemsControl.ItemTemplate property. Then, instead of cloning anything, you just create an ItemsControl when you want a copy of your visual for the data.
New answer after user's feedback that it is not working on Windows Phone
Complete final Windows Phone App can be downloaded here.
There are some API differences, for example instead of pinfo.SetMethod property we have to use pinfo.GetSetMethod() etc.
Secondly, I was unknowingly didn't check for Name property which must not be copied as otherwise we would be making another instance with same name.
Third, I posted for simple case of simple controls like Button, TextBox, Rectangle etc which do not contain children. If that is the case you have to go for recursive deep cloning to clone children too. As children could have more children and so on.
foreach (UIElement oldElem in Canvas1.Children)
{
try
{
Type t = oldElem.GetType();
UIElement newElem = (UIElement)Activator.CreateInstance(t);
PropertyInfo[] info = t.GetProperties();
int i = 0;
foreach (PropertyInfo pinfo in info)
{
if (pinfo.Name == "Name") continue;
try
{
if (pinfo.GetSetMethod() != null) // avoid read-only properties
pinfo.SetValue(newElem, pinfo.GetValue(oldElem, null),null);
}
catch (Exception ex)
{
Debug.WriteLine((++i).ToString() + " : " + pinfo.ToString());
}
}
Canvas.SetLeft(newElem, Canvas.GetLeft((oldElem)));
Canvas.SetTop(newElem, Canvas.GetTop((oldElem)));
Canvas2.Children.Add(newElem);
}
catch (Exception ex)
{
}
}
And if you are going for truly deep cloning then replace code in outer try block above with a simpler:
foreach (UIElement oldElem in Canvas1.Children)
{
try
{
UIElement newElem = oldElem.DeepClone();
Canvas2.Children.Add(newElem);
Canvas.SetLeft(newElem, Canvas.GetLeft(oldElem));
Canvas.SetTop(newElem, Canvas.GetTop(oldElem));
}
catch (Exception ex){ }
}
Old answer based on WPF only
Don't know about windows phone but in WPF this creates a fresh element and puts it in exactly same place in another canvas. Check if it fits your needs, else I will update it again.
foreach (UIElement oldElem in Canvas1.Children)
{
Type t = oldElem.GetType();
UIElement newElem = (UIElement)Activator.CreateInstance(t);
PropertyInfo[] info = t.GetProperties();
int i = 0;
foreach (PropertyInfo pinfo in info)
{
try
{
if (pinfo.SetMethod != null) // avoid read-only properties
pinfo.SetValue(newElem, pinfo.GetValue(oldElem));
}
catch (Exception ex)
{
Debug.WriteLine((++i).ToString() + " : " + pinfo.ToString());
}
}
Canvas.SetLeft(newElem, Canvas.GetLeft((oldElem)));
Canvas.SetTop(newElem, Canvas.GetTop((oldElem)));
Canvas.SetRight(newElem, Canvas.GetRight((oldElem)));
Canvas.SetBottom(newElem, Canvas.GetBottom((oldElem)));
Canvas2.Children.Add(newElem);
}
I've found a few answers around that work fine with modifying .Text, .Checked values and so, but none of them worked when I tried changing the .Value property. I can't get that to work on progress bars.
Last I tried:
foreach (Control c in this.Controls)
{
if (c.Name == "test" && c is ProgressBar)
{
((ProgressBar)c).Value = 23;
}
}
Am I missing a using statement or something?
Assuming that your progressbar control is named "test" (all lowercase letters) and is placed directly on the surface of your form (not inside a groupbox,panel or other control container) then this code should work and simplify your work
foreach (var c in this.Controls.OfType<ProgressBar>().Where(x => x.Name == "test")
{
c.Value = 23;
}
instead if the ProgressBar is placed inside a control container (like a panel) the above code should be changed to loop over the controls collection of the container
foreach (var c in this.panel1.Controls.OfType<ProgressBar>().Where(x => x.Name == "test")
{
c.Value = 23;
}
As pointed out in the comment by KingKing, if you are absolutely sure that a control named "test" exists in your groupbox then a simple lookup in the controls collection should result in your progressbar. Looping is not necessary in this case
ProgressBar pb = this.groupBox1.Controls["test"] as ProgressBar;
if(pb != null) pb.Value = 23;
The trick here is that Controls is not a List<> or IEnumerable but a ControlCollection.
I recommend using an extension of Control. Add this class to your project:
public static class ControlExtensionMethods
{
public static IEnumerable<Control> All(this System.Windows.Forms.Control.ControlCollection controls)
{
foreach (Control control in controls)
{
foreach (Control grandChild in control.Controls.All())
yield return grandChild;
yield return control;
}
}
}
Then you can do :
foreach(var textbox in this.Controls.All())
{
// Apply logic to a control
}
Source: Click
I've used this code before in another program, but now I'm having trouble understanding why it won't run the code after my second line.
foreach (Control c in Controls)
if (c.GetType() == typeof(TextBox)) //doesn't run any further
{
if ((string)c.Tag == "Filled")
{
...
}
...
}
I'm either missing some minor little detail or something else is incorrect. Any ideas?
EDIT: my textboxes are inside a panel.
It might be simpler to do this:
foreach ( TextBox tb in this.Controls.OfType<TextBox>())
{
if ((string)tb.Tag == "Filled")
// .....
}
When you call Control.Controls, it will only return the controls at the outermost level. It won't recursively descend into any container controls that hold other controls.
If your controls are in another container, you will need to use that container's .Controls property instead.
Alternatively you can generalize it by writing a method to recursively return all the controls from the parent and all it's children, like so:
public IEnumerable<Control> AllControls(Control container)
{
foreach (Control control in container.Controls)
{
yield return control;
foreach (var innerControl in AllControls(control))
yield return innerControl;
}
}
You can then use that instead of Control.Controls as follows:
private void test() // Assuming this is a member of a Form other class derived from Control
{
var textboxesWithFilledTag =
AllControls(this).OfType<TextBox>()
.Where(tb => (string) tb.Tag == "Filled");
foreach (var textbox in textboxesWithFilledTag)
Debug.WriteLine(textbox.Text);
}
As the comment says, I'm assuming that the test() method is a member of your Form or another class derived from Control. If it isn't, you will have to pass the parent control to it:
private void test(Control container)
{
var textboxesWithFilledTag =
AllControls(container).OfType<TextBox>()
.Where(tb => (string) tb.Tag == "Filled");
foreach (var textbox in textboxesWithFilledTag)
Debug.WriteLine(textbox.Text);
}
The following method has identical results to the one above, for reference (and is more readable IMHO):
private void test(Control container)
{
foreach (var textbox in AllControls(container).OfType<TextBox>())
if ((string)textbox.Tag == "Filled")
Debug.WriteLine(textbox.Text);
}
For your code, your button click handler might look something like this:
void button1_Click(object sender, EventArgs e)
{
foreach (var c in AllControls(this).OfType<TextBox>())
{
if ((string) c.Tag == "Filled")
{
// Here is where you put your code to do something with Textbox 'c'
}
}
}
Note that you also need the AllControls() method, of course.
To get all controls (not only the direct children of the form) you can use this recursive Linq
Func<Control, IEnumerable<Control>> allControls = null;
allControls = c => new Control[] { c }
.Concat(c.Controls.Cast<Control>()
.SelectMany(x=>allControls(x)));
Now you can filter the TextBoxes
var tbs = allControls(this).OfType<TextBox>()
.Where(t=>(string)t.Tag=="Filled")
.ToList();
Better use if (c is TextBox).
Furthermore, if you want to know why your code breaks, use try/catch
I'd recommend to use following syntax:
foreach (Control c in Controls)
if (c is TextBox)
Are you setting tag property from yourself. This is a string type of property.so you can try this:
if (c.Tag == "Filled")
{
Console.WriteLine(c.Name);
}
if you want to check that text box is not empty then you can simply try this :
if (c.Text.Trim().Length == 0)
{
Console.WriteLine(c.Name);
}
I have created a UserControl called AutorControl with a method to clear its textbox:
public void LimpiarAutorTextbox()
{
textBox1.Text = "";
}
Then my intention is from another form with a Panal, using a for loop add X ammount of the above user control. Then I want to call the UserControls method: "LimpiarAutorTextbox" (which is just a method for clearing the text of the textbox) using a foreach loop like this, however it's not working. I'm not sure what to do in this case:
AutorControl usercontrolAutorControl = new AutorControl();
private override void ClearControls()
{
txtTitulo.Text = "";
//Panel1 will only hold controls of the same type: "AutorControl"
foreach (Control X in panel1.Controls)
{
X as AutorControl;//?????? I want to access each created usercontrols' method.
}
}
The panel will always hold a usercontrol of AutorControl, never anything else. How can I achieve this programatically?
Thanks.
Your line here is fine:
X as AutorControl
just add:
(X as AutorControl).LimpiarAutorTextbox()
that should do the trick.
Also, I know you said that there would only be AutorControls in there, but you may want to do something more like this:
AutorControl currentControl == X as AutorControl;
if (AutorControl != null)
{
currentControl.LimpiarAutorTextbox();
}
Or alternatively, you can change your declaration of the for foreach loop to do the cast for you:
foreach(AutorControl currentControl in form.Controls)
{
if (currentControl != null)
{
currentControl.LimpiarAutorTextbox();
}
}
Some alternatives :)