Click here to Skip to main content
15,890,973 members
Please Sign up or sign in to vote.
4.50/5 (2 votes)
See more:
I can't seem to figure this one out for the life of me and I'm getting frustrated with myself. I have a fairly simple problem and I just can't seem to find a simple solution.

On my form I have MenuStrip and a ToolStrip. The ToolStrip is basically just a bunch of icons that serve the same purpose as the various menu options, but in a more visual way. So one of these icons on the ToolStrip is a ToolStripDropDownButton. I want to copy the items from under one of the MenuStrip's menu items to this drop down button. Problem!

This code doesn't work:
foreach (ToolStripItem item in myToolStripMenuItem.DropDownItems)
    myToolStripDropDownButton.DropDownItems.Add(item);

because, instead of copying the menu items, they are moved from the ToolStripMenuItem to the ToolStripDropDownButton.

I figured that I'd need to create new menu items for the ToolStripDropDownButton, copies of those under the ToolStripMenuItem.

This is easy enough, until I get to the point where I need to copy the event handlers. Here's the idea:
foreach (ToolStripItem item in myToolStripMenuItem.DropDownItems)
{
    ToolStripMenuItem newItem = new ToolStripMenuItem(item.Text);
    newItem.Click = item.Click; // problem line
    myToolStripDropDownButton.DropDownItems.Add(newItem);
}


But that way of referencing an event handler doesn't work. The compiler error is:
The event 'System.Windows.Forms.ToolStripItem.Click' can only appear on the left hand side of += or -=


Someone please tell me there is a simple solution to this because I really didn't think it was a complex problem.
Posted
Comments
Sergey Alexandrovich Kryukov 13-May-11 4:06am    
Good question, my 5.
--SA

The resolution of the problem if fairly easy. You cannot copy event instances, you can copy delegate instances.

