This documentation is archived and is not being maintained.

Walkthrough: Creating an MRU Menu List

[This topic is pre-release documentation and is subject to change in future releases. Blank topics are included as placeholders.]

This walkthrough builds on the Walkthrough: Creating a Submenu walkthrough, and guides you through the steps of adding a dynamic list to a submenu, thus forming the basis of a Most Recently Used (MRU) menu list.

A dynamic menu list starts with a placeholder in a menu. A VSPackage is asked for all commands that should be inserted in place of the placeholder item. This is done whenever the menu that contains the dynamic list is shown. Typically, dynamic menu lists are stored in their own submenu to isolate any changes to an obvious place in the menu. However, a dynamic list can occur at any point in a menu. In this walkthrough, for example, the MRU list is displayed at the bottom of an existing submenu, separated by a line.

Technically, a dynamic list can be applied to a toolbar but that is strongly discouraged: a toolbar should remain unchanged unless the user takes specific steps to alter the toolbar, typically through the Customize dialog box (available from the Tools menu).

This walkthrough creates an MRU list of four items that change their order each time an item from the MRU list is selected (the selected item moves to the top of the list).

For more information on menus and the .ctc file, see Menus and Toolbars.

This walkthrough requires the Visual Studio Industry Partner (VSIP) SDK to be installed. The result of this walkthrough writes information to the experimental registry hive for Visual Studio.

