set a single click event for a large group of buttons on a form?

R

Rich P

Lets say I have 50 panels on a form and a button on each panel. Rather
than write 50 click events (one for each button) I would like to write
one event which would be triggered by any of the 50 buttons.

Each panel displays different graphical information. When I click a
button on a respective panel I want to retrieve the underlying graph
data for that panel. I am thinking I could create a button array and
set the same click event to each button in the array and then determine
which button was clicked by checking the sender object (event arg e
...). Is there a more correct or effective way to do this?

Thanks

Rich
 
J

Jeff Johnson

Lets say I have 50 panels on a form and a button on each panel. Rather
than write 50 click events (one for each button) I would like to write
one event which would be triggered by any of the 50 buttons.

Each panel displays different graphical information. When I click a
button on a respective panel I want to retrieve the underlying graph
data for that panel. I am thinking I could create a button array and
set the same click event to each button in the array and then determine
which button was clicked by checking the sender object (event arg e
..). Is there a more correct or effective way to do this?

Everything sounds good except I question the need for the button array. You
could just put a value in the Tag property of each button and then examine
that (from the sender argument).
 
J

Jeff Gaines

I am thinking I could create a button array and
set the same click event to each button in the array and then determine
which button was clicked by checking the sender object (event arg e
..). Is there a more correct or effective way to do this?

The Tag property of the Button can be useful in situations like this - you
can set it to anything as long as you cast it correctly when retrieving
it. At its simplest it could be a unique integer representing each button.
 
P

Peter Duniho

Rich said:
Lets say I have 50 panels on a form and a button on each panel. Rather
than write 50 click events (one for each button) I would like to write
one event which would be triggered by any of the 50 buttons.

Each panel displays different graphical information. When I click a
button on a respective panel I want to retrieve the underlying graph
data for that panel. I am thinking I could create a button array and
set the same click event to each button in the array and then determine
which button was clicked by checking the sender object (event arg e
...). Is there a more correct or effective way to do this?

Well, the "more correct" way to do it is to not have 50 panels in a
single form. :)

But, even with fewer panels, the underlying question is perfectly valid,
so let's work on that.

First, a terminology issue: the "event" is the actual thing in a class
that you subscribe to. You don't "write" the events; you write the
"event handlers". I think I understand what you mean – you want to
write a single event handler and reuse it for each button – but it's
better if you use the correct terminology in the first place, so that
others don't have to make assumptions to understand your posts.

Now, with that assumption in mind, how to implement a single event
handler that you can apply to each Button in each Panel? There are a
couple of approaches I like to use in this scenario, depending on what
information is available. It's not clear from your post how your
objects are related; in particular, while it's easy enough to get the
Panel for a given Button (it's just that Button's parent object), unless
you already have a way to map from the Panel to the graph data, that
doesn't necessarily solve your issue.

But the simplest way is in fact when you already have a way to get
directly from a Panel instance to the graph data. In that case, your
event handler looks like this:

void GeneralPurposeClickHandler(object sender, EventArgs e)
{
Control ctl = (Control)sender;
Panel panel = (Panel)ctl.Parent;

// retrieve graph data from "panel" as appropriate
// and do whatever processing you want to do
}

Then you just make "GeneralPurposeClickHandler" the event handler for
each Button object.

An alternative way is to take advantage of anonymous methods and
hard-code an argument passed by an anonymous method for each Click
handler to a method that needs that information. This is actually a bit
like writing a new event handler for each Button, but the code is
simpler and easier to keep all in one place.

An example of that approach would be something like this:

