Build an MRU list in C#

[MRU list]

An MRU list (most recently used file list) displays in the File menu the files that a program has used most recently. If the user selects a file from the menu, the program reopens that file.

This example builds an MruList class that a program can use to make providing an MRU list easier. This example is fairly involved but many of its pieces are interesting so I’m going to describe them all, divided into the following sections.


Initialization

The following code shows variables that the MruList class uses to maintain its list of file names.

// The application's name.
private string ApplicationName;

// A list of the files.
private int NumFiles;
private List<FileInfo> FileInfos;

// The File menu.
private ToolStripMenuItem MyMenu;

// The menu items we use to display files.
private ToolStripSeparator Separator;
private ToolStripMenuItem[] MenuItems;

// Raised when the user selects a file from the MRU list.
public delegate void FileSelectedEventHandler(FileInfo file_info);
public event FileSelectedEventHandler FileSelected;

The ApplicationName variable holds the application’s name. This is used to store file information in the system Registry.

The NumFiles variable holds the number of files that the list can hold, not the number of files that it is actually holding at the time. Most programs set this value to 4 so the MRU list can hold at most 4 files.

The FileInfos list contains FileInfo objects describing the files currently in the list.

I use FileInfo objects because they easily store a file’s full path and short file name. You could use some other type of object instead if you wanted the MRU list to represent something other than files. For example, if you want to display a list of recently used engineering diagrams, you might want this list to hold objects that store the diagrams’ titles, file locations, and other data.

The MyMenu variable holds a reference to the program’s menu that should display the list. Normally that’s the program’s File menu. The Separator and MenuItems variables hold references to the menu items created by the MruList.

The FileSelectedEventHandler delegate defines the type of the FileSelected event that the MruList raises when the user selects a file from the list.

The following code shows the class’s constructor, which initializes the list.

// Constructor.
public MruList(ToolStripMenuItem menu, int num_files)
{
    ApplicationName = application_name;
    MyMenu = menu;
    NumFiles = num_files;
    FileInfos = new List<FileInfo>();

    // Make a separator.
    Separator = new ToolStripSeparator();
    Separator.Visible = false;
    MyMenu.DropDownItems.Add(Separator);

    // Make the menu items we may later need.
    MenuItems = new ToolStripMenuItem[NumFiles + 1];
    for (int i = 0; i < NumFiles; i++)
    {
        MenuItems[i] = new ToolStripMenuItem();
        MenuItems[i].Visible = false;
        MyMenu.DropDownItems.Add(MenuItems[i]);
    }

    // Reload items from the registry.
    LoadFiles();

    // Display the items.
    ShowFiles();
}

The constructor saves the application’s name, the menu that should hold the MRU list items, and the number of items that the list can hold. It then creates a new FileInfos list.

Next, the constructor creates the separator and menu items that it might need to display later. It finishes by calling LoadFiles to load saved files from the Registry and ShowFiles to display the correct menu items.

The following code shows the LoadFiles method.

// Load saved items from the Registry.
private void LoadFiles()
{
    // Reload items from the registry.
    for (int i = 0; i < NumFiles; i++)
    {
        string file_name = (string)RegistryTools.GetSetting(
            ApplicationName, "FilePath" + i.ToString(), "");
        if (file_name != "")
        {
            FileInfos.Add(new FileInfo(file_name));
        }
    }           
}

The LoadFiles method uses the RegistryTools class’s GetSetting method to load file names stored in the Registry. The following code shows the GetSetting method.

// Get a value.
public static object GetSetting(string app_name, string name,
    object default_value)
{
    RegistryKey reg_key =
        Registry.CurrentUser.OpenSubKey("Software", true);
    RegistryKey sub_key = reg_key.CreateSubKey(app_name);
    return sub_key.GetValue(name, default_value);
}

The GetSetting method opens the current user’s Software hive, gets a subkey named after the application, and then gets the desired setting, which in this case is a file path.

After the constructor has loaded saved file information from the Registry, it calls the following ShowFiles method to prepare the menu items for use.

// Display the files in the menu items.
private void ShowFiles()
{
    Separator.Visible = (FileInfos.Count > 0);
    for (int i = 0; i < FileInfos.Count; i++)
    {
        MenuItems[i].Text = string.Format("&{0} {1}",
            i + 1, FileInfos[i].Name);
        MenuItems[i].Visible = true;
        MenuItems[i].Tag = FileInfos[i];
        MenuItems[i].Click -= File_Click;
        MenuItems[i].Click += File_Click;
    }
    for (int i = FileInfos.Count; i < NumFiles; i++)
    {
        MenuItems[i].Visible = false;
        MenuItems[i].Click -= File_Click;
    }

    // Update the Registry.
    SaveFiles();
}

The ShowFiles method starts by displaying the separator if there are any files in the list. It then loops through the FileInfo objects in the FileInfos list. For each object, it sets the corresponding menu item’s Text, Visible, and Tag properties. It also attaches the File_Click event handler to the menu item’s Click event. (It first removes any previously installed event handler so the event handler isn’t installed twice.)

The code then hides any menu items that are not needed.

The following code shows the SaveFiles method that saves the current file list in the Registry.

// Save the current items in the Registry.
private void SaveFiles()
{
    // Delete the saved entries.
    for (int i = 0; i < NumFiles; i++)
    {
        RegistryTools.DeleteSetting(ApplicationName,
            "FilePath" + i.ToString());
    }

    // Save the current entries.
    int index = 0;
    foreach (FileInfo file_info in FileInfos)
    {
        RegistryTools.SaveSetting(ApplicationName,
            "FilePath" + index.ToString(), file_info.FullName);
        index++;
    }
}

