Click here to Skip to main content
Click here to Skip to main content

Add Most Recently Used Files (MRU) List to Windows Applications

By , 6 Feb 2003
 

Sample Image

Introduction

The Most Recently Used (MRU) files list is standard feature of most Windows applications. This article describes how to add MRU support to Windows application using the class MRUManager. This class is included in the demo project. It may be added to your C# project or compiled in DLL and used by developers working with any .NET language. While writing this class I tried to reproduce MFC's CRecentFileList class behaviour.

Using the MRUManager Class

The starting point is a simple image viewer application which has File - Open menu and shows an image file in the form's client area. When user selects the Open menu item, the program shows the Open File dialog:

private void mnuFileOpen_Click(object sender, System.EventArgs e)
{
    OpenFileDialog openDlg = new OpenFileDialog();
    openDlg.Filter  = "Jpeg files (*.jpg)|*.jpg|Bitmap files (*.bmp)|*.bmp|All Files (*.*)|*.*";

    openDlg.FileName = "" ;
    openDlg.CheckFileExists = true;
    openDlg.CheckPathExists = true;

    if ( openDlg.ShowDialog() != DialogResult.OK )
        return;

    OpenFile(openDlg.FileName);
}

The method OpenFile loads the image file into the class member Bitmap bitmap, and the Paint event handler shows this bitmap on the screen:

private Bitmap bitmap;

private void OpenFile(string fileName)
{
    Bitmap bmp;

    try
    {
        bmp = (Bitmap)Bitmap.FromFile(fileName, false);

        if ( bmp != null )
        {
            bitmap = (Bitmap) bmp.Clone();

            this.AutoScroll = true;
            this.AutoScrollMinSize = new Size (bitmap.Width, bitmap.Height);
            
            Invalidate();
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show(this, ex.Message, "Error loading from file");
    }
}

private void Form1_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
    if ( bitmap != null )
    {
        e.Graphics.DrawImage(
            bitmap, 
            new Rectangle(this.AutoScrollPosition.X, 
            this.AutoScrollPosition.Y, 
            bitmap.Width, 
            bitmap.Height));
    }
}

To add MRU support to this application, we need to take the following steps:

1. Add a Recent Files menu item to the application menu:


Sample Image

In the demo project this item is called mnuFileMRU.

2. Implement IMRUClient interface in the form. This interface is defined in the same file as MRUManager:

public interface IMRUClient
{
    void OpenMRUFile(string fileName);
}

Implementation:

public class Form1 : System.Windows.Forms.Form, IMRUClient
{
    public void OpenMRUFile(string fileName)
    {
        OpenFile(fileName);
    }
    
    // ...
}

3. Add an MRUManager member to the form class and initialize it:

private MRUManager mruManager;

private void Form1_Load(object sender, System.EventArgs e)
{
    mruManager = new MRUManager();
    mruManager.Initialize(this, mnuFileMRU, registryPath);

    // Optional:
    // mruManager.CurrentDir = ".....";        // default is current directory
    // mruManager.MaxMRULength = ...;          // default 10
    // mruMamager.MaxDisplayNameLength = ...;  // default 40
}

MRUManager is initialized with a reference to the owner form, Recent Files menu item and registry path to store the MRU. If the registry path is, for example, "Software\MyCompany\MyProgram", the MRU list is stored in the HKEY_CURRENT_USER\Software\MyCompany\MyProgram\MRU registry entry.

Additional properties (CurrentDir, MaxMRULength, MaxDisplayNameLength) may be optionally changed.

  • CurrentDir - the default value is the program's current directory at the time when the Initialize method is called. Files in this directory are shown in the menu without a path.
  • MaxMRULength - the default value is 10. This is the maximum number of files stored in MRU list.
  • MaxDisplayNameLength - the default value is 40. Long file names are truncated to this length in the menu.

4. Call MRUManager.Add() and Remove methods when necessary:

private void OpenFile(string fileName)
{
    Bitmap bmp;

    try
    {
        // ...

        // add successfully opened file to MRU list
        mruManager.Add(fileName);
    }
    catch (Exception ex)
    {
        // remove file from MRU list
        mruManager.Remove(fileName);

        // ...
    }

}

Add is called when file is open successfully. Remove is called when Open method failed.

The program is ready. Run it and see how it works. When the program runs the first time and the MRU list is empty, the Recent Files menu item is disabled. When the file is opened, its name is added to the MRU list. If a user opens the file which is already in the MRU list, the file name is moved to the first position.

The MRUManager Class Implementation

The class has the following members:

private Form ownerForm;                 // owner form
private MenuItem menuItemMRU;           // Recent Files menu item
private MenuItem menuItemParent;        // Recent Files menu item parent
private string registryPath;            // Registry path to keep MRU list
private int maxNumberOfFiles = 10;      // maximum number of files in MRU list
private int maxDisplayLength = 40;      // maximum length of file name for display
private string currentDirectory;        // current directory
private ArrayList mruList;              // MRU list (file names)
private const string regEntryName = "file";  // entry name to keep MRU (file0, file1...)

The Initialize method fills class members and subscribes to the Recent Files parent's Popup and owner form's Closing events:

public void Initialize(Form owner, MenuItem mruItem, string regPath)
{
    ownerForm = owner;

    // check if owner form implements IMRUClient interface
    if ( ! ( owner is IMRUClient ) )
    {
        throw new Exception("MRUManager: Owner form doesn't implement IMRUClient interface");
    }

    // keep reference to MRU menu item
    menuItemMRU = mruItem;

    // keep reference to MRU menu item parent
    try
    {
        menuItemParent = (MenuItem) menuItemMRU.Parent;
    }
    catch
    {
    }

    if ( menuItemParent == null )
    {
        throw new Exception("MRUManager: Cannot find parent of MRU menu item");
    }

    // keep Registry path adding MRU key to it
    registryPath = regPath;
    if ( registryPath.EndsWith("\\") )
         registryPath += "MRU";
    else
        registryPath += "\\MRU";

    // keep current directory in the time of initialization
    currentDirectory = Directory.GetCurrentDirectory();

    // subscribe to MRU parent Popup event
    menuItemParent.Popup += new EventHandler(this.OnMRUParentPopup);

    // subscribe to owner form Closing event
    ownerForm.Closing += new System.ComponentModel.CancelEventHandler(OnOwnerClosing);

    // load MRU list from Registry
    LoadMRU();
}

The LoadMRU method reads the MRU list from registry. The OnOwnerClosing method is called when the owner form is closed and saves the MRU list to the registry.

OnMRUParentPopup is called when the Recent Files menu item parent is opened. This method fills the MRU menu list:

private void OnMRUParentPopup(object sender, EventArgs e)
{
    // remove all childs
    if ( menuItemMRU.IsParent )
        menuItemMRU.MenuItems.Clear();
 
    // Disable menu item if MRU list is empty
    if ( mruList.Count == 0 )
    {
        menuItemMRU.Enabled = false;
        return;
    }

    // enable menu item and add child items
    menuItemMRU.Enabled = true;

    MenuItem item;
    IEnumerator myEnumerator = mruList.GetEnumerator();

    while ( myEnumerator.MoveNext() )
    {
        item = new MenuItem(GetDisplayName((string)myEnumerator.Current));

        // subscribe to item's Click event
        item.Click += new EventHandler(this.OnMRUClicked);

        menuItemMRU.MenuItems.Add(item);
    }
}

The GetDisplayName method gets the short file name from the full file name. If the file is in the current directory, it is shown without a path. The file name is also truncated to the maximum allowed length using the Shell API's PathCompactPathEx function (thanks to CodeProject and GotDotNet C# expert Richard Deeming for this solution).

The OnMRUClicked method is called when the user clicks selects one of the MRU menu items. It calls the owner form's OpenMRUFile method:

private void OnMRUClicked(object sender, EventArgs e)
{
    string s;

    try
    {
        MenuItem item = (MenuItem) sender;

        if ( item != null )
        {
            s = (string)mruList[item.Index];

            // call owner's OpenMRUFile function
            if ( s.Length > 0 )
            {
                ((IMRUClient)ownerForm).OpenMRUFile(s);
            }
        }
    }
    catch ( Exception ex )
    {
        Trace.WriteLine("Exception in OnMRUClicked: " + ex.Message);
    }
}

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Alex Fr
Software Developer
Israel Israel
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralMy vote of 3memberashish_ips0719 Jun '12 - 23:11 
nice artical
QuestionAre the MRUs registered in a way that Windows can recognize and work with?memberDannyStaten25 Apr '11 - 17:05 
Windows 7 in particular has some really cool features with recent files where you can right click on the icon in the task bar and it shows recently used files. It lets you pin them etc. How compatible is this with that ability? I am hoping to find a solution that works in the registry in a way so that it can leverage some of those nice features in Windows 7.
AnswerRe: Are the MRUs registered in a way that Windows can recognize and work with?memberAlex Fr26 Apr '11 - 7:17 
No, MRU list is kept in the private program Registry key. This article was written in 2003...
My guess is that this Win7 feature is based on association between a file extension and program name, but I don't know exactly.
GeneralThank You!memberMyCodeWorks1 Dec '09 - 9:30 
Thanks for a great article Smile | :)
GeneralPathCompactPathEx replacementmemberMatthew Pilgrim8 Sep '09 - 23:21 
Thanks for the code (and the .Net 2 corrections). If you like to avoid API calls then you can replace the GetShortDisplayName procedure with the one below. You'll need to convert to c# though Wink | ;) ...
 