class Form1 : Form
{
GraphData[] _data = /* ...initialized as appropriate... *;

public Form1()
{
InitializeComponent();

button1.Click += (sender, e) =>
GeneralPurposeClickHandler(sender, e, _data[0]);
button2.Click += (sender, e) =>
GeneralPurposeClickHandler(sender, e, _data[1]);
button3.Click += (sender, e) =>
GeneralPurposeClickHandler(sender, e, _data[2]);
// etc.

}

void GeneralPurposeClickHandler(object sender, EventArgs e,
GraphData gd)
{
Control ctl = (Control)sender;
Panel panel = (Panel)ctl.Parent;

// No need to retrieve the graph data from the "panel".
// It's been passed to the method as the third argument.
}
}

I like the latter approach in that I find it simple to implement, and
easier to read. But, it can get a bit tedious to do if you wind up with
a large number of event handlers you have to include, since of course
you are in a sense writing a new event handler for each control (each
"(sender, e) => GeneralPurposeClickHandler(sender, e, _data[x]);" is
basically a new method). If you can create a convenient data mapping
from each Panel instance to the appropriate GraphData instance, then the
former approach can work very well, and has the added benefit that there
really is only just the one method ever.

Pete
 
R

Rich P

thanks all for your replies. I will note the tagging method, but the
anonymous method seems a little more straight forward. All very nice.
Thanks all.

Rich
 
R

Rich P

Hi Pete,

Thanks for this suggestion. I do believe this is the one I will go with.
Note (question) can I do the anonymous method thing in the Form designer
where the controls get registered/initialized? And yes, I take note of
correct terminoloty. I did mean to say Event Handler - just like not
sure if a control gets registered or initialized in the form Designer
window. But is this where I would do the anonymous method thing with
the 3rd arg?

Also, I am drawing the graphs from scratch on each panel (drawing an
ellipse for each point and line segments connecting the ellipses). The
users wanted everything on a sort of continuous form.

Rich
 
P

Peter Duniho

Rich said:
Hi Pete,

Thanks for this suggestion. I do believe this is the one I will go with.
Note (question) can I do the anonymous method thing in the Form designer
where the controls get registered/initialized?

I'm not sure what you're asking. If you mean, can you assign an
anonymous method to be the event handler for each Button instance's
Click event, then no…the method has to have a name for the Designer to
use it.
And yes, I take note of
correct terminoloty. I did mean to say Event Handler - just like not
sure if a control gets registered or initialized in the form Designer
window. But is this where I would do the anonymous method thing with
the 3rd arg? [...]

Sorry, that last sentence didn't parse. Is what where you would do what
anonymous method thing?

Pete
 
R

Rich P

form1.Designer.cs#region Windows Form Designer generated code
...

#endregion

private System.Windows.Forms.Timer timer1;
private System.Windows.Forms.Button button1;
private System.Windows.Forms.Button button2;
private System.Windows.Forms.Button button3;
...
<

Is this where I could add the anonymous method to each button? Or do I
need to do that in form1.cs?

Rich
 
K

kndg

Peter said:
[...]
An example of that approach would be something like this:

class Form1 : Form
{
GraphData[] _data = /* ...initialized as appropriate... *;

public Form1()
{
InitializeComponent();

button1.Click += (sender, e) => GeneralPurposeClickHandler(sender,
e, _data[0]);
button2.Click += (sender, e) => GeneralPurposeClickHandler(sender,
e, _data[1]);
button3.Click += (sender, e) => GeneralPurposeClickHandler(sender,
e, _data[2]);
// etc.

}

void GeneralPurposeClickHandler(object sender, EventArgs e,
GraphData gd)
{
Control ctl = (Control)sender;
Panel panel = (Panel)ctl.Parent;

// No need to retrieve the graph data from the "panel".
// It's been passed to the method as the third argument.
}
}

I like the latter approach in that I find it simple to implement, and
easier to read. But, it can get a bit tedious to do if you wind up with
a large number of event handlers you have to include, since of course
you are in a sense writing a new event handler for each control (each
"(sender, e) => GeneralPurposeClickHandler(sender, e, _data[x]);" is
basically a new method). If you can create a convenient data mapping
from each Panel instance to the appropriate GraphData instance, then the
former approach can work very well, and has the added benefit that there
really is only just the one method ever.

Hi Pete,

Not to say that I don't agree with you... but I do think that the
anonymous method is overuse here. If the OP have 50 buttons, it will end
up having 50 anonymous methods in the compiled code. Not that the
increase of the executable size will effect the performance, but I have
read somewhere that the JIT will optimize the method that is called
frequently better that the less frequent method (50 buttons, 1 method vs
50 buttons, 50 methods).

Happy Holiday!
 
P

Peter Duniho

Rich said:
form1.Designer.cs
#region Windows Form Designer generated code
...

#endregion

private System.Windows.Forms.Timer timer1;
private System.Windows.Forms.Button button1;
private System.Windows.Forms.Button button2;
private System.Windows.Forms.Button button3;
...
<

Is this where I could add the anonymous method to each button? Or do I
need to do that in form1.cs?

The latter. Editing the *.Designer.cs file by hand is just asking for
all your code to disappear next time you use the Designer to modify the
form's layout.

Pete
 
P

Peter Duniho

kndg said:
Not to say that I don't agree with you... but I do think that the
anonymous method is overuse here.

I think you are saying you don't agree with me. :p
If the OP have 50 buttons, it will end
up having 50 anonymous methods in the compiled code.

50 buttons is a mess, no matter how you look at it. I can't imagine
presenting a user with 50 buttons at once.
Not that the
increase of the executable size will effect the performance, but I have
read somewhere that the JIT will optimize the method that is called
frequently better that the less frequent method (50 buttons, 1 method vs
50 buttons, 50 methods).

Assuming there is in fact such a different in JIT optimization (and I'd
be very curious about seeing a reference supporting such a
statement…I've never heard anything like it before with respect to
..NET), UI methods just aren't going to be called often enough for it to
matter.

Not only would optimization not affect performance _at all_ (there's no
way a user can click a button fast enough for the time to execute the
actual event handler to make any difference whatsoever), even having a
single shared event handler, that one method isn't going to be called
often enough to trigger any sort of special "frequently-called method"
optimization.

I don't object to the suggested use of the Tag property. It's yet
another viable approach to the problem. It's just not something I tend
to use. Even with the overhead of the anonymous method, I find the fact
that using that approach keeps everything nice and type-safe, and the
relative ease to code it, to outweigh any potential performance
improvement that might be had by using the Tag property.

YMMV. And you may even disagree with me. You wouldn't be the first. :)

Pete
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top