I love what Excel has done to the list of recently opened files. It went from up to nine files to up to 50 files. You can pin certain files so they never leave the list. Every item on the list has a keyboard shortcut. But is it every good enough? No. No it’s not.
I have my options set to show 50 recent files, buy they don’t all fit on the screen. And 50 is a bit much. In fact, 25 (the default setting) can be hard to look through. All of the recent files have keyboard shortcuts and all of the push pins for the files have keyboard shortcuts. By the time you get to about the 20th file, the keyboard shortcut is YY2. It gets a little unwieldy, I think.
What if we could filter the list by typing in a search box? What if indeed.
And I filter on 201303 (that’s the third quarter of 2013 for you non-accountants).
Now that’s a list I can get my head around. Let’s see how it works. First I fill the listboxes based on the Application.RecentFiles collection.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
Public Sub FillFilesAndPlaces(Optional sFilter As String = vbNullString) Dim dcPlaces As Scripting.Dictionary Dim rf As RecentFile Dim sPlace As String Dim sFile As String Dim aFiles() As String Dim lCnt As Long Set dcPlaces = New Scripting.Dictionary ReDim aFiles(1 To 2, 1 To 1) For Each rf In Application.RecentFiles sFile = Dir(rf.Name) sPlace = Replace(rf.Path, Dir(rf.Path), vbNullString) 'If file isn't found, don't include it If Len(sFile) > 0 Then 'Put file names and paths in an array If UCase(sFile) Like "*" & UCase(sFilter) & "*" Then lCnt = lCnt + 1 ReDim Preserve aFiles(1 To 2, 1 To lCnt) aFiles(1, lCnt) = rf.Path aFiles(2, lCnt) = sFile End If 'Put unique paths in a dictionary If UCase(sPlace) Like "*" & UCase(sFilter) & "*" Then If Not dcPlaces.Exists(sPlace) Then dcPlaces.Add sPlace, sPlace End If End If End If Next rf 'If files match, put to listbox, otherwise clear If lCnt = 1 Then Me.lbxFiles.Clear Me.lbxFiles.AddItem aFiles(1, 1) Me.lbxFiles.List(Me.lbxFiles.ListCount - 1, 1) = aFiles(2, 1) ElseIf lCnt > 1 Then Me.lbxFiles.List = Application.WorksheetFunction.Transpose(aFiles) Else Me.lbxFiles.Clear End If 'Write keys array to places listbox Me.lbxPlaces.List = dcPlaces.Keys End Sub |
When the userform is initialized, this procedure is called without the argument. That ensures that every file and place gets picked. I loop through the RecentFiles collection and capture the file name (using Dir) and the folder. The file names and fullnames go into a 2d array. The paths go into a dictionary so I can get a unique list. I never used to use dictionaries because I didn’t like the reference dependency. But that’s just stupid because every computer I use will have Scripting Runtime installed. So now I’ve embraced them. If I could use For Each with a dictionary in a custom class module, I’d quit using collections altogether.
Recent files can have the same name, which causes a problem. I included a textbox at the bottom of the form so I could get the whole path. I didn’t want the whole path in the listbox because it looks too cluttered. When something is selected in the files listbox, the textbox gets updated with the full path and filename.
1 2 3 4 5 6 7 8 9 10 11 12 |
Private Sub lbxFiles_Change() If Me.lbxFiles.ListIndex > -1 Then Me.tbxFullname.Text = Me.lbxFiles.Value Me.lbxPlaces.ListIndex = -1 Else Me.tbxFullname.Text = vbNullString End If EnableOpen End Sub |
I’m using the Enter and Change events of the listbox to make sure that only a file or a place is selected and not both. I originally had a button for each listbox, but that seemed silly. Now I’m wondering if I should have files and places all in the same listbox.
The Open button’s Enabled property is updated based on what’s selected.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
Public Sub EnableOpen() Dim wb As Workbook Me.cmdOpen.Enabled = False If Me.lbxPlaces.ListIndex > -1 Then Me.cmdOpen.Enabled = True ElseIf Me.lbxFiles.ListIndex > -1 Then On Error Resume Next Set wb = Workbooks(Me.lbxFiles.Column(1)) On Error GoTo 0 If wb Is Nothing Then Me.cmdOpen.Enabled = True Else If wb.FullName = Me.lbxFiles.Value Then Me.cmdOpen.Enabled = True Else Me.tbxFullname.Text = Me.tbxFullname.Text & Space(1) & "(naming conflict)" End If End If End If End Sub |
I start with disabling the button. Then if a place is selected, it becomes enabled. Places is a unique list, so there’s no conflicts that can happen or any other error checking that’s needed. If a file is selected, it gets a bit more complicated. You can’t have two files with the same name open at the same time, even if they are in different folders. I try to set a variable to an open workbook, wrapped in On Error. If wb is Nothing, that means there are no open workbooks with that same name, so I enable the Open button. If wb is something, I check to see if the Fullname is the same. If it is, I enable Open, but in reality Open will just activate the workbook. Finally, if there’s an open workbook with the same Name, but a different Fullname, I keep Open disabled and put a message in the textbox. It’s not a great system, but it’s what I’ve got so far.
The only other code of interest is actually opening the file or place.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
Private Sub cmdOpen_Click() Dim wb As Workbook If Me.lbxFiles.ListIndex > -1 Then On Error Resume Next Set wb = Workbooks(Me.lbxFiles.Column(1)) On Error GoTo 0 If wb Is Nothing Then Workbooks.Open Me.lbxFiles.Value Else wb.Activate End If Else ChDrive Left$(Me.lbxPlaces.Value, 3) ChDir Me.lbxPlaces.Value Application.Dialogs(xlDialogOpen).Show End If Unload Me End Sub |
If it’s a file, I do the same workbook variable trick to see if it’s already open. If it is, I activate it. I don’t have to worry about it being a different file with the same name, because if that was a problem, Open would be disabled. If it’s not open, open it. If it is open, activate it.
If it’s a place, I change the drive, change the directory, and show the File Open dialog. I just know that ChDrive is going to screw up at some point, like if I have UNC path in there. But this is just a first draft, so I’ll deal with those problems later.
Right now, I press Alt+F+R and look down the recent file list. If I don’t see the file, I ESC out and press Ctrl+O to navigate the old fashioned way. I’m thinking that this userform should replace all of that. I would press Ctrl+O and this userform would show up. I would start typing and the list gets filtered. If the list is filtered, I would click Open and it lets me navigate to the file – kind of like selecting a Recent Place that happens to be the current directory. That means I have to keep Open enabled all of the time and prevent workbook name conflicts some other way. Alternatively, I could just add another button to the form to navigate. So Ctrl+O to open the form then Alt+N to skip the recent part and just navigate to the file.
Update
I’ve been using this for a week and, with a few minor exceptions, it’s worked wonderfully. I added the Navigate button to go directly to File Open. And I assigned Ctrl+O to open this form. It’s a little disconcerting at first – expecting the file open dialog and getting this userform – but I quickly got used to it. I’ve only had to ‘navigate’ once and that’s because I wasn’t thinking. The folder I wanted was in the recent places list, but I tend to be thinking about file names when searching, not folders.
You can download URecent.zip
I love the idea, but I’m getting an error on the Dir(rf.Path) when my recent file comes from sharepoint workspace.
Hmmm, that stinks. I don’t have Sharepoint. What do the paths look like? I guess we’ll have to split the path rather than using Dir().
Dick,
To use a ‘for each’ loop for a dictionary inside of a custom class, just use (assuming pdct is the private name of the dictionary inside the class)
Public Property Get Keys() As Variant()
Keys = pdct.Keys
End Property
Public Property Get Items() As Variant()
Items = pdct.Items
End Property
It also seems not to give me the ‘Read Only’ prompt when someone else has it open. It just opens it read-only.
One of the best ideas you had so far :) Great work.
I liked the idea of filling a listbox with just the dictionary keys array so much that I decided to start to use dictionaries rather than collections (again) – only to find that the first time I tried to use dictionaries, I wanted to create the list in a sorted order and dictionaries do not allow the before and after keyword – bummer – back to collections for me unless someone knows a slick workaround
Dictionaries are just arrays under the covers. There should be a Sort method right in the class. But alas, you’re right. The only thing I’ve ever seen on it is to dump it to an array, sort it, and put it back. Blech.
If you want the list to be sorted you should use another library:
Public Sub FillFilesAndPlaces(Optional sFilter As String = vbNullString)
With CreateObject("System.Collections.ArrayList")
For Each rf In Application.RecentFiles
If Dir(rf.Path) <> "" And InStr(rf.Name, sFilter) Then .Add rf.Path
Next
.Sort
If .Count > 0 Then
lbxPlaces.List = Application.Transpose(.toarray())
lbxFiles.List = lbxPlaces.List
For j = 0 To UBound(lbxFiles.List)
lbxFiles.List(j) = Dir(lbxFiles.List(j))
Next
Else
lbxPlaces.Clear
lbxFiles.Clear
End If
End With
End Sub
I never knew that System was available via COM. That should keep me busy exploring.
http://msdn.microsoft.com/en-us/library/system.collections.arraylist.aspx
Never too old to….
But I have to admit: it’s hardly documented.
I also had the SharePoint files issue… whenever I expanded my list of rf’s to include one SharePoint file (that happens to not exist anymore anyway) I’d get Error 53 File Not Found. I threw a little error capture like this:
and everything worked fine. Not very elegant, but…
I’m sure the unusable files will get written off of the list eventually.
I also had problems with the cmdOpen_Click sub. If the file was on a network the ChDrive line would error out. I changed the code a little to use FileDialog:
Problem solved.
Now my problem is that 50 recent files isn’t nearly enough. I’ve made some updates to be posted soon, but now I’m thinking I need to maintain my own recent files list with, I don’t know, no limit. That can’t work, can it?
Yes, like
Sub M_snb()
MsgBox Replace(CreateObject("wscript.shell").exec("cmd /c Dir """ & CreateObject("wscript.shell").specialfolders(13) & """\*.xls* /b").stdout.readall, ".lnk", "")
End Sub
Nice code. I can’t figure out how many files windows keeps. It appears to be 31 days on my machine. And the Excel recent files and Windows recent files oddly don’t mesh.
There’s also
Sub M_snb()
MsgBox CreateObject("wscript.shell").exec("cmd /c dir """ & CreateObject("wscript.shell").specialfolders(5) & "\Microsoft\Office\Recent\*.*" & """ /b").stdout.readall
End Sub
But I have no idea where Excel stores the 'recent files'
It appears to be in the registry
Yes I found that just now:
Sub M_register()
For j = 1 To 50
c00 = c00 & vbLf & Split(CreateObject("Wscript.shell").regread("HKEY_CURRENT_USER\Software\Microsoft\Office\14.0\Excel\File MRU\Item " & j), "*")(1)
Next
MsgBox c00
End Sub
Well, I think combining those three would be better than keeping my own list. I’m still not convinced it’s a complete history.
If I did keep my own list, should I keep it in the registry? What’s a realistic limit for how much data to store in the registry? If I kept 1,000 files in there would I be labeled a registry hog?
I have no idea what happens if you add an item 51, item 52 etc. in the registry.
But I would prefer to load the registry as little as possible.
I love this form! Now I’m Finally able to see more than 20 or 30 recent files.
The Dir() commands did work for me, although quite slowly. For each letter I typed in the Search box, it takes a second to load.
I’ve changed the lines
sFile = Dir(rf.Name); and
sPlace = Replace(rf.Path, Dir(rf.Path), vbNullString)
with
sFile = Right(rf.Name, Len(rf.Name) – InStrRev(rf.Name, “\”)); and
sPlace = Replace(rf.Path, sFile, vbNullString)
which is much faster. Now I’m wondering if my work around is more error prone…
No, I don’t think it’s more error prone. The only advantage to Dir is that returns a zero length string if the file doesn’t exist. I have an updated version almost ready. I’ll be interested if that’s slow and, if so, maybe I should reduce the use of Dir.
Hey Dick…I’d like to try this out. When I click on the download link, I get a couple of files called URecent.frm and URecent.frx . What do I do with those?
Jeff – in case Dick doesn’t see this:
I found the same thing, but this link:
http://dailydoseofexcel.com/archives/2014/04/25/kwikopen-addin/
has a download link for Kwikopen.xlam, which is I guess what we want.