关于.net:如何配置OpenFileDialog选择文件夹?

How do you configure an OpenFileDialog to select folders?

在VS .NET中,当您为项目选择文件夹时,将显示一个对话框,看起来像一个OpenFileDialog或SaveFileDialog,但设置为仅接受文件夹。自从我看到这一点以来,我一直想知道它是如何完成的。我知道FolderBrowserDialog,但我从未真正喜欢过该对话框。它开始时太小,不能让我利用能够输入路径的优势。

我几乎可以肯定的是,目前尚无从.NET执行此操作的方法,但我也很好奇您也如何从非托管代码中执行此操作。缺少从头开始完全重新实现对话框的方法,如何修改对话框以具有此行为?

我还想重申一下,我知道FolderBrowserDialog,但有时我不喜欢使用它,除了真正好奇如何以这种方式配置对话框。告诉我只使用FolderBrowserDialog可以帮助我保持一致的UI体验,但不能满足我的好奇心,因此它不会被视为答案。

这也不是特定于Vista的事情;自VS .NET 2003以来,我一直在看到此对话框,因此它在Win2k和WinXP中是可行的。这不是一个"我想知道执行此操作的正确方法"的问题,而更多的是"自从我第一次在VS 2003中开始做以来,我对此一直感到好奇"。我知道Vista的文件对话框可以执行此操作,但是它在XP中一直在运行,因此我知道他们做了一些努力使其能够正常工作。 Vista特定的答案不是答案,因为问题上下文中不存在Vista。

更新:我接受Scott Wisniewski的回答,因为它附带了一个有效的示例,但是我认为Serge值得称赞的是他指出了对话框的自定义(虽然从.NET来看这很讨厌,但它确实有效),而Mark Ransom则指出了MS可能为此任务滚动了一个自定义对话框。


我有一个写的对话框,称为OpenFileOrFolder对话框,它使您可以打开文件夹或文件。

如果将其AcceptFiles值设置为false,则它仅在接受文件夹模式下运行。

您可以在此处从GitHub下载源代码


您可以使用FolderBrowserDialogEx-
内置FolderBrowserDialog的可重用派生类。这使您可以键入一个路径,甚至是UNC路径。您也可以使用它浏览计算机或打印机。就像内置的FBD一样工作,但是更好。

(编辑:我应该指出,可以将此对话框设置为选择文件或文件夹。)

