Edit: After consulting with a more knowledgeable friend, perhaps the better solution is, in hindsight, obvious. Just store your own registry key in HKEY_CURRENT_USER\SOFTWARE\YourCompanyOrAppName\Whatevs or something similar (not sure on best practices, or which folders you have read/write access to, do your own research in that) and avoid this problem altogether. By simply letting the user navigate to where they want once, and then storing the path in the registry (as a normal string, and not a PIDL) and retrieving that path the next time. For reference, see the MSDN articles on the Registry and RegistryKey classes, and their example in the RegistryKey/Methods/SetValue article. Still, I'll leave this post as is, as a point of curiosity, or if someone has a very specific problem and needs this solution. As always, good luck!
For any poor soul wandering through here in the future, it seems I figured out how to find the last used directory. Just like stated previously, it's stored in the registry, more specifically in HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\ComDlg32\OpenSavePidlMRU\
Here is a set of folders for each file extension, including a "*" for unknown file extensions. I'll do this for txt files, change the path as needed. To access this path we make a RegistryKey and call OpenSubKey (BTW, full code below)
string RegistryPath = @"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\ComDlg32\\OpenSavePidlMRU\\txt";
RegistryKey rk = Registry.CurrentUser.OpenSubKey(RegistryPath);
In here is a set of entries which all contain PIDLs (we'll get to that) of the last opened or saved items.
DO NOTE: the folder name OpenSavePidlMRU, i've seen called just OpenSaveMRU, and seems to be for versions older than win 10.
Also in here is an entry called "MRUListEx", MRU stands for "Most Recently Used". In this entry is an index of which item was... well, most recently used. So if I have 10 entries, named 0 to 9, and 9 was the last used, the first byte in MRUListEx will be 0x09. So for:
byte[] mrulistex = (byte[])rk.GetValue("MRUListEx");
byte Last = mrulistex[0];
Last will equal 0x09 (on my system)
Then we call GetValue again but for that entry
byte[] LastPathByteArray = (byte[])rk.GetValue(Last.ToString());
And here's where things get problematic, as this won't return a byte array where each byte is a character in our filepath, it returns what's known as a PIDL. While the byte array will seem to contain the path, in both char and wide char, it also contains a bunch of gibberish that can't be easily converted.
I won't pretend to understand it, but https://stackoverflow.com/a/4318663 provides a way to convert this to a string. (see code below)
string LastPath = GetPathFromPIDL(LastPathByteArray);
And we're done. PLEASE NOTE this doesn't necessarily represent a good solution, but I wasn't able to find much official documentation on this in my half hour of digging. And obviously this code doesn't check if the registry path is correct, if the registry keys exist, or do much error checking at all, but this does at least work.
using Microsoft.Win32; //for the registry class
using System.Runtime.InteropServices; //for converting the PIDL
//GetPathFromPIDL from matt.schechtman at https://stackoverflow.com/a/4318663
[DllImport("shell32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SHGetPathFromIDListW(IntPtr pidl, MarshalAs(UnmanagedType.LPTStr)] StringBuilder pszPath);
private string GetPathFromPIDL(byte[] byteCode)
{
//MAX_PATH = 260
StringBuilder builder = new StringBuilder(260);
IntPtr ptr = IntPtr.Zero;
GCHandle h0 = GCHandle.Alloc(byteCode, GCHandleType.Pinned);
try
{
ptr = h0.AddrOfPinnedObject();
}
finally
{
h0.Free();
}
SHGetPathFromIDListW(ptr, builder);
return builder.ToString();
}
public void OnClick_Button_OpenFile(object sender, RoutedEventArgs e)
{
string RegistryPath = @"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\ComDlg32\\OpenSavePidlMRU\\txt";
RegistryKey rk = Registry.CurrentUser.OpenSubKey(RegistryPath);
byte[] mrulistex = (byte[])rk.GetValue("MRUListEx");
byte Last = mrulistex[0];
byte[] LastPathByteArray = (byte[])rk.GetValue(Last.ToString());
string LastPath = GetPathFromPIDL(LastPathByteArray);
// Configure open file dialog box
Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog();`
dlg.InitialDirectory = LastPath;
result = dlg.ShowDialog();
if (result == true)
{
string filename = dlg.FileName;
}
//etc etc, rest of your code
}
Good luck.