Jump to content

Older Style Browse for Folder Dialog Routine with Enhancements


Go to solution Solved by pixelsearch,

Recommended Posts

Jump to the last post in this thread for the finished code.

============================================================================

Using the various Control... functions (such as Enable/Disable/SetText/Hide/Show), I can modify the Edit control on a Folder Browse dialog box generated using the _WinAPI_BrowseForFolderDlg function (which is actually using the windows SHBrowseForFolder function from shell32.dll). Is there a way to make this Edit control be ReadOnly? I could simply disable it, but then the user can't scroll left/right to see the entirety of a long string in the control. I want to keep the user from typing into the control, but still be able to scroll the contents left/right.

I'm using the older style of this dialog (because I like it) and the MSDN help notes that the function returns the hWnd to the dialog specifically to allow you "to modify the layout or contents of the dialog box. Because it is not resizable, modifying the older style dialog box is relatively straightforward." So that's what I'm trying to do - modify the older style dialog box a little. For example, I disable the OK button whenever a non-folder selection is highlighted in the Tree.

And yes, I've tried AutoIt's built-in FileSelectFolder function but I like the old dialog better for this application. Much less busy and single-purposed without a ton of bells and whistles that complicate it.

Here's my code (based largely on the example script in the help file for _WinAPI_BrowseForFolderDlg). Currently the test routine is uncommented so it's run-able. 

#include-once

#include <WinAPIDlg.au3>
#include <WinAPISysWin.au3>

; These includes were in the help sample but appear to be already included via other includes - no harm in leaving them
#include <APIDlgConstants.au3>
#include <MsgBoxConstants.au3>
#include <SendMessage.au3>
#include <WinAPIMem.au3>
#include <WinAPIMisc.au3>
#include <WinAPIShellEx.au3>
#include <WinAPIShPath.au3>

;#cs
; Test routine
Opt('MustDeclareVars' = 1)
ConsoleWrite('Returned Path = ' & _BrowseForFolder(@ScriptDir, 'Custom Title', 'Custom Prompt') & @TAB & '@extended = ' & @extended & @CRLF)
Exit
;#ce

Func _BrowseForFolder($sInitDir, $sTitle = 'Browse For Folder', $sPrompt = 'Select a folder from the list below.', $hParent_hWnd = 0)
    Global $s_BFF_Dialog_Title = $sTitle
    Local $hBrowseProc = DllCallbackRegister('_BrowseProc', 'int', 'hwnd;uint;lparam;ptr')
    Local $pBrowseProc = DllCallbackGetPtr($hBrowseProc)
    Local $pText = _WinAPI_CreateString($sInitDir)
    Local $sPath = _WinAPI_BrowseForFolderDlg('', $sPrompt, BitOR($BIF_RETURNONLYFSDIRS, $BIF_EDITBOX, $BIF_VALIDATE), $pBrowseProc, $pText, $hParent_hWnd)
    _WinAPI_FreeMemory($pText)
    DllCallbackFree($hBrowseProc)
    If $sPath = '' Then
        Return SetExtended(-1, '')
    Else
        Return $sPath
    EndIf
EndFunc   ;==>_BrowseForFolder

Func _BrowseProc($hWnd, $iMsg, $wParam, $lParam)
    Local $sPath
    Switch $iMsg
        Case $BFFM_INITIALIZED
            _WinAPI_SetWindowText($hWnd, $s_BFF_Dialog_Title)
            _SendMessage($hWnd, $BFFM_SETSELECTIONW, 1, $lParam)
            ControlDisable($hWnd, '', 'Edit1') ; <-- WOULD PREFER READONLY INSTEAD OF DISABLED
        Case $BFFM_SELCHANGED
            $sPath = _WinAPI_ShellGetPathFromIDList($wParam)
            If @error Or ($sPath = '') Then
                ControlDisable($hWnd, '', 'Button1') ; disable OK button for non-folder selections
            Else
                ControlSetText($hWnd, '', 'Edit1', $sPath) ; update Edit control to show full path
                ControlEnable($hWnd, '', 'Button1') ; enable OK button
            EndIf
        Case $BFFM_VALIDATEFAILED
            MsgBox(($MB_ICONERROR + $MB_SYSTEMMODAL), 'Error', _WinAPI_GetString($wParam) & ' is invalid.', 0, $hWnd)
            Return 1
    EndSwitch
    Return 0
EndFunc   ;==>_BrowseProc

Here's a screenshot of it showing an example of selecting a long folder path where you can't see the whole thing in the Edit control:

image.png.21238948f800f16879ee90a15081a5ed.png 

 

