Jump to content

Recommended Posts

Posted (edited)

Hi all, here's a UDF for creating Modeless or Modal Dialogs, or loading them in from a resource.

Basically you build or load a template, and launch the window based on that.

Original Post:

Spoiler

Hi all,

I've been having a play around today and thought someone might find it interesting.

Basically you can define a dialog template, and with that you can create modal windows outside of the ones provided by MsgBox & InputBox.  Theoretically these can also be added to compiled scripts as resources - but that'll be the next thing to sus out!

You'll see "Script Activity!" is continuously written to the console - but this stops with the "About" window open. (DialogBoxIndirectParam doesn't return until EndDialog is called).  
DialogBoxIndirectParam will also return different codes depending on which button you push ;)

Its also worth noting, the dialog coords are in "Dialog Units" - not pixels.  These base values apparently change depending on scaling and the typeface selected. So controls can be a bit squidgy placement wise, but it should help with legibility across a range of different setups. If you believe the internet anyway!

#AutoIt3Wrapper_Au3Check_Parameters=-q -d -w 1 -w 2 -w 3 -w 4 -w 5 -w 6 -w 7
;#AutoIt3Wrapper_UseX64=N

#include <WinAPISys.au3>
#include <WinAPISysWin.au3>
#include <GuiConstants.au3>
#include <WindowsConstants.au3>
#include <FontConstants.au3>
#include <GuiMenu.au3>

Global Const $tagDLGTEMPLATEEX_Base = "struct;" & _
            "word dlgVer;" & _
            "word signature;" & _
            "dword helpID;" & _
            "dword exStyle;" & _
            "dword style;" & _
            "word cDlgItems;" & _
            "short x;" & _
            "short y;" & _
            "short cx;" & _
            "short cy;" & _
            "word menu[%d];" & _
            "word windowClass[%d];" & _
            "wchar title[%d];" & _
            "word pointsize;" & _
            "word weight;" & _
            "byte italic;" & _
            "byte charset;" & _
            "wchar typeface[%d];" & _
            "endstruct;" & _
            "align 4;"

Global Const $tagDLGITEMTEMPLATEEX_Base = "struct;" & _
            "dword helpID;" & _
            "dword exStyle;" & _
            "dword style;" & _
            "short x;" & _
            "short y;" & _
            "short cx;" & _
            "short cy;" & _
            "dword id;" & _
            "word windowClass[%d];" & _
            "wchar title[%d];" & _
            "word extraCount;" & _
            "endstruct;"

Global Const $IDM_ABOUT = 104, $IDM_EXIT = 105
Global $hGUI, $hDlgProc, $pDlgProc

Example()

Func Example()
    $hDlgProc = DllCallbackRegister("DlgProc", "int_ptr", "hwnd;uint;wparam;lparam")
    $pDlgProc = DllCallbackGetPtr($hDlgProc)

    $hGUI = GUICreate("Example GUI", 400, 200)
    GUICtrlCreateLabel("Goto: Help > About to launch the dialog.", 10, 60, 380, 20, BitOR($SS_CENTERIMAGE, $SS_CENTER))
    GUIRegisterMsg($WM_COMMAND, "WM_COMMAND")

    Local $hMainMenu = _GUICtrlMenu_CreateMenu()
    _GUICtrlMenu_SetMenu($hGUI, $hMainMenu)

    Local $hFileMenu = _GUICtrlMenu_CreateMenu()
    _GUICtrlMenu_AppendMenu($hFileMenu, $MF_ENABLED, $IDM_EXIT, "E&xit")

    Local $hHelpMenu = _GUICtrlMenu_CreateMenu()
    _GUICtrlMenu_AppendMenu($hHelpMenu, $MF_ENABLED, $IDM_ABOUT, "&About")

    _GUICtrlMenu_InsertMenuItem($hMainMenu, 0, "&File", 0, $hFileMenu)
    _GUICtrlMenu_InsertMenuItem($hMainMenu, 1, "&Help", 0, $hHelpMenu)
    _GUICtrlMenu_DrawMenuBar($hGUI)
    GUISetState()

    Local $hTimer = TimerInit()
    Do
        If TimerDiff($hTimer) > 1000 Then
            ConsoleWrite("Scipt Activity!" & @CRLF)
            $hTimer = TimerInit()
        EndIf
    Until GUIGetMsg() = $GUI_EVENT_CLOSE

    GUIRegisterMsg($WM_COMMAND, "")
    GUIDelete($hGUI)
    DllCallbackFree($hDlgProc)
EndFunc   ;==>Example

