In my program I use an OpenFileDialog component and I set an initial directory.
However, if I decide to open a file from another folder, next time I want to open a file, the OpenFileDialog remembers what was last folder I used to open a file and it will open that folder, not the folder specified in InitialDirectory. And I am happy it is this way. Even if I close and reopen the application, the last folder used will still be remembered.
Is there a way to find out which is folder? Let's say, when I click on a Button, I want the last path displayed in a Label.
OpenFileDialog ofd = new OpenFileDialog();
ofd.InitialDirectory = @"C:\My Initial Folder";
Disclaimer:
the procedure described here is not supported: anything can change at any time without notice.
▶ The last path accessed by an application when the OpenFileDialog or SaveFileDialog are used to open or save a file, is stored in the Registry, in the HKEY_CURRENT_USER
branch, under this Key:
Software\Microsoft\Windows\CurrentVersion\Explorer\ComDlg32\LastVisitedPidlMRU
▶ The names of the more recent files opened or saved by an application, are stored - organized by file extension - under this other Key (same Registry branch):
Software\Microsoft\Windows\CurrentVersion\Explorer\ComDlg32\OpenSavePidlMRU
All the information is stored as a Binary Value.
The first Key - LastVisitedPidlMRU
- stores the last path accessed by an application, in two possible flavors:
IDlist
where the path is stored in both Unicode long path format and DOS short path format (and other small pieces of information not described here - see the Further reading links).IDlist
(same as before).The Binary Values contained in the Sub-Key items of the OpenSavePidlMRU
Key are stored as a simple IDlist
, so the stored Paths can be accessed directly using both SHGetPathFromIDListW() and SHGetPathFromIDListEx().
Here, I'm declaring both (may come in handy), but I'm using only the second, since it's slightly more flexible (well, it allows to retrieve the short path form, if required)
Further reading or testing:
There are three methods here:
→ ApplicationGetLastOpenSavePath
is used to retrieve the last path used by an application, given its Executable name (just the name, e.g., app.exe
).
Since this question is tagged WinForms
, you can get it as:
string exeName = Path.GetFileName(Application.ExecutablePath);
→ ApplicationGetLastOpenSaveFileNames
retrieves all file names opened by an application, in relation to the last accessed path (and this alone, since this is what the current question is about).
The method returns a named tuple, (string AppPath, List<string> FileNames)
:
AppPath
is the last accessed path.FileNames
is a List of file names - where the path is AppPath
- accessed by the application.→ The third is a helper method: its task is to handle the call to SHGetPathFromIDListEx()
, which requires some interop marshaling.
The method parameters represent the byte array extracted from the Registry Key and an offset, which represents the initial location of the IDlist
(as described, the binary data starts with the null-terminated Unicode string representing the executable name).
private string ApplicationGetLastOpenSavePath(string executableName)
{
if (string.IsNullOrEmpty(executableName)) return null;
string lastVisitedPath = string.Empty;
var lastVisitedKey = Registry.CurrentUser.OpenSubKey(
@"Software\Microsoft\Windows\CurrentVersion\Explorer\ComDlg32\LastVisitedPidlMRU", false);
string[] values = lastVisitedKey.GetValueNames();
foreach (string value in values) {
if (value == "MRUListEx") continue;
var keyValue = (byte[])lastVisitedKey.GetValue(value);
string appName = Encoding.Unicode.GetString(keyValue, 0, executableName.Length * 2);
if (!appName.Equals(executableName)) continue;
int offset = executableName.Length * 2 + "\0\0".Length; // clearly null terminated :)
lastVisitedPath = GetPathFromIDList(keyValue, offset);
break;
}
return lastVisitedPath;
}
This second method calls the first to retrieve the last path accessed by the application represented by string executableName
:
private (string AppPath, List<string> FileNames) ApplicationGetLastOpenSaveFileNames(string executableName)
{
string appPath = ApplicationGetLastOpenSavePath(executableName);
if (string.IsNullOrEmpty(appPath)) return (null, null);
var fileNames = new List<string>();
var extensionMRUKey = Registry.CurrentUser.OpenSubKey(
@"Software\Microsoft\Windows\CurrentVersion\Explorer\ComDlg32\OpenSavePidlMRU", false);
string[] subKeys = extensionMRUKey.GetSubKeyNames().ToArray();
foreach (string key in subKeys) {
var subKey = extensionMRUKey.OpenSubKey(key);
foreach (string keyValue in subKey.GetValueNames()) {
var value = (byte[])subKey.GetValue(keyValue);
string filePath = GetPathFromIDList(value, 0);
if (filePath.Contains(appPath)) {
fileNames.Add(filePath);
}
}
}
return (appPath, fileNames);
}
Helper method and Win32 declarations:
private string GetPathFromIDList(byte[] idList, int offset)
{
int buffer = 520; // 520 = MAXPATH * 2
var sb = new StringBuilder(buffer);
IntPtr ptr = Marshal.AllocHGlobal(idList.Length);
Marshal.Copy(idList, offset, ptr, idList.Length - offset);
// or -> bool result = SHGetPathFromIDListW(ptr, sb);
bool result = SHGetPathFromIDListEx(ptr, sb, buffer, GPFIDL_FLAGS.GPFIDL_UNCPRINTER);
Marshal.FreeHGlobal(ptr);
return result ? sb.ToString() : string.Empty;
}
[DllImport("shell32.dll", ExactSpelling = true, CharSet = CharSet.Unicode)]
internal static extern bool SHGetPathFromIDListW(
IntPtr pidl,
[MarshalAs(UnmanagedType.LPTStr)]
StringBuilder pszPath);
[DllImport("shell32.dll", CharSet = CharSet.Unicode)]
internal static extern bool SHGetPathFromIDListEx(
IntPtr pidl,
[MarshalAs(UnmanagedType.LPTStr)]
[In,Out] StringBuilder pszPath,
int cchPath,
GPFIDL_FLAGS uOpts);
internal enum GPFIDL_FLAGS : uint {
GPFIDL_DEFAULT = 0x0000,
GPFIDL_ALTNAME = 0x0001,
GPFIDL_UNCPRINTER = 0x0002
}