Edited by TimRude
Changed title, and linked to finished product
Link to post
Share on other sites

@TimRude I wondered if there was a way to display a tooltip while hovering over the Edit control.
This tooltip should constantly be updated and display the complete text of the Edit Control (e.g. 1 line) without having the user scroll right or left inside the Edit control.

The problem is that the dialog box displayed by _WinAPI_BrowseForFolderDlg() isn't created by the user, nor the treeview + edit control + 2 buttons + label found in this dialog box, so I didn't know exactly how to create the tooltip in this script where there is no While... WEnd loop.

Then I thought of Kafu's interesting script and the use of _WinAPI_SetTimer() helped me to achieve this. As there is no GUI created by the user in your script (TimRude) then I choosed the callback function associated to _WinAPI_SetTimer() rather than the impossible WM_TIMER registration when there is no GUI created by the user (if I'm not mistaken)

#include <WinAPIDlg.au3>
#include <WinAPISysWin.au3>
#include <GUIConstantsEx.au3>
#include <WindowsConstants.au3>

Opt('MustDeclareVars', 1)

Global $g_hEdit = 0, $g_aSize[2] ; width & height of edit control (client area)
Local $hTimerProc = DllCallbackRegister('_TimerProc', 'none', 'hwnd;uint;uint_ptr;dword')
Local $iTimerID = _WinAPI_SetTimer(0, 0, 200, DllCallbackGetPtr($hTimerProc)) ; 200ms

ConsoleWrite('Returned Path = ' & _BrowseForFolder(@ScriptDir, 'Custom Title', 'Custom Prompt') & @TAB & '@extended = ' & @extended & @CRLF)

_WinAPI_KillTimer(0, $iTimerID)
DllCallbackFree($hTimerProc)

;===================================================================================================================================
Func _BrowseForFolder($sInitDir, $sTitle = 'Browse For Folder', $sPrompt = 'Select a folder from the list below.', $hParent_hWnd = 0)

    Global $s_BFF_Dialog_Title = $sTitle
    Local $hBrowseProc = DllCallbackRegister('_BrowseProc', 'int', 'hwnd;uint;lparam;ptr')
    Local $pBrowseProc = DllCallbackGetPtr($hBrowseProc)
    Local $pText = _WinAPI_CreateString($sInitDir)
    Local $sPath = _WinAPI_BrowseForFolderDlg('', $sPrompt, BitOR($BIF_RETURNONLYFSDIRS, $BIF_EDITBOX, $BIF_VALIDATE), $pBrowseProc, $pText, $hParent_hWnd)
    _WinAPI_FreeMemory($pText)
    DllCallbackFree($hBrowseProc)
    If $sPath = '' Then
        Return SetExtended(-1, '')
    Else
        Return $sPath
    EndIf
EndFunc   ;==>_BrowseForFolder

;===============================================
Func _BrowseProc($hWnd, $iMsg, $wParam, $lParam)

    Local $sPath
    Switch $iMsg
        Case $BFFM_INITIALIZED
            _WinAPI_SetWindowText($hWnd, $s_BFF_Dialog_Title)
            _SendMessage($hWnd, $BFFM_SETSELECTIONW, 1, $lParam)

            $g_hEdit = ControlGetHandle($hWnd, "", "Edit1")
            _SendMessage($g_hEdit, 0x00CF, 1, 0) ; $EM_SETREADONLY = 0x00CF

            $g_aSize = WinGetClientSize($g_hEdit)

        Case $BFFM_SELCHANGED
            $sPath = _WinAPI_ShellGetPathFromIDList($wParam)
            If @error Or ($sPath = '') Then
                ControlDisable($hWnd, '', 'Button1') ; disable OK button for non-folder selections
            Else
                ControlSetText($hWnd, '', 'Edit1', $sPath) ; update Edit control to show full path
                ControlEnable($hWnd, '', 'Button1') ; enable OK button
            EndIf

        Case $BFFM_VALIDATEFAILED
            MsgBox(($MB_ICONERROR + $MB_SYSTEMMODAL), 'Error', _WinAPI_GetString($wParam) & ' is invalid.', 0, $hWnd)
            Return 1
        EndSwitch

    Return 0
EndFunc   ;==>_BrowseProc

;===============================================
Func _TimerProc($hWnd, $iMsg, $iTimerID, $iTime)
    #forceref $hWnd, $iMsg, $iTimerID, $iTime

    Local Static $tPoint = DllStructCreate("int X;int Y"), $bToolTip = False
    Local $aMPos = MouseGetPos()

    DllStructSetData($tPoint, "X", $aMPos[0])
    DllStructSetData($tPoint, "Y", $aMPos[1])
    _WinAPI_ScreenToClient($g_hEdit, $tPoint)

    If $tPoint.X > - 1 And $tPoint.X < $g_aSize[0] And $tPoint.Y > -1 And $tPoint.Y < $g_aSize[1] Then
