Why FolderBrowserDialog dialog does not scroll to selected folder?
Asked Answered
I

16

81

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.

enter image description here

Same dialog shows selected folder visible on different computer

enter image description here

I ran it on two computers both having windows 7. It works correctly on one but does not on 2nd. It looks something with windows environment instead some code issue? Can anyone suggest any fix?

There is no change in code. I used longer paths from different drives but results are same.

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 ();
            }

        }
Ixia answered 4/8, 2011 at 13:19 Comment(1)
Yes, it is environmental. The dialog is implemented in Windows, not in Silverlight. Could well be a Windows bug, I'm betting that the normally absent "Folder" text box is the root cause. Without it, the "Issues" folder would be visible. Contact Microsoft Support if you want to pursue this.Bloomy
D
91

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;
    }
}
Dnieper answered 15/3, 2013 at 19:46 Comment(12)
This should be marked as the answer. I've just run into the same issue, and this code worked perfectly. It's also a very detailed and well written explanation.Sima
What should be the second argument of the method ShowFolderBrowser ? The IWin32Window... ?Abortive
@Abortive - The IWin32Window argument is simply the parent form from which the folder chooser is being launched. If you are calling this directly from your Form code, you can just use the 'this' keyword as the parameter. (Technically, an IWin32Window is actually a wrapper around the hWnd behind the form, but C# hides all the ugly stuff involved with that from you!)Dnieper
On Win7 I observed that the scrolling occurred and then was reset as system folders such as Libraries etc were added to the tree after the dialog was initially shown. Setting an initial interval of 1000ms was enough to overcome this, though it is merely one more card on top!Conventioneer
I tried this and it work perfectly for me. But I would like to have a 'Default' button next to the textbox which will always show a particular folder. Any help will be great. ThanksOrthopedist
On Win10, like @Jonathan Mitchell noted, there is a timing issue. Setting t.Interval = 100; was enough to solve this on my machine (longer for slower machines?).Measly
I tried this from mfc/c++ code (without the timer) and GetDlgItem couldn't find the treeview. So i used EnumChildWindows to find it. I triggered it by BFFM_SELCHANGED and disabled it afterwards. As a sidenote i found that BFFM_SETSELECTION and BFFM_SETEXPANDED fail with nonexisting paths, so walking back the path until a valid one is found could be a solution.Sprague
@Brad Oestreicher - I tested your sample on windows 10 but it does not work. It did not scroll to selected directory at all.Ixia
@Brad, it hit the SendMessage on reaching retries to 9 but it does not scroll to selected directory/note though.Ixia
@Munawar. I got it working on Windows 10, though had to adjust for timing. Like you, I was seeing it hit the SendMessage (and return a success result), but not doing anything. Possibly the same issue that Jonathan mentioned above. I added a delay before it attempts to start looking for the dialog, and things are much improved now (this delay could be done by adding an 'if retries < 5' block below the line where we decrement the retry count for example)Chisel
About the interval time's setting, there are many different factors that can affect the last result. Especially, when you open a network folder, it will spend a long time to initialize the FolderBrowserDialog, you have to set 2000ms, even 5000ms.Colonic
I find it a wonderful answer. I think it worth writing a real good article! Live and learn from low level programmers! Thank you Brad and Paul.Subtype
T
11

on VB.Net code, just put this line of code right before showing the dialog.

SendKeys.Send ("{TAB}{TAB}{RIGHT}")
Treiber answered 8/8, 2016 at 18:21 Comment(3)
this answer is awesome! solves my problem for the time being.. really hated having to scroll down to the selection.. :+1:. would be my accepted answer if it were up to me :PKoweit
Sadly, this doesn't work on my system (Windows 10);Binturong
@Phil, if you have the New Folder button displayed SendKeys.Send ("{RIGHT}") worked for me.Elutriate
K
11

I know this thread is WAY old, but with extension methods, this can be added to the FolderBrowserDialog.ShowDialog method, and then used repeatedly where needed.

The sample (below) is just using the easy SendKeys method (which I hate doing, but in this case, it works well). When using the SendKeys method to jump to the selected folder in the dialog, if you are debugging this in Visual Studio, then the SendKeys call applies to the current window, which would be the active VS window. To be more foolproof and to avoid the wrong window from getting the SendKeys message, then the extension method would contain the external method calls to send messages to the specific window similar to what Marc F posted, but translated to 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);
    }
}
Kimono answered 10/9, 2016 at 4:7 Comment(1)
This was the most helpful for me on an x64 Windows 8 OS. However, I extended it by adding executing the sendkeys in a Timer_Tick event after 500 milliseconds, as it moved to the selected folder, then reverted to the root drive of that folder. So a delay was required.Caralie
M
8

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

