Как получить последнюю папку, используемую OpenFileDialog? - PullRequest
0 голосов
/ 03 мая 2020

В моей программе я использую компонент OpenFileDialog и устанавливаю начальный каталог. Однако, если я решу открыть файл из другой папки, в следующий раз, когда я захочу открыть файл, OpenFileDialog запомнит, какая была последняя папка, которую я использовал для открытия файла, и откроет эту папку, а не папку, указанную в InitialDirectory. И я счастлив, что именно так. Даже если я закрою и снова открою приложение, последняя использованная папка останется в памяти. Есть ли способ узнать, какая папка? Скажем, когда я нажимаю на кнопку, я хочу, чтобы последний путь отображался в метке.

OpenFileDialog ofd = new OpenFileDialog();
ofd.InitialDirectory = @"C:\My Initial Folder";

Ответы [ 4 ]

0 голосов
/ 04 мая 2020

Отказ от ответственности :
описанная здесь процедура не поддерживается: все может измениться в любое время без уведомления.


► Последний путь, к которому приложение обращается, когда 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
}
0 голосов
/ 03 мая 2020

Не существует метода, который даст вам эту информацию, вам просто нужно отследить ее самостоятельно.

Примерно так:

private string lastFolder;

private void button1_Click(object sender, System.EventArgs e)
{
    if (ofd.ShowDialog() == DialogResult.OK)
    {
        lastFolder = Path.GetDirectoryName(ofd.Filename);
        // Do something with the file.
    }
}
0 голосов
/ 03 мая 2020

Когда пользователь выбирает файл, я автоматически заставляю InitialDirectory быть каталогом с этим именем файла. Делая это каждый раз, когда я снова открываю OpenFileDialog собирается в последнюю папку. Может быть, это поможет вам. Имейте в виду, что я инициировал InitiaDirectory в другом месте, а не в том случае, если я открываю OpenFileDialog.

ofd.InitialDirectory = Path.GetDirectoryName (ofd.FileName) ;

0 голосов
/ 03 мая 2020

В Windows есть это понятие «рабочего каталога». По сути, это одна папка, на которую приложение смотрит прямо сейчас. Этот используется для завершения любых относительных путей, и папка сначала проверяется на наличие исполняемых файлов, если вы добавляете случайную строку, которая не является ключевым словом. Начальное значение определяется самой командой, которая запустила приложение - это всегда был полный путь.

Без InitialDirectory, очевидно, OpenFileDialog будет использовать этот. И по умолчанию он не установит его обратно: https://docs.microsoft.com/en-us/dotnet/api/system.windows.forms.filedialog.restoredirectory

Если вы просто опустите InitialDirectory для второго вызова, он будет работать автоматически. Но, конечно, вы также можете извлечь значение рабочего каталога. Некоторые простые переключатели bool сделают это, но вы должны реплицировать их везде, где вы устанавливаете каталог для диалога:

bool InitializedDialogs = false;

if(InitializedDialogs == false){
   ofd.InitialDirectory = @"C:\My Initial Folder";
   InitializedDialogs = true;
}

Один вызов https://docs.microsoft.com/en-us/dotnet/api/system.io.directory.setcurrentdirectory в центральном местоположении может быть лучше.

...