;~      If Not $bToolTip Then ; keep this test commented out for an accurate tooltip display
            ToolTip(_WinAPI_GetWindowText($g_hEdit))
            $bToolTip = True
;~      EndIf
    Else
        If $bToolTip Then
            ToolTip("")
            $bToolTip = False
        EndIf
    EndIf
EndFunc   ;==>_TimerProc

If anyone thinks of an easier way to achieve the tooltip part, please indicate it here, thanks :)

Edited by pixelsearch
Changed variable name $g_aPos[] to a more precise $g_aSize[]
Link to post
Share on other sites
1 minute ago, pixelsearch said:

I wondered if there was a way to display a tooltip while hovering over the Edit control.

I had the same thought but couldn't find a way to do it. So instead I just used the $EM_SETSEL message to make the Edit control always shows the very end of the line of text. So if there's a path that's too long to fit, you're at least seeing the most important part of it at all times.

And since the Edit control is now read-only instead of disabled, the user can always scroll around in or select and copy the entire path from the control if desired.

So my _BrowseProc function now looks like this:

Func _BrowseProc($hWnd, $iMsg, $wParam, $lParam)
    Local $sPath
    Switch $iMsg
        Case $BFFM_INITIALIZED
            _WinAPI_SetWindowText($hWnd, $s_BFF_Dialog_Title) ; set the title
            _SendMessage($hWnd, $BFFM_SETSELECTIONW, 1, $lParam) ; preselect specified folder
            Local $hEdit = ControlGetHandle($hWnd, '', 'Edit1')
            _SendMessage($hEdit, $EM_SETREADONLY, 1, 0) ; make edit control readonly
             ; turn off tab-stop for edit control and give focus to treeview at startup
            Local $Style = _WinAPI_GetWindowLong($hEdit, $GWL_STYLE)
            _WinAPI_SetWindowLong($hEdit, $GWL_STYLE, BitAND($Style, BitNOT($WS_TABSTOP)))
        Case $BFFM_SELCHANGED
            $sPath = _WinAPI_ShellGetPathFromIDList($wParam) ; get the selected path as a string
            If @error Or ($sPath = '') Then
                _SendMessage($hWnd, $BFFM_ENABLEOK, 0, 0) ; disable OK button for non-folder selections
            Else
                ControlSetText($hWnd, '', 'Edit1', $sPath) ; update edit control to show full path
                 ; make sure end of path is visible in edit control
                _SendMessage(ControlGetHandle($hWnd, '', 'Edit1'), $EM_SETSEL, StringLen($sPath), StringLen($sPath))
                _SendMessage($hWnd, $BFFM_ENABLEOK, 0, 1) ; enable OK button
            EndIf
    EndSwitch
    Return 0
EndFunc   ;==>_BrowseProc

In this version I also turned off the tab-stop for the Edit control so the dialog starts with focus on the TreeView. If the user wants to interact with the Edit control to scroll or copy text, they'll need to mouse it. And since the user can't type anything into the Edit control now, the $BFFM_VALIDATEFAILED message will never fire so I removed that branch of the Switch conditional.

Your approach with the tooltip is very interesting, but I'm wanting to end up with an include file that I can drop into any project and just call the _BrowseForFolder function without having to do anything else. So you'd need to roll the tooltip display and cleanup code into that _BrowseForFolder function so that one call does it all. Another thing that could make the tooltips look more "normal" is to not have them move or blink if the mouse cursor is moving but is still within the rectangle of the Edit control. In other words, when the mouse cursor moves into the Edit control, display the tooltip but then leave it displayed where it first appeared until the mouse cursor leaves the Edit control.

Link to post
Share on other sites

All your concerns should be easy to solve.

Edit  : the following code should do it all. If you wanna keep the tooltip part, then its separate callback function is required and after all, if you intend to place 2 functions in your include file (_BrowseForFolder and  _BrowseProc) then there should be room for 3, by adding the timer callback function _TimerProc in the include file, no harm done.

9 hours ago, TimRude said:

just call the _BrowseForFolder function without having to do anything else.

Solved. The timer part has been moved inside _BrowseForFolder, leaving only 1 line in main to call _BrowseForFolder

9 hours ago, TimRude said:

In other words, when the mouse cursor moves into the Edit control, display the tooltip but then leave it displayed where it first appeared until the mouse cursor leaves the Edit control.