Miscue answered 17/4, 2015 at 6:25 Comment(2)
Works for me. Fun to learn that the tab-tab-rightarrow keyboard sequence scrolls to the selected directory. In C#: SendKeys.Send("{TAB}{TAB}{RIGHT}");Pigmy
"this failure": I suppose that "this" refers to the SendKeys trick, and that "failure" should be "feature".Pigmy
D
5

this works for me

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

but only after the second use of the dialog

Delusion answered 28/8, 2014 at 10:47 Comment(0)
K
4

I have found that:

  1. If .SelectedPath ends with "\", the Dialog will scroll down to make the path visible.
  2. If .SelectedPath does not end with "\", the path is still selected, but not ensured visible.
Kenna answered 12/1, 2015 at 9:44 Comment(3)
Sorry: this solution works only halve of times. Seems like there is some race condition inside. Note: directory should exist.Peasecod
I have yet to see this work. The selection always stays at the root.Binturong
This trick isn't reliable, since it works occasionaly (tested on Windows 10). And note that the dialog returns selected path without trailing slash.Retral
I
3

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

Ixia answered 5/8, 2011 at 6:25 Comment(0)
C
3

I run into same problem in c++ /mfc. It worked for me to use ::PostMessage rather than ::SendMessage in the BFFM_INITIALIZED callback to place the TVM_ENSUREVISIBLE msg

    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;
}
Capeskin answered 29/1, 2016 at 16:54 Comment(0)
C
2

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#.

Czarism answered 11/7, 2015 at 19:50 Comment(2)
The question is quite old, so I would not put much hopes that someone would respond anytime soon. Thanks for the input!Drummer
This should be asked in its own question. Its not likely to be seen here.Mccracken
B
1

I have read the above discussion and solutions. Particularly Brat Oestreicher put me in the right direction. In essence, we must first find the TreeView control in the SHBrowseForFolder dialog, and send that window the TVM_ENSUREVISIBLE message. The following does this in C.

