Introduction
This is my first submission to CodeProject so please excuse for any amateur formatting or other editorial lapses. I wanted to do something with my new installation of Visual Studio 2005 and .NET 2.0 runtime, and I came up with this idea I call PruneRecentDocs. This application will allow you to selectively delete the entries as presented in the My Recent Documents view.
Background
The Windows OS basically allows for either single file deletion or wholesale removal of the previous file history. I wanted something that would allow for selective pruning of this fairly long list. If you have not deleted the history of recent file accesses on your computer then you will have hundreds of entries stored in the HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\RecentDocs registry entry. Somewhere, there is an optional registry setting that can control the depth of these entries, but unless set, I suspect this list will grow without bounds. But, I am not a registry expert so I welcome your corrections and feedback on this issue.
If you run RegEdit.exe and look at HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\RecentDocs, you will see something like this:
And if you go to Start -> My Recent Documents, you will see something like this:
From the My Recent Documents view, you can only right click the mouse and delete one file at time. The default number of files displayed in the popup list is 15. However, if you delete one or more of these displayed files and if you have more files stored in the RecentDocs registry, then upon the next popup display, you will be presented with the next 15 MRU files. Attempting to delete all these files via this mechanism is very tedious.
Using the code
The class diagram view as shown in Visual Studio 2005:
Some highlights of the code design and classes
I made use of .NET generics to greatly simplify the table lookup and correlation operations that would have otherwise required much more coding using simple for
loops. The first class of interest is RecentDocsFormat
. This class takes a registry key as the constructor parameter and then parses the contents into members of the class. The registry key values under RecentDocs are of type REG_BINARY
. The key values consist of a set of values with numerical names such as "0", "1", "2", etc. followed by the key value MRUListEx
which is easy to decode being an ordered list of Uint32
entries representing the Most Recent Used files and terminated by the value 0xFFFF. I could not find a definitive declaration of the numerical named key values but the first portion of the binary data is a Unicode string of the filename.
The RegTools
class has static
functions that assist in the manipulation of binary registry key values used in the RecentDocs key and its subkeys. This is where the .NET 2.0 generic Dictionary<T,T>
and List<T,T>
were helpful.
The constructor for RecentDocsFormat
This constructor will decode the binary values of the given registry key and construct Dictionary
cross mappings between the MRU numerical names and the user friendly file names contained within the key value:
public RecentDocsFormat(RegistryKey key)
{
FolderKey = key;
FileToIndexMapping = new Dictionary<String, String>();
IndexToFileMapping = new Dictionary<String, String>();
MruListEx = new List<string>();
sFileNames = new List<string>();
byte[] Bytes = (byte[])FolderKey.GetValue("MRUListEx");
for (int index = 0; index < Bytes.Length /
sizeof(UInt32); index++)
{
UInt32 val = RegTools.GetMruEntry(index, ref Bytes);
string sVal = val.ToString();
MruListEx.Add(sVal);
}
List<string> slist = RegTools.ExtractKeyValues(FolderKey);
foreach (string s in slist)
{
object obj = FolderKey.GetValue(s);
string filename =
RegTools.ExtractUnicodeStringFromBinary(obj);
FileToIndexMapping.Add(filename, s);
IndexToFileMapping.Add(s, filename);
sFileNames.Add(filename);
}
sIndexNames = RegTools.ExtractKeyValues(FolderKey);
}
How to delete a filename reference from RecentDocs
RecentDocsFormat
has several methods but the ultimate action taken in this application is to prune a filename reference from the various (more than one) registry keys. The RecentDocsFormat
method, DeleteThisFile(string filename)
, accomplishes this. Note the use of the Dictionary
key lookup such as String indexName = FileToIndexMapping[sFilename];
:
public void DeleteThisFile(String sFilename)
{
String indexName = FileToIndexMapping[sFilename];
try
{
FolderKey.DeleteValue(indexName);
MruListEx.Remove(indexName);
RegTools.RebuildMruList(FolderKey, MruListEx);
FileToIndexMapping.Remove(sFilename);
IndexToFileMapping.Remove(indexName);
sFileNames.Remove(sFilename);
sIndexNames.Remove(indexName);
}
catch (ArgumentException)
{
}
}
A non standard ERD
This Entity Relationship like diagram does not conform to any particular or popular methodology but it helped me write the code. Starting with the RecentDocs registry key, we construct the top level RecentDocsFormat
instance. Next, we extract the registry subkeys under RecentDocs and create a Dictionary<String, RecentDocsFormat>
with the filename extension as the dictionary key, returning an instance of RecentDocsFormat
(which encapsulates access to the registry key values). When it is time to delete a file, three targets are updated:
- the top level
RecentDocsFormat
,
- the entry in the associated filename extension subkey, and
- the Windows shortcut stored in the special folder accessed via
MyRecentDocsFolder = Environment.GetFolderPath(Environment.SpecialFolder.Recent);
:
Wildcard matching
This application extends the simple single filename selection with the wildcard option:
There is nothing fancy about this code. If you select *.txt, for example, it will enumerate over the key values looking for a match on the file extension.
Context menus and PInvoke
You can also right click on an item and that will popup a context sensitive menu:
This menu was constructed using a MouseDown
event handler on the ListView
class and a Click
event handler on the TooStripMenuItem
class. The first menu item will launch the associated application for this file. It does this by calling the shell function ShellExecute
via PInvoke to open the shortcut link. The OS will do the rest, finding the file source and launching the application:
listView1.MouseDown += new MouseEventHandler(listView1_MouseDown);
LaunchToolStripMenuItem.Name = "LaunchToolStripMenuItem";
LaunchToolStripMenuItem.Size = new System.Drawing.Size(119, 22);
LaunchToolStripMenuItem.Text = "Launch";
LaunchToolStripMenuItem.ToolTipText =
"Launch associated application for this file";
LaunchToolStripMenuItem.Click +=
new System.EventHandler(this.LaunchStripMenuItem_Click);
void listView1_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Right)
{
listView1.ContextMenuStrip.Show(listView1,
new Point(e.X, e.Y));
ListItemSelected = listView1.HitTest(e.X, e.Y);
}
}
private void LaunchStripMenuItem_Click(object sender,
EventArgs e)
{
ListViewItem item = ListItemSelected.Item;
if (item != null)
{
ShellApi.ShellExecute(IntPtr.Zero, "open",
item.Text + ".lnk", "", "",
ShellApi.ShowCommands.SW_SHOWNOACTIVATE);
}
}
Shell call via PInvoke
This class will allow the .NET application to call the shell32.sll function ShellExecute
via PInvoke. I would like to express my appreciation for the site PINVOKE.NET for an excellent reference on how to use PInvoke:
class ShellApi
{
public enum ShowCommands : int
{
SW_HIDE = 0,
SW_SHOWNORMAL = 1,
SW_NORMAL = 1,
SW_SHOWMINIMIZED = 2,
SW_SHOWMAXIMIZED = 3,
SW_MAXIMIZE = 3,
SW_SHOWNOACTIVATE = 4,
SW_SHOW = 5,
SW_MINIMIZE = 6,
SW_SHOWMINNOACTIVE = 7,
SW_SHOWNA = 8,
SW_RESTORE = 9,
SW_SHOWDEFAULT = 10,
SW_FORCEMINIMIZE = 11,
SW_MAX = 11
}
[DllImport("shell32.dll")]
public static extern IntPtr ShellExecute(
IntPtr hwnd,
string lpOperation,
string lpFile,
string lpParameters,
string lpDirectory,
ShowCommands nShowCmd);
}
Overall experience using Visual Studio 2005 for the first time
I found the Visual Studio 2005 IDE with IntelliSense to be wonderfully productive. Since I am new to most of the .NET 2.0 classes, I basically took a guess on what classes I could use for a particular feature and started typing. As the IntelliSense presented the choices matching my guesses, I could examine the types and parameters of the methods and quickly determine what is available.
ClickOnce deployment, signing, and automatic downloading of new versions
I have not done much with these features, but Visual Studio 2005 makes a really good canned solution for allowing the application to check a URL for updates and for downloading the latest version. I was able to simply choose a few options in the Publish pane, and publish this to a web site. If you navigate to the publish URL, you can automatically install the application. During installation, it will check for the prerequisite software such as the .NET 2.0 runtime and the Windows Installer 3.1. After installation, whenever you run the application, it will check for updates and optionally download the new version. When you use the Control Panel to uninstall this application, it will even allow you to rollback to a previous version or simply remove all versions for you. I don't know if this would be used by professional developers for retail software, but overall it was impressive in its scope and simplicity.
History
- 2006-02-06: 1.0.0.0
- 2006-02-09 - 1.1.0.0.
- Added context sensitive menus.
- Added ability to launch the associated file application via PInvoke.
- Fixed several exception handling errors.
During working hours is a real time firmware developer for custom ASIC's in embedded systems. Writes C/C++/C# code in his sleep and is moderately proficient in Java, Python, hardware description languages and other obscure domain specific languages. Deals with nano/microsecond data propagation delay paths, cache line hit/miss ratios, multicore asic design, etc.
However for fun likes to dabble in the new technologies of the day and deals at higher level software engineering aspects.