I have been following this thread with some interest, as I use the same dialog box in WinAGI in a number of places. I'm busy with another project right now, but I did find some time yesterday to take a look at my implementation to see if the behavior is similar. And it is- I find that on my Surface tablet, running latest version of Windows 11, it never seems to fail scrolling to the current item. (I got tired of opening/closing the dialog after several dozen attempts...) But on a Windows 10 Enterprise system, it has only scrolled properly once out of closer to a hundred tries. dunno why it's so different between OS versions.
In VB6, I can't access the folder browser directly, so I use an API call. This works really well, and it also allows me to set a callback function for the dialog, which I use to initialize the dialog box, and also to extract the selected folder when the user makes a change. So I thought it might be easy to fix by using the callback function to send the 'ensurevisible' message as part of initialization.
But that didn't work; it appears the dialog box is not completely set up when the callback function is sent the 'initialized' message, so results didn't change at all. When examining the messages sent to the callback function, it looks like some additional setup occurs after 'initialization', which affects when the treelist updates, so it sometimes 'resets' even after it was originally forced to show the selection.
But since the 'selection-changed' message for the default/initial selection seems to hit AFTER initialization, I modified the callback to force visibility whenever the selection CHANGES - that works perfectly! (Well, it hasn't failed yet on any system I've tested yet). And since the selection will always be visible after startup, it doesn't affect the dialog while the user is browsing/selecting after startup.
Here is the code I use, in case you're interested - it's in VB of course, but it's not that complicated, and I assume you could easily convert to C# if you wanted to. (I will do it myself at some point in order to use in my C# WinAGI project, but I have my other work to finish first.)
'folder dialog api declarations
Public Declare Function SHBrowseForFolder Lib "shell32" (lpbi As BrowseInfo) As Long
Public Declare Function SHGetPathFromIDList Lib "shell32" (ByVal pidList As Long, ByVal lpBuffer As String) As Long
Public Declare Function GetWindowText Lib "user32" (ByVal hWnd As Long, ByVal lpString As String, ByVal nMaxCount As Long) As Long
Public Declare Function GetClassName Lib "user32.dll" Alias "GetClassNameA" (ByVal hWnd As Long, ByVal lpClassName As String, ByVal nMaxCount As Long) As Long
'directory browser constants
Public Const MAX_PATH = 260
Public Const BFFM_INITIALIZED = 1
Public Const BFFM_SELCHANGED = 2
Public Const BFFM_VALIDATEFAILEDA = 3
Public Const BFFM_VALIDATEFAILEDW = 4
Public Const BFFM_IUNKNOWN = 5
Public Const BFFM_SETSTATUSTEXT = (WM_USER + 100)
Public Const BFFM_ENABLEOK = (WM_USER + 101)
Public Const BFFM_SETSELECTION = (WM_USER + 102)
Public Const BIF_RETURNONLYFSDIRS = 1
Public Const BIF_DONTGOBELOWDOMAIN = 2
Public Const BIF_STATUSTEXT = 4
Public Const BIF_USENEWUI = 64
Public Const TV_FIRST As Long = 4352
Public Const TVM_SELECTITEM = (TV_FIRST + 11)
Public Const TVM_GETNEXTITEM As Long = (TV_FIRST + 10)
Public Const TVM_GETITEM = (TV_FIRST + 12)
Public Const TVM_ENSUREVISIBLE As Long = (TV_FIRST + 20)
Public Const TVGN_ROOT = 0
Public Const TVGN_NEXT = 1
Public Const TVGN_CHILD = 4
Public Const TVGN_FIRSTVISIBLE = 5
Public Const TVGN_NEXTVISIBLE = 6
Public Const TVGN_CARET = 9
'according to most recent MSDN, use CoTaskMemFree in place of ITMalloc.Free
Private Declare Sub CoTaskMemFree Lib "ole32" (pv As Long)
Public Declare Function GetWindow Lib "user32" (ByVal hWnd As Long, ByVal uCmd As Long) As Long
Public Const GW_HWNDFIRST = 0
Public Const GW_HWNDLAST = 1
Public Const GW_HWNDNEXT = 2
Public Const GW_HWNDPREV = 3
Public Const GW_OWNER = 4
Public Const GW_CHILD = 5
Public Const GW_ENABLEDPOPUP = 6
'
' GetNewDir function - uses an API call to show the system folder selection dialog
'
' hWnd is the handle of the window that will be the parent of the dialog box
' DialogMsg is the text shown at top of the dialog as a description of its purpose
'
Public Function GetNewDir(ByVal hWnd As Long, DialogMsg As String)
On Error GoTo ErrHandler
Dim lpIDList As Long
Dim biNewDir As BrowseInfo
'build browser info structure
With biNewDir
'handle of parent window
.hWndOwner = hWnd
'message that appears above treelist
.lpszTitle = DialogMsg
'set flags
.ulFlags = BIF_RETURNONLYFSDIRS + BIF_DONTGOBELOWDOMAIN + BIF_USENEWUI + BIF_STATUSTEXT
'set pointer to callback address
.lpfnCallback = ByValAddressOf(AddressOf BrowseCallbackProc)
.pszDisplayName = String$(MAX_PATH, 32)
End With
'show browser, get pidl
lpIDList = SHBrowseForFolder(biNewDir)
'if not canceled (valid pidl returned)
If lpIDList Then
'last msg sent to callback function
'has gotten us the name of the chosen folder
GetNewDir = SelectedFolder
'according to most recent MSDN info, use CoTaskMemFree
'to free up the lpIDList handle and the root handle
CoTaskMemFree lpIDList
End If
' Whether successful or not, free the PIDL which was used to
' identify the My Computer virtual folder.
CoTaskMemFree biNewDir.pIDLRoot
Exit Function
ErrHandler:
'*'Debug.Print "bad thing happened: "; Err.Number, Err.Description
Resume Next
End Function
'
' BrowseCallbackProc function
'
Public Function BrowseCallbackProc(ByVal hWnd As Long, ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Dim rtn As Long
Dim lpDir As Long
Dim shCurDir As String
Dim lngLen As Long
Dim tmpStr As String
Dim treeitem As Long
On Error GoTo ErrHandler
Select Case uMsg
Case BFFM_INITIALIZED
'set directory by passing string pointer by Value
'(pass 1(true) for wParam)
rtn = SendMessageByString(hWnd, BFFM_SETSELECTION, 1, BrowserStartDir)
' it seems logical that we could force the selected item to be visible
' by sending a msg to the treelist right after initialization
' BUT it seems that there are additional things that happen to the
' control AFTER this initialization call which still cause the control
' to occasionally fail to scroll the treelist
' fix is to force it when a selection is changed; that seems to work
' every time!
Case BFFM_SELCHANGED
shCurDir = String$(MAX_PATH, 0)
If (SHGetPathFromIDList(wParam, shCurDir)) Then
shCurDir = Left$(shCurDir, InStr(shCurDir, vbNullChar) - 1)
lpDir = lstrcat(shCurDir, vbNullString)
rtn = SendMessage(hWnd, BFFM_SETSTATUSTEXT, 1, ByVal lpDir)
End If
SelectedFolder = shCurDir
' force the current selection to be visible by finding the treelist
' control and sending a ENSUREVISIBLE message
' begin with main dialog handle; find the child control under it
' that is the container for the tree (it has a class name of
' 'SHBrowseForFolder ShellNameSpace Control'
Debug.Print "main dialog window: "; Hex(hWnd)
rtn = GetWindow(hWnd, GW_CHILD)
Do Until rtn = 0
Debug.Print "dialog child: "; Hex(rtn); " - '";
tmpStr = Space(256)
lngLen = GetClassName(rtn, tmpStr, 255)
tmpStr = Left(tmpStr, lngLen)
Debug.Print tmpStr & "'"
'is this the container?
If tmpStr = "SHBrowseForFolder ShellNameSpace Control" Then
'get first child of the container
rtn = GetWindow(rtn, GW_CHILD)
Exit Do
End If
'if not, get next window
rtn = GetWindow(rtn, GW_HWNDNEXT)
Loop
' if found, now search for the actual treelist control inside
' the container control
Do Until rtn = 0
Debug.Print "container child: "; Hex(rtn); " - '";
tmpStr = Space(256)
lngLen = GetClassName(rtn, tmpStr, 255)
tmpStr = Left(tmpStr, lngLen)
Debug.Print tmpStr & "'"
'is this the treelist?
If tmpStr = "SysTreeView32" Then
'success
Exit Do
End If
'if not, get next window
rtn = GetWindow(rtn, GW_HWNDNEXT)
Loop
'if found, send the message to force it to be visible
If rtn > 0 Then
'get the selected item
treeitem = SendMessage(rtn, TVM_GETNEXTITEM, TVGN_CARET, 0)
Debug.Print "item: "; treeitem
If treeitem <> 0 Then
'now force it to show the selected item?
SendMessage rtn, WM_SETFOCUS, 0, 0
rtn = SendMessage(rtn, TVM_ENSUREVISIBLE, 0&, treeitem)
Debug.Print "selected - "; rtn
End If
Else
Debug.Print "Treelist not found"
End If
End Select
Exit Function
ErrHandler:
Debug.Print "error: "; Err.Number; " - "; Err.Description
Resume Next
End Function