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.
MRUMenuItem mRecent=new MRUMenuItem();
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;
mParent.FileOpened(mSender.Text);
}private void MRUChanged(object sender, System.EventArgs e) {
MRUMenuItem m=sender as MRUMenuItem;
}
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:
string[] mru;
int _count;public void Initialize(string[] files) {
mru=files; _count=files.Length;
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