#include <windows.h>
#include <objbase.h>
#include <objidl.h>
#include <Shlobj.h>
#include <Dsclient.h>
#include <wchar.h>
// 
//  EnumCallback - Callback function for EnumWindows 
// 
static BOOL CALLBACK EnumCallback(HWND hWndChild, LPARAM lParam)
{
   char szClass[MAX_PATH];
   HTREEITEM hNode;
   if (GetClassName(hWndChild, szClass, sizeof(szClass))
   &&  strcmp(szClass,"SysTreeView32")==0) {
      hNode = TreeView_GetSelection(hWndChild);    // found the tree view window
      TreeView_EnsureVisible (hWndChild, hNode);   // ensure its selection is visible
      return(FALSE);   // done; stop enumerating
   }
   return(TRUE);       // continue enumerating
}
// 
//  BrowseCallbackProc - Callback function for SHBrowseForFolder 
// 
static INT CALLBACK BrowseCallbackProc (HWND hWnd, UINT uMsg, LPARAM lParam, LPARAM lpData) 
{
    switch (uMsg) 
    { 
        case BFFM_INITIALIZED:
            SendMessage (hWnd, BFFM_SETEXPANDED, TRUE, lpData);    // expand the tree view
            SendMessage (hWnd, BFFM_SETSELECTION, TRUE, lpData);   // select the item
            break;
        case BFFM_SELCHANGED:
            EnumChildWindows(hWnd, EnumCallback,0);
            break;
    } 
    return 0; 
} 
// 
//  SelectDirectory - User callable entry point 
// 
int SelectDirectory (HWND hWndParent, char *path, int pathSize) 
{ 
    BROWSEINFO bi = {0};
    LPITEMIDLIST pidl = NULL;
    wchar_t ws[MAX_PATH];

    CoInitialize(0);
    if (pathSize < MAX_PATH) return(FALSE);

    swprintf(ws, MAX_PATH, L"%hs", path);

    bi.hwndOwner = hWndParent; 
    bi.lpszTitle = "Select Directory"; 
    bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_NEWDIALOGSTYLE;
    bi.lpfn = BrowseCallbackProc;
    bi.lParam = (LPARAM) ws;

    pidl = SHBrowseForFolder (&bi); 
    if (pidl != NULL) 
    { 
        LPMALLOC pMalloc = NULL; 
        SHGetPathFromIDList (pidl, path);
        path[pathSize-1]= '\0';

        SHGetMalloc(&pMalloc);
        pMalloc->lpVtbl->Free(pMalloc,pidl);    // deallocate item 
        pMalloc->lpVtbl->Release(pMalloc);

        return (TRUE);
    } 
    return (FALSE);
} 

Many thanks to Gary Beene.

Bard answered 18/1, 2018 at 16:56 Comment(0)
P
0

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

FolderBrowserDialog: Expanding the selected directory

Putman answered 18/3, 2014 at 0:8 Comment(2)
This expands the selected folder, but leaves the first child incorrectly selected and does not solve the scrolling problem.Measly
Why do you link to another site without at least copy the solution here?Solley
B
0

dlgFolder.RootFolder = Environment.SpecialFolder.DesktopDirectory;

is not the same as

dlgFolder.RootFolder = Environment.SpecialFolder.Desktop;

What's the difference between SpecialFolder.Desktop and SpecialFolder.DesktopDirectory?

The thread linked indicates that as a path, they do get the same result. But they are not the same, as one is a logical path and the other is a physical path.

I have found when either one is assigned to the RootFolder of the open folder dialog, the resulting behavior can be different.

As a .RootFolder assignment, some versions of windows, like win7, treat either one as "Desktop". That is, you can see the "Computer" sub-entry, and open that to see the individual drive letters. The .SelectedPath gets selected either way, but the selected path is only made visible when the logical path of the desktop is assigned to the .RootFolder.

Worse, when using the browse folder dialog in win10 pre-release, it appears that "DesktopDirectory" as just that, the contents of the Desktop Directory only, with no link whatsoever to the logical desktop directory. And not listing any sub-items under it. Very frustrating if an app written for win7 is trying to be used with win10.

I think the problem the OP is having is that they employed the physical desktop as the root, when they should have employed the logical desktop.

I don't have an explanation for why the OP's two different machines respond differently. I would speculate that they have two different versions of the .NET framework installed.

The fact that win10 prerelease has the "Stuck on Desktop" issue with the browse folder dialog may be due to the more recent .NET framework shipped with win10 prerelease. Unfortunately, I remain ignorant of all the facts in this (win10) case, as I have not updated yet.

P.S. I found that win8 also experiences the "Stuck on Desktop" symptom:

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

The workaround there was to select the alternate GUI in win8. Perhaps something similar can be done in win10 prerelease.

Breban answered 27/6, 2015 at 18:31 Comment(0)
L
0