Events are designed exclusively to introduce some limitations over delegate instances, in order to make event handling more fool-proof then delegates. The members ToolStripMenuItem.Click and Control.Click are events, so — in particular — they cannot copy. In general, the idea to change event handlers (that is event's invocation lists) on the fly hardly can get robust code. Now, let's assume you and me are not fools. Keep reading.

Your problem is very natural. You simply need to have some set of actions abstracted from the UI. Same action can happen on the button click, main menu item click or context sensitive menu item click; in the last case the same instance of the context menu can behave differently depending on the context. You can have pretty complex flow of events which effect all those controls: they can get hidden, renamed or disabled. Your requirement to the technology is to make it all abstracted from the UI controls/items: hidden, renamed or disabled should be abstract actions, not UI controls/items; in this way controls/items associated with identical abstract actions would behave consistently without using any ad-hoc programming, automatically.

So, do exactly this: introduce a notion of… let me name it — CommandAction (not to mixed with (System.Action). The type of CommandAction should include delegate instance (remember, me and you are not fools, so we can allow it), some status information (current name, enabled or not, hidden or not, any other states which you can encapsulate in one status word (use bit-mapped enumeration) and a containers of references to UI elements: for example, a container of instances of Control and instances of ToolStripMenuItem. Make some container of CommandAction items. Each time you add an instance of control/item to a command action, the appropriate UI Click event is "assigned" to the delegate of appropriate CommandAction instance. After each command, make a global update which should update all UI elements based on the status of the instances of CommandAction.

This is just a sketch. In this way, we created loose coupling between abstract actions and UI. When you code semantic behavior, you should work only with your container of CommandAction and never directly with UI controls/items. You will have more freedom while working with delegate instances (remember, me and you are not fools).

Now, let's go deeper. To make things even more consistent and go away from ad-hoc programming even more fundamentally, you need to study the following architectural design patterns:
Model–view–controller: http://en.wikipedia.org/wiki/Model–view–controller[^],
Model-view-presenter: http://en.wikipedia.org/wiki/Model-view-presenter[^],
Model–view–adapter: http://en.wikipedia.org/wiki/Model–view–adapter[^],
Model View ViewModel: http://en.wikipedia.org/wiki/Model_View_ViewModel[^].

On loose coupling, read http://en.wikipedia.org/wiki/Loose_coupling[^].

Good luck,
—SA
 
Share this answer
 
Comments
Dewald 13-May-11 4:12am    
Goodness, thanks. I'm wondering if your assumption that you and I are not fools might be only 50% because, after reading your response, I actually do feel a bit foolish ;-)

It sounds good but it seems way more complicated than I expected a solution to this simple problem to be. I haven't tested it as I have found another solution since, which I will post below, but thanks for the info anyway. Even if I don't use it, it certainly provides some interesting reading.
Sergey Alexandrovich Kryukov 13-May-11 4:27am    
:-)
No, no, just the opposite! It's pretty hard to explain in words, but I implemented something like that long ago -- in real application is looks much simpler as the attempts to manipulate UI elements directly, which easily turns into spaghetti code.

Did I understand your requirements right? You say "I'm iterating through a list of menu items, each with a different method handling it's click event". You want to make UI controls/item consistent and you want to calculate events, not to assign them ad-hoc, otherwise you would not ask the question; you would just click events in Designer.

By the way, another advice: I use the following policy: never add event handlers by designer! It's designed more for making fast demo, not for maintainability. Events (all those event "+=" operators) are written much better manually, possible in a separate file with "partial class MyForm {...}".

--SA
Dewald 13-May-11 4:42am    
Yes, you did understand correctly. Basically I created a MenuStrip (with event handlers) using the Designer. I also created a ToolStrip using the Designer but I wanted to add drop down items to one of the ToolStripButtons (a ToolStripDropDownButton to be more specific) programatically based on the drop down items I had added to the ToolstripMenuItem in the Designer.
Sergey Alexandrovich Kryukov 13-May-11 4:50am    
Then go this way. Better yet, would you start with learning the architectural design patterns and thinking about following one of them or some combination? It would not take too much time, hopefully but would be your better long-term investment of your working time? After you think and read you would probably get more clear vision of your event oriented design...
--SA
Sergey Alexandrovich Kryukov 13-May-11 4:52am    
By the way, thanks for accepting this answer formally and feel free to add some follow-up questions. (Better use "Improve question" where you can use proper formatting should you need to share a small piece of code. Please don't post as Solution.)
--SA
Try this.
C#
newItem.Click += item_Click // This should actually be the Method name of the Event Handler.

What you are trying to do is tell the compiler that one Controls Event is the other Controls Event. This is not the case (and can never be the case). Instead, both Events should be Handled by the same Method.
 
Share this answer
 
Comments
Sergey Alexandrovich Kryukov 13-May-11 3:02am    
OP commented:

I understand that (both controls can't have the same event) but your solution won't work either because at runtime I don't necessarily know what the method is for each menu item.

Remember, I'm iterating through a list of menu items, each with a different method handling it's click event. Is there a way to check at runtime what method handles a control's click event so that I can set the new control's event to be handled by the same method?
Sander Rossel 13-May-11 13:40pm    
Forgot that you were iterating. I was just pointing out why that line of code went wrong.
Sergey Alexandrovich Kryukov 13-May-11 3:05am    
The basics of this idea is correct, but the problem is deeper than it seems. My 4.
Please see my solution.
--SA
OK, so for anyone interested, here is the solution that I eventually came up with. It is probably not the most elegant but it solves my problem and I've spent enough time on it already that I'm quite ready to move on now. Just thought someone else who reads this might find it of value.

C#
foreach (ToolStripMenuItem item in myToolStripMenuItem.DropDownItems)
{
    ToolStripMenuItem newItem = new ToolStripMenuItem(item.Text);
    newItem.Name = item.Name;
    newItem.Click += new EventHandler(newItem_Click);
    myToolStripDropDownButton.DropDownItems.Add(newItem);
}


And then this is what the method looks like that handles the click event of the newly created items:
C#
void newItem_Click(object sender, EventArgs e)
{
    myToolStripMenuItem.DropDownItems[(sender as ToolStripMenuItem).Name].PerformClick();
}


So basically, for each ToolStripMenuItem in myToolStripMenuItem.DropDownItems, I create a new ToolStripMenuItem with the same text and name but all having their click events handled by the same method. That method then fires the PerformClick() method of the corresponding ToolStripMenuItem in the original myToolStripMenuItem.DropDownItems, based on the name of the ToolStripMenuItem.
 
Share this answer
 
Hi Dewald,

As suggested by Naerling, you can not directly assigned an event, you must have to attached event using EventHandler.

If you know that there will be only one event attached to your ToolStripItem then you can simply attached using '+=' operator, see below code

newItem.Click += item_Click // This should actually be the Method name of the Event Handler.


Now if you are not aware of such Events and you want to do it at runtime then below code should work for you.

In below code, there are 3 buttons, where Click events are there for button1 and button2. Now on clickign on button2 below code will add all events of button1 to button3. And then clicking on button3, it should work same as button1.

public void button2_Click(object sender, EventArgs e)
        {
            PropertyInfo piButton1 = button1.GetType().GetProperty("Events",
                BindingFlags.NonPublic | BindingFlags.Instance);
            EventHandlerList listButton1 = (EventHandlerList)piButton1.GetValue(button1, null);
            PropertyInfo piButton3 = button3.GetType().GetProperty("Events",
                BindingFlags.NonPublic | BindingFlags.Instance);
            EventHandlerList listButton3 = (EventHandlerList)pi.GetValue(button3, null);
            //Add all event handlers of button1 to button3
            listButton3.AddHandlers(listButton1);
}



This is little tricky but simple.

Regards
Rushi
 
Share this answer
 
Comments
Dewald 13-May-11 4:13am    
Thanks, that is what I had in mind. I've since found another solution which I will post below but I think yours is better.
Sergey Alexandrovich Kryukov 13-May-11 4:18am    
Ugly and not solving the problem -- this is what I think. It's ugly because the property is found by the immediate constant "Events" -- hard-coded. Nothing really guarantees such property exists; it cannot be validated by the compiler. Whole reflection method is not really required. Even though your sample shows you knowledge of Reflection and events behind-the-scene implementation, the whole approach is evil. Such problems are solved via adequate architecture, not but dirty trick. Yes, this is a dirty trick. Not complex at all, just dirty. My 1, sorry.
--SA
Joshi, Rushikesh 13-May-11 4:55am    
SAKryukov, thank you for your Vote. sometimes in existing code we can not implement new architecture concepts. a particular problem can be solved by hundered ways and each ways has benifits and drawback. Dewald wants a ready and instinct solution and he easily understand it.
Sergey Alexandrovich Kryukov 13-May-11 5:04am    
I appreciate your reply and explanation of motivation. Sorry, but I still think your solution (certainly original and showing knowledge) is kind of harmful in principle; and your argument about its usefulness and ease of understanding does not really work.
Thank you for understanding, my respect,
--SA

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900