Func WM_COMMAND($hWnd, $iMsg, $wParam, $lParam)
    #forceref $hWnd, $iMsg, $wParam, $lParam

    Switch _WinAPI_LoWord($wParam)
        Case $IDM_EXIT
            _WinAPI_PostMessage($hWnd, $WM_SYSCOMMAND, $SC_CLOSE, 0)

        Case $IDM_ABOUT
            Local $tDialog = CreateDlg("About", "Hello World!!")
            Local $aCall = DllCall("User32.dll", "int_ptr", "DialogBoxIndirectParamW", "handle", 0, "struct*", $tDialog, "hwnd", $hWnd, "ptr", $pDlgProc, "dword", 0)
            ConsoleWrite("Return: " & $aCall[0] & @CRLF)
            _WinAPI_SetActiveWindow($hWnd)

    EndSwitch

    Return $GUI_RUNDEFMSG
EndFunc   ;==>WM_COMMAND

Func DlgProc($hWnd, $iMsg, $wParam, $lParam)
    Switch $iMsg
        Case $WM_SYSCOMMAND
            If BitAND(0xFFF0, $wParam) = $SC_CLOSE Then
                DllCall("User32.dll", "bool", "EndDialog", "hwnd", $hWnd, "int_ptr", $IDCANCEL)
                Return 0
            EndIf

        Case $WM_COMMAND
            Switch _WinAPI_LoWord($wParam)
                Case $IDOK, $IDCANCEL
                    DllCall("User32.dll", "bool", "EndDialog", "hwnd", $hWnd, "int_ptr", $wParam)
                    Return 0

            EndSwitch

    EndSwitch

    Return _WinAPI_DefWindowProcW($hWnd, $iMsg, $wParam, $lParam)
EndFunc

Func CreateDlg($sTitle, $sMessage)
    Local $sTypeFace = "MS Shell Dlg"

    Local $tagDLGTEMPLATEEX = StringFormat($tagDLGTEMPLATEEX_Base, _
        1, 1, _
        StringLen($sTitle) + 1, _
        StringLen($sTypeFace) + 1)

    Local $iItemCnt = 3
    Local $asTitles = [$sMessage, "OK", "Cancel"]

    Local $tDlgTemplateItem, $tagDLGTEMPLATEITEMEX
    For $i = 1 To $iItemCnt
        $tagDLGTEMPLATEITEMEX = StringFormat($tagDLGITEMTEMPLATEEX_Base, 2, StringLen($asTitles[$i - 1]) + 1)
        $tDlgTemplateItem = DllStructCreate($tagDLGTEMPLATEITEMEX)
        $tagDLGTEMPLATEEX &= StringFormat("byte item%d[%d];", $i, DllStructGetSize($tDlgTemplateItem))
    Next

    Local $tDlgTemplate = DllStructCreate($tagDLGTEMPLATEEX)

    ;Header
    $tDlgTemplate.dlgVer = 1
    $tDlgTemplate.signature = 0xFFFF
    $tDlgTemplate.helpID = 0
    $tDlgTemplate.exStyle = $WS_EX_DLGMODALFRAME
    $tDlgTemplate.Style = BitOR($WS_POPUP, $WS_CAPTION, $WS_SYSMENU, $DS_MODALFRAME, $DS_SETFONT)
    $tDlgTemplate.cDlgItems = $iItemCnt
    $tDlgTemplate.x = 0
    $tDlgTemplate.y = 0
    $tDlgTemplate.cx = 200
    $tDlgTemplate.cy = 100
    $tDlgTemplate.title = $sTitle
    $tDlgTemplate.pointsize = 8
    $tDlgTemplate.weight = $FW_NORMAL
    $tDlgTemplate.italic = False
    $tDlgTemplate.charset = $DEFAULT_CHARSET
    $tDlgTemplate.typeface = $sTypeFace

    For $i = 1 To $iItemCnt
        $tagDLGTEMPLATEITEMEX = StringFormat($tagDLGITEMTEMPLATEEX_Base, 2, StringLen($asTitles[$i - 1]) + 1)
        $tDlgTemplateItem = DllStructCreate($tagDLGTEMPLATEITEMEX, DllStructGetPtr($tDlgTemplate, "item" & $i))

        Switch $i
            Case 1 ;Label
                $tDlgTemplateItem.windowClass(1) = 0xFFFF ;Label
                $tDlgTemplateItem.windowClass(2) = 0x0082
                $tDlgTemplateItem.title = $asTitles[$i - 1]
                $tDlgTemplateItem.x = 10
                $tDlgTemplateItem.y = 20
                $tDlgTemplateItem.cx = 180
                $tDlgTemplateItem.cy = 20
                $tDlgTemplateItem.Style = BitOR($SS_CENTERIMAGE, $SS_LEFT, $WS_VISIBLE, $WS_CHILD)
                $tDlgTemplateItem.exStyle = $WS_EX_WINDOWEDGE

            Case 2 ;OK Button
                $tDlgTemplateItem.windowClass(1) = 0xFFFF
                $tDlgTemplateItem.windowClass(2) = 0x0080
                $tDlgTemplateItem.id = $IDOK
                $tDlgTemplateItem.title = $asTitles[$i - 1]
                $tDlgTemplateItem.x = 90
                $tDlgTemplateItem.y = 75
                $tDlgTemplateItem.cx = 50
                $tDlgTemplateItem.cy = 20
                $tDlgTemplateItem.Style = BitOR($BS_CENTER, $BS_VCENTER, $WS_TABSTOP, $WS_VISIBLE, $WS_CHILD)
                $tDlgTemplateItem.exStyle = $WS_EX_WINDOWEDGE

            Case 3 ;Cancel Button
                $tDlgTemplateItem.windowClass(1) = 0xFFFF
                $tDlgTemplateItem.windowClass(2) = 0x0080
                $tDlgTemplateItem.id = $IDCANCEL
                $tDlgTemplateItem.title = $asTitles[$i - 1]
                $tDlgTemplateItem.x = 145
                $tDlgTemplateItem.y = 75
                $tDlgTemplateItem.cx = 50
                $tDlgTemplateItem.cy = 20
                $tDlgTemplateItem.Style = BitOR($BS_CENTER, $BS_VCENTER, $WS_TABSTOP, $WS_VISIBLE, $WS_CHILD)
                $tDlgTemplateItem.exStyle = $WS_EX_WINDOWEDGE
        EndSwitch
    Next

    Return $tDlgTemplate
