Dynamically Adding VS Menu Item

We can extend VS by using VS SDK. It provides a way to add a menu and menu items. When we do that, we may need to solvee cases where we only determine the menu items at run time. That requires us to dynamically add menu items. Luckily, there's a document and example in MSDN about how to do that (http://msdn.microsoft.com/en-us/library/bb166492.aspx). The approach seems straightforward. However, when I tried that out, I fell in a tricky trap, in my opinion. That took me a few days to figure out. So I want to write it down.

Let me summarize the approach:

1. Create a place holder menu item in the .vsct file. This menu item must have the CommandFlag DynamicVisibility. Other than that, it is similar to other menu items in the .vsct file. It is in a group and a menu, and it has its own guid and id.

2. Create a menu command class that inherits from OleMenuCommand. Override method DynamicItemMatch.
The place holder menu item is always a valid menu item. Then DynamicItemMatch is called for each consecutive id until it returns false.

3.  Add BeforeQueryStatus and other handlers for your menu items.
You can set status, text and other properties of the menu item in BeforeQueryStatus handler.

Here are my lessons.

1. The place holder menu item is always a valid .vsct menu item and shown in the menu, because it's declared in the .vsct file. DynamicItemMatch doesn't really determine it's validity. If you return false in this method, the framework just doesn't query for next id.

2. Be cautious of the id of the place holder menu item and the number of menu items you want to create. You don't want the ids of dynamically created menu items to overlap any one you use for other menu items. It can be out of control especially when you have multiple areas that you want to dynamically add menu item, and all of them are in the same menu and group.

3. DynamicItemMatch is just a way to let the framework know when to stop querying for dynamically generated menu items. You still need to set the right status and text for the generated menu item in BeforeQueryStatus handler.

4. We cannot rely on MatchedCommandId to identify a dynamically created menu item.

In the example in the MSDN document, MatchedCommandId is set to the command id if it's a match. In BeforeQueryStatus, it's used to identify a menu item and gets reset afterwards. But it's not used in the invoke handler. I didn't understand that at first. I didn't reset MatchedCommandId in BeforeQueryStatus handler, and use MatchedCommandId in the invoke handler. And when I click the first menu item, it's actually invoked on the last generated menu item. After a few debugging, I realized that all those dynamically generated menu items and the place holder menu item are actually the same instance. That means, the framework creates an instance of OleMenuCommand for the place holder menu item, and calls DynamicItemMatchBeforeQueryStatus handler and other handlers on the same instance. Once you set a property of that instance, it remains there. For example, MatchedCommandId will keep the last assigned value. Don't need to worry that dynamically generated menu items get weird text. It'll always call BeforeQueryStatus handler before setting the text or other status. Thus you always have a chance to set it to the right value for that particular menu item in the BeforeQueryStatus handler.

The approach in Visual Studio 2012 (https://msdn.microsoft.com/en-us/library/Bb166492%28v=vs.110%29.aspx) creates an instance for the place holder menu item, as well as an instance for each generated menu item. However, I believe that's not the right approach.

1. The framework still calls DynamicItemMatch of all the created instances to determine whether it should query for the next id.

2. The document of DynamicItemMatch indicates that it's used in the case where we dynamically add menu item. The approach in Visual Studio 2012 doesn't use it at all.

The above are what I learned. And since I have a data structure to represent the menu item, I don't need to use MatchedCommandId. I am not quite sure yet the usage of it. But at least, the Visual Studio 2015 approach still works and you should try to debug it if you see any problems.


Leave a comment

Your email address will not be published. Required fields are marked *