Change all buttons on a form - c#

I have come very close to finding a solution to this one; just missing one minor detail at this point.
What I am trying to do:
I want to change the cursor style of every button on my Form (Form1) through code. I know how to search through all controls on my form using foreach, but I'm not sure how to pass this control as a parameter through the routine that I wrote. I will show an example of what I am doing below.
private void Form1_Load(object sender, EventArgs e)
{
foreach (Button b in this.Controls)
{
ChangeCursor(b); // Here is where I'm trying to pass the button as a parameter. Clearly this is not acceptable.
}
}
private void ChangeCursor(System.Windows.Forms.Button Btn)
{
Btn.Cursor = Cursors.Hand;
}
Might anyone have a tip for me?
Thank you very much
Evan

The only thing I see is that if you have nested controls, this.Controls will not pick those up. you can try this
public IEnumerable<Control> GetSelfAndChildrenRecursive(Control parent)
{
List<Control> controls = new List<Control>();
foreach(Control child in parent.Controls)
{
controls.AddRange(GetSelfAndChildrenRecursive(child));
}
controls.Add(parent);
return controls;
}
and call
GetSelfAndChildrenRecursive(this).OfType<Button>.ToList()
.ForEach( b => b.Cursor = Cursors.Hand);

Change
foreach (Button b in this.Controls)
{
ChangeCursor(b); // Here is where I'm trying to pass the button as a parameter.
// Clearly this is not acceptable.
}
to
foreach (Control c in this.Controls)
{
if (c is Button)
{
ChangeCursor((Button)c);
}
}
Not every control on a form is a button.
Edit: You should also look for nested controls. See Bala R. answer.

Same principle as Bala R's answer but the way I do it is...
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
namespace AppName
{
public static class ControlExtensions
{
public static IEnumerable<Control> GetAllCtls(this Control control, Type type)
{
var controls = control.Controls.Cast<Control>();
return controls.SelectMany(ctrl => GetAllCtls(ctrl, type))
.Concat(controls)
.Where(c => c.GetType() == type);
}
}
}
Then use it like this...
foreach (Control ctl in this.GetAllCtls(typeof(Button)))
{
MessageBox.Show("Found a button on the form called '" + ctl.Text + "'");
}

That looks correct to me; is there a problem I'm not seeing?
EDIT: Ahh yes - if you have non-button controls in the collection, the cast will fail.
You want to only pass in controls that are buttons, so you'll want to add an IF statement.

If any of your controls fail to inherit from button I think your foreach will throw an exception.
try something like this:
foreach (Control b in this.Controls)
{
if (b is Button)
ChangeCursor((Button)b);
}

You could also use for a bit cleaner syntax:
foreach (Control c in this.Controls)
{
if (c is Button)
{
ChangeCursor(c as Button);
}
}

Related

Foreach button in my panel containing other panels