EndFunc

Dialogs v1.4.zip

Edited by MattyD
Posted

Actually, embedding the dialog isn't too difficult once we have it defined:

  • Compile ShowDlg.au3
  • Run AddDlgRes.au3 to add the dialog to ShowDlg.exe's resources 
    • (ShowDlg.exe must be in the same folder as  AddDlgRes.au3)
  • Run ShowDlg.exe > Help > About.

DialogRes.zip

Posted

This is really neat, Matty. Thanks for sharing it. I think that this could be incredibly useful to be able to have custom dialogs instead of the typical system MsgBox. This opens up quite a bit of possibilities. I've been messing around with this script for about an hour trying various things and it is tempting me to implement it in some of my older GUI projects now.

I do have a question and it's something that will likely come up at some point. How easy or difficult would it be to implement adding a background color to the dialog box?

Posted (edited)

No worries at all :)

9 hours ago, WildByDesign said:

How easy or difficult would it be to implement adding a background color to the dialog box?

Looks to be easy enough...

Func DlgProc($hWnd, $iMsg, $wParam, $lParam)
    Local Static $hBkColBr

    Switch $iMsg
        Case $WM_SYSCOMMAND
            If BitAND(0xFFF0, $wParam) = $SC_CLOSE Then
                DllCall("User32.dll", "bool", "EndDialog", "hwnd", $hWnd, "int_ptr", $IDCANCEL)
                Return 0
            EndIf

        Case $WM_COMMAND
            Switch _WinAPI_LoWord($wParam)
                Case $IDOK, $IDCANCEL
                    DllCall("User32.dll", "bool", "EndDialog", "hwnd", $hWnd, "int_ptr", $wParam)
                    Return 0

            EndSwitch

        Case $WM_INITDIALOG
            $hBkColBr = _WinAPI_CreateSolidBrush(0xAAAA00)

        Case $WM_CTLCOLORDLG, $WM_CTLCOLORBTN
            Return $hBkColBr

        Case $WM_CTLCOLORSTATIC
            _WinAPI_SetTextColor($wParam, 0x880088)
            _WinAPI_SetBkColor($wParam, 0xAAAA00)

            Return $hBkColBr

        Case $WM_DESTROY
            _WinAPI_DeleteObject($hBkColBr)

    EndSwitch

    Return _WinAPI_DefWindowProcW($hWnd, $iMsg, $wParam, $lParam)
EndFunc
Edited by MattyD
Posted
16 hours ago, MattyD said:

Looks to be easy enough...

That works very well. Thanks.

And the fact that you can inject this custom dialog into already compiled binaries is really interesting. I've got so many projects going on right now but I absolutely must do some more things with this and possibly expand on it some more.

Posted

Small correction. 

Apparently you don't need to run DefWindowProcW() for dialog procs, just True if you handle something or False if you don't. WM_CTLCOLORXXXX are exceptions to this rule however. (More info here) 

And if you do this,  closing out of the dialog (with the X) sends an IDCANCEL automatically!

So for a couple of buttons in a window, you can get away with something extremely generic:

Func DlgProc($hWnd, $iMsg, $wParam, $lParam)
    #forceref $hWnd, $iMsg, $wParam, $lParam

    Switch $iMsg
        Case $WM_COMMAND
            Return DllCall("User32.dll", "bool", "EndDialog", "hwnd", $hWnd, "int_ptr", $wParam)

    EndSwitch

    Return False
EndFunc
Posted (edited)

Here's a first crack at a UDF if its useful :), hopefully the demo makes sense!

We won't be able to get the KB input working properly for modeless dialogs without access to the msg pump. (which we can't hook) . So no tab-taby fo those unfortunately.  Modals do work fine though.

Edit: first bugfix! modeless from res should return hwnd

Edit: Removed attachment to save on space! (Current version is in Post 1) 

 

 

Edited by MattyD
Posted
14 hours ago, MattyD said:

We won't be able to get the KB input working properly for modeless dialogs without access to the msg pump. (which we can't hook) .

Well I was wrong, it turns out you can! Updated UDF in post 1.

Posted (edited)

Well its better than before. a message loop should more or less look ike this.

while (GetMessage(&msg, NULL, 0, 0)) {
    // Check if the message is for the modeless dialog
    if (!IsDialogMessage(hwndModeless, &msg)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
}

We hook the getmessage and do a IsDialogMessage for our modeless dialogs before it returns - so KB input now works.  AutoIt will always do a Translate/Dispatch though later on though  because its IsDialogMessage call is not looking for our windows, and will always fail. 

We'd rather it didn't Translate/Dispatchthat if our call to IsDialogMessage succeeds...  So I guess we'll need to find out how to discard msgs from within the hook.

Edited by MattyD
clarity
Posted

V1.3 Fixed what was a very badly coded hook in the last upload.

We can't avoid the translate/Dispatch as such - but we can change the WM_ code to WM_NULL.

Its interesting that  IsDialogMessage does returns True on mouse messages (if we allow it) - but that marking those as handled does break things. So I'm only feeding the func WM_KEYUP and WM_KEYDOWN, and that seems to be working...

Func __Dialog_GetMsgProc($iCode, $wParam, $lParam)
    Local $tMsg, $aCall

    ;$iCode only equals $HC_ACTION if the msg will be removed from the queue. (once getmsg or peekmsg returns).
    If $iCode = $HC_ACTION Then
    
        ;hwnds for all our modeless dialogs are in this map.
        For $iKey In MapKeys($__g_mHookedDialogs)
        
            If Not _WinAPI_IsWindow($__g_mHookedDialogs[$iKey]) Then
            
                ;Window no loger exists - so we don't check for it anymore!
                MapRemove($__g_mHookedDialogs, $iKey)
                
            Else
                $tMsg = DllStructCreate($tagMSG, $lParam)
                
                ;If msg is for this dialog (or one of its controls)
                If $tMsg.hwnd = $__g_mHookedDialogs[$iKey] Or _WinAPI_IsChild($tMsg.hwnd, $__g_mHookedDialogs[$iKey]) Then
                
                    ;Only handle key input.  Handling mouse messages breaks stuff for some reason!
                    If $tMsg.message = $WM_KEYUP OR $tMsg.message = $WM_KEYDOWN Then
                        
                        ;process message for dialog (if possible)
                        $aCall = DllCall("User32.dll", "bool", "IsDialogMessageW", "hwnd", $__g_mHookedDialogs[$iKey], "ptr", $lParam)
                        
                        If $aCall[0] Then
                            ;Message has been handled. Turn it into something inert for translate/dispatch msg (which will be called downstream.)
                            $tMsg.message = $WM_NULL
                            
                            ;Don't call next hook. (maybe we should, I haven't decided yet..)
                            Return 0
                        EndIf
                        
                    EndIf
                    
                    ;found the owner the msg. - no need to check against others in the map.
                    ExitLoop
                EndIf
            EndIf
        Next
    EndIf
    
    ;this msg is not for us. pass to next hook.
    Return _WinAPI_CallNextHookEx(0, $iCode, $wParam, $lParam)
EndFunc

 

Posted

Apologies for the daily revisions - things should slow down soon!

In today's upload:

  • Added menu support (albeit this needs to be a resource of your exe)
  • Added _Dialog_SetBkColor and _Dialog_SetTxtColor funcs
    • these are to be called once the dialog has started - its not part of the dialog template.
    • They're probably best called from WM_INITDIALOG.
  • Added _Dialog_SendItemMsg
    • this one's a wrapper for "SendDlgItemMessageW".
    • Its basically sendmsg - but all you need is the dialog hwnd and itemid. (no need to resolve the control's hwnd.

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
×
×
  • Create New...