Jump to content

Recommended Posts

Posted (edited)

Hi everybody :)

This script displays a big MessageBox having a height higher than @DesktopHeight
While the MessageBox is displayed, you can "navigate" inside it like this :

* Mouse Left-click drag : to move the window up and down (displaying a new panel of lines)
* Mouse Right-click : to display the window at its initial position
* Up key
* Down key
* PageUp Key   (Fn + up key on some laptops)
* PageDown Key (Fn + down key on some laptops)
* Home Key     (Fn + left key on some laptops)
* End Key      (Fn + right key on some laptops)

The 6 keyboard keys allow to navigate inside the window. Click on a button (placed at top of the window) to make your choice.

All this could have been scripted more easily using a GUI, an Edit control containing the text to display, plus the buttons to choose from, but I just wanted to try it using a native MessageBox

#include <APISysConstants.au3>  ; $GCL_HCURSOR
#include <ButtonConstants.au3>
#include <EditConstants.au3>
#include <GUIConstantsEx.au3>
#include <Misc.au3>
#include <MsgBoxConstants.au3>
#include <StaticConstants.au3>
#include <WinAPIRes.au3>        ; _WinAPI_LoadCursor()
#include <WinAPISysWin.au3>     ; _WinAPI_SetClassLongEx()
#include <WindowsConstants.au3>

Opt("MustDeclareVars", 1) ;0=no, 1=require pre-declaration
Opt("GUICloseOnESC", 0) ;1=ESC closes (default), 0=ESC won't close

Global $g_hGUI, $g_hDLL = DllOpen("user32.dll")
Global $g_aCaption, $g_sTitle

Example()

;===========================================
Func Example()

    $g_hGUI = GUICreate("Big MsgBox example (7b)", 400, 200)

    GUICtrlCreateLabel("Number of lines in MsgBox (2 - 1588)", 10, 20, 180, 20, $SS_SUNKEN)
    Local $idNbLines = GUICtrlCreateInput("200", 200, 20, 35, 20, BitOR($GUI_SS_DEFAULT_INPUT, $ES_NUMBER)), $iNbLines
    GUICtrlSetLimit($idNbLines, 4)

    Local $idMsgBox = GUICtrlCreateButton("Big MsgBox", 10, 60, 100, 25, $BS_DEFPUSHBUTTON)
    Local $idExit = GUICtrlCreateButton("Exit", 10, 110, 100, 25)

    GUISetState()

    While 1
        Switch GUIGetMsg()
            Case $GUI_EVENT_CLOSE, $idExit
                ExitLoop

            Case $idMsgBox
                Local $iRet = Prepare_MsgBox(GUICtrlRead($idNbLines))
                If $iRet Then MsgBox($MB_TOPMOST, "Big MsgBox return value : " & $iRet, Retrieve_Caption($iRet), 0, $g_hGUI)
                GUICtrlSetState($idNbLines, $GUI_FOCUS)
        EndSwitch
    WEnd

    DllClose($g_hDLL)
EndFunc   ;==>Example

;===========================================
Func Prepare_MsgBox(Const $iNbLines)

    If $iNbLines < 2 Or $iNbLines > 1588 Then
        MsgBox($MB_TOPMOST, "Error", "Enter a number of lines between 2 and 1588", 0, $g_hGUI)
        Return ; 0
    EndIf

    Local $sTxt = "Line 1 : first line" & @crlf
    For $i = 2 To $iNbLines - 1
        $sTxt &= "Line " & $i & @crlf
    Next
    $sTxt &= "Line " & $iNbLines & " : last line"

    Dim $g_aCaption[3][3] = [ ["Cancel"], ["Try Again"], ["Continue"] ] ; 1st column = button caption, 2nd = button handle, 3rd = button ID
    $g_sTitle = "MsgBox with 3 buttons"
    Local $iChoice = _MsgBox(BitOr($MB_CANCELTRYCONTINUE, $MB_TOPMOST), $g_sTitle, $sTxt, 0, $g_hGUI)

;~  Dim $g_aCaption[2][3] = [ ["Yes"], ["No"] ]
;~  $g_sTitle = "MsgBox with 2 buttons"
;~  Local $iChoice = _MsgBox(BitOr($MB_YESNO, $MB_TOPMOST), $g_sTitle, $sTxt, 0, $g_hGUI)

;~  Dim $g_aCaption[1][3] = [ ["Hello"] ]
;~  $g_sTitle = "MsgBox with 1 button"
;~  Local $iChoice = _MsgBox(BitOr($MB_OK, $MB_TOPMOST), $g_sTitle, $sTxt, 0, $g_hGUI)

    Return $iChoice
EndFunc   ;==>Prepare_MsgBox