Solved. The tooltip appears now at a fixed place just above the Edit control. In fact, the user shouldn't need too often to scroll right or left inside the edit control if a tooltip displays an accurate content of the edit control.

I updated the Func _BrowseProc function to take care of the changes you made in it

#include <EditConstants.au3>
#include <GUIConstantsEx.au3>
#include <WinAPIDlg.au3>
#include <WinAPISysWin.au3>
#include <WindowsConstants.au3>

Opt('MustDeclareVars', 1)

ConsoleWrite('Returned Path = ' & _BrowseForFolder(@ScriptDir, 'Custom Title', 'Custom Prompt') & @TAB & '@extended = ' & @extended & @CRLF)

;===================================================================================================================================
Func _BrowseForFolder($sInitDir, $sTitle = 'Browse For Folder', $sPrompt = 'Select a folder from the list below.', $hParent_hWnd = 0)

    Global $g_hEdit = 0
    Local $hTimerProc = DllCallbackRegister('_TimerProc', 'none', 'hwnd;uint;uint_ptr;dword')
    Local $iTimerID = _WinAPI_SetTimer(0, 0, 200, DllCallbackGetPtr($hTimerProc)) ; 200ms

    Global $s_BFF_Dialog_Title = $sTitle
    Local $hBrowseProc = DllCallbackRegister('_BrowseProc', 'int', 'hwnd;uint;lparam;ptr')
    Local $pBrowseProc = DllCallbackGetPtr($hBrowseProc)
    Local $pText = _WinAPI_CreateString($sInitDir)
    Local $sPath = _WinAPI_BrowseForFolderDlg('', $sPrompt, BitOR($BIF_RETURNONLYFSDIRS, $BIF_EDITBOX, $BIF_VALIDATE), $pBrowseProc, $pText, $hParent_hWnd)
    _WinAPI_FreeMemory($pText)
    DllCallbackFree($hBrowseProc)

    _WinAPI_KillTimer(0, $iTimerID)
    DllCallbackFree($hTimerProc)

    If $sPath = '' Then
        Return SetExtended(-1, '')
    Else
        Return $sPath
    EndIf
EndFunc   ;==>_BrowseForFolder

;===============================================
Func _BrowseProc($hWnd, $iMsg, $wParam, $lParam)

    Local $sPath
    Switch $iMsg
        Case $BFFM_INITIALIZED
            _WinAPI_SetWindowText($hWnd, $s_BFF_Dialog_Title) ; set the title
            _SendMessage($hWnd, $BFFM_SETSELECTIONW, 1, $lParam) ; preselect specified folder

            $g_hEdit = ControlGetHandle($hWnd, '', 'Edit1')
            _SendMessage($g_hEdit, $EM_SETREADONLY, 1, 0) ; make edit control readonly

             ; turn off tab-stop for edit control and give focus to treeview at startup
            Local $Style = _WinAPI_GetWindowLong($g_hEdit, $GWL_STYLE)
            _WinAPI_SetWindowLong($g_hEdit, $GWL_STYLE, BitAND($Style, BitNOT($WS_TABSTOP)))

        Case $BFFM_SELCHANGED
            $sPath = _WinAPI_ShellGetPathFromIDList($wParam) ; get the selected path as a string
            If @error Or ($sPath = '') Then
                _SendMessage($hWnd, $BFFM_ENABLEOK, 0, 0) ; disable OK button for non-folder selections
            Else
                ControlSetText($hWnd, '', 'Edit1', $sPath) ; update edit control to show full path
                 ; make sure end of path is visible in edit control
                 _SendMessage(ControlGetHandle($hWnd, '', 'Edit1'), $EM_SETSEL, StringLen($sPath), StringLen($sPath))
                _SendMessage($hWnd, $BFFM_ENABLEOK, 0, 1) ; enable OK button
            EndIf
    EndSwitch

    Return 0
EndFunc   ;==>_BrowseProc

;===============================================
Func _TimerProc($hWnd, $iMsg, $iTimerID, $iTime)
    #forceref $hWnd, $iMsg, $iTimerID, $iTime

    Local Static $bToolTip = False
    Local $aEditPos = WinGetPos($g_hEdit)
    If @error Then Return ; Edit window not found
    Local $aMPos = MouseGetPos()

    If   ($aMPos[0] > $aEditPos[0]) And ($aMPos[0] < $aEditPos[0] + $aEditPos[2]) _
    And  ($aMPos[1] > $aEditPos[1]) And ($aMPos[1] < $aEditPos[1] + $aEditPos[3]) Then
        ToolTip(_WinAPI_GetWindowText($g_hEdit), $aEditPos[0], $aEditPos[1] - 25)
        $bToolTip = True
    Else
        If $bToolTip Then
            ToolTip("")
            $bToolTip = False
        EndIf
    EndIf
