Как настроить OpenFileDialog для выбора папок? - PullRequest
243 голосов
/ 27 августа 2008

В VS .NET, когда вы выбираете папку для проекта, отображается диалоговое окно, которое выглядит как OpenFileDialog или SaveFileDialog, но настроено на прием только папок. С тех пор как я увидел это, я хотел знать, как это делается. Я знаю о FolderBrowserDialog, но мне никогда не нравился этот диалог. Он начинается слишком мало и не позволяет мне воспользоваться возможностью набирать путь.

Я почти уверен, что сейчас нет способа сделать это из .NET, но мне так же любопытно, как вы делаете это и из неуправляемого кода. Если не считать полного переопределения диалога с нуля, как вы измените диалог, чтобы иметь такое поведение?

Я также хотел бы повторить, что я знаю о FolderBrowserDialog, но иногда мне не нравится его использовать, в дополнение к искреннему любопытству, как настроить диалог таким образом. Указание мне просто использовать FolderBrowserDialog помогает мне поддерживать постоянный интерфейс, но не удовлетворяет мое любопытство, поэтому оно не считается ответом.

Это не специфичная для Vista вещь; Я видел этот диалог начиная с VS .NET 2003, поэтому его можно использовать в Win2k и WinXP. Это не вопрос «я хочу знать, как правильно это сделать», а вопрос «мне было любопытно с тех пор, как я впервые захотел сделать это в VS 2003». Я понимаю, что в файловом диалоге Vista есть возможность сделать это, но он работает в XP, поэтому я знаю, что они сделали что-то , чтобы заставить его работать. Ответы, специфичные для Vista, не являются ответами, потому что Vista не существует в контексте вопроса.

Обновление: я принимаю ответ Скотта Вишневского, потому что он идет с рабочим образцом, но я думаю, что Серж заслуживает похвалы за указание на настройку диалога (что, по общему признанию, неприятно из .NET, но делает работа) и Марка Рэнсома для выяснения, что MS, вероятно, развернула специальный диалог для этой задачи.

Ответы [ 17 ]

5 голосов
/ 28 августа 2008

Я полагаю, вы работаете в Vista с VS2008? В этом случае я думаю, что опция FOS_PICKFOLDERS используется при вызове диалогового окна файла Vista IFileDialog . Я боюсь, что в .NET-коде это потребовало бы большого количества мрачного P / Invoke-кода взаимодействия для работы.

2 голосов
/ 21 ноября 2015

Первое решение

Я разработал это как очищенную версию .NET Win 7 диалогового окна выбора папки в стиле Билла Седдона из lyquidity.com (у меня нет принадлежности). (Я узнал о его коде от другого ответа на этой странице ). Я написал свой собственный, потому что его решение требует дополнительного класса Reflection, который не нужен для этой целевой цели, использует управление потоком на основе исключений, не кэширует результаты своих вызовов отражений. Обратите внимание, что вложенный статический класс VistaDialog таков, что его статические переменные отражения не пытаются заполниться, если метод Show никогда не вызывается. Он возвращается к диалоговому окну до Vista, если не в достаточно высокой версии Windows. Должен работать в Windows 7, 8, 9, 10 и выше (теоретически).

using System;
using System.Reflection;
using System.Windows.Forms;

namespace ErikE.Shuriken {
    /// <summary>
    /// Present the Windows Vista-style open file dialog to select a folder. Fall back for older Windows Versions
    /// </summary>
    public class FolderSelectDialog {
        private string _initialDirectory;
        private string _title;
        private string _fileName = "";

        public string InitialDirectory {
            get { return string.IsNullOrEmpty(_initialDirectory) ? Environment.CurrentDirectory : _initialDirectory; }
            set { _initialDirectory = value; }
        }
        public string Title {
            get { return _title ?? "Select a folder"; }
            set { _title = value; }
        }
        public string FileName { get { return _fileName; } }

        public bool Show() { return Show(IntPtr.Zero); }

        /// <param name="hWndOwner">Handle of the control or window to be the parent of the file dialog</param>
        /// <returns>true if the user clicks OK</returns>
        public bool Show(IntPtr hWndOwner) {
            var result = Environment.OSVersion.Version.Major >= 6
                ? VistaDialog.Show(hWndOwner, InitialDirectory, Title)
                : ShowXpDialog(hWndOwner, InitialDirectory, Title);
            _fileName = result.FileName;
            return result.Result;
        }