;===========================================
Func _MsgBox($iFlag, $g_sTitle, $sText, $iTimeOut = 0, $hWnd = 0)

    GUIRegisterMsg($WM_HELP , "WM_HELP")

    Local $hTimerProc = DllCallbackRegister('_MsgBoxTimerProc', 'none', 'hwnd;uint;uint_ptr;dword')
    Local $iTimerID = _WinAPI_SetTimer(0, 0, 10, DllCallbackGetPtr($hTimerProc))

    Local $iChoice = MsgBox($iFlag, $g_sTitle, $sText, $iTimeOut, $hWnd)

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

    GUIRegisterMsg($WM_HELP, "")

    Return $iChoice
EndFunc   ;==>_MsgBox

;===========================================
Func _MsgBoxTimerProc($hWnd, $iMsg, $iTimerID, $iTime)

    If WinExists($g_sTitle) Then
        _WinAPI_KillTimer(0, $iTimerID)

        Local $hMsgBox = WinGetHandle($g_sTitle)
        For $i = 0 To Ubound($g_aCaption) - 1
            ControlSetText($hMsgBox, "", "Button" & ($i + 1), $g_aCaption[$i][0])
            $g_aCaption[$i][1] = ControlGetHandle($hMsgBox, "", "Button" & ($i + 1))
            $g_aCaption[$i][2] = _WinAPI_GetDlgCtrlID($g_aCaption[$i][1]) ; remember OK button ID is 1 ($MB_OKCANCEL) or 2 ($MB_OK) ...
            ; ... msdn "If a message box has a Cancel button, the function returns the IDCANCEL value (2) if either the ESC key is pressed
            ; or the Cancel button is selected."
            ; "If the message box has no Cancel button, pressing ESC will no effect - unless an MB_OK button is present.
            ; If an MB_OK button is displayed and the user presses ESC, the return value will be IDOK (1)" [personal: only one case for this]
        Next

        Local $aPosMsgBox = WinGetPos($hMsgBox)
        Local $bTooHigh = ($aPosMsgBox[3] > @DesktopHeight + 15) ? True : False
        If $bTooHigh Then
            Local $aPosButton, $aPosStatic = ControlGetPos($hMsgBox, "", "Static1") ; the text area
            For $i = 0 To Ubound($g_aCaption) - 1
                $aPosButton = ControlGetPos($hMsgBox, "", "Button" & ($i + 1))
                WinMove($g_aCaption[$i][1], "", $aPosButton[0], $aPosStatic[1])
            Next
            WinMove(ControlGetHandle($hMsgBox, "", "Static1"), "", $aPosStatic[0], $aPosStatic[1] + $aPosButton[3] + 10)
            _WinAPI_RedrawWindow($hMsgBox) ; +++
            Send("{F1}") ; => Func WM_HELP
        EndIf
    EndIf
EndFunc   ;==>_MsgBoxTimerProc

;==============================================
Func WM_HELP($hWnd, $iMsg, $wParam, $lParam)
    #forceref $hWnd, $iMsg, $wParam, $lParam

    If WinExists($g_sTitle) Then ; MessageBox window always exists at this stage
        Local $hMsgBox = WinGetHandle($g_sTitle)
        Local $aPosMsgBox = WinGetPos($hMsgBox), $aPosMsgBox_Init = $aPosMsgBox
        Local $aWinPos, $aMPos, $aMPosOld

        Local $hCursor = _WinAPI_LoadCursor(0, $OCR_SIZEALL) ; $OCR_SIZENS ok too
        Local $iPrev = _WinAPI_SetClassLongEx($hMsgBox, $GCL_HCURSOR, $hCursor) ; see "147c.au3"

        _ClearBuffer("0D") ; Enter key (in case pressed too long on button 'Big MsgBox' in main GUI, so it won't select a button in MsgBox)

        While 1
            If WinActive($hMsgBox) Then
                Select
                    Case _IsPressed("01", $g_hDLL) ; "01" = Left mouse button
                        $aMPosOld = MouseGetPos()
                        While _IsPressed("01", $g_hDLL)
                            $aMPos = MouseGetPos()
                            If $aMPos[1] <> $aMPosOld[1] Then
                                $aWinpos = WinGetPos($hMsgBox)
                                If ($aMPos[0] - 1 > $aWinpos[0]) And ($aMPos[0] + 1 < $aWinpos[0] + $aWinpos[2]) Then
                                    WinMove($hMsgBox, "", Default, $aWinpos[1] + ($aMPos[1] - $aMPosOld[1]))
                                    $aMPosOld = $aMPos
                                EndIf
                            EndIf
                            Sleep(10)
                        Wend

                    Case _IsPressed("02", $g_hDLL) ; Right mouse button
                        WinMove($hMsgBox, "", $aPosMsgBox_Init[0], $aPosMsgBox_Init[1])

                    Case _IsPressed("09", $g_hDLL) ; Tab key
                        _ClearBuffer("09")
                        _ChangeFocus($hMsgBox, "09")

                    Case _IsPressed("0D", $g_hDLL) ; Enter key
                        _ClearBuffer("0D")
                        ControlClick($hMsgBox, "", ControlGetFocus($hMsgBox))

                    Case _IsPressed("1B", $g_hDLL) ; Esc key
                        _ClearBuffer("1B")
                        For $i = 0 To Ubound($g_aCaption) - 1
                            If $g_aCaption[$i][2] = 2 Then ; Cancel button (or OK button when alone : see msdn notes above)
                                ControlClick($hMsgBox, "", $g_aCaption[$i][2])
                            EndIf
                        Next

                    Case _IsPressed("21", $g_hDLL) ; PageUp Key (Fn + up key on most laptops)
                        _ClearBuffer("21")
                        $aPosMsgBox = WinGetPos($hMsgBox)
                        WinMove( $hMsgBox, "", Default, ($aPosMsgBox[1] + @DesktopHeight > 0) ? 0 : $aPosMsgBox[1] + @DesktopHeight )

                    Case _IsPressed("22", $g_hDLL) ; PageDown Key (Fn + down key on most laptops)
                        _ClearBuffer("22")
                        $aPosMsgBox = WinGetPos($hMsgBox)
                        WinMove( $hMsgBox, "", Default, _
                            ($aPosMsgBox[3] + $aPosMsgBox[1] - @DesktopHeight < @DesktopHeight + 15) ? @DesktopHeight - $aPosMsgBox[3] : $aPosMsgBox[1] - @DesktopHeight )

                    Case _IsPressed("23", $g_hDLL) ; End Key (Fn + right key on most laptops)
                        $aPosMsgBox = WinGetPos($hMsgBox)
                        WinMove($hMsgBox, "", Default, @DesktopHeight - $aPosMsgBox[3])

                    Case _IsPressed("24", $g_hDLL) ; Home Key (Fn + left key on most laptops)
                        WinMove($hMsgBox, "", Default, 0)

                    Case _IsPressed("25", $g_hDLL) ; Left key
                        _ClearBuffer("25")
                        _ChangeFocus($hMsgBox, "25")

                    Case _IsPressed("26", $g_hDLL) ; Up Key
                        $aPosMsgBox = WinGetPos($hMsgBox)
                        WinMove($hMsgBox, "", Default, ($aPosMsgBox[1] + 12 > 0) ? 0 : $aPosMsgBox[1] + 12)

                    Case _IsPressed("27", $g_hDLL) ; Right key
                        _ClearBuffer("27")
                        _ChangeFocus($hMsgBox, "27")

                    Case _IsPressed("28", $g_hDLL) ; Down Key
                        $aPosMsgBox = WinGetPos($hMsgBox)
                        WinMove( $hMsgBox, "", Default, _
                            ($aPosMsgBox[3] + $aPosMsgBox[1] - 12 < @DesktopHeight) ? @DesktopHeight - $aPosMsgBox[3] : $aPosMsgBox[1] - 12 )
                EndSelect
            EndIf

            If Not BitAND(WinGetState($hMsgBox), $WIN_STATE_VISIBLE) Then ; one of MsgBox buttons was chosen by user
                ExitLoop
            EndIf

            Sleep(10)
        WEnd

        _WinAPI_SetClassLongEx($hMsgBox, $GCL_HCURSOR, $iPrev) ; needed, or cursor $OCR_SIZEALL will show... in final MsgBox from main loop
    EndIf

    Return $GUI_RUNDEFMSG
EndFunc   ;==>WM_HELP

;==============================================
Func _ClearBuffer($sKey)

    While _IsPressed($sKey, $g_hDLL)
        Sleep(10)
    Wend
EndFunc   ;==>_ClearBuffer

;==============================================
Func _ChangeFocus($hMsgBox, $sKey)

    Local $iCtrlGetFocus, $iCtrlSetFocus, $iStyle
    $iCtrlGetFocus = StringRight(ControlGetFocus($hMsgBox), 1) ; 1/2/3 (last char of "Button1"/"Button2"/"Button3")

    If $sKey = "27" Or $sKey = "09" Then ; Right key (27) or Tab (09)
        $iCtrlSetFocus = $iCtrlGetFocus < Ubound($g_aCaption) ? $iCtrlGetFocus + 1 : 1
    Else ; Left key (25)
        $iCtrlSetFocus = $iCtrlGetFocus > 1 ? $iCtrlGetFocus - 1 : Ubound($g_aCaption)
    EndIf

    ControlFocus($hMsgBox, "", "Button" & $iCtrlSetFocus)

    $iStyle = _WinAPI_GetWindowLong($g_aCaption[$iCtrlGetFocus - 1][1], $GWL_STYLE)
    _WinAPI_SetWindowLong($g_aCaption[$iCtrlGetFocus - 1][1], $GWL_STYLE, BitXOR($iStyle, $BS_DEFPUSHBUTTON))

    $iStyle = _WinAPI_GetWindowLong($g_aCaption[$iCtrlSetFocus - 1][1], $GWL_STYLE)
    _WinAPI_SetWindowLong($g_aCaption[$iCtrlSetFocus - 1][1], $GWL_STYLE, BitOR($iStyle, $BS_DEFPUSHBUTTON))
EndFunc   ;==>_ChangeFocus

;===========================================
Func Retrieve_Caption($iRet)

    If Ubound($g_aCaption) = 1 Then ; $MB_OK
        Return $g_aCaption[0][0]
    Else
        For $i = 0 To Ubound($g_aCaption) - 1
            If $g_aCaption[$i][2] = $iRet Then Return $g_aCaption[$i][0] ; button ID always = $iRet (except for $MB_OK, see note msdn above)
        Next
    EndIf
    MsgBox($MB_TOPMOST, "Warning", "This message should never be displayed", 0, $g_hGUI)
EndFunc   ;==>Retrieve_Caption

BigMsgBox.png.efda2805087f4739eaa38b3ed5fc6325.png

Update Feb 24, 2025 : Left key, Right key, Tab, Enter, Spacebar, Esc
These 6 keys react now like they do in any MsgBox

Feb 27, 2025 : minor correction

Edited by pixelsearch
Feb 27, 2025 : script updated

"I think you are searching a bug where there is no bug..."

Posted

Thank you. Works great.

Only thing is visual issue at last line for me. But no problem.

image.png.db743817ef613a1f29f5a7aee1ba1731.png

BR

funkey

Programming today is a race between software engineers striving to
build bigger and better idiot-proof programs, and the Universe
trying to produce bigger and better idiots.
So far, the Universe is winning.

Posted (edited)

Thanks to you guys, who found an interest in this experimental script :)

