Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

A Smart MenuItem

0.00/5 (No votes)
26 Dec 2002 1  
A Menuitem that acts as a MRU list

Introduction

For a project I work on, I decided I needed the possibility to open recent files. (Also called a MRU or Most Recently Used.) I first searched for it on CodeProject but I seemed to be the first ;).

So I thought of the following requirements. I needed a menu item (called Recent) with the recently opened files as sub menu-items. Persistence is not needed in my case because I use my own setting manager. (Perhaps a later article.) My menu-item should signal the change in the MRU list so I could persist it myself. Clicking by the user should also be signaled. Of course, I have to tell the menu item when a file is actually opened, so the list can be updated. I don't need it yet (so it is not in there) but when a file does not exist anymore, it should be removed from the list.

Crafting the Class

I started with MenuItem as base class and a private string array for the four items.

public class MRUMenuItem : MenuItem {
    string[] mru=new string[4];

I needed access to this list (to be able to persist it) so I added a property.

public string[] MRUFiles {
    get {return mru;}
}

Next, I need initialization. It is not done in the creator method because I want to integrate with Visual Studio (VS). When I want to use this MRUMenuItem, I start off by adding a normal MenuItem and I change the generated declaration and initialization to my own menu item. This is not changed by VS, but when you add extra creator parameters, they are removed.

The parameters are saved in the string array and for every string that is not "", a menu item is added. This menu item is added as a sub menu-item. Also, an event handler is added to handle the clicks by the user.

public void Initialize(string file1, string file2, 
                  string file3, string file4) { 
    mru[0]=file1;
    mru[1]=file2;
    mru[2]=file3;
    mru[3]=file4;
    for (int i= 0;i<4;i++) {
        if (""!=mru[i]) {
            MenuItem mmru = new MenuItem(mru[i], 
                      new EventHandler(OnMRUClick));
            this.MenuItems.Add(mmru);
        }
    }
}

The FileOpened method takes care of all changes. First, I search for the string, to see if it is in the list. If so, it should be removed and reinserted at the first position. If it is not in the list, the last item is removed. So, after the first four lines shown below, found has the index to remove. The next two lines take care of the moving up of the item to remove and insert the new item at the first position. Then, the new sub menu set is built. Finally, if an event handler is attached by the class-user, the MRUChanged event is fired.

public void FileOpened(string file) {
    int found=3;
    for (int j= 0;j<4;j++) {
        if (file== mru[j]) {
           found=j;
           break;
        }
    }
    while (found>0) mru[found]= mru[--found];
    mru[0]=file;
    this.MenuItems.Clear();
    for (int i= 0;i<4;i++) {
        if (""!=mru[i]) {
            MenuItem mmru = new MenuItem(mru[i], 
                     new EventHandler(OnMRUClick));
            this.MenuItems.Add(mmru);
        }
    }
    if (MRUChanged != null) {
        MRUChanged(this, new EventArgs()); 
    }
}

The class furthermore holds the definition of the events.

Using the Code

First, you create a new instance of the class. The easiest way to do that is to use VS as explained before. Then you initialize it with four strings and attach the event handlers.

// Only when needed (If not done by VS)
MRUMenuItem mRecent=new MRUMenuItem(); 
// Only when needed (If not done by VS)
this.mMain.MenuItems.Add(mRecent); 

mRecent.Initialize("File 1", "File 2", "File 3", "File 4");
mRecent.MRUClicked+=new EventHandler(MRUClick);
mRecent.MRUChanged+=new EventHandler(MRUChanged);

The two event handlers take care of the clicks and the changes. Be sure to use FileOpened.

private void MRUClick(object sender, System.EventArgs e) {
    MenuItem mSender = sender as MenuItem;
    MRUMenuItem mParent = mSender.Parent as MRUMenuItem;
    // Use mSender.Text to handle the clicked event
    // When the file is successfully opened, use FileOpened
    mParent.FileOpened(mSender.Text);
}
private void MRUChanged(object sender, System.EventArgs e) {
    MRUMenuItem m=sender as MRUMenuItem;
    // Persist m.MRUFiles[0] through m.MRUFiles[3]
}

Finally

I think this shows how you can extend the functionality of the .NET framework. The only thing left is a tighter integration in Visual Studio. I would like to use the MRUMenuItem in VS like the normal MenuItem. Initialize could be changed then in an event that can be chosen from the property box, along with MRUClick and MRUChanged. Any help and comment is much appreciated!

Changes

Marc Clifton mentioned the better option to have a variable amount of files. I changed the code as follows:

// No size anymore, the string[] from the Initialize is used
string[] mru;
int _count;
public void Initialize(string[] files) { 
    mru=files;            // Copy the whole array
    _count=files.Length;  // Remember the amount of files
                          // Everywhere in the rest of the code,
                          // be sure to use _count instead of 4
                          // and _count-1 instead of 3

Usage of the Initialize becomes something like this:

mRecent.Initialize(new string[] {"File 1", "File 2", "File 3", "File 4"});

But other sizes are now also possible.

License

This article has no explicit license attached to it, but may contain usage terms in the article text or the download files themselves. If in doubt, please contact the author via the discussion board below.

A list of licenses authors might use can be found here.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here