I have a Windows Form that contains only buttons. The final goal is to make a simple logic game I saw but for now the problem is that I want to perform different actions when my New button is clicked, but now it is part from all the buttons in the form so sometimes an action is performed on him too which should not happen. To make myself clear I have two screenshots :
So this is how I want it to be - I have a matrix - 3x3 (in this case, at the end it can be NxN). By clicking New I want to be able to do various things one of which is to make N buttons colored red. What happens now is sometimes my New button also get painted because I go over the buttons like this:
foreach (Control c in this.Controls)
{
if (c is Button)
{
...
and thus sometimes New get selected too, so I end up with this:
What I'm thinking right now is just to perform check whenever I need in the code and exclude my New button explicitly but I don't think it's a good way cause I may end up with a code doing this thing in a lot of places in my program so what is the right solution in this case? If some code is needed please ask.
Quite possibly the easiest solution is to put the Grid in its own Panel (pnlGrid). Put all of the buttons in there, then you could just do the following instead:
foreach (Control ctl in pnlGrid.Controls) {
if (ctl is Button) {
// Do your logic here
}
}
Instead of looping through controls, add all the matrix buttons to a list, and have the new button separated:
private Button[] buttons;
private Button newButton;
Now you can add as many buttons as you'd like to:
for (int i = 0; i < 9; i++)
{
buttons[i] = new Button();
buttons[i].Text = "Button" + i;
Controls.Add(buttons[i])
}
And lastly, your New button will loop through buttons:
private void newButton_Click(object sender, EventArgs e)
{
foreach (Button b in buttons)
{
...
}
}
You could inherit from the button class. Make your own button, use this control (that will have the same functionality that the parent one) for the set, and check for it when you iterate over the controls.
You could also use the Tag property for this pourpose, but I think that inherit will be more clear, adding semantic meaning to your code.
Related
I'm new to C#, I mean I did not code for a while so I forgot like everything. I'm trying to hide three objects with an array to make it easier with one line of code
This is how my code looks now.
button1.Hide();
button2.Hide();
button3.Hide();
But I tried every method that I could but nothing worked. This is how I think it should work.
Object[] Buttons = new Object{button1, button2, button3};
Buttons.Hide();
Could you place your buttons in a panel and hide that instead? That will scale to anything else in future too should you choose to modify your UI as long as everything which needs to be hidden is in the same Panel?
So instead of doing something like
void HideButtons(IEnumerable<Button> buttons)
{
foreach(var button in buttons)
button.Hide();
}
...
HideButtons(new[]{ button1, button2, button3 });
you can simply call Hide() on the containing panel
panel.Hide();
you can do it via for or froeach:
List<Button> buttons=
new List<Button>(){button1,button2,button3};
foreach(var button in Buttons)
button.hide();
or
new List<Button>(){button1,button2,button3}.ForEach(item=>item.hide());
I'm trying to make it to where a panel becomes visible and it sent to the front so it can be seen and interacted with, like this.
private void SettingsButton_Click(object sender, EventArgs e)
{
SettingsPanel.Visible = true;
SettingsPanel.BringToFront();
}
The problem is that after clicking a few of the buttons, it will either display the wrong panel or none at all. Is there a way to fix this?
EDIT: Before y'all ask, i'm using WinForms.
OK, so I was wrong, WinForms is smarter than I thought. Here's a test you can do. I'm not exactly sure what you are trying to do, but this should help you along. To start, we're going to build a small WinForms app. With one exception, we aren't going to set any properties of the controls we drop on the screen:
Create a new WinForms app
In the designer, drop a Panel (which will be named panel1) on the form
In the properties pane, set the BorderStyle to FixedSingle (that's the only property we are going to set)
Make two copies of that panel (panel2 and panel3). Position them so that panels do not overlap at all.
On each panel drop a couple of controls (I put labels (labels 1-3) and textboxes (also 1-3)) on each one
Beside each panel (arranged so that there is no overlap) drop three buttons on the form (buttons 1-3) make it so that visually, each button is associated with the similarly numbered panel
Duplicate panel3 including its contained controls (so that you get panel4, label4 and textbox4). Position the duplicate so that it significantly overlaps panel 3
Now we're going to look at the code that the designer creates for your form. Don't mess with this code (you can, but if you don't know what you are doing, it can turn out bad - and, we're keeping this simple).
In the Solution Explorer click the unfilled triangle to the left of Form1.cs. Note that it rotates and turns solid. Also note that Form1.Designer.cs is displayed. That's a normally hidden source file that contains all the designer created code that corresponds with the form and the controls you dropped on it.
Open Form1.Designer.cs
Click the little grey plus sign icon next to Windows Form Designer generated code
Inspect the file. Note that every action you did in the designer has a corresponding line of code in the Designer.cs file (more or less)
Look at the code for one of the panels (say panel1).
See that it includes:
this.panel1.Controls.Add(this.textBox1);
this.panel1.Controls.Add(this.label1);
Scroll all the way down to the Form1 code and see that the panels and buttons get added to the Form's Controls collection:
Like:
this.Controls.Add(this.button3);
this.Controls.Add(this.button2);
this.Controls.Add(this.button1);
this.Controls.Add(this.panel4);
this.Controls.Add(this.panel3);
this.Controls.Add(this.panel2);
this.Controls.Add(this.panel1);
Note that the order is reversed. The order is important, it sets the Z-Order (i.e., what overlaps what) for the form and the controls on the form.
Wiring up the buttons
Select all three buttons and press <Enter>. This will open the Form1.cs file and generate three button click handlers that you can fill in.
Use this code for the three button handlers:
private void button1_Click(object sender, EventArgs e) {
var wasVisible = panel1.Visible;
panel1.Visible = !wasVisible;
}
private void button2_Click(object sender, EventArgs e) {
panel2.BringToFront();
}
private void button3_Click(object sender, EventArgs e) {
panel3.BringToFront();
}
The first one will toggle the first panel's visibility (I put in an extra variable so you can set a breakpoint and see what's going on). The second one brings panel2 to the front, changing its Z-Order (it's called Z-Order because the position on the screen is measure in X and Y, which the overlap position is related to the "depth" of the screen, or the Z-coordinate). The last one does the same thing to panel3.
Run the program.
When you press the first button, the first panel and its controls disappear (this surprised me, WinForms is smarter than I thought)
When you press the second button, nothing appears to happen. This is because the only thing that panel2 intersects is the form, and panel2 already covers the form, so you don't see any effect. (and because WinForms is smarter than I thought)
When you press the third button, panel2 (and it's controls) jump to the front of the stack of controls, covering the intersecting part of panel4.
Does this help you understand how Visible and BringToFront() work?
What you're describing is similar to a TabControl alternative. Here's an example:
You can manage the current panel simply by making it visible and docked to fill. Hide the other panels.
public partial class FormTabsAlternative
: Form
{
int m_current = 0;
List<Panel> m_tabs = new List<Panel>();
public FormTabsAlternative()
{
InitializeComponent();
AddTab(pnl1);
AddTab(pnl2);
AddTab(pnl3);
AddTab(pnl4);
SetUpTabsAndButtons();
}
private void AddTab(Panel pnl)
{
m_tabs.Add(pnl);
pnl.Dock = DockStyle.Fill;
}
private void OnLeftClick(object sender, EventArgs e)
{
if (m_current > 0)
{
m_current--;
SetUpTabsAndButtons();
}
}
private void OnRightClick(object sender, EventArgs e)
{
if (m_current < m_tabs.Count - 1)
{
m_current++;
SetUpTabsAndButtons();
}
}
private void SetUpTabsAndButtons()
{
for (int index = 0; index < m_tabs.Count; index++)
{
var panel = m_tabs[index];
panel.Visible = index == m_current;
}
btnLeft .Enabled = m_current > 0;
btnRight.Enabled = m_current < m_tabs.Count - 1;
}
}
I made a simple tic tac toe game in C# (Visual Studio Winform). I added a menu strip containing a file menu which has a "New Game" option in it. What i'm trying to do is to reset all the buttons' text and I had disabled them so I want to enable them again. I used the following code when I only had a menustrip and the buttons on my form, and it worked. But now I added a label to my form, and this code is not working. I know I can simply reset the value one by one for each button, but I wanted to know what went wrong when I added a label. Thanks in advance. :)
try
{
foreach (Control c in Controls)
{
Button b = (Button)c;
b.Text = "";
b.Enabled = true;
}
}
catch { }
Your code doesn't work anymore because the Controls collection now contains also the Label, so when your loop reaches the Label control it throws an exception at this line
Button b = (Button)c;
You cannot cast a Label to be a Button, but this exception is swallowed by the empty try catch and your loop terminates without any warning. It is considered a bad practice to have empty try/catch, better remove them and let the exception show.
Your problem could be avoided using the OfType enumerable extension to get only the controls of type Button. The rest of your code now doesn't need any other changes.
foreach (Button b in Controls.OfType<Button>())
{
b.Text = "";
b.Enabled = true;
}
However keep in mind that this loop works on the Form Controls container. If you add some other kind of container (like a GroupBox or a Panel) and move your buttons inside that control then you need to loop on that container Controls property.
EDIT
To exclude a particular button from the loop you could add a call to IEnumerable<T>.Where to the sequence returned by OfType. For example
foreach (Button b in Controls.OfType<Button>().Where(x => x.Name != "button10")
.....
this.Controls.OfType<Button>().ToList().ForEach(b => { b.Text = ""; b.Enabled = true; });
I made a Tic Tac Toe game and I'm trying to add a few features.
I was used to handle the taken buttons with
button.enabled=false;
The problems is that the text on the buttons turns grey.
So I made a button click for each button:
A1_Click, A2_Click, and so on
This is my code into A1_click, and it's the same for the other buttons, the only thing that changes is "A1", "A2", and so on
Button b = (Button)sender;
if (!A1.Text.Equals(""))
{
MessageBox.Show("Not A Valid Input");
}
I get "not a valid input" when I click a button I've already clicked before, I'd just like to be able to click an another button.
I don't want to lose my turn if I click on an already taken button
Based on what I have read I think I know what you are looking for. You are looking for a way to force the user to lose a turn when they click a button that has already been taken, and you do not want to set the enabled property to false. If that is what you want, then I might have some code that could help. First if you are assigning the same click event to multiple buttons with different functions you should try something like this:
for (int i = 1; i < 10; i++)
{
string currentButtonName = "A" + i;
Control currentButton = this.Controls.Find(currentButtonName, true).FirstOrDefault();
currentButton.Click += OnGameButton_Click;
}
What this is doing is searching your form for a control that has a specified name, and since your buttons have a similar name we can easily search for them. Then we can bind a specific function to all of them so that instead of 9 functions to modify you only have 1, and you can validate that they all work the same. Here is the OnGameButton_Click() event code:
private void OnGameButton_Click(object sender, EventArgs e)
{
if (!hasGameStarted || shouldLoseTurn)
return;
// This is the current button user pressed
Button b = (sender as Button);
if (b.Name.Contains('A') && b.Enabled)
{
if (!b.Text.Equals(""))
{
MessageBox.Show("Not A Valid Input! You have lost your turn.");
shouldLoseTurn = true;
}
else
{
b.Text = currentPlayersLetter;
shouldLoseTurn = false;
}
}
}
As you can see with some flags we can monitor the game, and force the buttons to react accordingly take a look at the beginning of the function. We have to validate that the game is engaged, and that the current user has not lost their turn due to pressing the same button. From there we just need modify what you had so that if they do press the same button twice then we modify the shouldLoseTurn flag as needed.
The other approach is to just simply use the Button.Enabled property to disable the button from use. I know you do not what the button to be grayed out, but if you create your own style guide for the button you could make it how you want. This can be challenging though because you will have to modify the default style template for the button to achieve this. Here is another question that discusses just that here
I'm attempting to make a grid based dungeon system at the moment in Visual Studio 2010. I have a main user control which contains 64 other smaller user control objects, which I've called GridSquares, organised into an 8x8 grid. The idea behind the grid squares is to act as potential movement spaces within the 'dungeon'. The problem I have at the moment is that I need to be able to call a click event on the user controls (GridSquares) themselves, which have been placed on screen so I can retrieve their coordinate (name) for comparison. However the event does not work when I call it (through clicking).
I am aware that the events work when I place them within the usercontrol (GridSquare object) but I need the click even to work when the user control itself is clicked.
Given that all 64 objects placed are the same type I can not work within the GridSquare class as I require the name of the user control to be returned through the event.
I hope this makes sense but please ask if I need to explain further.
Many thanks, Liam
EDIT:
I'm not sure how much this will help or what code to display but the GridSpace controls have already been added to the 'dungeon' user control. Then within I add all 64 to a dictionary:
gridSpaces.Add(gs11.Name, gs11);
Where gs11 is the name of the GridSquare.
From here I tried creating event handlers for the individual user controls on the dungeon screen, which failed to call.
I think i get what your saying. Add this code to your user control:
public new event EventHandler Click {
Add {
base.Click += value;
foreach(Control i in Controls) {
i.Click+=value;
}
}
remove {
base.Click -= value;
foreach(Control i in Controls) {
i.Click -= value;
}
}
}
this will add the click event to everything in your user control, i hope i didnt make any errors, and that this helps
You can use the same handler for each GridSquare and use the sender parameter to decide which one was clicked:
protected void Page_Load(object sender, EventArgs e)
{
for (int i = 0; i < 64; i++)
{
GridSquare square = new GridSquare();
square.Click += new EventHandler(gridSquare_Click);
grid.Add(gridSquare);
}
}
void gridSquare_Click(object sender, EventArgs e)
{
GridSquare square = (GridSquare)sender;
// do something cool with the clicked square here
}