views:

1219

answers:

3

I have an array of buttons which shall all call the same method but with the index of the button as an argument.

using System;
using Gtk;

public class Input : Gtk.Window {

    private Gtk.Button[] plus;

    public Input() : base(Gtk.WindowType.Toplevel) {

        plus = new Button[10];

[...]

        for (uint i = 0; i < 10; i++) {
            plus[i] = new Button();
            plus[i].Name = i.ToString();
            plus[i].ButtonPressEvent += AddButtonPressed;
        }
    }

I tried it using this method, but it seems it gets not even called as there is no output:

    protected virtual void AddButtonPressed(object sender, System.EventArgs e) {
        Console.WriteLine("Button pressed");
        for (uint i = 0; i < plus.Length; i++) {
        if (sender.Equals(plus[i])) {
            uint index = i;
            i = (uint)plus.Length;
            Console.WriteLine(index);
        }
    }

Maybe someone can point me in the right direction? Thanks.

A: 

I'm fairly certain you need to actually add the buttons to the GTK window hierarchy, something like:

    for (uint i = 0; i < 10; i++) {
        plus[i] = new Button();
        plus[i].Name = i.ToString();
        plus[i].ButtonPressEvent += AddButtonPressed;
        Add(plus[i]);
    }

Should be similar to that, never actually used GTK <.<

Blindy
They are all correctly added to a vbox and shown when executing the program, but pressing the buttons doesn't work.Sorry, I thought it was clear...
Oh well I tried!
Blindy
thanks though ;-)
A: 

Use Tag property, if Gtk button has one.

for (uint i = 0; i < 10; i++) {
    plus[i] = new Button();
    plus[i].Name = i.ToString();
    plus[i].ButtonPressEvent += AddButtonPressed;
    plus[i].Tag = i;
    Add(plus[i]);
}

Handler:

protected virtual void AddButtonPressed(object sender, System.EventArgs e) {
   Console.WriteLine("Button pressed");
   Gtk.Button button = sender as Gtk.Button;
   Console.WriteLine("Index: {0}", button.Tag);
}
mnn
Well Gtk.Button has no property "Tag", but I tried using the Name as identifier which should work the same way.Anyway this doesn't help me with the problem that the method won't get called when pressing the button, but thanks.
You have wrong signature of your event handler. This is from Mono documentation:public delegate void ButtonPressEventHandler (object o, ButtonPressEventArgs args)
mnn
This is wrong. As you could have figured out if you'd actually tested, "method group to delegate conversions are contravariant in their argument types" (http://blogs.msdn.com/ericlippert/archive/2007/10/19/covariance-and-contravariance-in-c-part-three-member-group-conversion-variance.aspx). Since AddButtonPressed accepts /any/ EventArgs, it inherently accepts any ButtonPressEventArgs, because ButtonPressEventArgs are just another kind of EventArgs after all.
Matthew Flaschen
A: 

Quick answer:

[GLib.ConnectBefore]
protected virtual void AddButtonPressed(object sender, EventArgs e)     {
    Console.WriteLine("Button pressed");
    for (uint i = 0; i < plus.Length; i++) {
        if (sender.Equals(plus[i])) {
     uint index = i;
     i = (uint)plus.Length;
     Console.WriteLine(index);
        }
    }
}

Rambling explanation:

This is actually an interesting question. It took a bit of searching to find, but GTK#'s FAQ (but I guess not frequently linked to) says,

"As of release 0.15, Gtk# started using the CONNECT_AFTER flag when connecting event handlers to signals. This means that the event handlers are not run until after the default signal handlers, which means that the widget will be updated when the event handlers run. A side effect of this change is that in the case where default handlers return true to stop signal propogation, Gtk# events will not be emitted. This is the case for example in Gtk.Button, where the button-press-event default signal handler is overridden to emit Pressed events.

While potentially confusing, this is not really a bug. When you use a Gtk.Button, you are getting a widget that emits Pressed events in response to Button1 presses. If you also want your Button to change colors, or popup a context menu on Button3 presses, that's not a Gtk.Button. The correct way to implement such a widget is to subclass Gtk.Button and override the OnButtonPressEvent virtual method to implement the new behaviors you desire."

If it weren't for, "public outcry" (rarely a sign of a good interface), there would be no way to avoid this, except subclassing which is sometimes annoying in C# due to the lack of anonymous classes. But luckily, you're not the first person to have this issue. So that's where the GLib.ConnectBefore attribute comes in. It basically says, call this event handler first so the event isn't devoured by Gtk+.

The annoyance doesn't end there though. I originally was going to suggest applying a good proven solution to passing "extra" parameters to event handlers. In this case, this would allow you to find the index without using equals or the Name string It basically involves creating a wrapper delegate that "pretends" to be a ButtonPressEventHandler but internally passes an int to your backing method:

    Func<uint, ButtonPressEventHandler> indexWrapper = ((index) => ((s, e) => { AddButtonPressed_wrapped(s, e, index); }));   

    ...

    plus[i].ButtonPressEvent += indexWrapper(i);

    ...

    protected virtual void AddButtonPressed_wrapped(object sender, EventArgs e, uint index)
    {
      Console.WriteLine("Button pressed");
      Console.WriteLine("Index = {0}", index);
    }

It compiles and runs without errors, but it has the same problem, the event never fires. I realized that you can't put an attribute directly on a delegate/lambda. So even though the backing method has [GLib.ConnectBefore] the delegate doesn't, so it fails.

As a final note, you could use the Clicked event as in this API example. I verified that it works as expected. One would think that it would only fire on mouse-clicks, but it actually does fire on spacebar as well.

Matthew Flaschen
I took the short hacky way for now ;-)Thank you for this very detailed answer and your invested time!A little correction by the way: it has to be [GLib.ConnectBeforeAttribute]
Actually, it works either way. See http://www.go-mono.com/docs/index.aspx?link=C%3AGtk.ButtonPressEventArgs . I also just found http://msdn.microsoft.com/en-us/library/aa288454(VS.71).aspx , which says "By convention, all attribute names end with the word "Attribute" to distinguish them from other items in the .NET Framework. However, you do not need to specify the attribute suffix when using attributes in code."
Matthew Flaschen