The SaveFiles method deletes any existing Registry entries and then saves the current files’ full paths.


Adding and Removing Files

When the main program opens a file or saves a newly created file, it should call the MruLists object’s AddFile method shown in the following code.

// Add a file to the list, rearranging if necessary.
public void AddFile(string file_name)
{
    // Remove the file from the list.
    RemoveFileInfo(file_name);

    // Add the file to the beginning of the list.
    FileInfos.Insert(0, new FileInfo(file_name));

    // If we have too many items, remove the last one.
    if (FileInfos.Count > NumFiles) FileInfos.RemoveAt(NumFiles);

    // Display the files.
    ShowFiles();

    // Update the Registry.
    SaveFiles();
}

This method calls the RemoveFileInfo method to remove the file from the FileInfos list and then inserts the new file at the beginning of the list. This prevents the list from containing duplicates. Next, if the list contains too many files, the code removes the last item. The method finishes by calling ShowFiles to update the menu items and SaveFiles to save the current list in the Registry.

The following code shows the RemoveFileInfo method.

// Remove a file's info from the list.
private void RemoveFileInfo(string file_name)
{
    // Remove occurrences of the file's information from the list.
    for (int i = FileInfos.Count - 1; i >= 0; i--)
    {
        if (FileInfos[i].FullName == file_name)
            FileInfos.RemoveAt(i);
    }
}

This method loops through the file entries from back to front. If it finds an entry with the target file name, it removes that entry. (The code searches the list from back to front so removing an item doesn’t renumber the remaining items.) Note that the code cannot simply use the list’s Remove method because that method only looks for matching FileInfo objects not objects that are different but that represent the same file.

The RemoveFile method shown in the following code removes a file from the MRU list. The main program should call this method whenever it needs to remove a file from the list. For example, if the program tries to open a file and fails, most programs remove that file from the MRU list. (Strangely, some programs such as Visual Studio prompt the user to ask if it should remove the non-existent project from the MRU list. I guess they’re assuming that the project may magically reappear later.)

// Remove a file from the list, rearranging if necessary.
public void RemoveFile(string file_name)
{
    // Remove the file from the list.
    RemoveFileInfo(file_name);

    // Display the files.
    ShowFiles();

    // Update the Registry.
    SaveFiles();
}

This method calls RemoveFileInfo to remove the file from the list. It then calls ShowFiles to update the menu items and SaveFiles to update the Registry.


Handling Events

When the user selects a file from the MRU list, the following File_Click event handler executes.

// The user selected a file from the menu.
private void File_Click(object sender, EventArgs e)
{
    // Don't bother if no one wants to catch the event.
    if (FileSelected != null)
    {
        // Get the corresponding FileInfo object.
        ToolStripMenuItem menu_item = sender as ToolStripMenuItem;
        FileInfo file_info = menu_item.Tag as FileInfo;

        // Raise the event.
        FileSelected(file_info.FullName);
    }
}

This method simply raises the FileSelected event, passing it the FileInfo object representing the selected file. The program should then reload that file or do whatever is appropriate for the application.


Using MruList

The example program uses the following code to define and initialize its MruList variable.

// The MruList.
MruList MyMruList;

// Make the MruList.
private void Form1_Load(object sender, EventArgs e)
{
    MyMruList = new MruList("howto_mru_list", mnuFile, 4);
    MyMruList.FileSelected += MyMruList_FileSelected;
}

The program defines the variable MyMruList at the form level so all of its methods can use it. The form’s Load event handler initializes the MruList and registers the MyMruList_FileSelected event handler to handle the list’s FileSelected event.

The following code shows how the program tries to open a file.

// Open a file and add it to the MRU list.
private void OpenFile(string file_name)
{
    try
    {
        // Load the file.
        rchFile.Clear();
        if (file_name.ToLower().EndsWith(".rtf"))
        {
            rchFile.LoadFile(file_name);
        }
        else
        {
            rchFile.Text = File.ReadAllText(file_name);
        }

        // Add the file to the MRU list.
        MyMruList.AddFile(file_name);
    }
    catch (Exception ex)
    {
        // Remove the file from the MRU list.
        MyMruList.RemoveFile(file_name);

        // Tell the user what happened.
        MessageBox.Show(ex.Message);
    }
}

If the file’s name ends with .rtf, the program uses its RichTextBox control’s LoadFile method to load the file as an RTF file. If the name doesn’t end in .rtf, the program loads the file as text. If it successfully loads the file, the program calls the MruList object’s AddFile method to add the file to the MRU list.

If the program fails to load the file, it calls the MruList object’s RemoveFile method to remove the file from the MRU list if it is present. It then tells the user that there was a problem.

If the user selects a file from the MRU list, the MruList object raises its FileSelected event and the following event handler executes.

// Open a file selected from the MRU list.
private void MyMruList_FileSelected(string file_name)
{
    OpenFile(file_name);
}

This code simply calls the previous OpenFile method to open the selected file.

This program doesn’t have Save or Save As commands, but if it did they would call the MruList object’s AddFile method whenever the program saved a new or renamed file.


Download Example   Follow me on Twitter   RSS feed   Donate




This entry was posted in files, programs, registry and tagged , , , , , , , , , , , , , . Bookmark the permalink.

3 Responses to Build an MRU list in C#

  1. V Hellin says:

    Thanks for the code.
    Don’t you forget a parameter of type string called application_name for the constructor ?

  2. Rod Stephens says:

    Sorry about that. I’m not sure how that declaration got removed from the text. It is in the downloadable example.

  3. Pingback: Build an MRU list that uses project settings in C# - C# HelperC# Helper

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.