为什么FolderBrowserDialog对话框不滚动到选定的文件夹?

如该屏幕截图所示,所选文件夹不在视图中。 它需要向下滚动查看选定的文件夹。

在这里输入图像说明

相同的对话框显示在不同计算机上可见的选定文件

在这里输入图像说明

我跑了两台电脑都有Windows 7的。它能正常工作,但不在第二。 它看起来与Windows环境,而不是一些代码问题? 任何人都可以提出任何修复

代码没有变化。 我从不同的驱动器使用更长的path,但结果是一样的。

private void TestDialog_Click ( object sender, EventArgs e ) { //Last path store the selected path, to show the same directory as selected on next application launch. //Properties.Settings.Default.LastPath FolderBrowserDialog dlgFolder = new FolderBrowserDialog (); dlgFolder.RootFolder = Environment.SpecialFolder.DesktopDirectory; dlgFolder.SelectedPath = Properties.Settings.Default.LastPath; if (dlgFolder.ShowDialog () == System.Windows.Forms.DialogResult.OK) { Properties.Settings.Default.LastPath = dlgFolder.SelectedPath; Properties.Settings.Default.Save (); } } 

根本的问题是在FolderBrowserDialog糟糕的devise决定。 首先,我们需要认识到FolderBrowserDialog不是.NET控件,而是Common Dialog ,是Windows的一部分。 这个对话框的devise者在对话框显示并select一个初始文件夹之后select不发送TreeView控件一个TVM_ENSUREVISIBLE消息。 此消息导致TreeView控件滚动,以便当前选定的项目在窗口中可见。

所以,我们所需要做的就是发送属于FolderBrowserDialog一部分的TreeView, TVM_ENSUREVISIBLE消息,一切都会很好。 对? 那么,不是那么快。 这确实是答案,但也有一些东西在阻碍我们。

  • 首先,由于FolderBrowserDialog实际上不是一个.NET控件,因此它没有内部Controls集合。 这意味着我们不能从.NET中find并访问TreeView子控件。

  • 其次,.NET FolderBrowserDialog类的devise者决定FolderBrowserDialog这个类。 这个不幸的决定阻止我们从它派生出来,并覆盖窗口消息处理程序。 如果我们能够做到这一点,当我们在消息处理程序中收到TVM_ENSUREVISIBLE消息时,我们可能试图发布TVM_ENSUREVISIBLE消息。

  • 第三个问题是我们不能发送TVM_ENSUREVISIBLE消息,直到树视​​图控件真正作为一个真正的窗口存在,直到我们调用ShowDialog方法时才存在。 但是,这个方法会阻塞,所以一旦调用这个方法,我们就没有机会发布消息了。

为了解决这些问题,我创build了一个可以用来显示一个FolderBrowserDialog的单一方法的静态助手类,并使其滚动到选定的文件夹。 我通过在调用对话框的ShowDialog方法之前启动一个短的Timer ,然后在Timer处理程序(即,在显示对话框之后)跟踪TreeView控件的句柄并发送我们的TVM_ENSUREVISIBLE消息来处理这个问题。

这个解决scheme并不完美,因为它取决于有关FolderBrowserDialog一些先前的知识。 具体来说,我使用它的窗口标题find对话。 这将打破非英文安装。 我使用他们的对话项目ID来追踪对话中的子控件,而不是标题文本或类名,因为我觉得这样做会随着时间的推移变得更可靠。

此代码已在Windows 7(64位)和Windows XP上testing过。

这里是代码:(您可能需要: using System.Runtime.InteropServices;

 public static class FolderBrowserLauncher { /// <summary> /// Using title text to look for the top level dialog window is fragile. /// In particular, this will fail in non-English applications. /// </summary> const string _topLevelSearchString = "Browse For Folder"; /// <summary> /// These should be more robust. We find the correct child controls in the dialog /// by using the GetDlgItem method, rather than the FindWindow(Ex) method, /// because the dialog item IDs should be constant. /// </summary> const int _dlgItemBrowseControl = 0; const int _dlgItemTreeView = 100; [DllImport("user32.dll", SetLastError = true)] static extern IntPtr FindWindow(string lpClassName, string lpWindowName); [DllImport("user32.dll")] static extern IntPtr GetDlgItem(IntPtr hDlg, int nIDDlgItem); [DllImport("user32.dll", CharSet = CharSet.Auto)] static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam); /// <summary> /// Some of the messages that the Tree View control will respond to /// </summary> private const int TV_FIRST = 0x1100; private const int TVM_SELECTITEM = (TV_FIRST + 11); private const int TVM_GETNEXTITEM = (TV_FIRST + 10); private const int TVM_GETITEM = (TV_FIRST + 12); private const int TVM_ENSUREVISIBLE = (TV_FIRST + 20); /// <summary> /// Constants used to identity specific items in the Tree View control /// </summary> private const int TVGN_ROOT = 0x0; private const int TVGN_NEXT = 0x1; private const int TVGN_CHILD = 0x4; private const int TVGN_FIRSTVISIBLE = 0x5; private const int TVGN_NEXTVISIBLE = 0x6; private const int TVGN_CARET = 0x9; /// <summary> /// Calling this method is identical to calling the ShowDialog method of the provided /// FolderBrowserDialog, except that an attempt will be made to scroll the Tree View /// to make the currently selected folder visible in the dialog window. /// </summary> /// <param name="dlg"></param> /// <param name="parent"></param> /// <returns></returns> public static DialogResult ShowFolderBrowser( FolderBrowserDialog dlg, IWin32Window parent = null ) { DialogResult result = DialogResult.Cancel; int retries = 10; using (Timer t = new Timer()) { t.Tick += (s, a) => { if (retries > 0) { --retries; IntPtr hwndDlg = FindWindow((string)null, _topLevelSearchString); if (hwndDlg != IntPtr.Zero) { IntPtr hwndFolderCtrl = GetDlgItem(hwndDlg, _dlgItemBrowseControl); if (hwndFolderCtrl != IntPtr.Zero) { IntPtr hwndTV = GetDlgItem(hwndFolderCtrl, _dlgItemTreeView); if (hwndTV != IntPtr.Zero) { IntPtr item = SendMessage(hwndTV, (uint)TVM_GETNEXTITEM, new IntPtr(TVGN_CARET), IntPtr.Zero); if (item != IntPtr.Zero) { SendMessage(hwndTV, TVM_ENSUREVISIBLE, IntPtr.Zero, item); retries = 0; t.Stop(); } } } } } else { // // We failed to find the Tree View control. // // As a fall back (and this is an UberUgly hack), we will send // some fake keystrokes to the application in an attempt to force // the Tree View to scroll to the selected item. // t.Stop(); SendKeys.Send("{TAB}{TAB}{DOWN}{DOWN}{UP}{UP}"); } }; t.Interval = 10; t.Start(); result = dlg.ShowDialog( parent ); } return result; } } 

我使用了https://www.daniweb.com/software-development/csharp/threads/300578/folderbrowserdialog-expanding-the-selected-directory-

 FolderBrowserDialog^ oFBD = gcnew FolderBrowserDialog; oFBD->RootFolder = Environment::SpecialFolder::MyComputer; oFBD->SelectedPath = i_sPathImport; oFBD->ShowNewFolderButton = false; // use if appropriate in your application SendKeys::Send ("{TAB}{TAB}{RIGHT}"); // <<-- Workaround ::DialogResult oResult = oFBD->ShowDialog (); 

这不是最好的方式,但它适用于我。
没有RootFolder它不能在第一个电话上工作,而是在第二个电话上。 有了它,它始终工作。

正如其他人所观察到的,这种失败取决于操作系统:
我正在使用Win 7 Pro x64 SP1

我在不同的论坛上看到,它可能是由于RootFolder,因为SelectedPath和RootFolder是互斥的,这意味着两者不能共存,但默认的RootFolder(.Desktop),它允许,至less爬树(导航驱动器/文件夹)。

但是,如果RootFolder更改为Desktop以外的其他用户,则无法导航到UNCpath。

Hans Passant的答复:我试过这个带有TextBox的Dialog Extension,但没有运气。

自定义浏览文件夹对话框以显示path

在VB.Net代码中,只需在显示对话框之前放置这一行代码即可。

 SendKeys.Send ("{TAB}{TAB}{RIGHT}") 

我在VB.NET中计算了一些东西,所以很容易将其转换为C#。 我是法国人,而且我是VB的初学者。 无论如何,你可以尝试我的解决scheme。

我的想法是在显示folderBrowserDialog之前启动一个asynchronous任务。

我自己find了这个,但是我被Brad的post所激励。 这是我的代码:

 Imports System.Threading.Tasks Imports Microsoft.VisualBasic.FileIO.FileSystem Public Enum GW HWNDFIRST = 0 HWNDLAST = 1 HWNDNEXT = 2 HWNDPREV = 3 OWNER = 4 CHILD = 5 ENABLEDPOPUP = 6 End Enum Public Declare Function SendMessageW Lib "user32.dll" (ByVal hWnd As IntPtr, ByVal msg As UInteger, ByVal wParam As Integer, <MarshalAs(UnmanagedType.LPWStr)> ByVal lParam As String) As IntPtr Public Declare Function FindWindowExW Lib "user32.dll" (ByVal hWndParent As IntPtr, ByVal hWndChildAfter As IntPtr, <MarshalAs(UnmanagedType.LPWStr)> ByVal lpszClass As String, <MarshalAs(UnmanagedType.LPWStr)> ByVal lpszWindow As String) As IntPtr Public Declare Function GetWindow Lib "user32" (ByVal hwnd As IntPtr, ByVal wCmd As Long) As Long Public Declare Function GetDesktopWindow Lib "user32" () As IntPtr Public Declare Function GetClassName Lib "user32" Alias "GetClassNameA" (ByVal hwnd As IntPtr, ByVal lpClassName As System.Text.StringBuilder, ByVal nMaxCount As Integer) As Integer Private Sub FolderBrowserDialog_EnsureVisible(FB As FolderBrowserDialog, _Owner As IntPtr) Dim hwnd As IntPtr Dim sClassname As New System.Text.StringBuilder(256) Thread.Sleep(50) 'necessary to let FolderBrowserDialog construct its window hwnd = GetDesktopWindow() 'Desktop window handle. hwnd = GetWindow(hwnd, GW.CHILD) 'We will find all children. Do Until hwnd = 0 If GetWindow(hwnd, GW.OWNER) = _Owner Then 'If one window is owned by our main window... GetClassName(hwnd, sClassname, 255) If sClassname.ToString = "#32770" Then 'Check if the class is FolderBrowserDialog. Exit Do 'Then we found it. End If End If hwnd = GetWindow(hwnd, GW.HWNDNEXT) 'Next window. Loop 'If no found then exit. If hwnd = 0 Then Exit Sub Dim hChild As IntPtr = 0 Dim hTreeView As IntPtr = 0 Dim i As Integer = 0 Do i += 1 If i > 1000 Then Exit Sub 'Security to avoid infinite loop. hChild = FindWindowExW(hwnd, hChild, Nothing, Nothing) 'Look for children windows of FolderBrowserDialog. hTreeView = FindWindowExW(hChild, 0, "SysTreeView32", Nothing) 'Look for treeview of FolderBrowserDialog. Thread.Sleep(5) 'delay necessary because FolderBrowserDialog is in construction, then treeview maybe not yet exist. Loop While hTreeView = 0 If SendMessageW(hwnd, &H46A, 1, FB.SelectedPath) = 0 Then 'Send message BFFM_SETEXPANDED to FolderBrowserDialog. SendMessageW(hTreeView, &H7, 0, Nothing) 'Send message WM_SETFOCUS to the treeeview. End If End Sub Dim My_save_dir = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) & "\My-Saves" Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click Dim FolderBrowserDialog1 As New FolderBrowserDialog FolderBrowserDialog1.Description = "Choose your save files path." If Directory.Exists(My_save_dir) Then FolderBrowserDialog1.SelectedPath = My_save_dir Else FolderBrowserDialog1.SelectedPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) End If Dim Me_handle = Me.Handle 'Store the main handle to compare after with each windows owner. Task.Run(Sub() FolderBrowserDialog_EnsureVisible(FolderBrowserDialog1, Me_handle)) 'Here's the trick, run an asynchronous task to modify the folderdialog. If FolderBrowserDialog1.ShowDialog(Me) = System.Windows.Forms.DialogResult.OK Then My_save_dir = FolderBrowserDialog1.SelectedPath End If End Sub 

我在等你的build议。 有人可以把它翻译成C#,因为我不知道C#。

我在c ++ / mfc中遇到了同样的问题。 它在我的使用:: PostMessage而不是:: SendMessage在BFFM_INITIALIZEDcallback放置TVM_ENSUREVISIBLE消息

  case BFFM_INITIALIZED: { // select something ::SendMessage(m_hDialogBox, BFFM_SETSELECTION, TRUE, (LPARAM) pszSelection); // find tree control m_hTreeCtrl = 0; HWND hchild = GetWindow(hWnd, GW_CHILD) ; while (hchild != NULL) { VS_TChar classname[200] ; GetClassName(hchild, classname, 200) ; if (VS_strcmp(classname, _T("SHBrowseForFolder ShellNameSpace Control")) == 0) { HWND hlistctrl = GetWindow(hchild, GW_CHILD) ; do { GetClassName(hlistctrl, classname, 200) ; if (lstrcmp(classname, _T("SysTreeView32")) == 0) { m_hTreeCtrl = hlistctrl; break ; } hlistctrl = GetWindow(hlistctrl, GW_HWNDNEXT) ; } while (hlistctrl != NULL); } if (m_hTreeCtrl) break; hchild = GetWindow(hchild, GW_HWNDNEXT); } if (m_hTreeCtrl) { int item = ::SendMessage(m_hTreeCtrl, TVM_GETNEXTITEM, TVGN_CARET, 0); if (item != 0) ::PostMessage(m_hTreeCtrl, TVM_ENSUREVISIBLE,0,item); } break; } 

我知道这个线程是旧的,但与扩展方法,这可以被添加到FolderBrowserDialog.ShowDialog方法,然后在需要重复使用。

下面的示例只是使用简单的SendKeys方法(我讨厌这样做,但在这种情况下,它运行良好)。 当使用SendKeys方法跳转到对话框中的选定文件夹时,如果您正在Visual Studio中进行debugging,则SendKeys调用将应用于当前窗口,该窗口将是活动的VS窗口。 为了更加简单,并避免错误的窗口获取SendKeys消息,那么扩展方法将包含外部方法调用,以便将消息发送到特定的窗口,类似于Marc F发布的,但转换为C#。

 internal static class FolderBrowserDialogExtension { public static DialogResult ShowDialog(this FolderBrowserDialog dialog, bool scrollIntoView) { return ShowDialog(dialog, null, scrollIntoView); } public static DialogResult ShowDialog(this FolderBrowserDialog dialog, IWin32Window owner, bool scrollIntoView) { if (scrollIntoView) { SendKeys.Send("{TAB}{TAB}{RIGHT}"); } return dialog.ShowDialog(owner); } } 

这个链接有一个简单的答案,对我来说很好(我有Windows 8.1)

http://www.daniweb.com/software-development/csharp/threads/300578/folderbrowserdialog-expanding-the-selected-directory-

我发现:

  1. 如果.SelectedPath以“\”结尾,则对话框将向下滚动以使path可见。
  2. 如果.SelectedPath不以“\”结尾,path仍处于选中状态,但不能保证可见。

dlgFolder.RootFolder = Environment.SpecialFolder.DesktopDirectory;

是不一样的

dlgFolder.RootFolder = Environment.SpecialFolder.Desktop;

SpecialFolder.Desktop和SpecialFolder.DesktopDirectory有什么区别?

链接的线程表示作为path,他们得到相同的结果。 但它们并不相同,因为一个是逻辑path,另一个是物理path。

我发现当任何一个被分配到打开的文件夹对话框的RootFolder时,结果的行为可能是不同的。

作为一个.RootFolder作业,Windows的一些版本,如win7,将其中一个视为“桌面”。 也就是说,您可以看到“计算机”子条目,然后打开以查看单个驱动器号。 .SelectedPath会以任一方式被选中,但只有当桌面的逻辑path被分配给.RootFolder时,选定的path才会显示出来。

更糟糕的是,当使用win10预发行版中的浏览文件夹对话框时,看起来“DesktopDirectory”就是这样,只是桌面目录的内容,没有任何链接到逻辑桌面目录。 并没有列出它下面的任何子项目。 非常令人沮丧,如果一个为win7编写的应用程序试图与win10一起使用。

我认为OP所面临的问题是,他们应该使用物理桌面作为根,当他们应该使用逻辑桌面。

我没有解释为什么OP的两个不同的机器有不同的响应。 我会推测他们有两个不同版本的.NET框架安装。

事实上,win10预发行版在浏览文件夹对话框中出现“Stuck on Desktop”问题,这可能是由于win10预发行版附带了更新的.NET框架。 不幸的是,由于我还没有更新,我仍然不知道这个(win10)案件中的所有事实。

PS我发现win8也遇到了“卡在桌面上”的症状:

https://superuser.com/questions/869928/windows-8-1-folder-selection-dialog-missing-my-computer-and-sub-items

解决方法是在win8中select备用GUI。 也许类似的事情可以在win10 prerelease中完成。

这对我有用

 folderBrowserDialog1.Reset(); folderBrowserDialog1.RootFolder = Environment.SpecialFolder.MyComputer; folderBrowserDialog1.SelectedPath = WorkingFolder; 

但仅在第二次使用对话框之后