完整的源代码(一个简短的C#模块)。自由。 MS-公共许可证。

使用它的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var dlg1 = new Ionic.Utils.FolderBrowserDialogEx();
dlg1.Description ="Select a folder to extract to:";
dlg1.ShowNewFolderButton = true;
dlg1.ShowEditBox = true;
//dlg1.NewStyle = false;
dlg1.SelectedPath = txtExtractDirectory.Text;
dlg1.ShowFullPathInEditBox = true;
dlg1.RootFolder = System.Environment.SpecialFolder.MyComputer;

// Show the FolderBrowserDialog.
DialogResult result = dlg1.ShowDialog();
if (result == DialogResult.OK)
{
    txtExtractDirectory.Text = dlg1.SelectedPath;
}


有Windows API代码包。它有很多与Shell相关的东西,包括CommonOpenFileDialog类(在Microsoft.WindowsAPICodePack.Dialogs名称空间中)。这是完美的解决方案-通常只显示文件夹的打开对话框。

这是一个如何使用它的示例:

1
2
3
CommonOpenFileDialog cofd = new CommonOpenFileDialog();
cofd.IsFolderPicker = true;
cofd.ShowDialog();

不幸的是,Microsoft不再提供此软件包,但是有几个人非正式地将二进制文件上传到NuGet。一个例子可以在这里找到。该软件包只是特定于shell的东西。如果您需要它,则同一用户还有其他几个软件包,这些软件包提供原始软件包中提供的更多功能。


Ookii.Dialogs程序包包含围绕新的(Vista风格)文件夹浏览器对话框的托管包装。在较旧的操作系统上,它也会正常降级。

  • 用于WPF目标.NET 4.5的Ookii对话框,在NuGet上可用
  • 适用于.NET 4.5的Windows窗体的Ookii对话框,可在NuGet上使用

最好为此使用FolderBrowserDialog。

1
2
3
4
5
6
7
8
using (FolderBrowserDialog dlg = new FolderBrowserDialog())
{
    dlg.Description ="Select a folder";
    if (dlg.ShowDialog() == DialogResult.OK)
    {
        MessageBox.Show("You selected:" + dlg.SelectedPath);
    }
}

经过数小时的搜索,我通过leetNightShade找到了一个可行的解决方案。

我认为有三件事可以使此解决方案比其他所有解决方案好得多。

  • 使用简单。
    它只需要在项目中包含两个文件(无论如何都可以合并为一个)。
  • 在XP或更旧的系统上使用时,它会退回到标准的FolderBrowserDialog。
  • 作者授权将代码用于您认为合适的任何目的。

    There’s no license as such as you are free to take and do with the code what you will.

  • 在此处下载代码。


    精确音频复制在Windows XP上可以这种方式工作。将显示标准文件打开对话框,但文件名字段包含文本"文件名将被忽略"。

    只是在这里猜测,但是我怀疑每次对对话框进行重大更改时,字符串都会注入到组合框编辑控件中。只要该字段不是空白,并且对话框标志设置为不检查文件是否存在,就可以正常关闭对话框。

    编辑:这比我想象的要容易得多。这是C ++ / MFC中的代码,您可以将其转换为您选择的环境。

    1
    2
    CFileDialog dlg(true, NULL,"Filename will be ignored", OFN_HIDEREADONLY | OFN_NOVALIDATE | OFN_PATHMUSTEXIST | OFN_READONLY, NULL, this);
    dlg.DoModal();

    编辑2:这应该是对C#的翻译,但是我不精通C#,所以如果它不起作用,请不要开枪。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    OpenFileDialog openFileDialog1 = new OpenFileDialog();

    openFileDialog1.FileName ="Filename will be ignored";
    openFileDialog1.CheckPathExists = true;
    openFileDialog1.ShowReadOnly = false;
    openFileDialog1.ReadOnlyChecked = true;
    openFileDialog1.CheckFileExists = false;
    openFileDialog1.ValidateNames = false;

    if(openFileDialog1.ShowDialog() == DialogResult.OK)
    {
        // openFileDialog1.FileName should contain the folder and a dummy filename
    }

    编辑3:最后查看了在Visual Studio 2005中存在问题的实际对话框(我之前没有访问权限)。它不是标准文件打开对话框!如果检查Spy ++中的窗口并将它们与打开的标准文件进行比较,则会看到结构和类名不匹配。当您仔细观察时,还可以发现对话框内容之间的一些差异。我的结论是,Microsoft完全替换了Visual Studio中的标准对话框来提供此功能。我的解决方案或类似的解决方案将尽可能地接近您,除非您愿意从头开始编写自己的代码。


    好,让我尝试连接第一个点;-)
    用Spy ++或Winspector播放一点,表明VS Project Location中的Folder文本框是对标准对话框的自定义。它与标准文件对话框(如"记事本"中的文件对话框)中的"文件名"文本框不同。

    从那里开始,我认为VS隐藏了文件名和文件类型文本框/组合框,并使用自定义对话框模板在对话框底部添加了自己的部分。

    编辑:这是这样的自定义以及如何进行此操作的示例(在Win32。不是.NET):

    m_ofn是文件对话框基础的OPENFILENAME结构。添加以下两行:

    1
    2
      m_ofn.lpTemplateName = MAKEINTRESOURCE(IDD_FILEDIALOG_IMPORTXLIFF);
      m_ofn.Flags |= OFN_ENABLETEMPLATE;

    其中IDD_FILEDIALOG_IMPORTXLIFF是自定义对话框模板,将添加到对话框底部。请参见下面红色部分。
    alt text
    (来源:apptranslator.com)

    在这种情况下,自定义部分仅是标签+超链接,但它可以是任何对话框。它可能包含一个"确定"按钮,使我们可以验证仅文件夹选择。

    但是我不知道该如何摆脱对话框标准部分中的某些控件。

    此MSDN文章中有更多详细信息。


    您可以对文件对话框进行子类化,并访问其所有控件。每个都有一个可用于获取其窗口句柄的标识符。然后,您可以显示和隐藏它们,从中获取有关选择更改的消息等。所有这些都取决于您要付出多少努力。

    我们使用WTL类支持来完成我们的工作,并自定义文件对话框以包括自定义位置栏和插件COM视图。

    MSDN提供了有关如何使用Win32进行此操作的信息,此CodeProject文章包括一个示例,而此CodeProject文章提供一个.NET示例。


    您可以使用这样的代码

    • 筛选器是隐藏文件
    • 文件名是隐藏第一个文本

    要高级隐藏文本框的文件名,您需要查看
    OpenFileDialogEx

    编码:

    1
    2
    3
    4
    5
    6
    7
    {
        openFileDialog2.FileName ="
    ";
        openFileDialog1.Filter ="folders|*.neverseenthisfile";
        openFileDialog1.CheckFileExists = false;
        openFileDialog1.CheckPathExists = false;
    }

    我假设您在使用VS2008的Vista上?在那种情况下,我认为调用Vista文件对话框IFileDialog时会使用FOS_PICKFOLDERS选项。恐怕在.NET代码中,这将涉及大量讨厌的P / Invoke互操作代码才能起作用。


    第一个解决方案

    我将其开发为lyquidity.com的Bill Seddon(我没有隶属关系)的.NET Win 7样式文件夹选择对话框的清理版本。 (我从此页面上的另一个答案获悉了他的代码)。我写自己的书是因为他的解决方案需要一个额外的Reflection类,而该类不需要为此专门的目的而使用,它使用基于异常的流控制,并且不缓存其反射调用的结果。请注意,嵌套的静态VistaDialog类是这样的,因此,如果从未调用Show方法,则不会尝试填充其静态反射变量。如果没有足够高的Windows版本,它将退回到Vista之前的对话框。理论上应该在Windows 7、8、9、10和更高版本中运行。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    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|
    ";

                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窗体中的用法如下:

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

    您当然可以尝试使用它的选项以及它公开的属性。例如,它允许在Vista样式对话框中进行多选。

    第二解决方案

    西蒙·穆里尔(Simon Mourier)给出了一个答案,该答案显示了如何直接使用针对Windows API的互操作来完成完全相同的工作,但是如果在较旧的Windows版本中,则必须对他的版本进行补充才能使用较旧的样式对话框。不幸的是,当我制定解决方案时,我还没有找到他的职位。命名你的毒药!


    您可以使用这样的代码

    过滤器为空字符串。
    文件名是AnyName,但不能为空

    1
    2
    3
    4
            openFileDialog.FileName ="AnyFile";
            openFileDialog.Filter = string.Empty;
            openFileDialog.CheckFileExists = false;
            openFileDialog.CheckPathExists = false;

    在Vista上,您可以将IFileDialog与FOS_PICKFOLDERS选项一起使用。这将导致显示类似OpenFileDialog的窗口,您可以在其中选择文件夹:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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及更高版本上工作的示例。


    WPF的Ookii对话框库具有一个类,该类提供WPF文件夹浏览器对话框的实现。

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

    enter image description here

    还有一个适用于Windows窗体的版本。


    尝试从Codeproject(贷给Nitron)进行以下操作:

    我认为这与您正在谈论的对话框相同-也许您添加屏幕截图会有所帮助?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    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;
    }


    我知道问题是关于OpenFileDialog的配置的,但是看到Google将我带到这里,我可能会指出,如果您仅查找文件夹,则应该使用FolderBrowserDialog,而不是下面的另一个SO问题所回答

    如何在vb.net中使用打开文件对话框指定路径?


    推荐阅读