-2

What I am trying to do is to change background color of the button once it's active. So far I achieved it by this way:

 private void button3_Click(object sender, EventArgs e) // METEO BUTTON
        {
            DefaultButtons();
            button3.BackColor = Color.LimeGreen;
// REST OF THE CODE HOES HERE
        }

While DefaultButtons function is like this:

public void DefaultButtons()
        {
            List<Button> buttonsToDefualt = new List<Button>()
        {
                // MAIN MENU
            button1,
            button2,
           [...]
            buttonX
        };
            foreach (var item in buttonsToDefualt)
            {
                item.BackColor = Color.Green;
            }
        }

Now swapping buttons works like this: Change entire list to default color, then activated button change color to LimeGreen. It would be fine but: 1) I have to launch DefaultButtons(); for EACH button Click 2) I have to manually add all buttons to list, and now I have more than 120 buttons (Yeah, building custom interface...), and keep adding that by hand is tiring.

I tried this:

void DefaultButtonsNew()
        {
            foreach (Button b in this.Controls)
            {
                if (b != null)
                {
                    b.BackColor = Color.Green;
                }
            }
        } 

But I've got an Exception: System.InvalidCastException: 'Can't throw object 'System.Windows.Forms.SplitContainer' on type 'System.Windows.Forms.Button'.'

  • 1
    `foreach (Button b in this.Controls.OfType – Jimi Apr 10 '20 at 22:38
  • 1
    Or mixed: `foreach (Control c in this.Controls) { if (c is Button b){ b.BackColor = Color.Green; } if (c is TextBox t) { t.BackColor = Color.Red; } }` – Jimi Apr 10 '20 at 22:42
  • Note that you haven't specified whether these Controls are child of the Form or instead of another Container (Panel, TableLayoutPanel etc.), or both cases are possible. If this is the case, of course `this.Controls` won't find them. It's unclear why you're not using your `List – Jimi Apr 10 '20 at 23:44
  • Are you trying to change *all* buttons on the form, or just those within a specific container? How are you determining the buttons that need to change backcolor? – Rufus L Apr 11 '20 at 00:10
  • I guess you should use the Leave or LostFocus events. – Alexander Petrov Apr 11 '20 at 00:39

2 Answers2

0

The iterator on Controls collection returns all the controls and trying to cast it to Button should an do fail.

Change your method like this:

    void DefaultButtonsNew()
    {
        foreach (Control b in this.Controls)
        {
            if (b is Button)
            {
                b.BackColor = Color.Green;
            }
        }
    }
Oguz Ozgul
  • 6,809
  • 1
  • 14
  • 26
0

If you're looking for a way to reset all buttons on the form, and some buttons are inside other containers, then we need to recursively loop through each control's Controls collection to find all the buttons.

One easy way to do that is to write a method that takes in a container (like the form), iterates through its Controls collection, changes the BackColor of any Button controls, and calls itself for the other control types:

private void ResetButtons(Control container)
{
    // Loop through each control in this container
    foreach (Control control in container.Controls)
    {
        var button = control as Button;

        // If the control is a button, change it's backcolor
        if (button != null) button.BackColor = Color.Green;
        // Otherwise check it's controls collection (recursive call)
        else ResetButtons(control);
    }
}

Next, it sounds like you're looking for a way to avoid writing out a call to this method, and to change the BackColor of the current button, in every button click event.

One easy way around this is to simply add this method, and the BackColor change, to every button click in code. We can write a method to do this using a similar pattern - loop through every control in every container, and if it's a button, add a method to it's click event:

private void HookupButtonClickEvent(Control container)
{
    // Loop through each control in this container
    foreach (Control control in container.Controls)
    {
        var button = control as Button;

        // If the control is a button, add a method to it's click event
        if (button != null)
        {
            button.Click += (s, e) =>
            {
                ResetButtons(container);
                button.BackColor = Color.LimeGreen; // Change this button's color
            };
        }
        // Otherwise check it's controls collection (recursive call)
        else HookupButtonClickEvent(control);
    }
}

Now, all we have to do is call the ResetButtons and HookupButtonClickEvent in our form's constructor, and every button will start with the same backcolor and will have our reset method call in it's click event:

public Form1()
{
    InitializeComponent();

    HookupButtonClickEvent(this);
    ResetButtons(this);
}

Note that this does not prevent you from adding additional click events to the buttons. It merely provides a way to hook up the common functionality to all buttons without writing a bunch of duplicated code.

You can still double-click the controls on your form to add other Click event handlers:

private void button1_Click(object sender, EventArgs e)
{
    MessageBox.Show("Button one clicked - doing something unique here");
}

private void button2_Click(object sender, EventArgs e)
{
    MessageBox.Show("Button two clicked - doing something else here");
}
Rufus L
  • 36,127
  • 5
  • 30
  • 43
  • Almost perfect, but how can I add an exception (2 buttons out of 110 NOT to be changed)? Plus this there's a bunch of new buttons appearing after you press particular button (kind of menu -> Submenu scheme), and your code, while works perfectly, changes everything: https://prnt.sc/rx3eqa This is how it supposes to work: https://prnt.sc/rx3fk0 Should've mention that in the beginning, sorry about that. – Mateusz Żymła Apr 11 '20 at 01:01
  • To handle the different menus, could you just pass in a different container name? So instead of `ResetButtons(this);` it would be `ResetButtons(mainMenuContainer);`. For the extra button or two, one idea would be to add something to its `Tag` property, like `"IgnoreMe"`, and then in the code `if (button != null)` would change to `if (button != null && button.Tag != "IgnoreMe")` – Rufus L Apr 11 '20 at 01:47
  • I updated the `HookupButtonClickEvent` code so that it only calls `ResetButtons` for the controls in the container which was passed in. – Rufus L Apr 11 '20 at 01:59