Why FolderBrowserDialog dialog does not scroll to selected folder?

前端 未结 14 1527
失恋的感觉
失恋的感觉 2020-11-30 02:28

As show in this screen shot, the selected folder is not in the view. It needs to be scrolled down to view the selected folder.

相关标签:
14条回答
  • 2020-11-30 02:34

    I have used a workaround from 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 ();
    

    It's not the nicest way, but it works for me.
    Without the RootFolder it does NOT work on the first call, but on the 2nd and following. With it, it works always.

    As others have observed that this failure is dependent on the operating system:
    I am using Win 7 Pro x64 SP1

    0 讨论(0)
  • 2020-11-30 02:35

    The fundamental problem is a poor design decision in the FolderBrowserDialog. First, we need to realize that the FolderBrowserDialog is not a .NET control, but is rather the Common Dialog and is part of Windows. The designer of this dialog elected not to send the TreeView control a TVM_ENSUREVISIBLE message after the dialog is displayed and an initial folder is selected. This message causes a TreeView control to scroll so that the currently selected item is visible in the window.

    So, all we need to do to fix this is to send the TreeView that is part of the FolderBrowserDialog the TVM_ENSUREVISIBLE message and everything will be great. Right? Well, not so fast. This is indeed the answer, but there some things standing in our way.

    • First, because the FolderBrowserDialog is not really a .NET control, it does not have an internal Controls collection. This means that we can't just find and access the TreeView child control from .NET.

    • Second, the designers of the .NET FolderBrowserDialog class decided to seal this class. This unfortunate decision prevents us from deriving from it and overriding the window message handler. Had we been able to do this, we might have tried to post the TVM_ENSUREVISIBLE message when we got the WM_SHOWWINDOW message in the message handler.

    • The third issue is that we can’t send the TVM_ENSUREVISIBLE message until the Tree View control actually exists as a real window, and it does not exist until we call the ShowDialog method. However, this method blocks, so we won’t have the opportunity to post our message once this method is called.

    To get around these issues, I created a static helper class with a single method that can be used to show a FolderBrowserDialog, and will cause it to scroll to the selected folder. I manage this by starting a short Timer just prior to calling the dialogue's ShowDialog method, and then tracking down the handle of the TreeView control in the Timer handler (i.e., after the dialogue is displayed) and sending our TVM_ENSUREVISIBLE message.

    This solution is not perfect because it depends on some prior knowledge about the FolderBrowserDialog. Specifically, I find the dialogue using its window title. This will break with non-English installations. I track down the child controls in the dialogue using their dialogue Item IDs, rather than title text or class name, because I felt this would be more reliable over time.

    This code has been tested on Windows 7 (64 bit), and Windows XP.

    Here is the code: (You may need: 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;
        }
    }
    
    0 讨论(0)
  • 2020-11-30 02:35

    This link has a simple answer that worked for me fine (I have windows 8.1)

    FolderBrowserDialog: Expanding the selected directory

    0 讨论(0)
  • 2020-11-30 02:37

    I read at different forums that it could be due to RootFolder because SelectedPath and RootFolder are are mutually exclusive, that means both cannot co-exists but with default RootFolder(.Desktop), It allows ,at least, climbing the Tree(navigate the drive/folders).

    However, if RootFolder is changed to other than Desktop, you would not be able to navigate to UNC paths.

    Answer to Hans Passant: I tried this Dialog Extension, which has TextBox, but no luck.

    Customising the browse for folder dialog to show the path

    0 讨论(0)
  • 2020-11-30 02:37

    this works for me

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

    but only after the second use of the dialog

    0 讨论(0)
  • 2020-11-30 02:39

    I computed something in VB.NET, so it would be easy to transform it into C#. I'm French, and I'm beginner in VB. Anyway, you can try my solution.

    My idea is to launch an asynchronous task just before showing the folderBrowserDialog.

    I found this myself, but I was inspired by Brad post. Here's my code:

    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
    

    I'm waiting for your suggestions. And someone can translate it into C# because I don't know C#.

    0 讨论(0)
提交回复
热议问题