EndFunc   ;==>_TimerProc

Good luck, whatever way you'll choose :bye:

Edited by pixelsearch
Link to post
Share on other sites

@pixelsearch I like It! :thumbsup:

I did make a couple more little tweaks, however. o:)

In the _BrowseForFolder function I added another Global variable, for the hWnd of the dialog so it could be used in the _TimeProc function. And then I stored the hWnd of the dialog in the _BrowserProc function.

Func _BrowseForFolder($sInitDir, $sTitle = 'Browse For Folder', $sPrompt = 'Select a folder from the list below.', $hParent_hWnd = 0)

    Global $g_hBrowseDlg = 0, $g_hEdit = 0

...

Func _BrowseProc($hWnd, $iMsg, $wParam, $lParam)

    $g_hBrowseDlg = $hWnd

Then I changed the If conditional in the _TimerProc function from this:

If   ($aMPos[0] > $aEditPos[0]) And ($aMPos[0] < $aEditPos[0] + $aEditPos[2]) _
    And  ($aMPos[1] > $aEditPos[1]) And ($aMPos[1] < $aEditPos[1] + $aEditPos[3]) Then
        ToolTip(_WinAPI_GetWindowText($g_hEdit), $aEditPos[0], $aEditPos[1] - 25)
        $bToolTip = True
    Else
        If $bToolTip Then
            ToolTip("")
            $bToolTip = False
        EndIf
    EndIf

To this:

If   WinActive($g_hBrowseDlg) _
    And ($aMPos[0] > $aEditPos[0]) And ($aMPos[0] < $aEditPos[0] + $aEditPos[2]) _
    And ($aMPos[1] > $aEditPos[1]) And ($aMPos[1] < $aEditPos[1] + $aEditPos[3]) Then
        If Not $bToolTip Then ToolTip(_WinAPI_GetWindowText($g_hEdit), $aEditPos[0], $aEditPos[1])
        $bToolTip = True
    Else
        If $bToolTip Then
            ToolTip("")
            $bToolTip = False
        EndIf
    EndIf

These little tweaks accomplish 3 things:

1) The tooltip gets dismissed if the dialog stops being the active window. Otherwise the tooltip bleeds through to other windows if you happen to have the mouse positioned where the Edit control is, even if the dialog is hidden below the other windows.

2) If the tooltip is already displayed it doesn't try to display it again. It just leaves it alone. That eliminates flickering.

3) Instead of positioning the tooltip higher than the Edit control, I positioned it directly covering the Edit control. This is more consistent with Microsoft UI design guidelines. The article here states:

Quote

The difference between ordinary and in-place tooltips is positioning. By default, when the mouse pointer hovers over a region that has a tooltip associated with it, the tooltip is displayed adjacent to the region. However, tooltips are windows, and they can be positioned anywhere you choose by calling SetWindowPos. Creating an in-place tooltip is a matter of positioning the tooltip window so that it overlays the text string.

In fact, if you happen to have (or create) a folder name that's too long to display fully in the TreeView portion of the dialog, the dialog already displays an in-place tooltip for it within the TreeView control. So doing the same for the Edit control is more consistent with the over dialog design.

image.png.403cc4a0950cfcb0255b3bd2465664a1.pngimage.png.70c7c8961c7d276bf00d606558e09749.png

So here's the complete listing with my tweaks added:

#include <EditConstants.au3>
#include <GUIConstantsEx.au3>
#include <WinAPIDlg.au3>
#include <WinAPISysWin.au3>
#include <WindowsConstants.au3>

Opt('MustDeclareVars', 1)

ConsoleWrite('Returned Path = ' & _BrowseForFolder(@ScriptDir, 'Custom Title', 'Custom Prompt') & @TAB & '@extended = ' & @extended & @CRLF)