In response to Marc F's post - I've converted the VB.Net to C#

    public enum GW
    {
        HWNDFIRST = 0,
        HWNDLAST = 1,
        HWNDNEXT = 2,
        HWNDPREV = 3,
        OWNER = 4,
        CHILD = 5,
        ENABLEDPOPUP = 6
    }

    [System.Runtime.InteropServices.DllImport("user32.dll", EntryPoint = "SendMessageW", ExactSpelling = true, CharSet = System.Runtime.InteropServices.CharSet.Ansi, SetLastError = true)]
    public static extern IntPtr SendMessageW(IntPtr hWnd, uint msg, int wParam, [MarshalAs(UnmanagedType.LPWStr)] string lParam);
    [System.Runtime.InteropServices.DllImport("user32.dll", EntryPoint = "FindWindowExW", ExactSpelling = true, CharSet = System.Runtime.InteropServices.CharSet.Ansi, SetLastError = true)]
    public static extern IntPtr FindWindowExW(IntPtr hWndParent, IntPtr hWndChildAfter, [MarshalAs(UnmanagedType.LPWStr)] string lpszClass, [MarshalAs(UnmanagedType.LPWStr)] string lpszWindow);
    [System.Runtime.InteropServices.DllImport("user32", EntryPoint = "GetWindow", ExactSpelling = true, CharSet = System.Runtime.InteropServices.CharSet.Ansi, SetLastError = true)]
    public static extern UInt32 GetWindow(IntPtr hwnd, UInt32 wCmd);
    [System.Runtime.InteropServices.DllImport("user32", EntryPoint = "GetDesktopWindow", ExactSpelling = true, CharSet = System.Runtime.InteropServices.CharSet.Ansi, SetLastError = true)]
    public static extern IntPtr GetDesktopWindow();
    [System.Runtime.InteropServices.DllImport("user32", EntryPoint = "GetClassNameA", ExactSpelling = true, CharSet = System.Runtime.InteropServices.CharSet.Ansi, SetLastError = true)]
    public static extern int GetClassName(IntPtr hwnd, System.Text.StringBuilder lpClassName, int nMaxCount);

    private void FolderBrowserDialog_EnsureVisible(FolderBrowserDialog FB, IntPtr _Owner)
    {
        IntPtr hwnd = System.IntPtr.Zero;
        System.Text.StringBuilder sClassname = new System.Text.StringBuilder(256);
        Thread.Sleep(50); //necessary to let FolderBrowserDialog construct its window
        hwnd = GetDesktopWindow(); //Desktop window handle.
        hwnd = (System.IntPtr)GetWindow(hwnd, (UInt32)GW.CHILD); //We will find all children.
        while (!(hwnd == (System.IntPtr)0))
        {
            if (GetWindow(hwnd, (UInt32)GW.OWNER) == (UInt32)_Owner) //If one window is owned by our main window...
            {
                GetClassName(hwnd, sClassname, 255);
                if (sClassname.ToString() == "#32770") //Check if the class is FolderBrowserDialog.
                {
                    break; //Then we found it.
                }
            }
            hwnd = (System.IntPtr)GetWindow(hwnd, (UInt32)GW.HWNDNEXT); //Next window.
        } //If no found then exit.
        if (hwnd == (System.IntPtr)0)
        {
            return;
        }
        IntPtr hChild = (System.IntPtr)0;
        IntPtr hTreeView = (System.IntPtr)0;
        int i = 0;
        do
        {
            i += 1;
            if (i > 1000) //Security to avoid infinite loop.
            {
                return;
            }
            hChild = FindWindowExW(hwnd, hChild, null, null); //Look for children windows of FolderBrowserDialog.
            hTreeView = FindWindowExW(hChild, (System.IntPtr)0, "SysTreeView32", null); //Look for treeview of FolderBrowserDialog.
            Thread.Sleep(5); //delay necessary because FolderBrowserDialog is in construction, then treeview maybe not yet exist.
        } while (hTreeView == (System.IntPtr)0);
        if (SendMessageW(hwnd, 0x46A, 1, FB.SelectedPath) == (System.IntPtr)0) //Send message BFFM_SETEXPANDED to FolderBrowserDialog.
        {
            SendMessageW(hTreeView, 0x7, 0, null); //Send message WM_SETFOCUS to the treeeview.
        }
    }

Tested this and it works fine. Make sure you reference System.Runtime.InteropServices, System.Threading, an System.Threading.Tasks

Leontineleontyne answered 13/3, 2018 at 2:33 Comment(0)
T
0

I tried the code SendKeys.Send("{TAB}{TAB}{RIGHT}");, but the same code sometimes just didn't work.

At the end the best solution which I went with, where I can't get it not working, was:

public static class FolderBrowserDialogExt
{
    public static void ScrollSelectedPathIntoView(this FolderBrowserDialog fbd)
    {
        System.Threading.Tasks.Task.Run(() =>
        {
            SendKeys.SendWait("{TAB}");
            SendKeys.SendWait("{TAB}");
            SendKeys.SendWait("{RIGHT}");
        });
    }
}

void SomeMeth(string selPath)
{
    using (var fbd = new FolderBrowserDialog())
    {
        fbd.SelectedPath = selPath;
        fbd.ScrollSelectedPathIntoView();
        fbd.ShowDialog();//fbd.ShowDialog(owner);
    }
}
Tomikotomkiel answered 25/5, 2022 at 10:39 Comment(0)
M
0

This is how I use the folder browser dialog. This code solves the selected folder issue, and also selects the folder from the clipboard or the registry (if any), and if the folder is deleted it goes up throw parents until selecting an existing folder. This makes using the dialog very comfortable:

Dim FldrBr As New FolderBrowserDialog With {
        .RootFolder = Environment.SpecialFolder.Desktop,
        .Description = "Chose a flder",
        .ShowNewFolderButton = False
    }

    Dim x = Clipboard.GetText()
    Dim lastDir = GetSetting("Mp4Joiner", "SrcFolder", "Path", "")

    Try
        If x = "" Then
            x = lastDir
        ElseIf File.Exists(x) Then
            x = Path.GetDirectoryName(x)
        ElseIf Not Directory.Exists(x) Then
            x = lastDir
        End If
    Catch
        x = lastDir
    End Try

    Do
        If x = "" OrElse Directory.GetDirectoryRoot(x) = x OrElse Directory.Exists(x) Then
            Exit Do
        End If
        x = Path.GetDirectoryName(x)
    Loop

    FldrBr.SelectedPath = x
    Dim th As New Threading.Thread(
        Sub()
            Threading.Thread.Sleep(300)
            SendKeys.SendWait("{TAB}{TAB}{RIGHT}")
        End Sub)
    th.Start()

    If FldrBr.ShowDialog(Me) = Windows.Forms.DialogResult.OK Then
        SaveSetting("Mp4Joiner", "SrcFolder", "Path", FldrBr.SelectedPath)
       ' ........
    End If
Madura answered 16/2, 2023 at 0:45 Comment(0)
P
-2

The best approach, at least the most reliable is to make your own browser class dialog box. The tree scrolling issue has been a pain for many years - it will never get fixed!

If you know how to render in paint there is not much you can't do.. fast in paint well that is another story.

The first place I would look is at the open source .Net source code on GitHub, in your .Net version of choice, for the dialog class you're interested in improving. You may be surprised what you can achieve with a little effort and follow through. Just duplicate the control and debug to the point where the error occurs and patch - that'a what Microsoft does, so too can you!

Since this is an old thread and posting samples may never get read. It would make more since to post if asked.

Yet for someone looking to solve such an issue as with tree scrolling to the "expected" directory, here is some solid advise. If an issue exists with a control or library that has no immediate solution, create your own version, when possible extend the original and patch the problem. I've revamped everything from the Windows.Form.Control class to Win32 libraries for the sole purpose of getting predictable and accurate results.

The good news is that with C# there is a lot of low level control available to achieve almost any reasonable objective and the is C too.

In the past I have spent way too many hours searching for a solution to a problem where had I just recreated what was not working a lot of time would have been saved.

Princely answered 23/6, 2018 at 0:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.