Private Function GetShortDisplayName(ByVal longName As String, ByVal maxLen As Integer) As String
 
            GetShortDisplayName = longName 'default

            Dim strFileName As String = System.IO.Path.GetFileName(longName)
            Dim idiff As Integer = maxLen - (strFileName.Length + 4)
            If idiff > 1 Then
                GetShortDisplayName = Left(longName, idiff) & "...\" & strFileName
            Else
                If maxLen < 4 Then
                    GetShortDisplayName = Left(strFileName, maxLen)
                Else
                    GetShortDisplayName = Left(strFileName, maxLen - 3) & "..."
                End If
 
            End If
End Function
 
Matt
 
modified on Wednesday, September 9, 2009 5:42 AM

QuestionAbout licensememberkimikazu12 Aug '07 - 21:28 
Thank you for very useful code.
 
I am thinking of use this code as a part of our software which is open source.
Is it allowed?
 
Anyway, I would like to know about its license.
GeneralCleaning MRU from registrymembergeff_chang4 Jun '07 - 23:24 
How would I go about removing the entries from the registry,
if i wanted to uninstall my software?
QuestionFind all applications?memberGerhard200718 Mar '06 - 8:16 
Thanks for writing this code.
Unfortunately I did not succeed in listing istalled applications like WORD, EXCEL, etc. The only thing I saw was "Office".
Frown | :-(
 
Well, does anybody have an idea how to get the applications listed, like they are in the Windows Application lister (dialog)?
 
Best regards,
 
Big Grin | :-D
 
Gerhard
General.NET 2.0 compatiblitymembergdmang28 Dec '05 - 7:59 
Hello *
 
Just in case anybody needs this too: I modified the great work from Alex to support the new ToolStripMenuItem class used in VS Studio 2005 instead of the MenuItem class.
 
The changes in MRUManager.cs are (the linenumber is shown before the line):
 
106: private ToolStripMenuItem menuItemMRU;
107: private ToolStripMenuItem menuItemParent;
230: public void Initialize(Form owner, ToolStripMenuItem mruItem, string regPath)
248: menuItemParent = (ToolStripMenuItem) menuItemMRU.Parent;
272: menuItemParent.DropDownOpening += new EventHandler (this.OnMRUParentPopup);
334: if ( menuItemMRU.HasDropDownItems)
335: menuItemMRU.DropDownItems.Clear();
349: ToolStripMenuItem item;
355: item = new ToolStripMenuItem(GetDisplayName((string)myEnumerator.Current));
360: menuItemMRU.DropDownItems.Add(item);
375: // cast sender object to ToolStripMenuItem
376: ToolStripMenuItem item = (ToolStripMenuItem) sender;
381: s = item.Text;
 


 
Guenther
AnswerRe: .NET 2.0 compatiblitymembergdmang28 Dec '05 - 10:38 
The above solution has a problem, if the filename is truncated in the menu. I corrected this via Tooltips, which adds convenience for the user:
 
Line 355-:
                        string fileName = (string)myEnumerator.Current;
                        item = new ToolStripMenuItem(GetDisplayName(fileName));
                        item.ToolTipText=fileName;
 
Line 382:
                              // Get file name from list using item index
                              s = item.ToolTipText;
 

GeneralRe: .NET 2.0 compatiblitymemberwatterman12 Apr '07 - 20:23 
Indeed is great work. Thanks to Alex.
One change I had to make to Guenthers mods (also very useful thankyou) was:
 
248: menuItemParent = (ToolStripMenuItem) menuItemMRU.Parent;
 
Change to:
 
248: menuItemParent = (ToolStripMenuItem) menuItemMRU.OwnerItem;
 
otherwise would get compile error: "Parent is inaccessible due to protection level"
 
Thanks for the very useful class.
regards,
Adrian.
GeneralRe: .NET 2.0 compatiblitymemberSteve_Harris13 May '08 - 4:48 
Nice mods all!
GeneralRe: .NET 2.0 compatiblitymemberLPlateDeveloper15 Aug '08 - 22:10 
Big thanks to the author and others for posting, thank you.
GeneralRe: .NET 2.0 compatiblitymemberAndy Quay20 Jan '10 - 7:58 
Works great with C++/CLI too when compiled into a DLL.
GeneralRe: .NET 2.0 compatiblitymemberProshuto23 Sep '12 - 2:52 
Works great with .NET 3.5 as well. Many thanks to all the contributors.
GeneralTrouble with MDI Child Forms and Merged MenusmemberCraig Eddy7 Oct '03 - 4:03 
I have a project that uses MDI and one of the child forms has its own menus which get merged when the form is loaded. Once this happens, the recent files stuff causes the application to crash whenever the parent menu of the menu item being managed by the mruManager is opened.
 
I've had this problem with other MRU menu manager classes as well. Any clues? Unfortunately it seems to be deep in the System.Forms assembly that the error occurs and there's no way to trace it.
 
Thanks,
Craig
GeneralClass LibrarymemberJerry Maguire7 Feb '03 - 5:55 
Hi,
 
Nice work, this feature you describe is also implemented in a greater context at article
 
.:Greets from Jerry Maguire:.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web02 | 2.6.130523.1 | Last Updated 7 Feb 2003
Article Copyright 2003 by Alex Fr
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid