Click here to Skip to main content
13,559,658 members
Click here to Skip to main content
Add your own
alternative version


31 bookmarked
Posted 18 Feb 2008
Licenced CPOL

RecentFileList: a WPF MRU

, 20 Feb 2008
Rate this:
Please Sign up or sign in to vote.
A Most Recently Used menu item control for WPF applications

Table of Contents


Although this is an article for use in WPF applications, it includes exactly two snippets of xaml and those are for the example usage. Please don't expect fancy data binding or clever style triggers. It just works.


There are some development tasks that are repeated for many new applications. Implementing a MRU, or recent file list, is common to most document-based applications. It's not rocket science and it's been done before. So I've written a control that you can just plug in so you can get on with the interesting new stuff.

I have tried to make the code as easy to use as possible. As a minimum, you can add just two lines of xaml and a few lines of code. However, it is configurable. For instance, with one more line of code you can persist to an xml file instead of the registry. You can also fully specify the text displayed in your File menu by implementing a callback method.

The code is all in one file: RecentFileList.cs. If you're using C#, you can just add this file to your project. In the attached source and binaries, I have wrapped the file in a library, so you could reference that instead. I have also included a demo application that exercises the class. It doesn't actually create files, it is just a simulation.


CPian Joe Woodbury [^] in his article Most Recently Used (MRU) Menu Class for .NET 2.0 in C# [^] provides a fine implementation for Windows Forms. I have taken one of his functions, that shortens long file paths, but the rest of this article is new.


This section describes what you must do to get a working Recent File List. The rest of the article describes what you can do.

Firstly, either include the file RecentFileList.cs, or reference the assembly RecentFileListLib.dll.

Then add the Common namespace to your Window:



   xmlns:common="clr-namespace:Common; assembly=RecentFileListLib"



And add the control to your File menu. It will render as a Separator. The recommended place is just above your Exit menu item.

<MenuItem Header="_File">
   <common:RecentFileList x:Name="RecentFileList" />
   <MenuItem Header="E_xit" ... />

Then in your code, hook the MenuClick event.

partial class Window1
   public Window1()
      RecentFileList.MenuClick += ( s, e ) => FileOpenCore( e.Filepath );

And then you can call two methods. Call InsertFile whenever a file is successfully opened or saved. Call RemoveFile whenever a file fails to open.

partial class RecentFileList
   public void InsertFile( string filepath )
   public void RemoveFile( string filepath )

And that's it. You now have a working Recent File List that persists to the Registry under HKCU \ Software \ <CompanyName> \ <ProductName> \ RecentFileList


Base class

The main class, RecentFileList, derives from Separator. This means that when the list is empty, only the base Separator is rendered. When some files have been inserted, MenuItem's are added along with a closing Separator.

using System.Windows.Controls;

partial class RecentFileList : Separator


The RecentFileList class handles all the logic, but relies on an implementation of IPersist to handle storage using one of my favourite design patterns: Strategy.

partial class RecentFileList
   public interface IPersist
      List<string> RecentFiles( int max );
      void InsertFile( string filepath, int max );
      void RemoveFile( string filepath, int max );
   public IPersist Persister { get; set; }

Two implementations of IPersist are provided. The default is the RegistryPersister and the other is the XmlPersister.


RecentFileList hooks its own Loaded event. When this fires, it finds its parent ( which must be a MenuItem ) and hooks its SubmenuOpened event. This event fires when the menu is opening and this is when the extra MenuItem's are added.


RecentFileList exposes one event: MenuClick. This fires when one of the MenuItems is clicked and just passes the filepath to any Observers.

partial class RecentFileList
   public event EventHandler<MenuClickEventArgs> MenuClick;

Using the Code


You can set the maximum number of files to list:

partial class RecentFileList
   public int MaxNumberOfFiles { get; set; } // default = 9


Here are the members that control which implementation of IPersist is used:
partial class RecentFileList
   public IPersist Persister { get; set; }
   public void UseRegistryPersister()
   public void UseRegistryPersister( string key )
   public void UseXmlPersister()
   public void UseXmlPersister( string filepath )
   public void UseXmlPersister( Stream stream )

The RegistryPersister is used by default. You can provide your own implementation of IPersist, or use the methods to select and configure one of the existing implementations. The RegistryPersister uses the key: HKCU \ Software \ <CompanyName> \ <ProductName> \ RecentFileList by default, but you can override this by providing your own key. The XmlPersister uses the file: <Environment.SpecialFolder.ApplicationData> \ <CompanyName> \ <ProductName> \ RecentFileList.xml by default, but again you can provide your own filepath. You can also provide a Stream and do what you like with the XML. The Stream must be readable, writable and seekable, so you would most probably use a MemoryStream.

Display format

There are a number of ways to control the text displayed in the MenuItems:

partial class RecentFileList
   public int MaxPathLength { get; set; } // default = 50
   public static string ShortenPathname( string pathname, int maxLength )
   public string MenuItemFormatOneToNine { get; set; } // default = "_{0} {2}"
   public string MenuItemFormatTenPlus { get; set; } // default = "{0} {2}"
   public delegate string GetMenuItemTextDelegate( int index, string filepath );
   public GetMenuItemTextDelegate GetMenuItemTextHandler { get; set; }

They are used internally by this method:

partial class RecentFileList
   private string GetMenuItemText( int index, string filepath, string displaypath )
      GetMenuItemTextDelegate delegateGetMenuItemText = GetMenuItemTextHandler;
      if ( delegateGetMenuItemText != null )
         return delegateGetMenuItemText( index, filepath );
      string format =
         ( index < 10 ? MenuItemFormatOneToNine : MenuItemFormatTenPlus );
      string shortPath = ShortenPathname( displaypath, MaxPathLength );
      return String.Format( format, index, filepath, shortPath );

The static method ShortenPathname ( which Joe Woodbury [^] wrote ) takes a filepath and shortens it to less than MaxPathLength characters, by replacing parts of the path with an ellipsis.

By default, String.Format is called using either MenuItemFormatOneToNine or MenuItemFormatTenPlus, depending on the index. You can set these format strings if this will fulfill your needs. If you require full control over the display text, you can provide a method ( a GetMenuItemTextDelegate ) that takes the index and filepath, and returns the formatted string.

Points of Interest

Application attributes

The System.Windows.Forms.Application had handy static properties like CompanyName. You could reference this assembly, but that just doesn't seem right for these few properties. Instead, you can access the attributes directly through reflection:

using System.Reflection;

static partial class ApplicationAttributes
   static readonly Assembly _Assembly = null;
   static readonly AssemblyCompanyAttribute _Company = null;
   static readonly AssemblyProductAttribute _Product = null;
   public static string CompanyName { get; private set; }
   public static string ProductName { get; private set; }
   static ApplicationAttributes()
      CompanyName = String.Empty;
      ProductName = String.Empty;
      _Assembly = Assembly.GetEntryAssembly();
      if ( _Assembly != null )
         object[] attributes = _Assembly.GetCustomAttributes( false );
         foreach ( object attribute in attributes )
            Type type = attribute.GetType();
            if ( type == typeof( AssemblyCompanyAttribute ) )
               _Company = ( AssemblyCompanyAttribute ) attribute;
            if ( type == typeof( AssemblyProductAttribute ) )
               _Product = ( AssemblyProductAttribute ) attribute;
      if ( _Company != null ) CompanyName = _Company.Company;
      if ( _Product != null ) ProductName = _Product.Product;


This is just a little project that I hope will save people from reinventing the wheel.


2008 Feb 19:First published
2008 Feb 20:Fixed bug when viewed in xaml designer


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


About the Author

Nicholas Butler
United Kingdom United Kingdom

I built my first computer, a Sinclair ZX80, on my 11th birthday in 1980.
In 1992, I completed my Computer Science degree and built my first PC.
I discovered C# and .NET 1.0 Beta 1 in late 2000 and loved them immediately.
I have been writing concurrent software professionally, using multi-processor machines, since 1995.

In real life, I have spent 3 years travelling abroad,
I have held a UK Private Pilots Licence for 20 years,
and I am a PADI Divemaster.

I now live near idyllic Bournemouth in England.

If you would like help with multithreading, please contact me via my website:

I can work 'virtually' anywhere!

You may also be interested in...

Comments and Discussions

QuestionMy vote of 5 Pin
Gary Wheeler21-Mar-16 4:38
memberGary Wheeler21-Mar-16 4:38 
QuestionMRU list using MVVM LIGHT Pin
MTaunton21-Sep-15 19:31
memberMTaunton21-Sep-15 19:31 
QuestionLate to the party Pin
Ken Billing12-Dec-14 11:27
memberKen Billing12-Dec-14 11:27 
GeneralMy vote of 5 Pin
rakkeshbisht9-Jun-14 6:00
memberrakkeshbisht9-Jun-14 6:00 
QuestionStyle for MRU Pin
ricardosobrado20-Aug-13 0:27
memberricardosobrado20-Aug-13 0:27 
GeneralMy vote of 5 Pin
radsd28-Aug-12 12:53
memberradsd28-Aug-12 12:53 
QuestionProposal to make Persister chosable in xaml Pin
EvAlexHimself16-Feb-12 22:09
memberEvAlexHimself16-Feb-12 22:09 
GeneralMy vote of 5 Pin
DanM224-May-11 6:26
memberDanM224-May-11 6:26 
GeneralMy vote of 5 Pin
Sebastian Rettig7-Apr-11 10:11
memberSebastian Rettig7-Apr-11 10:11 
GeneralCaching of entries to improve performance Pin
Alexey Mednonogov5-Sep-10 8:06
memberAlexey Mednonogov5-Sep-10 8:06 
GeneralRecently Used Files Pin
Brian C Thompson16-Dec-09 2:47
memberBrian C Thompson16-Dec-09 2:47 
GeneralRe: Recently Used Files Pin
Nick Butler18-Dec-09 1:23
mentorNick Butler18-Dec-09 1:23 
GeneralMy vote of 2 Pin
John Simmons / outlaw programmer18-Nov-09 1:11
mvpJohn Simmons / outlaw programmer18-Nov-09 1:11 
GeneralRe: My vote of 2 Pin
Nick Butler19-Nov-09 1:59
mentorNick Butler19-Nov-09 1:59 
GeneralNamespace Pin
MTaunton27-Jul-08 21:57
memberMTaunton27-Jul-08 21:57 
GeneralRe: Namespace Pin
RichQ29-Dec-08 8:48
memberRichQ29-Dec-08 8:48 

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

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web02-2016 | 2.8.180527.1 | Last Updated 20 Feb 2008
Article Copyright 2008 by Nicholas Butler
Everything else Copyright © CodeProject, 1999-2018
Layout: fixed | fluid