Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

As the title implies, I want a Button class with an OnClick event handler. It should fire off connected events when it is clicked.

This is as far as I've made it:

public class Button {

    public event EventHandler OnClick;

    public Rectangle Rec { get; set; }
    public string Text { get; set; }

    public Button(Rectangle rec, string text) {
        this.Rec = rec;
        this.Text = text;
    }

}

I have no clue what I'm doing with regards to events. I know how to use them but creating them myself is another matter entirely. I've also made buttons without using events that work on a case-by-case basis.

So basically, I want to be able to attach methods to the OnClick EventHandler that will fire when the Button is clicked (i.e., the mouse intersects Rec and the left mouse button is clicked).

share|improve this question

3 Answers 3

up vote 3 down vote accepted

To do this, you need to sample the state of the mouse regularly, and when it's clicked, check the coordinates against the button and see how that goes.

Usually, I write something like this in Button:

public void Update() { // called every tick
  var mouseState = Mouse.GetState();
  if (this.OnClick != null && mouseState.LeftButton == ButtonState.Pressed && oldState.LeftButton == ButtonState.Released) {
    // Someone's listening, and we have a click
    this.OnClick.Invoke();
  }
  this.oldState = mouseState;
}

The core is really:

  • Check the mouse state now
  • Compare to the old state last tick. If we weren't pressed, and now we are, that's a click.
  • Check if anyone's subscribing to our event (this.OnClick != null), and if so, notify them of the click.

By the way, your event can use Action if you pass no parameters to the subscribers of the click event (C# Forms defaults is to use sender and event args); Action can take parameters if you do want to send some data over.

Eg. you can make your event public event Action OnClick.

share|improve this answer
    
Wouldn't your code call the OnClick event no matter where the mouse is actually located when it's clicked? Unintentional mistake? –  Djentleman Jan 1 '13 at 5:49
    
Yea, just add some Rectangle-point detection. –  Luis Estrada Jan 1 '13 at 6:55
    
events do have parameters too. And it's better to register a delegate, than use a generic action. this way you have a well defined event –  Gustavo Maciel Jan 1 '13 at 10:03
    
I intentionally wrote a simplistic example. I agree that using delegates gives better-defined events. –  ashes999 Jan 1 '13 at 12:51
    
Thanks for this, had to adapt it a bit but it helped a lot. Also posted my adapted solution as a separate answer. –  Djentleman Jan 3 '13 at 4:46

For those interested, this is my final (so far) Button class:

public class Button {

    public event Action OnClick;
    public event Action OnHover;

    public Rectangle Rec { get; set; }
    public string Text { get; set; }

    public Button(Rectangle rec, string text) {
        this.Rec = rec;
        this.Text = text;
    }

    public void Update(MouseState prev, MouseState cur) { // called every tick 
        if (this.OnClick != null && Rec.Contains(cur.X, cur.Y) && cur.LeftButton == ButtonState.Pressed && prev.LeftButton == ButtonState.Released) { 
            // Someone's listening, and we have a click 
            this.OnClick.Invoke();
        }
        if (this.OnHover != null && Rec.Contains(cur.X, cur.Y)) {
            // Someone's listening, and we have a hover 
            this.OnHover.Invoke();
        }
    }

    public void Draw(SpriteBatch spriteBatch, Color btnCol, Color textCol) {
        spriteBatch.Draw(Game1.TheGame.GuiTex, Rec, btnCol);
        Vector2 fontCenter = Game1.TheGame.DebugFont.MeasureString(Text) / 2;
        spriteBatch.DrawString(Game1.TheGame.DebugFont, Text, new Vector2(Rec.Center.X, Rec.Center.Y), textCol, 0, fontCenter, 1f, SpriteEffects.None, 0);
    }
}

And here is an example of how it is used:

Button[] menuOptions;
int selectedOption = 0;

...

menuOptions = new Button[3] {
    new Button(new Rectangle(x, y, width, height), "New Game"),
    new Button(new Rectangle(x, y, width, height), "Load Game"),
    new Button(new Rectangle(x, y, width, height), "Settings")
};

...

for (int i = 0; i < menuOptions.Length; i++) {
    int j = i; // this is due to the way C# deals with closures and captured variables
    menuOptions[i].OnHover += delegate() { selectedOption = j; };
    menuOptions[i].OnClick += selectOption;
}

...

public void Draw(GameTime gameTime, SpriteBatch spriteBatch) {
    spriteBatch.Begin();
    foreach (Button btn in menuOptions) {
        Color btnCol = (btn == menuOptions[selectedOption]) ? Color.DarkBlue : Color.CadetBlue;
        Color textCol = (btn == menuOptions[selectedOption]) ? Color.White : Color.Black;
        btn.Draw(spriteBatch, btnCol, textCol);
    }
    spriteBatch.End();
}

...

private void selectOption() {
    if (selectedOption == 0) { // New Game
        // do new game stuff
    } else if (selectedOption == 1) { // Load Game
        // do load game stuff
    } else if (selectedOption == 2) { // Settings
        // do settings stuff
    }
}

This example is far from perfect and it would have to be adapted to any specific needs, but it should help.

share|improve this answer
1  
+1, I didn't know you can use Rec.Contains. That's quite awesome –  ashes999 Jan 3 '13 at 15:27

The button WinForms control is able to add themes, rotation and transparency, images and text to C#.NET projects as well as creating rectangle buttons.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.