在VS . net中,当你为项目选择文件夹时,会显示一个类似于OpenFileDialog或SaveFileDialog的对话框,但设置为只接受文件夹。自从我看到这个,我就想知道它是怎么做到的。我知道FolderBrowserDialog,但我从来都不喜欢那个对话框。它开始太小,不让我利用能够键入路径的优势。

到目前为止,我几乎可以肯定在。net中没有办法做到这一点,但我很好奇在非托管代码中如何做到这一点。如果不从头开始完全重新实现对话框,如何修改对话框以使其具有这种行为呢?

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

It's not a Vista-specific thing either; I've been seeing this dialog since VS .NET 2003, so it is doable in Win2k and WinXP. This is less of a "I want to know the proper way to do this" question and more of a "I have been curious about this since I first wanted to do it in VS 2003" question. I understand that Vista's file dialog has an option to do this, but it's been working in XP so I know they did something to get it to work. Vista-specific answers are not answers, because Vista doesn't exist in the question context.

更新:我接受Scott Wisniewski的答案,因为它附带了一个工作示例,但我认为Serge值得赞扬,因为他指出了对话框定制(这在。net中确实很讨厌,但它确实有效),Mark Ransom指出微软可能为这个任务推出了一个自定义对话框。


最好使用FolderBrowserDialog。

using (FolderBrowserDialog dlg = new FolderBrowserDialog())
{
    dlg.Description = "Select a folder";
    if (dlg.ShowDialog() == DialogResult.OK)
    {
        MessageBox.Show("You selected: " + dlg.SelectedPath);
    }
}

我猜你用的是Vista的VS2008?在这种情况下,我认为FOS_PICKFOLDERS选项在调用Vista文件对话框IFileDialog时被使用。我担心在。net代码中,这将涉及大量粗糙的P/Invoke互操作代码。


好的,让我试着连接第一个点;-) 玩一点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文章。


试试下面这个来自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;
}

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

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

MSDN提供了关于如何使用Win32实现这一点的信息,这篇CodeProject文章包含了一个示例,这篇CodeProject文章提供了一个. net示例。


精确的音频复制工作方式在Windows XP。标准文件打开对话框显示,但文件名字段包含文本“filename将被忽略”。

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

编辑:这比我想象的要简单得多。下面是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
}

Edit 3: Finally looked at the actual dialog in question, in Visual Studio 2005 (I didn't have access to it earlier). It is not the standard file open dialog! If you inspect the windows in Spy++ and compare them to a standard file open, you'll see that the structure and class names don't match. When you look closely, you can also spot some differences between the contents of the dialogs. My conclusion is that Microsoft completely replaced the standard dialog in Visual Studio to give it this capability. My solution or something similar will be as close as you can get, unless you're willing to code your own from scratch.


Ookii。对话框包包含一个托管包装器围绕新的(vista风格)文件夹浏览器对话框。它在旧的操作系统上也会优雅地退化。

面向。net 4.5的WPF Ookii对话框,在NuGet上可用 面向。net 4.5的Windows窗体Ookii对话框,可在NuGet上使用


您可以使用这样的代码

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

到高级隐藏文本框的文件名,你需要看 OpenFileDialogEx

代码:

{
    openFileDialog2.FileName = "\r";
    openFileDialog1.Filter = "folders|*.neverseenthisfile";
    openFileDialog1.CheckFileExists = false;
    openFileDialog1.CheckPathExists = false;
}

我写了一个名为OpenFileOrFolder的对话框,它允许你打开文件夹或文件。

如果将其AcceptFiles值设置为false,则它只在接受文件夹模式下操作。

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


您可以使用folderbrowserdialgex - 内置FolderBrowserDialog的可重用衍生物。这个选项允许您输入路径,甚至是UNC路径。你也可以用它浏览电脑或打印机。工作原理就像内置的FBD,但是…更好。

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

完整的源代码(一个简短的c#模块)。免费的。微软公开许可。

使用它的代码:

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 . dialogues命名空间中)。这是一个完美的解决方案-通常只显示文件夹的打开对话框。

下面是一个如何使用它的例子:

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

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


在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及更高版本上的工作示例可以在这里找到。


您可以使用这样的代码

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

        openFileDialog.FileName = "AnyFile";
        openFileDialog.Filter = string.Empty;
        openFileDialog.CheckFileExists = false;
        openFileDialog.CheckPathExists = false;

经过几个小时的搜索,我通过leetNightShade找到了一个有效的解决方案。

我认为有三件事使这个解决方案比其他解决方案要好得多。

使用起来很简单。 它只需要您在项目中包含两个文件(无论如何都可以合并为一个文件)。 当在XP或旧系统上使用时,它会退回到标准的FolderBrowserDialog。 作者允许您将代码用于您认为合适的任何目的。 没有什么许可可以让你随心所欲地使用代码。

在这里下载代码。


第一个解决方案

I developed this as a cleaned up version of .NET Win 7-style folder select dialog by Bill Seddon of lyquidity.com (I have no affiliation). (I learned of his code from another answer on this page). I wrote my own because his solution requires an additional Reflection class that isn't needed for this focused purpose, uses exception-based flow control, doesn't cache the results of its reflection calls. Note that the nested static VistaDialog class is so that its static reflection variables don't try to get populated if the Show method is never called. It falls back to the pre-Vista dialog if not in a high enough Windows version. Should work in Windows 7, 8, 9, 10 and higher (theoretically).

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中,他的版本必须补充使用旧样式的对话框。不幸的是,当我制定解决方案时,我还没有找到他的帖子。说出你的毒药!


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

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


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

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

还有一个版本可以使用Windows窗体。