Walkthrough: Creating an MRU Menu List

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

注意

Beginning with Visual Studio 2008 SDK, use XML Command Table (.vsct) files instead of command table configuration (.ctc) files to define how menus and commands appear in your VSPackages. For more information, see XML-Based Command Table Configuration (.vsct) Files.

A dynamic menu list starts with a placeholder in a menu. The Visual Studio integrated development environment (IDE) asks a VSPackage 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 constrain any changes to an obvious place in the menu. However, a dynamic list can occur at any place on a menu. In this walkthrough, for example, the MRU list is displayed at the bottom of an existing submenu, separated from the rest of the submenu by a line.

Technically, a dynamic list can also be applied to a toolbar. However, that is strongly discouraged because a toolbar should remain unchanged unless the user takes specific steps to alter the toolbar, typically through the Customize dialog box (available on 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 about menus and the .ctc file, see Menus and Toolbars.

Prerequisites

This walkthrough requires the Visual Studio SDK to be installed. The result of this walkthrough writes information to the experimental registry hive for Visual Studio 2008.

Creating a VSPackage

To create the MyTopLevelMenuPackage VSPackage

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

    注意

    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.

Creating a Dynamic Item List Command

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 click 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 click 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 2008 by using one of the following methods:

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

    • Click Start, and then navigate to Visual Studio 2008 Experimental and open it. This shortcut is added when the Visual Studio SDK is installed.

    • Press F5 or click Start on the Debug menu. Doing this runs Visual Studio 2008 Experimental under the debugger and lets you debug your VSPackage.

  10. Open the My Test Menu to see a new submenu called My Sub Menu. Open 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.

    注意

    You must close Visual Studio 2008 Experimental before you continue to the next procedure.

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

Filling 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 by using unmanaged code".

To fill the MRU List by using managed code

  1. In Solution Explorer, expand the MyTopLevelMenuPackage project to find the PkgCmdID.cs file. Right-click PkgCmdID.cs and then click 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 VsPkg.cs and then click 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. You may have to locate the hidden region labeled "Package Members" and expand it by clicking 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, you must create a menu command object for each possible item in the MRU list. The IDE 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 IDE to know that there are no more items is to create all possible items first. If you want, you can initially mark additional items as not visible by using 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. On the Build menu, click Build Solution to build the solution. Correct any errors that may arise.

  12. Continue to the procedure "To test the MRU menu list".

To fill the MRU list by using unmanaged code

  1. In Solution Explorer, expand the Header Files folder in the MyTopLevelMenuPackage project to find the VsPkg.h file. Right-click VSPkg.h and then click 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 VSPkg.cpp and then click 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. On the Build menu, click Build Solution to build the solution. Correct any errors that may arise.

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

Testing the MRU List

To test the MRU menu list

  1. Open an instance of Visual Studio 2008 Experimental by using one of the following methods:

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

    • Click Start, and then navigate to Visual Studio 2008 Experimental and then open it. This shortcut is added when the Visual Studio SDK is installed.

    • Press F5 or click Start on the Debug menu. Doing this runs Visual Studio 2008 Experimental under the debugger and lets you debug your VSPackage.

  2. On the My Test Menu, click My Test Command. Doing this displays a message box that indicates that the command was selected.

    注意

    This step is required to force your VSPackage to load and correctly 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, ensure that you have followed the instructions in step 13. When you click Item 3, a message box should appear and display the text, "Selected Item 3".

  4. Open the My Sub Menu submenu again. Notice that Item 3 is now at the top of the list and the other items have been pushed down one position. Click Item 3 again and notice that the message box still displays "Selected Item 3", which indicates that the text has correctly moved to the new position.

See Also

Concepts

Menu and Toolbar Command Walkthroughs

Menus and Toolbars