258

在 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 值得称赞可能为此任务滚动了一个自定义对话框。

4

17 回答 17

61

我有一个我编写的对话框,称为 OpenFileOrFolder 对话框,它允许您打开文件夹或文件。

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

你可以在这里从 GitHub 下载源代码

于 2009-02-05T02:58:25.693 回答
51

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

以下是如何使用它的示例:

CommonOpenFileDialog cofd = new CommonOpenFileDialog();
cofd.IsFolderPicker = true;
cofd.ShowDialog();

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

于 2011-02-14T23:51:20.837 回答
49

您可以使用FolderBrowserDialogEx - 内置 FolderBrowserDialog 的可重用衍生产品。这个允许您输入路径,甚至是 UNC 路径。您还可以使用它浏览计算机或打印机。就像内置的 FBD 一样工作,但是……更好。

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

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

使用它的代码:

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;
}
于 2009-03-09T05:02:26.647 回答
38

Ookii.Dialogs包包含一个围绕新(Vista 风格)文件夹浏览器对话框的托管包装器它还在旧操作系统上优雅地降级。

于 2009-02-04T12:34:53.737 回答
28

最好使用 FolderBrowserDialog 。

using (FolderBrowserDialog dlg = new FolderBrowserDialog())
{
    dlg.Description = "Select a folder";
    if (dlg.ShowDialog() == DialogResult.OK)
    {
        MessageBox.Show("You selected: " + dlg.SelectedPath);
    }
}
于 2008-08-27T19:59:17.763 回答
23

经过数小时的搜索,我找到了leetNightShade工作解决方案的答案

我相信有三件事使这个解决方案比其他所有解决方案都好得多。

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

    没有许可证,您可以随意获取和使用代码。

在此处下载代码。

于 2013-06-09T15:03:12.667 回答
18

好的,让我尝试连接第一个点 ;-) 稍微使用 Spy++ 或 Winspector 显示 VS 项目位置中的文件夹文本框是标准对话框的自定义。它与标准文件对话框(如记事本中的文件名)中的文件名文本框不同。

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

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

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

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

其中 IDD_FILEDIALOG_IMPORTXLIFF 是将添加到对话框底部的自定义对话框模板。见下图红色部分。 (来源:apptranslator.com替代文字

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

但是我们将如何摆脱对话框标准部分中的一些控件,我不知道。

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

于 2009-02-03T08:02:01.200 回答
17

Exact Audio Copy在 Windows XP 上以这种方式工作。显示标准文件打开对话框,但文件名字段包含文本“文件名将被忽略”。

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

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

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

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

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++ 中的窗口并将它们与打开的标准文件进行比较,您会发现结构和类名不匹配。当您仔细观察时,您还可以发现对话框内容之间的一些差异。我的结论是,微软完全取代了 Visual Studio 中的标准对话框来赋予它这种能力。我的解决方案或类似的解决方案将尽可能接近,除非您愿意从头开始编写自己的代码。

于 2009-02-04T04:10:22.617 回答
10

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

我们使用 WTL 类支持并自定义文件对话框以包含自定义位置栏和插件 COM 视图。

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

于 2009-02-03T22:04:16.133 回答
10

您可以使用这样的代码

  • 过滤器是隐藏文件
  • 文件名是隐藏第一个文本

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

编码:

{
    openFileDialog2.FileName = "\r";
    openFileDialog1.Filter = "folders|*.neverseenthisfile";
    openFileDialog1.CheckFileExists = false;
    openFileDialog1.CheckPathExists = false;
}
于 2009-02-05T02:09:09.193 回答
5

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

于 2008-08-28T01:32:43.280 回答
3

第一个解决方案

我将其开发为.NET Win 7 风格的文件夹选择对话框的清理版本,由lyquidity.com的 Bill Seddon开发(我没有从属关系)。(我从this page的另一个答案中了解到他的代码)。我自己编写了自己的解决方案,因为他的解决方案需要一个额外的 Reflection 类,该类不需要这个重点目的,使用基于异常的流控制,不缓存其反射调用的结果。请注意,嵌套的静态类是这样的,如果从不调用该方法,则VistaDialog不会尝试填充其静态反射变量。Show如果 Windows 版本不够高,它会退回到 Vista 之前的对话框。应该在 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 风格的对话框中进行多选。

第二种解决方案

Simon Mourier 给出了一个答案,展示了如何直接使用针对 Windows API 的互操作来完成完全相同的工作,尽管如果在旧版本的 Windows 中,他的版本必须补充以使用旧样式对话框。不幸的是,当我制定解决方案时,我还没有找到他的帖子。说出你的毒药!

于 2015-11-20T21:27:08.000 回答
1

试试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;
}
于 2009-02-03T08:39:09.393 回答
1

在 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 及更高版本上运行的工作示例可在此处找到。

于 2012-01-04T01:18:33.443 回答
1

您可以使用这样的代码

过滤器是空字符串。文件名是 AnyName 但不是空白

        openFileDialog.FileName = "AnyFile";
        openFileDialog.Filter = string.Empty;
        openFileDialog.CheckFileExists = false;
        openFileDialog.CheckPathExists = false;
于 2012-03-19T02:42:08.123 回答
1

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

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

Ookii 文件夹浏览器对话框

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

于 2018-10-04T03:31:48.690 回答
-1

我知道问题是关于配置的,OpenFileDialog但是看到谷歌把我带到了这里,我不妨指出,如果你只是在寻找文件夹,你应该使用一个FolderBrowserDialog代替,正如下面另一个 SO 问题所回答的那样

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

于 2017-10-12T15:04:35.480 回答