Advertisement:

Skystone Software

http://www.SkystoneSoftware.com

.NET Programming

Implementing an MRU (Most Recently Used) List
Published: 2/9/2007

Overview

Any application that allows a user to edit files would not be complete without an MRU (Most Recently Used) file list, usually located right above the Exit menu item on the File menu. Maintaining such a list, however, can be a tricky process because of the complications introduced by the requirements that the items appear in order from most recently used to least recently used and that each item only appear once in the list. This article will walk developers through creating a simple text file reader that maintains an MRU using a collection to maintain the list while the program is running, and a text file to maintain it on disk.

NOTE: The sample code was written in Visual Studio .NET 2005, and utilize code unavailable in previous versions (in the System.Collections.Generic and System.IO.File namespaces). The concept applies to any programming language, but you must have VS 2005 (or VB .NET 2005 Express) in order to run the code.

Getting Started
Our application is made up of a single form (frmMain) with a TextBox (txtFile) and a MainMenu (mnuMain) control on it. The MainMenu control has 4 menu items, mnuOpen, mnuSep1 (separator), mnuSep2 (separator), and mnuExit. mnuSep1 is hidden (mnuSep1.Visible = False). Our MRU will be displayed to the user between the two separator menu items.

The following code handles the Open and Exit menu click events:

    Private Sub mnuFileOpen_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles mnuFileOpen.Click 
        ' prompt for a text file to open... 
        Using clsPrompt As New System.Windows.Forms.OpenFileDialog() 
            With clsPrompt 
                .Title = "Select File" 
                .Filter = "Text Files (*.txt)/*.txt" 
                If .ShowDialog() = Windows.Forms.DialogResult.OK Then 
                    ' pass the path to the function responsible for 
                    ' opening files... 
                    OpenFile(.FileName) 
                End If 
            End With 
        End Using 
    End Sub 

    Private Sub mnuFileExit_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles mnuFileExit.Click 
        Me.Close() 
    End Sub 

    Private Sub OpenFile(ByVal Path As String) 
        ' open file - add contents to TextBox... 
        Me.txtFile.Text = System.IO.File.ReadAllText(Path) 
        ' add the item to the MRU (Most Recently Used) list... 
        AddToMRU(Path) 
    End Sub 

To store our MRU list while the program is running we will declare a module-level variable called "m_clsMRU" of type "List(Of String)":

Public Class frmMain 

    ' declare a variable to contain the MRU... 
    Private m_clsMRU As New System.Collections.Generic.List(Of String) 

NOTE: The use of the Generic type "List(Of String)" allows us to refer directly to items in the list as String variables, rather than having to typecast them. An ArrayList would provide the same collection functionality, but would require items to be typecast when accessed.

Maintaining the MRU List in Memory
The above code refers to a routine called "AddToMRU". This routine is quite simple, and merely adds the specified path to the MRU list:

    Private Sub AddToMRU(ByVal Path As String) 
        ' remove the item from the collection if exists so that we can 
        ' re-add it to the beginning... 
        If m_clsMRU.Contains(Path) Then m_clsMRU.Remove(Path) 
        ' add to MRU list.. 
        m_clsMRU.Add(Path) 
        ' make sure there are only ever 5 items... 
        While m_clsMRU.Count > 5 
            m_clsMRU.RemoveAt(0) 
        End While 
        ' update UI.. 
        UpdateMRU() 
    End Sub 

Note that if the item already exists in the list it is removed first; essentially moving it to the top of the list. The list is also kept to a maximum of 5 items (which is standard for more MRU lists).

Once the list is updated, a routine called "UpdateMRU" is called. This routine takes the list and renders it visually to the user (creating menu items between the two separator menu items on the File menu). This routine looks like this:

    Private Sub UpdateMRU() 
        ' clear MRU menu items... 
        Dim clsItems As New System.Collections.Generic.List(Of ToolStripItem) 
        ' create a temporary collection containing every MRU menu item 
        ' (identified by the tag text when added to the list)... 
        For Each clsMenu As ToolStripItem In mnuFile.DropDownItems 
            If Not clsMenu.Tag Is Nothing Then 
                If (clsMenu.Tag.ToString().StartsWith("MRU:")) Then 
                    clsItems.Add(clsMenu) 
                End If 
            End If 
        Next 
        ' iterate through list and remove each from menu... 
        For Each clsMenu As ToolStripItem In clsItems 
            mnuFile.DropDownItems.Remove(clsMenu) 
        Next 
        ' display items (in reverse order so the most recent is on top)... 
        For iCounter As Integer = m_clsMRU.Count - 1 To 0 Step -1 
            Dim sPath As String = m_clsMRU(iCounter) 
            ' create new ToolStripItem, displaying the name of the file... 
            Dim clsItem As New ToolStripMenuItem(System.IO.Path.GetFileName(sPath)) 
            ' set the tag - identifies the ToolStripItem as an MRU item and 
            ' contains the full path so it can be openened later... 
            clsItem.Tag = "MRU:" & sPath 
            ' hook into the click event handler so we can open the file later... 
            AddHandler clsItem.Click, AddressOf mnuFileMRU_Click 
            ' insert into DropDownItems list... 
            mnuFile.DropDownItems.Insert(mnuFile.DropDownItems.Count - 2, clsItem) 
        Next 
        ' show separator... 
        mnuFileSep1.Visible = True 
    End Sub 