To create the MyTopLevelMenuPackage VSPackage

  • Follow the procedures described in Walkthrough: Creating a Submenu to create the submenu that is modified here.

    NoteNote

    If you are going to use Visual C++ as the language for your VSPackage, the procedures described in Walkthrough: Creating a Top Level Menu (C#) can be used with the exception of selecting Visual C++ as the programming language. In this walkthrough for adding an MRU menu list, adding the list to a Visual C++ VSPackage is a very different process than adding the list to a Visual C# VSPackage. Therefore, two procedures are provided for adding an MRU list: one for Visual C# and the other for Visual C++. However, the process of adding the MRU list to the submenu is the same for both programming languages.

    The rest of the procedures in this walkthrough assume the VSPackage name of MyTopLevelMenuPackage, which is the name used in the Walkthrough: Creating a Top Level Menu (C#) walkthrough.

To create a dynamic item list command

  1. In Solution Explorer, expand the CtcComponents folder in the MyTopLevelMenuPackage project, right-click CommandIds.h, and then select Open to open it in a text editor.

  2. In the Menu Group IDs section, add the definition for MyMRUListGroup after the exiting group IDs:

    #define MyMRUListGroup 0x1200
    
  3. In the Command IDs group, add the definition for the cmdidMRUList after the existing command IDs:

    // All command IDs from 0x200 onward are dedicated to the MRU list.
    #define cmdidMRUList 0x200
    
  4. In the CtcComponents folder in the MyTopLevelMenuPackage project, right-click MyTopLevelMenuPackage.ctc and then select Open.

  5. In the NEWGROUPS_BEGIN section, add the following after the existing group entries:

    guidMyTopLevelMenuPackageCmdSet:MyMRUListGroup,  // Group ID
        guidMyTopLevelMenuPackageCmdSet:MySubMenu,   // Menu ID
        0x0100;                                      // Priority
    
  6. In the BUTTONS_BEGIN section, add the following after the existing button entries:

    guidMyTopLevelMenuPackageCmdSet:cmdidMRUList,       // Command ID
        guidMyTopLevelMenuPackageCmdSet:MyMRUListGroup, // Parent Group
        0x0000,                                         // Priority
        OI_NOID,                                        // Icon ID (no icon allowed)
        BUTTON,                                         // Button Type
        DYNAMICITEMSTART,                               // Flags
        "MY MRU PLACE HOLDER";                          // Button Text
    
  7. On the Build menu, click Build Solution.

  8. This rebuilds the .ctc file with the changes. Correct any errors that may occur during building (the most common error is using the wrong case for a GUID label or a command ID; GUID labels and command IDs are always case-sensitive).

  9. To test the display of the new command, open an instance of the experimental Visual Studio any one of the following methods::

    • From the Visual Studio command prompt, type devenv /rootsuffix exp.

    • From the Start Menu, select the experimental Visual Studio shortcut, Microsoft Visual Studio 2005 Experimental. This shortcut is added when the VSIP SDK is installed.

    • Press F5 or select Start from the Debug menu (this runs the experimental Visual Studio under the debugger and allows debugging of your VSPackage).

  10. Open the My Test Menu to see a new submenu called My Sub Menu. Open the My Sub Menu to see the new command, MY MRU PLACE HOLDER. This text is going to be replaced with a list of items as implemented in the next procedure.

    NoteNote

    You must close the experimental Visual Studio before continuing to the next step.

  11. Continue to the next procedure to add the MRU list.

The following procedure assumes you created the VSPackage in Visual C#. For a VSPackage created in Visual C++, see the procedure "To fill the MRU List in unmanaged code".

To fill the MRU List in managed code

  1. In Solution Explorer, expand the MyTopLevelMenuPackage project to find the PkgCmdID.cs file. Right-click on the PkgCmdID.cs file and select Open to open it in a text editor.

  2. Add the following command ID after the existing command IDs in the PkgCmdID.cs file:

    public const uint cmdidMRUList = 0x200;
    
  3. In Solution Explorer, find the VsPkg.cs file in the MyTopLevelMenuPackage project. Right-click on the VsPkg.cs file and select Open to open it in a text editor.

  4. At the top of the file, at the end of the list of using statements, add the following:

    using System.Collections; // for ArrayList
    
  5. Locate the Initialize method. If necessary, locate the hidden region labeled "Package Members" and expand it by clicking on the plus sign in the left margin. The Initialize method is inside this hidden region.

  6. To the Initialize method, add the following lines right after the last call to the AddCommand method.

    this.InitMRUMenu(mcs);
    
  7. At the end of the MyTopLevelMenuPackage class, add the following code right after the SubItemCallback method. This code initializes the list of strings that represent the items to be shown on the MRU menu list.

    //----------------------------------------------------------------
    private int numMRUItems = 4;
    private int baseMRUID = (int)PkgCmdIDList.cmdidMRUList;
    private ArrayList mruList;
    
    private void InitializeMRUList()
    {
        if (null == this.mruList)
        {
            this.mruList = new ArrayList();
            if (null != this.mruList)
            {
                for (int i = 0; i < this.numMRUItems; i++)
                {
                    this.mruList.Add(string.Format(CultureInfo.CurrentCulture,
                                                   "Item {0}", i + 1));
                }
            }
        }
    }
    
  8. After the InitializeMRUList method, add the following InitMRUMenu method. This initializes the MRU List menu commands.

    private void InitMRUMenu(OleMenuCommandService mcs)
    {
        InitializeMRUList();
        for (int i = 0; i < this.numMRUItems; i++)
        {
            CommandID cmdID   = new CommandID(GuidList.guidMyTopLevelMenuPackageCmdSet,
                                              this.baseMRUID + i);
            OleMenuCommand mc = new OleMenuCommand(new EventHandler(OnMRUExec), cmdID);
            mc.BeforeQueryStatus += new EventHandler(OnMRUQueryStatus);
            mcs.AddCommand(mc);
        }
    }
    

    In managed code, it is necessary to create a menu command object for each possible item in the MRU list. The Visual Studio shell calls the OnMRUQueryStatus method for each item in the MRU list until there are no more items. In managed code, the only way for the shell to know there are no more items is to create all possible items first. If desired, additional items can be initially marked as not visible with mc.Visible = false; after the menu command is created. These items can then be made visible later with mc.Visible = true; in the OnMRUQueryStatus method.

  9. After the InitMRUMenu method, add the following OnMRUQueryStatus method. This is the handler that sets the text for each MRU item.

    private void OnMRUQueryStatus(object sender, EventArgs e)
    {
        OleMenuCommand menuCommand = sender as OleMenuCommand;
        if (null != menuCommand)
        {
            int MRUItemIndex = menuCommand.CommandID.ID - this.baseMRUID;
            if (MRUItemIndex >= 0 && MRUItemIndex < this.mruList.Count)
            {
                menuCommand.Text = this.mruList[MRUItemIndex] as string;
            }
        }
    }
    
  10. After the OnMRUQueryStatus method, add the following OnMRUExec method. This is the handler for selecting an MRU Item. This method moves the selected item to the top of the list and then displays the item selected in a message box.

    private void OnMRUExec(object sender, EventArgs e)
    {
        OleMenuCommand menuCommand = sender as OleMenuCommand;
        if (null != menuCommand)
        {
            int MRUItemIndex = menuCommand.CommandID.ID - this.baseMRUID;
            if (MRUItemIndex >= 0 && MRUItemIndex < this.mruList.Count)
            {
                string selection = this.mruList[MRUItemIndex] as string;
                for (int i = MRUItemIndex; i > 0; i--)
                {
                    this.mruList[i] = this.mruList[i - 1];
                }
                this.mruList[0] = selection;
                System.Windows.Forms.MessageBox.Show(
                    string.Format(CultureInfo.CurrentCulture,
                                  "Selected {0}", selection));
            }
        }
    }
    
  11. Select Build Solution from the Build menu to build the solution. Correct any errors that may arise.

  12. Skip down to the procedure "To test the MRU menu list".

To fill the MRU list in unmanaged code

  1. In the Solution Explorer, expand the Header Files folder in the MyTopLevelMenuPackage project to find the VsPkg.h file. Right-click on the VSPkg.h file and select Open to open it in a text editor.

  2. At the end of the CMyTopLevelMenuPackagePackage class, after the declaration of OnMySubCommandClicked method in the private section, add the following:

        // MRU List stuff.
        int   numMRUItems;
        int   baseMRUID;
        BSTR *mruList;
    
        void    InitializeMRUList();
        void    FreeMRUList();
        bool    OnMRUQueryStatus(int cmdID, OLECMDTEXT *pText);
        HRESULT OnMRUExec(int cmdID);
    
  3. In Solution Explorer, expand the Source Files folder in the MyTopLevelMenuPackage project to find the VsPkg.cpp file. Right-click on the VSPkg.cpp file and select Open to open it in a text editor.

  4. Find the constructor CMyTopLevelMenuPackagePackage::CMyTopLevelMenuPackagePackage and add the following to the end of the constructor:

        numMRUItems = 4;
        baseMRUID   = cmdidMRUList;
        mruList     = NULL;
    
  5. In the destructor, CMyTopLevelMenuPackagePackage::~CMyTopLevelMenuPackagePackage, add the following at the end of the destructor:

        FreeMRUList();
    
  6. Find the CMyTopLevelMenuPackagePackage::QueryStatus method and modify the default statement in the switch statement to look like the following:

                default:
                    if (OnMRUQueryStatus(prgCmds[0].cmdID, pCmdText))
                    {
                        cmdf = OLECMDF_SUPPORTED | OLECMDF_ENABLED;
                    }
                    else
                    {
                        return OLECMDERR_E_NOTSUPPORTED;
                    }
                    break;
    
  7. Find the CMyTopLevelMenuPackagePackage::Exec method and modify the default statement in the switch statement to look like the following:

                default:
                    hr = OnMRUExec(nCmdID);
                    if (FAILED(hr))
                    {
                         return OLECMDERR_E_NOTSUPPORTED;
                    }
                    break;
    
  8. At the end of the VSPkg.cpp file, add the following methods. These methods initialize and free the list of strings displayed in the MRU list.

    void CMyTopLevelMenuPackagePackage::InitializeMRUList()
    {
        if (NULL == mruList)
        {
            mruList = new BSTR[numMRUItems];
            if (NULL != mruList)
            {
                wchar_t itemName[12];
    
                for (int i = 0; i < numMRUItems; i++)
                {
                    wnsprintf(itemName, countof(itemName), L"Item %d", i + 1);
                    mruList[i] = SysAllocString(itemName);
                }
            }
        }
    }
    
    
    void CMyTopLevelMenuPackagePackage::FreeMRUList()
    {
        if (NULL != mruList)
        {
            for (int i = 0; i < numMRUItems; i++)
            {
                SysFreeString(mruList[i]);
            }
            delete[] mruList;
            mruList = NULL;
        }
    }
    
  9. After the FreeMRUList method, add the following OnMRUQueryStatus method. This handles returning the text for an MRU menu item.

    bool CMyTopLevelMenuPackagePackage::OnMRUQueryStatus(int cmdID, OLECMDTEXT *pText)
    {
        bool fCmdSupported = false;
        InitializeMRUList();
        int MRUItemIndex = cmdID - baseMRUID;
        if (MRUItemIndex >= 0 && MRUItemIndex < numMRUItems)
        {
            fCmdSupported = true;
            if (NULL != pText && NULL != mruList[MRUItemIndex])
            {
                pText->cmdtextf = OLECMDTEXTF_NAME;
                wcsncpy(pText->rgwz, mruList[MRUItemIndex], pText->cwBuf);
                pText->cwActual = wcslen(mruList[MRUItemIndex]);
            }
        }
        return(fCmdSupported);
    }
    
  10. After the OnMRUQueryStatus method, add the following OnMRUExec method. This is the handler for selecting an MRU Item. This method moves the selected item to the top of the list and then displays the selected item in a message box.

    HRESULT CMyTopLevelMenuPackagePackage::OnMRUExec(int cmdID)
    {
        HRESULT hr = E_FAIL;
        int MRUItemIndex = cmdID - baseMRUID;
        if (MRUItemIndex >= 0 && MRUItemIndex < numMRUItems)
        {
            BSTR selection = mruList[MRUItemIndex];
            if (NULL != selection)
            {
                hr = S_OK;
                for (int i = MRUItemIndex; i > 0; i--)
                {
                    mruList[i] = mruList[i - 1];
                }
                mruList[0] = selection;
    
                CComPtr<IVsUIShell> srpUiShell;
                hr = _AtlModule.QueryService(SID_SVsUIShell,
                                             IID_IVsUIShell,
                                             (void **)&srpUiShell);
                if (SUCCEEDED(hr))
                {
                    CComBSTR prompt(L"Selected ");
                    prompt += selection;
                    // Show Message Box to prove we were here
                    LONG lResult;
                    hr = srpUiShell->ShowMessageBox(
                        0,
                        CLSID_NULL,
                        L"MyTopLevelMenuPackage",
                        (LPOLESTR)prompt,
                        NULL,
                        0,
                        OLEMSGBUTTON_OK,
                        OLEMSGDEFBUTTON_FIRST,
                        OLEMSGICON_INFO,
                        0,
                        &lResult);
                }
            }
        }
        return(hr);
    }
    
  11. Select Build Solution from the Build menu to build the solution. Correct any errors that may arise.

  12. Continue to the next procedure to test the MRU menu list.

To test the MRU menu list

  1. Open an instance of the experimental Visual Studio with one of the following steps:

    • From the Visual Studio command prompt, type devenv /rootsuffix exp.

    • From the Start Menu, select the experimental Visual Studio shortcut, Microsoft Visual Studio 2005 Experimental. This shortcut is added when the Visual Studio SDK is installed.

    • Press F5 or select Start from the Debug menu (this runs the experimental Visual Studio under the debugger and allows debugging of your VSPackage).

  2. Select My Test Command from the My Test Menu. This displays a message box indicating the command was selected.

    NoteNote

    This step is necessary to force your VSPackage to load and properly display the MRU list. If you skip this step, the MRU list is not displayed.

  3. Open the My Sub Menu submenu in the My Test Menu. A list of four items is displayed at the end of the submenu, after a separator (if the list does not display, make sure you have followed the instructions in step 13). Select Item 3 and a message box should appear displaying the text "Selected Item 3".

  4. Open the My Sub Menu submenu once more in the My Text Menu. Note how Item 3 is now at the top of the list, with the other items having been pushed down one position. Select Item 3 again and the message box still displays "Selected Item 3", indicating that the text has properly moved to the new position.

Show: