How to get events assigned to ToolStripMenuItem.Click

  • Thread starter Thread starter Academia
  • Start date Start date
A

Academia

In the code below I

Call CopyToolStripMenuItem which effectively copies the properties of
saveToolStripMenuItem one at a time into newItem.

The next statement changes the Text of newItem to "New"

The next statement adds newItem to the DropDownItems.

Finally I assign an event to newItem.Click.

But what I really want to assign to newItem.Click is whatever is assigned to
saveToolStripMenuItem.Click

Do you know how to do that?



Thanks



ToolStripMenuItem newItem;

newItem = CloneToolStripMenuItem(saveToolStripMenuItem);

newItem.Text="New";

this.fileToolStripMenuItem.DropDownItems.AddRange(new
System.Windows.Forms.ToolStripItem[] {

newItem});

newItem.Click += new System.EventHandler(this.newToolStripMenuItem_Click);
 
In the code below I

Call CopyToolStripMenuItem which effectively copies the properties of
saveToolStripMenuItem one at a time into newItem.

The next statement changes the Text of newItem to "New"

The next statement adds newItem to the DropDownItems.

Finally I assign an event to newItem.Click.

But what I really want to assign to newItem.Click is whatever is
assigned to
saveToolStripMenuItem.Click

Do you know how to do that?

As far as I know, the only way to do that is to subclass ToolStripItem (or
ToolStripMenuItem) and provide a property or method that exposes that
information. Even then, you would have to implement a new event, rather
than using the existing Click event, as the auto-generated field used by
the Click event would be private and inaccessible to your sub-class.

Of course, if you do make a sub-class, you could implement a Clone()
method too, which would make that "CloneToolStripMenuItem()" method a
little nicer to use. :)

For what it's worth, you could track the subscribers elsewhere. For
example, you could use the Tag property to keep track of who's subscribed
to the event. It'd be a pain, and would be fragile, as you'd have to make
sure you always updated the property any time you subscribed or
unsubscribed from the Click event. But it could be done.

I think it's kind of too bad that the events in the Control class aren't
virtual. It would be nice to be able to override their default behavior,
for situations like this.

Pete
 
Peter Duniho said:
As far as I know, the only way to do that is to subclass ToolStripItem (or
ToolStripMenuItem) and provide a property or method that exposes that
information.

I'm not going to try that - I don't think I'd be sccessful. But I'm
curious. If I did, could I get the Designer to create a menu using it?


Thanks for the info
 
Academia said:
Finally I assign an event to newItem.Click. But what I really want to
assign to newItem.Click is whatever is assigned to
saveToolStripMenuItem.Click

You can cause the New button to send a simulated mouse click to the Save
button, i.e.

void newItem_Click(object sender, EventArgs e)
{
saveToolStripMenuItem.PerformClick();
}

Eq.
 
I'm not going to try that - I don't think I'd be sccessful.

Why not? It should be relatively simple. The basic steps are:

* Sub-class the class
* Add a new event (e.g. "public event CloneableClick")
* Add an override for the OnClick() method in which you raise the
CloneableClick event
* Add a public property that returns the current value of the
CloneableClick delegate
* Subscribe click handlers to the CloneableClick event instead of the
Click event

Optional:
* Add a Clone() method, implementing ICloneable, in which you do all
the stuff you would have otherwise done in CloneToolStripMenuItem()
* Hide the base class Click event with a private version. This won't
stop any code from casting up to the base class and getting at it that
way, but it should at least prevent it from being seen in the designer and
wherever you're using your own class explicitly.

I'm not familiar enough with ToolStripItem and its descendants, but it's
_possible_ that you could simply do the last step, and use
Object.MemberwiseClone() to do the cloning instead of handling everything
explicitly. I'm a little worried that the base classes, like
MarshalByRefObject and Component might have some unmanaged stuff that
can't be safely duplicated that way, but it might be worth a try. The
worst that could happen is that it wouldn't work and you're no worse off
than before, and the best that could happen is that it would work, and
then you're done. :)

I think the above would be more more maintainable than trying to track the
subscribers somewhere else.
But I'm
curious. If I did, could I get the Designer to create a menu using it?

I don't know. For regular custom controls, those show up in the Toolbox
as soon as they are compiled and valid within the current project (i.e.
they show up when you're looking at the same project in which they are
defined, as well as when you're looking at any project that references
that assembly).

But menu items aren't usually "drag-and-drop" into the menu, and I don't
recall off the top of my head whether a custom ToolStripMenuItem shows up
anywhere accessible that would allow you to put it in the menu using the
Designer.

You could always try it and see though. :) Just to test that you
wouldn't have to write any code...just create the sub-class without adding
anything and see if you can then design a menu that uses it.

Pete
 
Peter Duniho said:
Why not? It should be relatively simple. The basic steps are:

* Sub-class the class
* Add a new event (e.g. "public event CloneableClick")
* Add an override for the OnClick() method in which you raise the
CloneableClick event



Thanks for so much info.
I'm new at c# and don't know how to do the next item
 
[...]
Thanks for so much info.
I'm new at c# and don't know how to do the next item
* Add a public property that returns the current value of the
CloneableClick delegate

In the class where the event is declared, the delegate is available using
the same name as the event. In other words, if you have declared an event
named "MyEvent" in class "MyClass", when you access MyClass.MyEvent from
outside the class, you get the event, but when you access it from within
the class, you get the delegate field itself.

Jon Skeet's got a good article that you should find informative, with
respect to how events and delegates work together. You can find it here:
http://www.yoda.arachsys.com/csharp/events.html

Very briefly, doing what I describe above might look something like this:

class MyClass
{
public event EventHandler CloneableClick;

public EventHandler CloneableClickSubcribers
{
get { return CloneableClick; }
}
}

Pete
 
I did try this it works and is a straightforward way. There have been times
when I've called (in vb) the event handler routine directly - this is
better. Can't miss understanding what is intended.

Thanks
 
I did try this it works and is a straightforward way. There have been
times
when I've called (in vb) the event handler routine directly - this is
better. Can't miss understanding what is intended.

Yes, if that was your actual intent, I agree that's a better way to
approach the question.

Note, of course, that that's not the question you actually asked.

Pete
 
Below I was just remarking about an additional usage.
Yes, if that was your actual intent, I agree that's a better way to
approach the question.

Note, of course, that that's not the question you actually asked.

Pete

thanks
 
Great article but one reading is not enough! I'll read it again. But I don't
think my answer is in there (although it might be.)

I believe you showed me how to create a clone that exposes it's click
EventHandler. Good stuff!

I make a MenuStrip using the Ide. I want to clone one of the
ToolStripMenuItems. To do that I need to get the EventHandler delegate it
has stored for click. But I can't seem to do that. If you know how to do
that I'd appreciate learning how - I've been trying for a couple of days
now.


Thanks for the help


Peter Duniho said:
[...]
Thanks for so much info.
I'm new at c# and don't know how to do the next item
* Add a public property that returns the current value of the
CloneableClick delegate

In the class where the event is declared, the delegate is available using
the same name as the event. In other words, if you have declared an event
named "MyEvent" in class "MyClass", when you access MyClass.MyEvent from
outside the class, you get the event, but when you access it from within
the class, you get the delegate field itself.

Jon Skeet's got a good article that you should find informative, with
respect to how events and delegates work together. You can find it here:
http://www.yoda.arachsys.com/csharp/events.html

Very briefly, doing what I describe above might look something like this:

class MyClass
{
public event EventHandler CloneableClick;

public EventHandler CloneableClickSubcribers
{
get { return CloneableClick; }
}
}

Pete
 
[...]
I make a MenuStrip using the Ide. I want to clone one of the
ToolStripMenuItems. To do that I need to get the EventHandler delegate it
has stored for click. But I can't seem to do that. If you know how to do
that I'd appreciate learning how - I've been trying for a couple of days
now.

That depends on what you mean. If you want the delegate stored for the
Click event, there is no direct way to do that. It's private to the class
that declares the event, which means that if you can't modify the actual
class that declares the event, there's no way to get at the value via
normal C# syntax. And of course, you can't modify the ToolStripMenuItem
class.

You _might_ be able to do it via reflection (see
http://msdn2.microsoft.com/en-us/library/system.reflection.aspx), which
doesn't do anything to prevent you from accessing private members.
However, even assuming it works IMHO that would be a poor solution. It's
less efficient, and more importantly it obscures the relatively abnormal
use of the Click event by hiding it in whatever code does the reflection.
It's more awkward to create a subclass that exposes a new event that you
can clone, or to create a subclass that has a separate method for
subscribing to the Click event in which you make a copy of the delegate
passed in, or any of the other techniques you might use to do this.

But in my own personal opinion, that awkwardness is actually a good thing,
as it highlights the unusual nature of the implementation.

As far as using reflection goes, keep in mind also that doing so would be
very fragile. You'd be relying on a specific implementation, which could
change with any revision of .NET. If and when it does, your own code will
break.

I don't know how the ToolStripMenuItem deals with its events, but it's my
recollection that the Control class does _not_ use the default C# event
declarations. Instead, it implements its own add/remove methods for each
event, and stores event subscribers in a separate data structure (probably
a dictionary or something like that). It does this because in the vast
majority of cases, almost every event remains unsubscribed for the entire
lifetime of the control, and so not having a separate field for every
single event saves a lot of memory.

If the ToolStripMenuItem handles events like this now, then you're already
in for an uphill battle hooking into that code (you'll probably want to
use Reflector or something similar to figure out how the events are being
stored). And even if it doesn't do this now, it could in the future.
Either way, with every revision of .NET, you run the risk of your code no
longer working with the new implementation of ToolStripMenuItem.

A number of non-reflective solutions have been posted in this thread, and
there are plenty of variations on those themes. None will do _exactly_
what you're asking, but all will accomplish very much the same thing. I
strongly recommend you stick with one of these alternative approaches, and
forget about trying to get at the actual value of the Click delegate in
the ToolStripMenuItem class itself. It's a private member and for better
or worse, you're not supposed to have it.

Pete
 
Back
Top