Отказ от ответственности :
описанная здесь процедура не поддерживается: все может измениться в любое время без уведомления.
► Последний путь, к которому приложение обращается, когда OpenFileDialog или SaveFileDialog используются для открытия или сохранения файла, хранятся в реестре, в ветви HKEY_CURRENT_USER
, под этим ключом:
Software\Microsoft\Windows\CurrentVersion\Explorer\ComDlg32\LastVisitedPidlMRU
► Имена более поздних файлов, открытых или сохраненных приложением, сохраняются и упорядочиваются по расширению файла в этом другом ключе (та же ветка реестра):
Software\Microsoft\Windows\CurrentVersion\Explorer\ComDlg32\OpenSavePidlMRU
Вся информация хранится как двоичное значение.
Первый ключ - LastVisitedPidlMRU
- хранит последний путь, к которому обращается приложение, в двух возможных вариантах:
- Родительская оболочка элемент папки в форме GUID classi c (обычно представляет папку рабочего стола), за которой следует дочерний элемент
IDlist
, где путь хранится как в формате длинного пути Unicode, так и в формате короткого пути в стиле DOS (и других небольших фрагментах информация не описана здесь - см. ссылки (далее ). - Строка Unicode с нулевым символом в конце, представляющая имя исполняемого файла, за которым следует
IDlist
(как и прежде).
Здесь я буду использовать эту структуру для извлечения последнего пути, использованного указанный исполняемый файл.
Двоичные значения, содержащиеся в элементах Sub-Key ключа OpenSavePidlMRU
, хранятся как простые IDlist
, поэтому к сохраненным путям можно обращаться напрямую, используя оба SHGetPathFromIDListW и SHGetPathFromIDListEx .
Здесь я объявляю оба (может пригодиться), но я использую только второе, так как оно немного более гибкое (ну, это позволяет получить форму короткого пути, если требуется)
Дальнейшее чтение или тестирование:
Здесь есть три метода:
→ Первый, ApplicationGetLastOpenSavePath
, используется для получения последнего пути, используемого приложением, с учетом его исполняемого файла. name (только имя, например, app.exe
).
Поскольку этот вопрос помечен как WinForms, вы можете получить его как:
string exeName = Path.GetFileName(Application.ExecutablePath);
→ Второй метод, ApplicationGetLastOpenSaveFileNames
, извлекает все имена файлов, открытые приложением, относительно последнего пути доступа (и только этого, поскольку это то, о чем идет речь в текущем вопросе).
Метод возвращает именованный кортеж, (string AppPath, List<string> FileNames)
:
AppPath
- последний доступный путь. FileNames
- список имен файлов, где путь AppPath
- доступ к приложению.
→ Третий метод - это вспомогательный метод: его задача - обработать вызов SHGetPathFromIDListEx
, который требует некоторого маршалинга.
Параметр метода представляет байт массив, извлеченный из ключа реестра и смещение, которое представляет начальное расположение IDlist
(как описано, двоичные данные начинаются с строки Unicode с нулевым символом в конце, представляющей имя исполняемого файла).
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;
}
Этот второй метод вызывает первый для получения последнего пути, к которому обращается приложение, представленного 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);
}
Вспомогательный метод и объявления Win32 :
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
}