.NET Forum / Languages / C# / March 2008
How to get events assigned to ToolStripMenuItem.Click
|
|
Thread rating:  |
Academia - 28 Feb 2008 01:39 GMT 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);
Peter Duniho - 28 Feb 2008 02:52 GMT > In the code below I > [quoted text clipped - 12 lines] > > 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
Academia - 28 Feb 2008 08:06 GMT >> In the code below I >> [quoted text clipped - 16 lines] > 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
>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 [quoted text clipped - 15 lines] > > Pete Peter Duniho - 28 Feb 2008 16:25 GMT >> 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. 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
Academia - 28 Feb 2008 23:52 GMT >>> As far as I know, the only way to do that is to subclass ToolStripItem >>> (or [quoted text clipped - 9 lines] > * 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
> * Add a public property that returns the current value of the > CloneableClick delegate [quoted text clipped - 41 lines] > > Pete Peter Duniho - 29 Feb 2008 06:02 GMT > [...] > 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
Academia - 01 Mar 2008 15:57 GMT 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
>> [...] >> Thanks for so much info. [quoted text clipped - 26 lines] > > Pete Peter Duniho - 01 Mar 2008 17:54 GMT > [...] > 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
Paul E Collins - 28 Feb 2008 11:03 GMT > 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.
Academia - 29 Feb 2008 13:28 GMT 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
>> Finally I assign an event to newItem.Click. But what I really want to >> assign to newItem.Click is whatever is assigned to [quoted text clipped - 9 lines] > > Eq. Peter Duniho - 29 Feb 2008 18:53 GMT > 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
Academia - 29 Feb 2008 20:26 GMT >> I did try this it works and is a straightforward way. Below I was just remarking about an additional usage.
>>There have been times >> when I've called (in vb) the event handler routine directly - this is [quoted text clipped - 6 lines] > > Pete thanks
Free MagazinesGet these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...
|
|
|