The first thing the routine does is to collect a list of all of the MRU menu items already in the list. These items are identified in our program by a Tag property that contains a String starting with "MRU:", and are collected in a generic List(Of ToolStripItem). Once all items are collected, the temporary list is iterated through and each item is removed from the File menu. This effectively clears the displayed MRU list so that a new one can be added.

Once the list is cleared, the MRU list (m_clsMRU) is iterated through backwards (so that the most recetly-used item is at the top of the list, visually) and a ToolStripItem is created for each path in the list. Using the AddHandler statement, each ToolStripItem's Click event is hooked into an event handler (see below) that will open the file associated with the MRU item. The items are then added to the File menu at the third-to-last position in the list, putting them immediately before the last separator before the Exit menu.

In order to make the MRU menu items functional, the following event handler must be declared:

    Private Sub mnuFileMRU_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) 
        ' open the file... 
        Me.OpenFile(DirectCast(sender, ToolStripItem).Tag.ToString().Substring(4)) 
    End Sub 

This routine retrieves the path to the MRU item from the Tag property of the MenuItem and calls the OpenFile routine to process the file.

Maintaining the MRU List on Disk
So that the MRU perists on the File menu even after the application is closed and restarted, it must also be kept on disk. In this example, the MRU list will be loaded from disk in the form Load event, and saved to disk in the FormClosing event.

NOTE: To prevent lost changes in the event of an application crash, you may consider saving the MRU to disk every time it changes rather than on the FormClosing event.

To save the MRU to disk, use the following code:

    Protected Overrides Sub OnFormClosing(ByVal e As System.Windows.Forms.FormClosingEventArgs) 
        'enable events... 
        MyBase.OnFormClosing(e) 
        ' save MRU - delete existing file... 
        If System.IO.File.Exists(MRUPath) Then System.IO.File.Delete(MRUPath) 
        ' write each item to the file... 
        For Each sPath As String In m_clsMRU 
            System.IO.File.AppendAllText(MRUPath, sPath & vbCrLf) 
        Next 
    End Sub 

...and to load the MRU from disk, use the following code:

    Protected Overrides Sub OnLoad(ByVal e As System.EventArgs) 
        ' enable events... 
        MyBase.OnLoad(e) 
        ' load MRU... 
        If (System.IO.File.Exists(MRUPath)) Then 
            ' read file into array... 
            Dim sPaths() As String = System.IO.File.ReadAllLines(MRUPath) 
            ' work through each item... 
            For Each sPath As String In sPaths 
                ' only add items that are not empty... 
                If Not String.IsNullOrEmpty(sPath) Then 
                    ' only add files that still exist... 
                    If System.IO.File.Exists(sPath) Then 
                        ' add to mru... 
                        m_clsMRU.Add(sPath) 
                    End If 
                End If 
            Next 
        End If 
        ' display MRU if there are any items to display... 
        If m_clsMRU.Count > 0 Then UpdateMRU() 
    End Sub 

Note that the code that loads the MRU will only add items that are still present on disk to the list. This is an optional practice, as your file open code should check to see if the file exists before opening and display a warning to the user if they are no longer available.

Summary This article contains code to maintain an ordered MRU list in memory as well as on disk in order to persist it across application uses. By maintaining a List of recently-used paths in memory and dynamically creating menu items from that list when it changes, the list can be easily worked with and reordered as it changes, providing the functionality that the user expects from a standard MRU.

NOTE: If you implement this code in your application, be sure to add appropriate error handling; no error handling was used in the example code for the sake of simplicity.



Written by Scott Waletzko for Skystone Software.
Copyright 2005-2007 by Echosoft Design Studios, LLC, All Rights Reserved.