;===================================================================================================================================
Func _BrowseForFolder($sInitDir, $sTitle = 'Browse For Folder', $sPrompt = 'Select a folder from the list below.', $hParent_hWnd = 0)

    Global $g_hBrowseDlg = 0, $g_hEdit = 0
    Local $hTimerProc = DllCallbackRegister('_TimerProc', 'none', 'hwnd;uint;uint_ptr;dword')
    Local $iTimerID = _WinAPI_SetTimer(0, 0, 200, DllCallbackGetPtr($hTimerProc)) ; 200ms

    Global $s_BFF_Dialog_Title = $sTitle
    Local $hBrowseProc = DllCallbackRegister('_BrowseProc', 'int', 'hwnd;uint;lparam;ptr')
    Local $pBrowseProc = DllCallbackGetPtr($hBrowseProc)
    Local $pText = _WinAPI_CreateString($sInitDir)
    Local $sPath = _WinAPI_BrowseForFolderDlg('', $sPrompt, BitOR($BIF_RETURNONLYFSDIRS, $BIF_EDITBOX, $BIF_VALIDATE), $pBrowseProc, $pText, $hParent_hWnd)
    _WinAPI_FreeMemory($pText)
    DllCallbackFree($hBrowseProc)

    _WinAPI_KillTimer(0, $iTimerID)
    DllCallbackFree($hTimerProc)

    If $sPath = '' Then
        Return SetExtended(-1, '')
    Else
        Return $sPath
    EndIf
EndFunc   ;==>_BrowseForFolder

;===============================================
Func _BrowseProc($hWnd, $iMsg, $wParam, $lParam)

    $g_hBrowseDlg = $hWnd
    Local $sPath
    Switch $iMsg
        Case $BFFM_INITIALIZED
            _WinAPI_SetWindowText($hWnd, $s_BFF_Dialog_Title) ; set the title
            _SendMessage($hWnd, $BFFM_SETSELECTIONW, 1, $lParam) ; preselect specified folder

            $g_hEdit = ControlGetHandle($hWnd, '', 'Edit1')
            _SendMessage($g_hEdit, $EM_SETREADONLY, 1, 0) ; make edit control readonly

             ; turn off tab-stop for edit control and give focus to treeview at startup
            Local $Style = _WinAPI_GetWindowLong($g_hEdit, $GWL_STYLE)
            _WinAPI_SetWindowLong($g_hEdit, $GWL_STYLE, BitAND($Style, BitNOT($WS_TABSTOP)))

        Case $BFFM_SELCHANGED
            $sPath = _WinAPI_ShellGetPathFromIDList($wParam) ; get the selected path as a string
            If @error Or ($sPath = '') Then
                _SendMessage($hWnd, $BFFM_ENABLEOK, 0, 0) ; disable OK button for non-folder selections
            Else
                ControlSetText($hWnd, '', 'Edit1', $sPath) ; update edit control to show full path
                 ; make sure end of path is visible in edit control
                 _SendMessage(ControlGetHandle($hWnd, '', 'Edit1'), $EM_SETSEL, StringLen($sPath), StringLen($sPath))
                _SendMessage($hWnd, $BFFM_ENABLEOK, 0, 1) ; enable OK button
            EndIf
    EndSwitch

    Return 0
EndFunc   ;==>_BrowseProc

;===============================================
Func _TimerProc($hWnd, $iMsg, $iTimerID, $iTime)
    #forceref $hWnd, $iMsg, $iTimerID, $iTime

    Local Static $bToolTip = False
    Local $aEditPos = WinGetPos($g_hEdit)
    If @error Then Return ; Edit window not found
    Local $aMPos = MouseGetPos()

    If   WinActive($g_hBrowseDlg) _
    And ($aMPos[0] > $aEditPos[0]) And ($aMPos[0] < $aEditPos[0] + $aEditPos[2]) _
    And ($aMPos[1] > $aEditPos[1]) And ($aMPos[1] < $aEditPos[1] + $aEditPos[3]) Then
        If Not $bToolTip Then ToolTip(_WinAPI_GetWindowText($g_hEdit), $aEditPos[0], $aEditPos[1])
        $bToolTip = True
    Else
        If $bToolTip Then
            ToolTip("")
            $bToolTip = False
        EndIf
    EndIf
EndFunc   ;==>_TimerProc

Thanks again for your invaluable assistance with this. It's ended up being a pretty slick little include, and works exactly as I'd hoped.

Link to post
Share on other sites

Glad you made it !
As I'm having a busy day today, then I'll read your last post carefully later in the evening.

One thing I quickly noticed concerns the following line :

If Not $bToolTip Then ToolTip(_WinAPI_GetWindowText($g_hEdit), $aEditPos[0], $aEditPos[1])

It will display a wrong "static" tooltip if the user does the following :
* leave the mouse cursor over the edit control
* move through items in the treeview using the keyboard
* the tooltip won't display the correct path while the user uses keyup/keydown in the treeview

You'll decide if it's acceptable or not to have a tooltip not reflecting in real time the changes of path, when the keyboard is used to move through items in the treeview, while the mouse pointer is waiting patiently over the edit control.

See you later

Link to post
Share on other sites

