I made a application which will place out buttons in a grid where the user specifies how big the playfield should be.
I create the buttons in a list, specify some data like backgroundimage, size, and location. I then need to, in some way make the different buttons execute different code. I figured I could do this in one method, (if there aren't any good ways to programmatically create methods), if I could somehow make the buttons send a unique piece of information to the method to identify which button is pressed.
public void buttonplacer()
{
int nbrofbtns = Form2.puzzlesize * Form2.puzzlesize;
List<Button> btnslist = new List<Button>();
for (int i = 0; i < nbrofbtns; i++)
{
Button newButton = new Button();
btnslist.Add(newButton);
this.Controls.Add(newButton);
newButton.Name = "btn" + i.ToString();
newButton.Width = btnsidelength;
newButton.Height = btnsidelength;
newButton.Top = btnsidelength * Convert.ToInt32(Math.Floor(Convert.ToDouble(i / Form2.puzzlesize)));
newButton.Left = btnsidelength * Convert.ToInt32(Math.Floor(Convert.ToDouble(i)) - Math.Floor((Convert.ToDouble(i)) / (Form2.puzzlesize)) * (Form2.puzzlesize));
newButton.BackgroundImage = Lights_out_.Properties.Resources.LightsOutBlack;
newButton.Click += new EventHandler(Any_Button_Click);
}
}
void Any_Button_Click(object sender, EventArgs e)
{
}
(If you want to know I'm doing a game called "Light's out")
Thanks in advance!
The Any_Button_Click method receives an object sender that is the button that got clicked. You just need to cast it to a Button:
void Any_Button_Click(object sender, EventArgs e)
{
Button b = (Button)sender;
// do stuff here
}
You can use the button's Location property to figure out where it sits on the game board, or you can assign an arbitrary object to the button with any information you choose at initialization time using the Tag property like this:
button.Tag = "someHelpfulString";
or like this:
button.Tag = new Tuple<int, int>(xpos, ypos);
(where xpos and ypos are positions in the button grid)
or like this:
button.Tag = new ButtonInfoObject(foo, bar, baz);
(Here it's up to you to define the ButtonInfoObject class.)
As an alternative to other answers, and in particular to somewhat address the part "good ways to programmatically create methods", there is part of the C# language called Lambda Expressions. To keep long story short, you could write something along these lines:
newButton.Click += (s, e) =>
{
//here you have access to all variables accessible in current scope,
//including "newButton" and "i";
//you could, for example, call some method passing "i" as an argument
//or just put that method's code inside this block
};
The only downside of this approach is that you need to take some extra care if you're planning to unregister the handler at some later point (see this question or this question for reference).
EDIT
As pointed in comments I overlooked the fact that i stays in scope for the whole for loop, so using it inside lambda is pretty much pointless (all handlers will use it's final value). To make it behave like expected one can simply define a variable inside the loop so it goes out of scope at the end of each iteration and is stored separately for each handler:
var btnNo = i;
newButton.Click += (s, e) =>
{
//use "btnNo" instead of "i"
//you can still safely use "newButton" reference
//since it's defined inside the loop
}
Use the sender object to get the button's name that was pressed:
void Any_Button_Click(object sender, EventArgs e)
{
switch ((sender as Button).Name)
{
case "btn0":
//...
break;
case "btn1":
//...
break;
//...
}
}
Related
How can I add a function to a button that will run a method I've created. I want to write out an array into a message dialog box with a press of a button, but I don't seem to be getting anywhere so i turned to stackoverflow for some help, since googling didn't really help me with my problem.
static void Tractors(Tractor[] tractors)
{
for (int i = 0; i < tractors.Length; i++)
{
Console.WriteLine((i + 1) + ", " + tractors[i].ToString());
}
}
This is my function that writes out the table of "Tractors".
private void button1_Click(object sender, EventArgs e)
{
}
What should I write into the button1_click method so that it would work?
You need to bind the event handler with the button control and write the logic inside this event handler. If its windows form application you can do like this.
this.button1 = new System.Windows.Forms.Button();
this.button1.Click += new System.EventHandler(this.button1_Click);
private void button1_Click(object sender, EventArgs e)
{
//Call your methods here
}
You would call Tractor() the exactly same way you call Console.WriteLine(). Both are static functions.
However the function is utterly messed up and propably not salvageable. The proper name would be printTractorsToConsole(). As it contains the Console.WriteLine() call, it is strongly tied to Console Applications - avoid tying functions to one Display Technology like that.
You need a more general function that creates and returns a string. You can then send that string to WriteLine(), assign it to Label.Text or wherever else you want the string to be. Strings primarily exist for intput from or output towards the user - and there is too many ways to get it to them.
//Not tested against a compiler, may contain syntax errors
static string TractorArrayToString(Tractor[] tractors){
string output = "";
for (int i = 0; i < tractors.Length; i++)
{
output += (i + 1) + ", " + tractors[i].ToString()) + Environment.NewLine;
}
return output;
}
But even function might be a bad idea, as that function would tie all representations to a single format. Generally you would write that loop directly into the Click Event. But this function looks like it is for printing for debug purposes, so it might work.
I don't know if I still have missing on this but whenever you want to add PictureBox, you just find it on Toolbox and drag it on the UI. But what I did is I coded it by adding PictureBox then what I want is I have to move it to the right. I coded it on a Timer and this is how I do.
private void enemyMove_Tick(object sender, EventArgs e)
{
GameMenu menu = new GameMenu();
if (menu.cmbox_Level.Text.Equals("Easy"))
{
this.Controls.Add(menu.EnemyTank);
menu.EnemyTank.Width = 26;
menu.EnemyTank.Height = 32;
menu.EnemyTank.BackgroundImage = Properties.Resources.Tank_RIGHT_v_2;
menu.EnemyTank.BackgroundImageLayout = ImageLayout.Stretch;
menu.EnemyTank.Left += 2;
}
}
Don't ask me if the Timer is started. Yes it already started, it was coded on the Form. But somehow when I start the program it doesn't move. But then I tried adding PictureBox, this time I tried dragging it to UI then add an Image on it, then coded it by moving to the right. When I start the program it works. But what I want is that whenever I start the button it will just make the random PictureBox move to the right. Idk what's the missing in here.
I made some assumptions. If I my assumptions aren't correct, please update your post for details. Following content is copied from my comment.
A new GameMenu will always create a new EnemyTank.
A EnemyTank's default Left value is fixed;
Your enemyMove_Tick is keep creating GameMenu and EnemyTank.
That is, every tick, a EnemyTank is created and value changed to "default value plus 2" in your code.
To avoid it, you have to keep EnemyTank instance somewhere instead of keep creating it.
Here's a example.
GameMenu m_menu;
void YourConstructor()
{
// InitializeComponent();
// something else in your form constructor
m_menu = new GameMenu();
m_menu.EnemyTank.Width = 26;
m_menu.EnemyTank.Height = 32;
m_menu.EnemyTank.BackgroundImage = Properties.Resources.Tank_RIGHT_v_2;
m_menu.EnemyTank.BackgroundImageLayout = ImageLayout.Stretch;
this.Controls.Add( m_menu.EnemyTank );
}
private void enemyMove_Tick(object sender, EventArgs e)
{
if (menu.cmbox_Level.Text.Equals("Easy"))
{
m_menu.EnemyTank.Left += 2;
}
}
I know this has to have an easy answer, but I'm utterly failing to fathom the wealth of information on custom events, event handlers, and delegates. I have a custom messagebox class. I am trying to add the capability to do something based off of the state of a check box if the OK button is clicked. The buttons and the checkbox are added dynamically based upon input into a static Show method somewhat like the following:
if (!String.IsNullOrWhiteSpace(suicideCheckboxID))
{
suicideCheckBox = new CheckBox();
suicideCheckBox.AutoSize = true;
suicideCheckBox.Text = "Do not show this message again.";
suicideCheckBox.Location = new Point(xMargin, label.Bottom + yMargin);
suicideCheckBox.Checked = false;
suicideCheckBoxHeight = suicideCheckBox.Height;
form.Controls.Add(suicideCheckBox);
}
Button okButton = NewButton(DialogResult.OK, scaleFactor);
int x = (form.Width - okButton.Width) / 2;
okButton.Location = new Point(x, buttonYPosition);
form.Controls.Add(okButton);
form.AcceptButton = okButton;
form.CancelButton = okButton;
That's not the exact code, but it's fairly representative. My impulse is to use okButton.Clicked += new EventHandler(OKButton_clicked), but if I do that, the event generated only carries arguments for object sender and EventArgs e and I really need it to operate off of the state of the checkbox and an additional piece of text to indicate which messagebox is being shown so that the values can be stored in the registry.
My first attempt was to do something like okButton.Clicked += processSuicideCheckbox(suicideCheckboxID, suicideCheckBox);, but that seems to just process the contents and allow one to return an EventHandler that points to a method with the signature of object sender and EventArgs e. What am I missing here? What is the best way to pass in the arguments actually relevant to me?
You don't get to choose what is in the event handler for the Click event. Microsoft has already done that. You are stuck with the (object sender, EventArgs e) signature.
You do have a couple options:
Simply store the state in the class itself; the event handler will have access to it because it is inside the class.
Utilize a closure to do the same thing:
myButton.Click += (s, e) => ActualFunction(checkBox1.Checked);
Note that using the closure (via a lambda expression) is just hiding the details of maintaining this state (creating the class-level variables).
I can't find solution similar to what I expect to happen. How can I add additional parameter to the method i want to subscribe to?
Suppose this line below is the code that subscribe to mouseover:
Panel cagePanel = new Panel();
cagePanel.MouseHover += new EventHandler(frmMain_MouseHover);
void frmMain_MouseHover(object sender, EventArgs e){
// I wanna add some 'int index' parameter after 'e' variable)
}
What I really want is just like this:
void frmMain_MouseHover(object sender, EventArgs e, int index){
strings[index] = "bla bla bla";
}
I can't find solution similar to what I expect to happen. How can I add additional parameter to the method i want to subscribe to?
You can't. How would the code raising the event (Panel.OnMouseHover or whatever) know what value to provide?
If you know at the time you subscribe the event, you can use a lambda expression to basically delegate the call, providing the extra information:
// 10 is just an example here - use whatever value you want for index
cagePanel.MouseHover += (sender, args) => frmMain_MouseHover(sender, args, 10);
EDIT: To iterate over each control in an array and subscribe an appropriate handler for each, you could use something like this:
for (int i = 0; i < array.Length; i++)
{
// See http://tinyurl.com/3b2hoft for reasons behind the copy
int index = i;
array[i].MouseHover += (s, args) => frmMain_MouseHover(s, args, index);
}
I would personally try to avoid needing this, however - can you not detect the index via the sender part, potentially using Array.IndexOf?
You can't, but if the extra data you need is related to what sent the event, you could use the sender value. For example, if you want to know the index in a list of controls of the item that raised the event, you could do something like:
int i = controlList.IndexOf(sender);
I've created an array of RadioButtonList class, but apparently can't seem to access it or use the answer retrieved from it. I always get the exception: Object reference not set to an instance of an object
static int jimmy = 0;
protected void Button5_Click(object sender, EventArgs e)
{
int sizeOfPain = GlobalVariables.sympLCWR1Pain.Count;
RadioButtonList[] RBLPain = new RadioButtonList[sizeOfPain];
Label1.Visible = false;
RadioButtonList1.Visible = false;
Label[] Labella = new Label[sizeOfPain];
if (jimmy < sizeOfPain)
{
Labella[jimmy] = new Label();
RBLPain[jimmy] = new RadioButtonList();
Labella[jimmy].Text = GlobalVariables.sympLCWR1Pain[jimmy];
RBLPain[jimmy].Items.Add("Yes");
RBLPain[jimmy].Items.Add("No");
Panel1.Controls.Add(Labella[jimmy]);
Panel1.Controls.Add(RBLPain[jimmy]);
if (RBLPain[jimmy].SelectedIndex == 0)
{
GlobalVariables.sympLCWR1Yes.Add(GlobalVariables.sympLCWR1Pain[jimmy]);
}
}
else
{
Label2.Text = "YOUS DONE!";
Label3.Text = GlobalVariables.sympLCWR1Yes[0];
Button5.Visible = false;
}
jimmy++;
}
i get the exception at the if condition. Any help would be appreciated thanks :)
What that error means is that you are trying to access something that hasn't yet been instantiated. In your updated code, I see you have the following within your click event handler:
RadioButtonList[] RBLPain = new RadioButtonList[sizeOfPain];
Label[] Labella = new Label[sizeOfPain];
This means that every time the click event is handled, you are redeclaring the RBLPain and Labella arrays. Also, when execution leaves leaves the handler, the variables fall out of scope, so you will not be able to use them in other functions, or use the changes made within the handler from one call to the next. I don't know what the rest of your code is doing, but despite the seemingly unnecessary arrays, execution should survive your click event.
In your original post you were trying to access the SelectedItem.Text property of the RBLPain[jimmy]. In this revision you are checking the SelectedIndex instead. When SelectedIndex is -1, SelectedItem will be null, perhaps this led to your original problem. Regardless of what is changed on your form, because you are creating a new RadioButtonList during every click event, you are not working with the values from your form - SelectedIndex will always be -1 from what I can see.
I dont understand why you checking the condition .If you are creating rbl on buttonclick first item should always get selected. Anyway use RBLPain[jimmy].SelectedIndex=0;before if condition.