I'm using a custom Button which contains other elements and color styles like TopColor and BotColor. I need to handle this Button inside a panel with other panels.
I'm trying this:
foreach(CustomButton btn in panel1.Controls)
{
if(btn is CustomButton)
{
btn.TopColor=Color.Red;
}
Inside panel1 I'm containing other panels too. And the error I'm getting is
it can't be conversion element panel in a button.
One solution is to separate buttons in one panel. But I want to ask if there is some way to avoid other elements. The reason I don't want to use foreach (Control a in this.Controls) is it doesn't recognise my custom color style TopColor and BotColor
Take a look
Loop through all your controls (as Controls), check if it's a button, then cast it before you try and set the colour.
foreach(Control c in panel1.Controls)
{
if (c is CustomButton)
{
(c as CustomButton).TopColor = Color.Red;
}
}
I hope this solution works for you.
private void SetStylesToCustomButtons(Control.ControlCollection controls)
{
foreach (Control control in controls)
{
if (control is CustomButton)
{
(control as CustomButton).TopColor = Color.Red;
}
else if (control is Panel)
{
SetStylesToCustomButtons((control as Panel).Controls);
}
}
}
The reason you're getting an error is that you're trying to cast all your controls to CustomButton, even the panels. You already know the type that you're looking for, so you don't have to loop through every control in your panel.
Assuming all your custom buttons are in panel1 and that you don't need to recurse, you should rather filter the items down to the type that you want and then work with them:
var customButtons = panel1.Controls.OfType<CustomButton>();
foreach (CustomButton customButton in customButtons)
{
//do what you need here
}

Loop through all controls of a Form, even those in GroupBoxes

I'd like to add an event to all TextBoxes on my Form:
foreach (Control C in this.Controls)
{
if (C.GetType() == typeof(System.Windows.Forms.TextBox))
{
C.TextChanged += new EventHandler(C_TextChanged);
}
}
The problem is that they are stored in several GroupBoxes and my loop doesn't see them. I could loop through controls of each GroupBox individually but is it possible to do it all in a simple way in one loop?
The Controls collection of Forms and container controls contains only the immediate children. In order to get all the controls, you need to traverse the controls tree and to apply this operation recursively
private void AddTextChangedHandler(Control parent)
{
foreach (Control c in parent.Controls)
{
if (c.GetType() == typeof(TextBox)) {
c.TextChanged += new EventHandler(C_TextChanged);
} else {
AddTextChangedHandler(c);
}
}
}
Note: The form derives (indirectly) from Control as well and all controls have a Controls collection. So you can call the method like this in your form:
AddTextChangedHandler(this);
A more general solution would be to create an extension method that applies an action recursively to all controls. In a static class (e.g. WinFormsExtensions) add this method:
public static void ForAllControls(this Control parent, Action<Control> action)
{
foreach (Control c in parent.Controls) {
action(c);
ForAllControls(c, action);
}
}
The static classes namespace must be "visible", i.e., add an appropriate using declaration if it is in another namespace.
Then you can call it like this, where this is the form; you can also replace this by a form or control variable whose nested controls have to be affected:
this.ForAllControls(c =>
{
if (c.GetType() == typeof(TextBox)) {
c.TextChanged += C_TextChanged;
}
});
A few simple, general purpose tools make this problem very straightforward. We can create a simple method that will traverse an entire control's tree, returning a sequence of all of it's children, all of their children, and so on, covering all controls, not just to a fixed depth. We could use recursion, but by avoiding recursion it will perform better.
public static IEnumerable<Control> GetAllChildren(this Control root)
{
var stack = new Stack<Control>();
stack.Push(root);
while (stack.Any())
{
var next = stack.Pop();
foreach (Control child in next.Controls)
stack.Push(child);
yield return next;
}
}
Using this we can get all of the children, filter out those of the type we need, and then attach the handler very easily:
foreach(var textbox in GetAllChildren().OfType<Textbox>())
textbox.TextChanged += C_TextChanged;
Try this
AllSubControls(this).OfType<TextBox>().ToList()
.ForEach(o => o.TextChanged += C_TextChanged);
where AllSubControls is
private static IEnumerable<Control> AllSubControls(Control control)
=> Enumerable.Repeat(control, 1)
.Union(control.Controls.OfType<Control>()
.SelectMany(AllSubControls)
);
LINQ is great!
Haven't seen anyone using linq and/or yield so here goes:
public static class UtilitiesX {
public static IEnumerable<Control> GetEntireControlsTree(this Control rootControl)
{
yield return rootControl;
foreach (var childControl in rootControl.Controls.Cast<Control>().SelectMany(x => x.GetEntireControlsTree()))
{
yield return childControl;
}
}
public static void ForEach<T>(this IEnumerable<T> en, Action<T> action)
{
foreach (var obj in en) action(obj);
}
}
You may then use it to your heart's desire:
someControl.GetEntireControlsTree().OfType<TextBox>().ForEach(x => x.Click += someHandler);
As you have stated, you will have to go deeper than just cycling over each element in your form. This, unfortunately, implies the use of a nested loop.
In the first loop, cycle through each element. IF the element is of type GroupBox, then you know you'll need to cycle through each element inside the groupbox, before continuing; else add the event as normal.
You seem to have a decent grasp of C# so I won't give you any code; purely to ensure you develop all the important concepts that are involved in problem solving :)
you can only loop through open forms in windows forms using form collection for example to set windows start position for all open forms:
public static void setStartPosition()
{
FormCollection fc = Application.OpenForms;
foreach(Form f in fc)
{
f.StartPosition = FormStartPosition.CenterScreen;
}
}
I know that this is an older topic, but would say the code snippet from http://backstreet.ch/coding/code-snippets/mit-c-rekursiv-durch-form-controls-loopen/ is a clever solution for this problem.
It uses an extension method for ControlCollection.
public static void ApplyToAll<T>(this Control.ControlCollection controlCollection, string tagFilter, Action action)
{
foreach (Control control in controlCollection)
{
if (!string.IsNullOrEmpty(tagFilter))
{
if (control.Tag == null)
{
control.Tag = "";
}
if (!string.IsNullOrEmpty(tagFilter) && control.Tag.ToString() == tagFilter && control is T)
{
action(control);
}
}
else
{
if (control is T)
{
action(control);
}
}
if (control.Controls != null && control.Controls.Count > 0)
{
ApplyToAll(control.Controls, tagFilter, action);
}
}
}
Now, to assign an event to all the TextBox controls you can write a statement like (where 'this' is the form):
this.Controls.ApplyToAll<TextBox>("", control =>
{
control.TextChanged += SomeEvent
});
Optionally you can filter the controls by their tags.
Since the Question regarding "Adding an Event to your TextBoxes"; was already answered; I'm providing some explanation and adding an iteration alternative using a for loop instead.
Problem:
Being Unable to Get Controls Inside a Container.
Solution:
In order to retrieve the Controls inside a Container you have to specify the Container that Contains the Controls you wish to access to.
Therefore your loop must check the Controls inside a Container.
Otherwise your loop will not find the Controls inside a Container.
i.e:
foreach (Control control in myContainer.Controls)
{
if (control is TextBox) { /* Do Something */ }
}
In case you have several Containers:
Initially iterate the Containers.
Then iterate over the controls inside the container (the container found in the initial iteration).
Pseudo Code Example on How to use a for Loop Instead:
/// <summary> Iterate Controls Inside a Container using a for Loop. </summary>
public void IterateOverControlsIncontainer()
{
// Iterate Controls Inside a Container (i.e: a Panel Container)
for (int i = 0; i < myContainer.Controls.Count; i++)
{
// Get Container Control by Current Iteration Index
// Note:
// You don't need to dispose or set a variable to null.
// The ".NET" GabageCollector (GC); will clear up any unreferenced classes when a method ends in it's own time.
Control control = myContainer.Controls[i];
// Perform your Comparison
if (control is TextBox)
{
// Control Iteration Test.
// Shall Display a MessageBox for Each Matching Control in Specified Container.
MessageBox.Show("Control Name: " + control.Name);
}
}
}
Updated answer:
I needed to disable all the controls in a form, including groupboxes. This code worked:
private void AlterControlsEnable(bool ControlEnabled)
{
foreach (Control i in Controls)
i.Enabled = ControlEnabled;
}

Clear controls Dynamically

I have groupbox I want to clear all the control in it , I try
public void ClearPanels(GroupBox control)
{
foreach (Control p in control.Controls)
{
control.Controls.Remove(p);
}
}
but a panel remain it , the problem I create the controls in runtime , and want to remove it in runtime
Better use this which clears all the controls at once without using a loop:
public void ClearPanels(GroupBox control)
{
control.Controls.Clear();
}
Use RemoteAt
while (control.Controls.Count > 0)
{
control.Controls.RemoveAt(0);
}
or Clear
control.Controls.Clear();

Iterating through TextBoxes in asp.net - why is this not working?

I have 2 methods I tried to iterate through all my textboxes in an asp.net page. The first is working, but the second one is not returning anything. Could someone please explain to me why the second one is not working?
This works ok:
List<string> list = new List<string>();
foreach (Control c in Page.Controls)
{
foreach (Control childc in c.Controls)
{
if (childc is TextBox)
{
list.Add(((TextBox)childc).Text);
}
}
}
and the "not working" code:
List<string> list = new List<string>();
foreach (Control control in Controls)
{
TextBox textBox = control as TextBox;
if (textBox != null)
{
list.Add(textBox.Text);
}
}
Your first example is doing one level of recursion, so you're getting TextBoxes that are more than one control deep in the control tree. The second example only gets top-level TextBoxes (which you likely have few or none).
The key here is that the Controls collection is not every control on the page - rather, it is only the immediate child controls of the current control (and a Page is a type of Control). Those controls may in turn have child controls of their own. To learn more about this, read about the ASP.NET Control Tree here and about NamingContainers here. To truly get every TextBox anywhere on the page, you need a recursive method, like this:
public static IEnumerable<T> FindControls<T>(this Control control, bool recurse) where T : Control
{
List<T> found = new List<T>();
Action<Control> search = null;
search = ctrl =>
{
foreach (Control child in ctrl.Controls)
{
if (typeof(T).IsAssignableFrom(child.GetType()))
{
found.Add((T)child);
}
if (recurse)
{
search(child);
}
}
};
search(control);
return found;
}
Which is used as an extension method, like so:
var allTextBoxes = this.Page.FindControls<TextBox>(true);
You need to recurse. The controls are in a tree structure - Page.Controls is not a flattened list of all controls on the page. You'd need to do something like the following to get all values of TextBoxes:
void GetTextBoxValues(Control c, List<string> strings)
{
TextBox t = c as TextBox;
if (t != null)
strings.Add(t.Text);
foreach(Control child in c.Controls)
GetTextBoxValues(child, strings);
}
...
List<string> strings = new List<string>();
GetTextBoxValues(Page, strings);
you can try this piece of code to get list of all TextBoxes
public partial class _Default : System.Web.UI.Page
{
public List<TextBox> ListOfTextBoxes = new List<TextBox>();
protected void Page_Load(object sender, EventArgs e)
{
// after execution this line
FindTextBoxes(Page, ListOfTextBoxes);
//ListOfTextBoxes will be populated with all text boxes with in the page.
}
private void FindTextBoxes(Control Parent, List<TextBox> ListOfTextBoxes)
{
foreach (Control c in Parent.Controls) {
// if c is a parent control like panel
if (c.HasControls())
{
// search all control inside the panel
FindTextBoxes(c, ListOfTextBoxes);
}
else {
if (c is TextBox)
{
// if c is type of textbox then put it into the list
ListOfTextBoxes.Add(c as TextBox);
}
}
}
}
}

How do I get all controls of a form in Windows Forms?

I have a Form named A.
A contains lots of different controls, including a main GroupBox. This GroupBox contains lots of tables and others GroupBoxes. I want to find a control which has e.g. tab index 9 in form A, but I don't know which GroupBox contains this control.
How can I do this?
With recursion...
public static IEnumerable<T> Descendants<T>( this Control control ) where T : class
{
foreach (Control child in control.Controls) {
T childOfT = child as T;
if (childOfT != null) {
yield return (T)childOfT;
}
if (child.HasChildren) {
foreach (T descendant in Descendants<T>(child)) {
yield return descendant;
}
}
}
}
You can use the above function like:
var checkBox = (from c in myForm.Descendants<CheckBox>()
where c.TabIndex == 9
select c).FirstOrDefault();
That will get the first CheckBox anywhere within the form that has a TabIndex of 9. You can obviously use whatever criteria you want.
If you aren't a fan of LINQ query syntax, the above could be re-written as:
var checkBox = myForm.Descendants<CheckBox>()
.FirstOrDefault(x=>x.TabIndex==9);
Recursively search through your form's Controls collection.
void FindAndSayHi(Control control)
{
foreach (Control c in control.Controls)
{
Find(c.Controls);
if (c.TabIndex == 9)
{
MessageBox.Show("Hi");
}
}
}
void iterateControls(Control ctrl)
{
foreach(Control c in ctrl.Controls)
{
iterateControls(c);
}
}
You can make a method like this:
public static Control GetControl(Control.ControlCollection controlCollection, Predicate<Control> match)
{
foreach (Control control in controlCollection)
{
if (match(control))
{
return control;
}
if (control.Controls.Count > 0)
{
Control result = GetControl(control.Controls, match);
if (result != null)
{
return result;
}
}
}
return null;
}
...that is used like this:
Control control = GetControl(this.Controls, ctl => ctl.TabIndex == 9);
Note however that TabIndex is a tricky case, since it starts at 0 within each container, so there may be several controls in the same form having the same TabIndex value.
Either way, the method above can be used for checking pretty much any property of the controls:
Control control = GetControl(this.Controls, ctl => ctl.Text == "Some text");
I hate recursion, so I always use a stack for this sort of thing. This assigns a common event handler to the CheckedChanged event of every RadioButton control in the current control hierarchy:
Stack<Control> controlStack = new Stack<Control>();
foreach (Control c in this.Controls)
{
controlStack.Push(c);
}
Control ctl;
while (controlStack.Count > 0 && (ctl = controlStack.Pop()) != null)
{
if (ctl is RadioButton)
{
(ctl as RadioButton).CheckedChanged += new EventHandler(rb_CheckedChanged);
}
foreach (Control child in ctl.Controls)
{
controlStack.Push(child);
}
}
You could easily retrofit Josh Einstein's extension method to work this way.

Categories