Good catch! 

The fix:

Add another Global variable to _BrowseForFolder function to track if the path in the Treeview has changed

Global $g_hBrowseDlg = 0, $g_hEdit = 0, $b_BFF_PathChanged = False

In the _BrowseProc function, set that variable to True in the $BFFM_SELCHANGED event

Case $BFFM_SELCHANGED
    $b_BFF_PathChanged = True

In the _TimerProc function, include that variable in the decision whether to update the Tooltip, and then reset it back to False again

If (Not $bToolTip) Or $b_BFF_PathChanged Then ToolTip(_WinAPI_GetWindowText($g_hEdit), $aEditPos[0], $aEditPos[1])
$b_BFF_PathChanged = False
$bToolTip = True

Now you can leave the mouse over the Edit control to display the Tooltip and scroll through the TreeView with the keyboard and see the Tooltip update immediately.

Link to post
Share on other sites

Hi TimRude,
You did great improvements, by covering the Edit control with the ToolTip, also using $g_hBrowseDlg to test if the dialog window is active or not.

As the test on WinActive($g_hBrowseDlg) becomes important in the function _TimerProc() then I think there is no point anymore on testing in all cases the position of the edit control and the mouse position during the function _TimerProc() .  Reordering its lines like the following seem a bit more efficient, especially during the phases when the dialog window isn't active. Also we'll get rid of the (now) completely useless line "If @error Then Return ; Edit window not found"

Func _TimerProc($hWnd, $iMsg, $iTimerID, $iTime)
    #forceref $hWnd, $iMsg, $iTimerID, $iTime

    Local Static $bToolTip = False
    If WinActive($g_hBrowseDlg) Then
        Local $aEditPos = WinGetPos($g_hEdit)
        Local $aMPos = MouseGetPos()

        If  ($aMPos[0] > $aEditPos[0]) And ($aMPos[0] < $aEditPos[0] + $aEditPos[2]) _
        And ($aMPos[1] > $aEditPos[1]) And ($aMPos[1] < $aEditPos[1] + $aEditPos[3]) Then
            If (Not $bToolTip) Or $b_BFF_PathChanged Then ToolTip(_WinAPI_GetWindowText($g_hEdit), $aEditPos[0], $aEditPos[1])
            $b_BFF_PathChanged = False
            $bToolTip = True
            Return
        EndIf
    EndIf

    If $bToolTip Then
        ToolTip("")
        $bToolTip = False
    EndIf
EndFunc   ;==>_TimerProc

In case you don't have any new modification, maybe you could post the final amended script for users that might be interested.
Well... it was an interesting journey, it's time for new AutoIt adventures :)

Link to post
Share on other sites

Finished product. The only other final modifications were cosmetic, mainly changing the function names _BrowseProc and _TimerProc to _BFF_BrowseProc and _BFF_TimerProc to help avoid any conflicts with potentially existing function names in code that this include might be used with, and adding a UDF header.

#include-once

#include <WinAPIDlg.au3>
#include <WinAPISysWin.au3>
#include <EditConstants.au3>
#include <WindowsConstants.au3>

; #FUNCTION# ====================================================================================================================
; Name ..........: _BrowseForFolder
; Description ...: Display older style Browse for Folder dialog with some enhancements
; Syntax ........: _BrowseForFolder([$sInitDir = '', [$sTitle = 'Browse For Folder'[, $sPrompt = 'Select a folder from the list below.'[, $hParent_hWnd = 0]]]])
; Parameters ....: $sInitDir            - [optional] a string value. Initial folder to display in dialog. If blank, starts at 'This PC'.
;                  $sTitle              - [optional] a string value. Title of dialog box. Default is 'Browse For Folder'.
;                  $sPrompt             - [optional] a string value. Prompt text for dialog box. Default is 'Select a folder from the list below.'.
;                  $hParent_hWnd        - [optional] a handle value. Handle of parent window. Default is 0.
; Return values .: Folder path selected. If Cancel or an invalid selection is made, returns nothing and sets @extended to -1.
; Author ........: TimRude, pixelsearch
; Modified ......:
; Remarks .......: Initially based on code in the help for _WinAPI_BrowseForFolderDlg function.
; Related .......: _WinAPI_BrowseForFolderDlg by Yashied, jpm
; ===============================================================================================================================
Func _BrowseForFolder($sInitDir = '', $sTitle = 'Browse For Folder', $sPrompt = 'Select a folder from the list below.', $hParent_hWnd = 0)
    Global $g_hBrowseDlg = 0, $g_hEdit = 0, $b_BFF_PathChanged = False
    Local $hTimerProc = DllCallbackRegister('_BFF_TimerProc', 'none', 'hwnd;uint;uint_ptr;dword')
    Local $iTimerID = _WinAPI_SetTimer(0, 0, 200, DllCallbackGetPtr($hTimerProc)) ; 200ms
    Global $s_BFF_Dialog_Title = $sTitle
    Local $hBrowseProc = DllCallbackRegister('_BFF_BrowseProc', 'int', 'hwnd;uint;lparam;ptr')
    Local $pBrowseProc = DllCallbackGetPtr($hBrowseProc)
    Local $pText = _WinAPI_CreateString($sInitDir)
    Local $sPath = _WinAPI_BrowseForFolderDlg('', $sPrompt, BitOR($BIF_RETURNONLYFSDIRS, $BIF_EDITBOX), $pBrowseProc, $pText, $hParent_hWnd)
    _WinAPI_FreeMemory($pText)
    DllCallbackFree($hBrowseProc)
    _WinAPI_KillTimer(0, $iTimerID)
    ToolTip('') ; ensure tooltip is dismissed when dialog is dismissed
    DllCallbackFree($hTimerProc)
    If $sPath = '' Then
        Return SetExtended(-1, '')
    Else
        Return $sPath
    EndIf