@funkey tomorrow I'll be able to run the script on a Win11 laptop, to see if the little visual issue you described will show there.

Meanwhile, I'm trying to add a few useful _IsPressed() keys inside Func WM_HELP()
For example, in a MsgBox, one press on Right key (or on Tab) should focus the button on the right of the actual focused button. In the pic of my preceding post, the "Cancel" button should then be "unfocused" and the "Try again" button should be focused :

1rijghtkeypressed.png.7fd2a4dc4d04ab5b87619a41fbfa2a5b.png

Unfortunately, in the doctored MessageBox from the script, nothing happens when you press the Right key !
The beginning of the solution could be scripted like this :

Case _IsPressed("27", $g_hDLL) ; Right key (nearly same code for Tab key = 09)
    While _IsPressed("27", $g_hDLL)
        Sleep(10)
    Wend
    Local $iCtrlGetFocus = StringRight(ControlGetFocus($hMsgBox), 1) ; 1/2/3 (last char of "Button1"/"Button2"/"Button3")
    Local $iCtrlSetFocus = $iCtrlGetFocus < Ubound($g_aCaption) ? $iCtrlGetFocus + 1 : 1
    ControlFocus($hMsgBox, "", "Button" & $iCtrlSetFocus) ; correct display after window change. How to improve it ?

1) But that's not enough. If you insert the 7 preceding lines in the script (inside the While 1 loop of Func Help) then run the script, press one time the Right key, then... the focus doesn't change : it is still on the 'Cancel' button :(

BUT now if you select manually another window, then back to the MsgBox window, you'll see that the focus IS correct ('Try again') . So the question is : what should be done easily to "refresh" the MsgBox so it displays correctly the new focused button ?

Because swapping manually windows to refresh the focus isn't user friendly at all, _WinAPI_RedrawWindow or _WinAPI_SetWindowPos don't seem to help here (tested) . So if someone got a simple solution for this "focused button refresh", I'll take it, thanks.

2) In fact I just found a working solution for this "non refreshment" issue, based on $BS_DEFPUSHBUTTON : if we remove it from the button that doesn't have the focus anymore ('Cancel') and add it to the button having the focus ('Try again') then the display of the new focused button is always correct.

So if no new idea appears during the week-end, then I'll add the $BS_DEFPUSHBUTTON part in the script, at the beginning of the week (taking care of Left key too etc...)

Thanks for reading and have a great week-end everybody :bye:

Edit Feb 24, 2025 : fixed all this in an updated script (1st post)

Edited by pixelsearch

"I think you are searching a bug where there is no bug..."

Posted

Update Feb 24, 2025 : Left key, Right key, Tab, Enter, Spacebar, Esc
These 6 keys react now like they do in any MsgBox

Listing updated in 1st post.

"I think you are searching a bug where there is no bug..."

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