MattyD Posted March 1 Posted March 1 (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! expandcollapse popup#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 14 hours ago by MattyD ioa747, Danyfirex and argumentum 3
MattyD Posted March 1 Author Posted March 1 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 WildByDesign 1
WildByDesign Posted March 1 Posted March 1 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?
MattyD Posted March 1 Author Posted March 1 (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... expandcollapse popupFunc 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 March 1 by MattyD argumentum and WildByDesign 1 1
WildByDesign Posted March 2 Posted March 2 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. MattyD 1
MattyD Posted Wednesday at 12:23 PM Author Posted Wednesday at 12:23 PM 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 WildByDesign 1
MattyD Posted Saturday at 10:59 AM Author Posted Saturday at 10:59 AM (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 yesterday at 01:15 AM by MattyD WildByDesign 1
MattyD Posted yesterday at 01:16 AM Author Posted yesterday at 01:16 AM 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.
MattyD Posted yesterday at 02:11 AM Author Posted yesterday at 02:11 AM (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 yesterday at 02:15 AM by MattyD clarity argumentum 1
MattyD Posted yesterday at 10:30 AM Author Posted yesterday at 10:30 AM 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... expandcollapse popupFunc __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
MattyD Posted 14 hours ago Author Posted 14 hours ago 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. argumentum and WildByDesign 1 1
Recommended Posts
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 accountSign in
Already have an account? Sign in here.
Sign In Now