EndFunc   ;==>_BrowseForFolder

Func _BFF_BrowseProc($hWnd, $iMsg, $wParam, $lParam) ; Browse dialog callback
    $g_hBrowseDlg = $hWnd
    Local $sPath
    Switch $iMsg
        Case $BFFM_INITIALIZED
            _WinAPI_SetWindowText($hWnd, $s_BFF_Dialog_Title) ; set the title
            _SendMessage($hWnd, $BFFM_SETSELECTIONW, 1, $lParam) ; preselect specified folder
            $g_hEdit = ControlGetHandle($hWnd, '', 'Edit1')
            _SendMessage($g_hEdit, $EM_SETREADONLY, 1, 0) ; make edit control readonly
            ; turn off tab-stop for edit control and give focus to treeview at startup
            Local $Style = _WinAPI_GetWindowLong($g_hEdit, $GWL_STYLE)
            _WinAPI_SetWindowLong($g_hEdit, $GWL_STYLE, BitAND($Style, BitNOT($WS_TABSTOP)))
        Case $BFFM_SELCHANGED
            $b_BFF_PathChanged = True
            $sPath = _WinAPI_ShellGetPathFromIDList($wParam) ; get the selected path as a string
            If @error Or ($sPath = '') Then
                _SendMessage($hWnd, $BFFM_ENABLEOK, 0, 0) ; disable OK button for non-folder selections
            Else
                ControlSetText($hWnd, '', 'Edit1', $sPath) ; update edit control to show full path
                ; make sure end of path is visible in edit control
                _SendMessage(ControlGetHandle($hWnd, '', 'Edit1'), $EM_SETSEL, StringLen($sPath), StringLen($sPath))
                _SendMessage($hWnd, $BFFM_ENABLEOK, 0, 1) ; enable OK button
            EndIf
    EndSwitch
    Return 0
EndFunc   ;==>_BFF_BrowseProc

Func _BFF_TimerProc($hWnd, $iMsg, $iTimerID, $iTime)
    #forceref $hWnd, $iMsg, $iTimerID, $iTime
    Local Static $bToolTip = False
    If WinActive($g_hBrowseDlg) Then
        Local $aEditPos = WinGetPos($g_hEdit)
        Local $aMPos = MouseGetPos()
        If  ($aMPos[0] > $aEditPos[0]) And ($aMPos[0] < $aEditPos[0] + $aEditPos[2]) _
        And ($aMPos[1] > $aEditPos[1]) And ($aMPos[1] < $aEditPos[1] + $aEditPos[3]) Then
            If (Not $bToolTip) Or $b_BFF_PathChanged Then ToolTip(_WinAPI_GetWindowText($g_hEdit), $aEditPos[0], $aEditPos[1])
            $b_BFF_PathChanged = False
            $bToolTip = True
            Return
        EndIf
    EndIf
    ; If we get to here, either the dialog isn't active or the mouse isn't over the Edit control, so turn off the tooltip
    If $bToolTip Then
        ToolTip('')
        $bToolTip = False
    EndIf
EndFunc   ;==>_BFF_TimerProc

 

Edited by TimRude
Add command to ensure tooltip is dismissed when dialog closes
Link to post
Share on other sites
  • TimRude changed the title to Older Style Browse for Folder Dialog Routine with Enhancements

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    No registered users viewing this page.

×
×
  • Create New...