Popular Post LarsJ Posted April 19, 2020 Popular Post Share Posted April 19, 2020 (edited) GUIs of interest here are non-AutoIt GUIs not created with GUICreate(). They are created with Windows API functions eg. _WinAPI_CreateWindowEx(). (2020-04-26) Is it possible in such a non-AutoIt GUI to implement a Windows message loop? Which functionality and techniques do and do not work in such non-AutoIt GUIs? Which internal functions can be used? Can AutoIt and non-AutoIt GUIs exist side by side in the same script? Can existing UDFs be used? This example started last week with inspiration from this thread. After several tests during the week, I've decided to make a little more of the example. This is an update of first post. Over the coming weeks I'll be adding more code in new posts. If it all works well and stable, the ideas in the project may be seen in a wider perspective. Some information has been deleted during this update. The information will be added again later. Basic functionality What is a Windows message loop In Microsoft documentation, a message loop is usually coded like this: while GetMessage(&msg, 0, 0, 0) { // Retrieve a message from the message queue // Processes accelerator keystrokes if (!TranslateAccelerator( hwndMain, // handle to receiving window haccel, // handle to active accelerator table &msg)) // message data { TranslateMessage(&msg); // Translate a virtual-key message into a character message DispatchMessage(&msg); // Send a message to the window procedure } } And the window procedure is defined like this: LRESULT CALLBACK WinProc( HWND hwnd, // handle to window UINT uMsg, // message identifier WPARAM wParam, // first message parameter LPARAM lParam) // second message parameter { switch (uMsg) { case WM_CREATE: // Initialize the window. return 0; case WM_PAINT: // Paint the window's client area. return 0; case WM_SIZE: // Set the size and position of the window. return 0; case WM_DESTROY: // Clean up window-specific data objects. return 0; // // Process other messages. // default: return DefWindowProc(hwnd, uMsg, wParam, lParam); } return 0; } Message loop The purpose of a message loop is first and foremost to keep the program running and prevent it from ending immediately. It's exactly the same in AutoIt. But why so complicated code as shown above? It's necessary for advanced message handling eg. to use keyboard accelerators that are local to the individual program and not global as hot keys. Window procedure The window procedure handles the messages sent from the DispatchMessage() function in the message loop. Even in a simpler message loop, messages are sent to the window procedure. This is done through deault code in the Windows API functions and the operating system. If a message isn't handled by the window procedure, it's forwarded to the default window procedure DefWindowProc(). 0) _WinAPI_RegisterClassEx.au3 This is the example of _WinAPI_RegisterClassEx() in the help file. _WinAPI_CreateWindowEx() is used to create the GUI window. The example here is a slightly modified version: expandcollapse popup#AutoIt3Wrapper_Au3Check_Parameters=-d -w 1 -w 2 -w 3 -w 4 -w 5 -w 6 -w 7 #AutoIt3Wrapper_UseX64=y Opt( "MustDeclareVars", 1 ) #include <WinAPIRes.au3> #include <WinAPISys.au3> #include <WindowsConstants.au3> Global $bExit = False Example() Func Example() Local Const $sClass = "MyWindowClass" Local Const $sName = "_WinAPI_RegisterClassEx" ; Get module handle for the current process Local $hInstance = _WinAPI_GetModuleHandle( 0 ) ; Create a class cursor Local $hCursor = _WinAPI_LoadCursor( 0, 32512 ) ; IDC_ARROW ; Create a class icons (large and small) Local $tIcons = DllStructCreate( "ptr;ptr" ) _WinAPI_ExtractIconEx( @SystemDir & "\shell32.dll", 130, DllStructGetPtr( $tIcons, 1 ), DllStructGetPtr( $tIcons, 2 ), 1 ) Local $hIcon = DllStructGetData( $tIcons, 1 ) Local $hIconSm = DllStructGetData( $tIcons, 2 ) ; Create DLL callback function (window procedure) Local $pWinProc = DllCallbackGetPtr( DllCallbackRegister( "WinProc", "lresult", "hwnd;uint;wparam;lparam" ) ) ; Create and fill $tagWNDCLASSEX structure Local $tWCEX = DllStructCreate( $tagWNDCLASSEX & ";wchar szClassName[" & ( StringLen( $sClass ) + 1 ) & "]" ) DllStructSetData( $tWCEX, "Size", DllStructGetPtr( $tWCEX, "szClassName" ) - DllStructGetPtr( $tWCEX ) ) DllStructSetData( $tWCEX, "Style", 0 ) DllStructSetData( $tWCEX, "hWndProc", $pWinProc ) DllStructSetData( $tWCEX, "ClsExtra", 0 ) DllStructSetData( $tWCEX, "WndExtra", 0 ) DllStructSetData( $tWCEX, "hInstance", $hInstance ) DllStructSetData( $tWCEX, "hIcon", $hIcon ) DllStructSetData( $tWCEX, "hCursor", $hCursor ) DllStructSetData( $tWCEX, "hBackground", _WinAPI_CreateSolidBrush( _WinAPI_GetSysColor( $COLOR_3DFACE ) ) ) DllStructSetData( $tWCEX, "MenuName", 0 ) DllStructSetData( $tWCEX, "ClassName", DllStructGetPtr( $tWCEX, "szClassName" ) ) DllStructSetData( $tWCEX, "hIconSm", $hIconSm ) DllStructSetData( $tWCEX, "szClassName", $sClass ) ; Register a window class _WinAPI_RegisterClassEx( $tWCEX ) ; Create a window _WinAPI_CreateWindowEx( 0, $sClass, $sName, BitOR( $WS_CAPTION, $WS_POPUPWINDOW, $WS_VISIBLE ), ( @DesktopWidth - 826 ) / 2, ( @DesktopHeight - 584 ) / 2, 826, 584, 0 ) ; Main msg loop While Sleep(10) If $bExit Then ExitLoop WEnd ; Unregister window class and release resources _WinAPI_UnregisterClass( $sClass, $hInstance ) _WinAPI_DestroyCursor( $hCursor ) _WinAPI_DestroyIcon( $hIcon ) _WinAPI_DestroyIcon( $hIconSm ) EndFunc ; Window procedure Func WinProc( $hWnd, $iMsg, $wParam, $lParam ) Switch $iMsg Case $WM_CLOSE $bExit = True EndSwitch Return _WinAPI_DefWindowProcW( $hWnd, $iMsg, $wParam, $lParam ) EndFunc Note that the Esc key, which can normally be used to close an AutoIt window, doesn't work. But you can use Alt+F4 to close the window. Alt+F4 is a hot key. In the example, the main message loop is coded this way: ; Main msg loop While Sleep(10) If $bExit Then ExitLoop WEnd And the window procedure that handles messages: Func WinProc( $hWnd, $iMsg, $wParam, $lParam ) Switch $iMsg Case $WM_CLOSE $bExit = True EndSwitch Return _WinAPI_DefWindowProcW( $hWnd, $iMsg, $wParam, $lParam ) EndFunc Esc key In AutoIt, the Esc key to exit the program is implemented as an accelerator key. But this simple message loop is unable to handle keyboard accelerators. Keyboard accelerators The Microsoft documentation for keyboard accelerators can be found here. To use keyboard accelerators in a program, the following code steps must be implemented: Create a keyboard accelerator struct to store information Fill in the accelerator key structure with information Create an accelerator table with CreateAcceleratorTable() Include TranslateAccelerator() in the message loop Include a WM_COMMAND message handler in the window procedure Includes\WinMsgLoop.au3 (2020-04-26) WinMsgLoop.au3 implements the functions to use keyboard accelerators and to create a Windows message loop: expandcollapse popup#include-once ; Message structure Global Const $tagMSG = "hwnd hwnd;uint message;wparam wParam;lparam lParam;dword time;int X;int Y" ; Keyboard accelerator structure Global Const $tagACCEL = "byte fVirt;word key;word cmd;" ; Values of the fVirt field Global Const $FVIRTKEY = TRUE Global Const $FNOINVERT = 0x02 Global Const $FSHIFT = 0x04 Global Const $FCONTROL = 0x08 Global Const $FALT = 0x10 #cs ; One accelerator key Local $tAccel = DllStructCreate( $tagACCEL ) DllStructSetData( $tAccel, "fVirt", $FVIRTKEY ) DllStructSetData( $tAccel, "key", $VK_ESCAPE ) DllStructSetData( $tAccel, "cmd", $VK_ESCAPE ) ; cmd = key to keep it simple ; Two accelerator keys Local $tAccel = DllStructCreate( $tagACCEL & $tagACCEL ) DllStructSetData( $tAccel, 1, $FVIRTKEY ) DllStructSetData( $tAccel, 2, $VK_KEY1 ) DllStructSetData( $tAccel, 3, $VK_KEY1 ) DllStructSetData( $tAccel, 4, $FVIRTKEY ) DllStructSetData( $tAccel, 5, $VK_KEY2 ) DllStructSetData( $tAccel, 6, $VK_KEY2 ) #ce ; Error handling ; @error = 0: No errors ; 1: Parameter error ; Create a keyboard accelerator table Func WinMsgLoop_CreateAcceleratorTable( $tAccel ) Local $iSize = DllStructGetSize( $tAccel ) If Mod( $iSize, 6 ) Then Return SetError(1,0,0) ; SetError ( code [, extended = 0 [, return value]] ) Return DllCall( "User32.dll", "handle", "CreateAcceleratorTableW", "struct*", $tAccel, "int", $iSize/6 )[0] EndFunc ; Retrieve a message from the message queue Func WinMsgLoop_GetMessage( ByRef $tMsg ) Return DllCall( "User32.dll", "bool", "GetMessageW", "struct*", $tMsg, "hwnd", 0, "uint", 0, "uint", 0 )[0] EndFunc ; Processes accelerator keystrokes Func WinMsgLoop_TranslateAccelerator( $hWnd, $hAccel, ByRef $tMsg ) Return DllCall( "User32.dll", "int", "TranslateAcceleratorW", "hwnd", $hWnd, "handle", $hAccel, "struct*", $tMsg )[0] EndFunc ; Processes dialog box messages Func WinMsgLoop_IsDialogMessage( $hWnd, ByRef $tMsg ) Return DllCall( "User32.dll", "bool", "IsDialogMessageW", "hwnd", $hWnd, "struct*", $tMsg )[0] EndFunc ; Translate a virtual-key message into a character message Func WinMsgLoop_TranslateMessage( ByRef $tMsg ) DllCall( "User32.dll", "bool", "TranslateMessage", "struct*", $tMsg ) EndFunc ; Send a message to the window procedure Func WinMsgLoop_DispatchMessage( ByRef $tMsg ) DllCall( "User32.dll", "lresult", "DispatchMessageW", "struct*", $tMsg ) EndFunc ; Destroy a keyboard accelerator table Func WinMsgLoop_DestroyAcceleratorTable( $hAccel ) DllCall( "User32.dll", "bool", "DestroyAcceleratorTable", "handle", $hAccel ) EndFunc ; Posts a WM_QUIT message to the message queue ; WinMsgLoop_GetMessage() returns 0 on WM_QUIT and the message loop terminates Func WinMsgLoop_PostQuitMessage( $iExitCode = 0 ) Return DllCall( "User32.dll", "none", "PostQuitMessage", "int", $iExitCode )[0] EndFunc 1) Windows message loop.au3 In this example, the Esc and End keys can be used to exit the program. It contains a complete Windows message loop. The script contains a number of ConsoleWrites so you can see what's going on in SciTE console. Keyboard accelerators: #cs ; Esc accelerator key to Exit ; Create keyboard accelerator structure Local $tAccel = DllStructCreate( $tagACCEL ) DllStructSetData( $tAccel, "fVirt", $FVIRTKEY ) DllStructSetData( $tAccel, "key", $VK_ESCAPE ) DllStructSetData( $tAccel, "cmd", $VK_ESCAPE ) ; cmd = key to keep it simple #ce ; Esc/End accelerator keys to Exit ; Create keyboard accelerator structure Local $tAccel = DllStructCreate( $tagACCEL & $tagACCEL ) DllStructSetData( $tAccel, 1, $FVIRTKEY ) DllStructSetData( $tAccel, 2, $VK_ESCAPE ) DllStructSetData( $tAccel, 3, $VK_ESCAPE ) ; cmd = key to keep it simple DllStructSetData( $tAccel, 4, $FVIRTKEY ) DllStructSetData( $tAccel, 5, $VK_END ) DllStructSetData( $tAccel, 6, $VK_END ) ; Create a keyboard accelerator table Local $hAccel = WinMsgLoop_CreateAcceleratorTable( $tAccel ) ConsoleWrite( "$hAccel = " & $hAccel & @CRLF & @CRLF ) Using dialog box keys (2020-04-26) To use dialog box keys, the message loop must contain the IsDialogMessage() function. The function identifies and processes the keys. Windows message loop: (2020-04-26) ; Windows message loop Local $tMsg = DllStructCreate( $tagMSG ) While WinMsgLoop_GetMessage( $tMsg ) ; Retrieve a message from the message queue ConsoleWrite( "0x" & Hex( DllStructGetData( $tMsg, "message" ), 4 ) & @CRLF ) If Not WinMsgLoop_TranslateAccelerator( $hWnd, $hAccel, $tMsg ) And _ ; Processes accelerator keystrokes Not WinMsgLoop_IsDialogMessage( $hWnd, $tMsg ) Then ; Processes dialog box messages WinMsgLoop_TranslateMessage( $tMsg ) ; Translate a virtual-key message into a character message WinMsgLoop_DispatchMessage( $tMsg ) ; Send a message to the window procedure EndIf If $bExit Then ExitLoop WEnd WM_COMMAND message handler: (2020-04-26) Case $WM_COMMAND ConsoleWrite( @CRLF & "$WM_COMMAND" & @CRLF ) Switch BitShift( $wParam, 16 ) ; HiWord Case 1 ; Accelerator key ConsoleWrite( "Accelerator key" & @CRLF ) Switch BitAND( $wParam, 0xFFFF ) ; LoWord Case $VK_ESCAPE ConsoleWrite( "$VK_ESCAPE" & @CRLF ) _WinAPI_DestroyWindow( $hWnd ) Return 0 ; Don't call DefWindowProc() Case $VK_END ConsoleWrite( "$VK_END" & @CRLF ) _WinAPI_DestroyWindow( $hWnd ) Return 0 EndSwitch EndSwitch Program termination code: (2020-04-26) Case $WM_CLOSE ConsoleWrite( @CRLF & "$WM_CLOSE" & @CRLF ) If MsgBox( $MB_OKCANCEL, "Really close?", "My application", 0, $hWnd ) = $IDOK Then _WinAPI_DestroyWindow( $hWnd ) Else ConsoleWrite( @CRLF ) EndIf Return 0 ; Don't call DefWindowProc() Case $WM_DESTROY ConsoleWrite( @CRLF & "$WM_DESTROY" & @CRLF ) $bExit = True Return 0 2) Windows message loop 2.au3 (2020-04-26) Optimized version of the message loop because DllCall() is used directly instead of more time-consuming functions in WinMsgLoop.au3 UDF: ; Windows message loop Local $tMsg = DllStructCreate( $tagMSG ) While DllCall( "User32.dll", "bool", "GetMessageW", "struct*", $tMsg, "hwnd", 0, "uint", 0, "uint", 0 )[0] ; Retrieve a message from the message queue ConsoleWrite( "0x" & Hex( DllStructGetData( $tMsg, "message" ), 4 ) & @CRLF ) If Not DllCall( "User32.dll", "int", "TranslateAcceleratorW", "hwnd", $hWnd, "handle", $hAccel, "struct*", $tMsg )[0] And _ ; Processes accelerator keystrokes Not DllCall( "User32.dll", "bool", "IsDialogMessageW", "hwnd", $hWnd, "struct*", $tMsg )[0] Then ; Processes dialog box messages DllCall( "User32.dll", "bool", "TranslateMessage", "struct*", $tMsg ) ; Translate a virtual-key message into a character message DllCall( "User32.dll", "lresult", "DispatchMessageW", "struct*", $tMsg ) ; Send a message to the window procedure EndIf If $bExit Then ExitLoop WEnd 3) Navigating with Tab key.au3 (2020-04-26) When IsDialogMessage() is added to the code in the message loop, dialog box keys work immediately. Eg. Tab and Shift+Tab. Demonstrated in the example with three buttons. 4) Adding virtual ListView.au3 A virtual listview with cell background colors is created in the window. As it's a virtual list view, a WM_NOTIFY message handler is needed to handle LVN_GETDISPINFO notifications. Background colors are drawn through NM_CUSTOMDRAW notifications. A virtual and custom drawn listview is very message intensive and therefore interesting to test. ; Create ListView $hListView = _GUICtrlListView_Create( $hWnd, "", 10, 10, 800, 538, $LVS_DEFAULT+$LVS_OWNERDATA-$LVS_SINGLESEL, $WS_EX_CLIENTEDGE ) _GUICtrlListView_SetExtendedListViewStyle( $hListView, $LVS_EX_DOUBLEBUFFER+$LVS_EX_FULLROWSELECT ) ; Add columns For $i = 0 To $iCols - 1 _GUICtrlListView_AddColumn( $hListView, "Col " & $i, 96, 2 ) ; 2 = Centered text Next ; ListView items For $i = 0 To $iRows - 1 For $j = 0 To $iCols - 1 $aItems[$i][$j] = $i & "/" & $j Next Next ; ListView colors Local $aLVColors = [ 0xCCCCFF, 0xCCFFFF, 0xCCFFCC, 0xFFFFCC, 0xFFCCCC, 0xFFCCFF ] ; BGR For $i = 0 To $iRows - 1 For $j = 0 To $iCols - 1 $aColors[$i][$j] = $aLVColors[Random( 0,5,1 )] Next Next ; Set number of rows in virtual ListView DllCall( "user32.dll", "lresult", "SendMessageW", "hwnd", $hListView, "uint", $LVM_SETITEMCOUNT, "wparam", $iRows, "lparam", 0 ) Case $WM_NOTIFY Switch DllStructGetData( DllStructCreate( $tagNMHDR, $lParam ), "Code" ) Case $LVN_GETDISPINFOW ; Fill virtual listview Local Static $tText = DllStructCreate( "wchar[100]" ), $pText = DllStructGetPtr( $tText ) Local $tNMLVDISPINFO = DllStructCreate( $tagNMLVDISPINFO, $lParam ) If Not BitAND( DllStructGetData( $tNMLVDISPINFO, "Mask" ), $LVIF_TEXT ) Then Return Local $sItem = $aItems[DllStructGetData($tNMLVDISPINFO,"Item")][DllStructGetData($tNMLVDISPINFO,"SubItem")] DllStructSetData( $tText, 1, $sItem ) DllStructSetData( $tNMLVDISPINFO, "TextMax", StringLen( $sItem ) ) DllStructSetData( $tNMLVDISPINFO, "Text", $pText ) Return 0 ; Don't call DefWindowProc() Case $NM_CUSTOMDRAW ; Draw back colors Local $tNMLVCUSTOMDRAW = DllStructCreate( $tagNMLVCUSTOMDRAW, $lParam ) Local $dwDrawStage = DllStructGetData( $tNMLVCUSTOMDRAW, "dwDrawStage" ), $iItem Switch $dwDrawStage ; Holds a value that specifies the drawing stage Case $CDDS_PREPAINT ; Before the paint cycle begins Return $CDRF_NOTIFYITEMDRAW ; Notify the parent window of any item-related drawing operations Case $CDDS_ITEMPREPAINT ; Before painting an item Return $CDRF_NOTIFYSUBITEMDRAW ; Notify the parent window of any subitem-related drawing operations Case $CDDS_ITEMPREPAINT + $CDDS_SUBITEM ; Before painting a subitem $iItem = DllStructGetData( $tNMLVCUSTOMDRAW, "dwItemSpec" ) DllStructSetData( $tNMLVCUSTOMDRAW, "ClrTextBk", $aColors[$iItem][DllStructGetData($tNMLVCUSTOMDRAW,"iSubItem")] ) Return $CDRF_NEWFONT ; $CDRF_NEWFONT must be returned after changing font or colors EndSwitch EndSwitch Usage of the code I don't think there's such a great need for code like this. At least the code shows that translating Windows API functions into AutoIt works pretty well. Implementing the code in the WinMsgLoop.au3 UDF was straightforward. There is a question about using keyboard accelerators in non-AutoIt GUIs in this post. If you use AutoIt for prototyping, you might get some ideas here. Because the examples do not contain internal AutoIt functions but only Windows API functions, they are easy to translate into C/C++ and other languages. When translating back and forth between AutoIt and other languages, the internal functions are always the problem. How to translate these functions? Recent performance issue Recently, a problem was raised in this post regarding a multiple 100% performance degradation on Windows 10 versions later than 1803 as soon as a GUI element became visible in a script. The problem persisted throughout the rest of the code, even though the GUI element was deleted. I've tested the issue under Windows 10 1809 with all the examples here. All of the examples suffer from the problem. How much of the code needs to be translated into C/C++ before the problem disappears? Only the line which contains the _WinAPI_CreateWindowEx() function that creates and displays the window? Or does the problem still exist when all code is translated? That could be interesting. I've already done examples of translating AutoIt code into C/C++ eg. in this example and this example so it's not that hard. 7z-file The 7z-file contains source code for the UDFs and examples. You need AutoIt 3.3.12 or later. Tested on Windows 7 and Windows 10. Comments are welcome. Let me know if there are any issues. WinMsgLoop.7z Edited April 26, 2020 by LarsJ Updates and new 7z-file Gianni, MrCreatoR, Danyfirex and 3 others 5 1 Controls, File Explorer, ROT objects, UI Automation, Windows Message MonitorCompiled code: Accessing AutoIt variables, DotNet.au3 UDF, Using C# and VB codeShell menus: The Context menu, The Favorites menu. Shell related: Control Panel, System Image ListsGraphics related: Rubik's Cube, OpenGL without external libraries, Navigating in an image, Non-rectangular selectionsListView controls: Colors and fonts, Multi-line header, Multi-line items, Checkboxes and icons, Incremental searchListView controls: Virtual ListViews, Editing cells, Data display functions Link to comment Share on other sites More sharing options...
LarsJ Posted April 26, 2020 Author Share Posted April 26, 2020 Update of first post. New 7z-file at bottom. Controls, File Explorer, ROT objects, UI Automation, Windows Message MonitorCompiled code: Accessing AutoIt variables, DotNet.au3 UDF, Using C# and VB codeShell menus: The Context menu, The Favorites menu. Shell related: Control Panel, System Image ListsGraphics related: Rubik's Cube, OpenGL without external libraries, Navigating in an image, Non-rectangular selectionsListView controls: Colors and fonts, Multi-line header, Multi-line items, Checkboxes and icons, Incremental searchListView controls: Virtual ListViews, Editing cells, Data display functions Link to comment Share on other sites More sharing options...
kovlad Posted April 21, 2021 Share Posted April 21, 2021 (edited) Dear LarsJ, by calling the GetMessage function, You are breaking the inner AutoIt loop. To confirm my words, I suggest that You try to exit the script via the tray icon in one of Your examples " Windows message loop...". It won't work at the first time. This is just one example of a violation of AutoIt functionality. AutoIt can also break your script by calling GetMessage or PeekMessage, in which case Your loop will not receive this message. I am working on this problem, and if You or other forum participants have a desire, I will be happy to tell you about my experiments. Edited April 22, 2021 by kovlad Link to comment Share on other sites More sharing options...
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