        private struct ShowDialogResult {
            public bool Result { get; set; }
            public string FileName { get; set; }
        }

        private static ShowDialogResult ShowXpDialog(IntPtr ownerHandle, string initialDirectory, string title) {
            var folderBrowserDialog = new FolderBrowserDialog {
                Description = title,
                SelectedPath = initialDirectory,
                ShowNewFolderButton = false
            };
            var dialogResult = new ShowDialogResult();
            if (folderBrowserDialog.ShowDialog(new WindowWrapper(ownerHandle)) == DialogResult.OK) {
                dialogResult.Result = true;
                dialogResult.FileName = folderBrowserDialog.SelectedPath;
            }
            return dialogResult;
        }

        private static class VistaDialog {
            private const string c_foldersFilter = "Folders|\n";

            private const BindingFlags c_flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
            private readonly static Assembly s_windowsFormsAssembly = typeof(FileDialog).Assembly;
            private readonly static Type s_iFileDialogType = s_windowsFormsAssembly.GetType("System.Windows.Forms.FileDialogNative+IFileDialog");
            private readonly static MethodInfo s_createVistaDialogMethodInfo = typeof(OpenFileDialog).GetMethod("CreateVistaDialog", c_flags);
            private readonly static MethodInfo s_onBeforeVistaDialogMethodInfo = typeof(OpenFileDialog).GetMethod("OnBeforeVistaDialog", c_flags);
            private readonly static MethodInfo s_getOptionsMethodInfo = typeof(FileDialog).GetMethod("GetOptions", c_flags);
            private readonly static MethodInfo s_setOptionsMethodInfo = s_iFileDialogType.GetMethod("SetOptions", c_flags);
            private readonly static uint s_fosPickFoldersBitFlag = (uint) s_windowsFormsAssembly
                .GetType("System.Windows.Forms.FileDialogNative+FOS")
                .GetField("FOS_PICKFOLDERS")
                .GetValue(null);
            private readonly static ConstructorInfo s_vistaDialogEventsConstructorInfo = s_windowsFormsAssembly
                .GetType("System.Windows.Forms.FileDialog+VistaDialogEvents")
                .GetConstructor(c_flags, null, new[] { typeof(FileDialog) }, null);
            private readonly static MethodInfo s_adviseMethodInfo = s_iFileDialogType.GetMethod("Advise");
            private readonly static MethodInfo s_unAdviseMethodInfo = s_iFileDialogType.GetMethod("Unadvise");
            private readonly static MethodInfo s_showMethodInfo = s_iFileDialogType.GetMethod("Show");

            public static ShowDialogResult Show(IntPtr ownerHandle, string initialDirectory, string title) {
                var openFileDialog = new OpenFileDialog {
                    AddExtension = false,
                    CheckFileExists = false,
                    DereferenceLinks = true,
                    Filter = c_foldersFilter,
                    InitialDirectory = initialDirectory,
                    Multiselect = false,
                    Title = title
                };

                var iFileDialog = s_createVistaDialogMethodInfo.Invoke(openFileDialog, new object[] { });
                s_onBeforeVistaDialogMethodInfo.Invoke(openFileDialog, new[] { iFileDialog });
                s_setOptionsMethodInfo.Invoke(iFileDialog, new object[] { (uint) s_getOptionsMethodInfo.Invoke(openFileDialog, new object[] { }) | s_fosPickFoldersBitFlag });
                var adviseParametersWithOutputConnectionToken = new[] { s_vistaDialogEventsConstructorInfo.Invoke(new object[] { openFileDialog }), 0U };
                s_adviseMethodInfo.Invoke(iFileDialog, adviseParametersWithOutputConnectionToken);

                try {
                    int retVal = (int) s_showMethodInfo.Invoke(iFileDialog, new object[] { ownerHandle });
                    return new ShowDialogResult {
                        Result = retVal == 0,
                        FileName = openFileDialog.FileName
                    };
                }
                finally {
                    s_unAdviseMethodInfo.Invoke(iFileDialog, new[] { adviseParametersWithOutputConnectionToken[1] });
                }
            }
        }

        // Wrap an IWin32Window around an IntPtr
        private class WindowWrapper : IWin32Window {
            private readonly IntPtr _handle;
            public WindowWrapper(IntPtr handle) { _handle = handle; }
            public IntPtr Handle { get { return _handle; } }
        }
    }
}

Используется как в форме Windows:

var dialog = new FolderSelectDialog {
    InitialDirectory = musicFolderTextBox.Text,
    Title = "Select a folder to import music from"
};
if (dialog.Show(Handle)) {
    musicFolderTextBox.Text = dialog.FileName;
}

Конечно, вы можете поэкспериментировать с его опциями и какими свойствами он предоставляет. Например, он разрешает множественный выбор в диалоге в стиле Vista.

Второе решение

Саймон Мурье дал ответ , который показывает, как выполнить ту же самую работу, используя взаимодействие с API-интерфейсом Windows напрямую, хотя его версию придется дополнить, чтобы использовать диалоговое окно более старого стиля, если в более старой версии Windows. К сожалению, я еще не нашел его пост, когда работал над решением. Назови свой яд!

1 голос
/ 04 октября 2018

Библиотека Ookii для WPF имеет класс, который обеспечивает реализацию диалогового окна браузера папок для WPF.

https://github.com/caioproiete/ookii-dialogs-wpf

enter image description here

Существует также версия, которая работает с Windows Forms .

1 голос
/ 19 марта 2012

Вы можете использовать код, подобный этому

Фильтр - пустая строка. Имя файла - AnyName, но не пустое

        openFileDialog.FileName = "AnyFile";
        openFileDialog.Filter = string.Empty;
        openFileDialog.CheckFileExists = false;
        openFileDialog.CheckPathExists = false;
1 голос
/ 04 января 2012

В Vista вы можете использовать IFileDialog с установленной опцией FOS_PICKFOLDERS. Это приведет к отображению окна, похожего на OpenFileDialog, где вы можете выбрать папки:

var frm = (IFileDialog)(new FileOpenDialogRCW());
uint options;
frm.GetOptions(out options);
options |= FOS_PICKFOLDERS;
frm.SetOptions(options);

if (frm.Show(owner.Handle) == S_OK) {
    IShellItem shellItem;
    frm.GetResult(out shellItem);
    IntPtr pszString;
    shellItem.GetDisplayName(SIGDN_FILESYSPATH, out pszString);
    this.Folder = Marshal.PtrToStringAuto(pszString);
}

Для старых версий Windows вы всегда можете прибегнуть к хитрости с выбором любого файла в папке.

Рабочий пример, который работает на .NET Framework 2.0 и более поздних версиях, можно найти здесь .

1 голос
/ 03 февраля 2009

Попробуйте это из Codeproject (кредит Nitron):

Я думаю, что это тот же диалог, о котором вы говорите - может быть, это поможет, если вы добавите скриншот?

bool GetFolder(std::string& folderpath, const char* szCaption=NULL, HWND hOwner=NULL)
{
    bool retVal = false;

    // The BROWSEINFO struct tells the shell how it should display the dialog.
    BROWSEINFO bi;
    memset(&bi, 0, sizeof(bi));

    bi.ulFlags   = BIF_USENEWUI;
    bi.hwndOwner = hOwner;
    bi.lpszTitle = szCaption;

    // must call this if using BIF_USENEWUI
    ::OleInitialize(NULL);

    // Show the dialog and get the itemIDList for the selected folder.
    LPITEMIDLIST pIDL = ::SHBrowseForFolder(&bi);

    if(pIDL != NULL)
    {
        // Create a buffer to store the path, then get the path.
        char buffer[_MAX_PATH] = {'\0'};
        if(::SHGetPathFromIDList(pIDL, buffer) != 0)
        {
            // Set the string value.
            folderpath = buffer;
            retVal = true;
        }       

        // free the item id list
        CoTaskMemFree(pIDL);
    }

    ::OleUninitialize();

    return retVal;
}
0 голосов
/ 12 октября 2017

Я знаю, что вопрос был о конфигурации OpenFileDialog, но, увидев, что Google привел меня сюда, я также могу указать, что если вы ТОЛЬКО ищете папки, вы должны использовать FolderBrowserDialog Вместо этого, как ответили на другой вопрос SO ниже

Как указать путь, используя диалог открытия файла в vb.net?

...