Jump to content

After WM_Command is triggered, _ArrayDisplay locks up script


 Share

Recommended Posts

Hi, everyone.

I am writing a script to help organize files in folders. I have hit a snag using _Arr ayDisplay. It works fine until I press a button that triggers a WM_Command. After a button is clicked, _ArrayDisplay locks up and crashes the script. I could continue without seeing a list, but it would be a lot easier if _ArrayDisplay worked. Also, I just  would like to know why it's crashing.

Any thoughts?

 

#include <ButtonConstants.au3>
#include <GUIConstantsEx.au3>
#include <WindowsConstants.au3>
#include <Array.au3>
#include <WinAPI.au3>
#include <WinAPIConstants.au3>
#include <WinAPISys.au3>
#include <GuiButton.au3>
#include <File.au3>

$guiForm1 = GUICreate("Form1", 305, 356)
GUISetState(@SW_SHOW)

$arrWinList = _GetListOfOpenExplorerWindows(1, 3) ; creates an array of windows that are open
_ArraySort($arrWinList, 0, 1)

If IsArray($arrWinList) Then ; List the open
    If $arrWinList[0][0] > 12 Then
        WinMove($guiForm1, "", @DesktopWidth/2 - 305, Default, 610, 378)
    EndIf
    For $i = 1 To $arrWinList[0][0]
        If $i < 13 Then
            $y = (($i - 1) * 28) + 8
            Assign("guiButton" & $i, GUICtrlCreateButton($arrWinList[$i][0], 8, $y , 283, 25, $WS_GROUP))
        ElseIf $i > 12 Then
            $y = (($i - 13) * 28) + 8
            Assign("guiButton" & $i, GUICtrlCreateButton($arrWinList[$i][0], 305, $y, 283, 25, $WS_GROUP))
            If $i = 24 Then
                ExitLoop
            EndIf
        EndIf
    Next
EndIf

GUIRegisterMsg($WM_COMMAND, "WM_COMMAND")
; _ArrayDisplay($arrWinList)  ; _arraydisplay works before a window is selected

While 1
    $nMsg = GUIGetMsg()
    Switch $nMsg
        Case $GUI_EVENT_CLOSE
            Exit
    EndSwitch
WEnd

Func WM_COMMAND($hWnd, $iMsg, $iwParam, $ilParam) ; if button is clicked, give popup confirming it.
    ;_ArrayDisplay($arrWinList) ; once a button is pressed, _arraydisplay locks up
    Local $hWndFrom, $iIDFrom, $iCode, $hWndEdit
    $hWndFrom = $ilParam
    $iIDFrom = _WinAPI_LoWord($iwParam)
    $iCode = _WinAPI_HiWord($iwParam)
    If $iCode = $BN_CLICKED Then
        $text = _GUICtrlButton_GetText ( $hWndFrom )
        $arrPos = _ArraySearch($arrWinList, $text)
        _OrganizeFolder($arrWinList[$arrPos][1])
    EndIf
EndFunc

Func _GetListOfOpenExplorerWindows($vIncludeArrayCount = 0, $vColumnReturn = 3) ; (0 = No, 1 = Yes), (1 = Short Names Only, 2 = Long Addresses, 3 or 1 + 2 = Return Both)
    Local $aWinList, $sGetText, $aWinText, $varStrLen, $sFormat, $i, $j, $k, $vRowsToDel
    $aWinList = WinList()
    Local $aOutput[0][2]
    $k = 0
    For $i = 1 To $aWinList[0][0]
        $sGetText = WinGetText($aWinList[$i][0])
        $aWinText = StringSplit($sGetText, @LF)
        For $j = 1 To $aWinText[0]
            If StringInStr($aWinText[$j], "Address:") Then
                $sFormat = StringReplace($aWinText[$j], "Address: ", "")
                If DirGetSize($sFormat) > -1 Then
                    ReDim $aOutput[UBound($aOutput) + 1][2]
                    $aOutput[$k][0] = $aWinList[$i][0]
                    $aOutput[$k][1] = $sFormat
                    $k += 1
                EndIf
            EndIf
        Next
    Next
    For $i = 1 To UBound($aOutput)
        $varStrLen = StringLen($aOutput[$i - 1][0])
        If $varStrLen = 0 Then
            $vRowsToDel = $vRowsToDel & $i - 1 & ";"
        EndIf
    Next
    $vRowsToDel = StringTrimRight($vRowsToDel, 1)
    _ArrayDelete($aOutput, $vRowsToDel)
    If $vIncludeArrayCount = 1 Then
        _ArrayInsert($aOutput, 0, UBound($aOutput))
    EndIf
    If $vColumnReturn = 1 Then
        _ArrayColDelete($aOutput, 1)
    ElseIf $vColumnReturn = 2 Then
        If $vIncludeArrayCount = 1 Then
            If UBound($aOutput) > 0 Then
                $aOutput[0][1] = UBound($aOutput)
            EndIf
        EndIf
        _ArrayColDelete($aOutput, 0)
    EndIf
    If UBound($aOutput) = 0 Then
        Return -1
    Else
        Return $aOutput
    EndIf
EndFunc


Func _OrganizeFolder($strFolderName) ; code to organize the content of the folder
    $arrFileList = _FileListToArrayRec($strFolderName & "\", "*", 1, 0)
    _ArrayDisplay($arrFileList)
EndFunc

 

Link to comment
Share on other sites

Hi abberration :)

It freezes/crash because you spend too long time inside Func WM_COMMAND()

Help file GUIRegisterMsg  :

Warning: blocking of running user functions which executes window messages with commands such as "MsgBox()" can lead to unexpected behavior, the return to the system should be as fast as possible !!!

_ArrayDisplay() takes some time to display its content, and as it's (indirectly) called from WM_COMMAND() ... also _FileListToArrayRec() takes time too.

A solution (successfully tested) is to move code from WM_COMMAND to While... Wend loop, by modifying this part of code :

...

Global $text, $arrPos, $bButton_Clicked = False

While 1
    $nMsg = GUIGetMsg()
    Switch $nMsg
        Case $GUI_EVENT_NONE
            If $bButton_Clicked Then                
                _OrganizeFolder($arrWinList[$arrPos][1])
                $bButton_Clicked = False                
            EndIf

        Case $GUI_EVENT_CLOSE
            Exit
    EndSwitch
WEnd

Func WM_COMMAND($hWnd, $iMsg, $iwParam, $ilParam) ; if button is clicked, give popup confirming it.
    ;_ArrayDisplay($arrWinList) ; once a button is pressed, _arraydisplay locks up
    Local $hWndFrom, $iIDFrom, $iCode, $hWndEdit
    $hWndFrom = $ilParam
    $iIDFrom = _WinAPI_LoWord($iwParam)
    $iCode = _WinAPI_HiWord($iwParam)
    If $iCode = $BN_CLICKED Then
        $text = _GUICtrlButton_GetText ( $hWndFrom )
        $arrPos = _ArraySearch($arrWinList, $text)
        ; _OrganizeFolder($arrWinList[$arrPos][1]) ; <======== commented
        $bButton_Clicked = True ; <======== a flag, read within While... Wend loop
    EndIf
EndFunc

...

That's the 2nd time I use $GUI_EVENT_NONE (first time was a couple of days ago), seems to work fine :)

Edited by pixelsearch
Link to comment
Share on other sites

Hi, pixelsearch

Ah, yes, I remember reading that in the help file in the past. I didn't think about that. Also, I was working on this in my car waiting for someone, so I wasn't well equipped for programming at the time.

Thanks for your answer. I will play with the code to learn more.

Link to comment
Share on other sites

7 hours ago, abberration said:

Thanks for your answer. I will play with the code to learn more.

You're welcome. Here are some personal suggestions :

1) Where to place the line $bButton_Clicked = False ?
Because In case you click again on a GUI button while _ArrayDisplay is active, then you'll notice a different behavior with these 2 scripts, it's always instructive to experiment them both :

If $bButton_Clicked Then
    _OrganizeFolder($arrWinList[$arrPos][1])
    $bButton_Clicked = False                
EndIf

versus

If $bButton_Clicked Then
    $bButton_Clicked = False                
    _OrganizeFolder($arrWinList[$arrPos][1])
EndIf

2) As WM_COMMAND message is not needed while _ArrayDisplay is active, then Unregistering/Re-registering the message before/after _ArrayDisplay seems a good option , i.e in your case before/after _OrganizeFolder($arrWinList[$arrPos][1])

GUIRegisterMsg($WM_COMMAND, "")
...
GUIRegisterMsg($WM_COMMAND, "WM_COMMAND")

3) Disabling/Enabling the GUI while _ArrayDisplay is active, that's another good option to prevent clicking GUI buttons  (no need to cumulate it with previous option) :

GUISetState(@SW_DISABLE, $guiForm1)
_OrganizeFolder($arrWinList[$arrPos][1])
GUISetState(@SW_ENABLE, $guiForm1)

4) Using a dummy control (instead of $GUI_EVENT_NONE) would work too. It should be triggered during the function WM_COMMAND and tested within the While... Wend loop

Good luck :)

Edited by pixelsearch
deleted superfluous comments in 4)
Link to comment
Share on other sites

Hi, pixelsearch. Those are some really great suggestions. I will indeed use some of them. I really like the idea of unregistering the WM_Command and re-registering it. 

I never understood the purpose of a dummy until now. I played with sending a dummy the text of the button that was clicked in the WM_Command and in the Case, I sent it nothing and then called my function. That seems to work. I'm not an expert on dummies, but I think I understand them better now.

Thanks again for all the great advice!

Link to comment
Share on other sites

19 hours ago, abberration said:

I never understood the purpose of a dummy until now.

Hi abberration
Just like you, I didn't use dummy controls when I started to learn AutoIt (2.5 years now)
But they really can be useful at times and your script is a perfect example for using them, because you don't get specific controls (in the While... Wend loop) corresponding to each button created dynamically by your script.

Applied to your script, here are the 3 steps to create, activate, then check the dummy control :

$guiForm1 = GUICreate("Form1", 305, 356)
$idDummy = GUICtrlCreateDummy() ; <========== 1) Create the dummy control
GUISetState(@SW_SHOW)
GUIRegisterMsg($WM_COMMAND, "WM_COMMAND")

...

Func WM_COMMAND($hWnd, $iMsg, $iwParam, $ilParam)
    Local $hWndFrom, $iIDFrom, $iCode, $hWndEdit
    $hWndFrom = $ilParam
    $iIDFrom = _WinAPI_LoWord($iwParam)
    $iCode = _WinAPI_HiWord($iwParam)
    If $iCode = $BN_CLICKED Then
        $text = _GUICtrlButton_GetText ( $hWndFrom )
        GUICtrlSendToDummy($idDummy, $text) ; <========== 2) Activate the dummy control
    EndIf
EndFunc

...

While 1
    $nMsg = GUIGetMsg()
    Switch $nMsg
        Case $GUI_EVENT_CLOSE
            Exit

        Case $idDummy  ; <========== 3) Check the dummy control
            GUIRegisterMsg($WM_COMMAND, "")
            $text_read = GUICtrlRead($idDummy)
            $arrPos = _ArraySearch($arrWinList, $text_read)
            _OrganizeFolder($arrWinList[$arrPos][1])
            GUIRegisterMsg($WM_COMMAND, "WM_COMMAND")
    EndSwitch
WEnd

See how easy it is ?
Even the important string "button text" has been sent to the main loop with that single line, avoiding a global variable.

GUICtrlSendToDummy($idDummy, $text)

Also, there's no need to use _ArraySearch() within the WM_COMMAND function, because _ArraySearch() takes time too (in case of a big array) and it may freeze the script when used within a registered message (as we discussed earlier). It's now moved to the main loop too.

As you seemed to like the unregistration/re-registration phase, I added it to the script above. Now you can click on any button in the GUI (while ArrayDisplay is active), nothing will happen after ArrayDisplay is ended (tested)

GUIRegisterMsg($WM_COMMAND, "")
...
GUIRegisterMsg($WM_COMMAND, "WM_COMMAND")

By the way, I succeeded to create dynamically 12 buttons (it fits the original GUI) then a 13th => 24th button would appear in your enlarged GUI, well done abberration :)

Link to comment
Share on other sites

You have been extremely helpful. I finished the script and my code is extremely similar to yours. Exactly what it does is organizes TV episodes into folders and sub-folders (the way I prefer them). The show must have something like S01E14 in it (or 1x14, which I added code to convert it). It will create a folder for that show if it doen't exist and create a sub-folder like S01. Finally, it moves the file to that folder. If anyone is interested in the final code, here it is (hey, maybe someone else might be able to use it):

#Region ;**** Directives created by AutoIt3Wrapper_GUI ****
#AutoIt3Wrapper_Icon=Custom-Icon-Design-Pretty-Office-5-Folder-Add.ico
#AutoIt3Wrapper_UseX64=y
#EndRegion ;**** Directives created by AutoIt3Wrapper_GUI ****
#include <ButtonConstants.au3>
#include <GUIConstantsEx.au3>
#include <WindowsConstants.au3>
#include <Array.au3>
#include <WinAPI.au3>
#include <WinAPIConstants.au3>
#include <WinAPISys.au3>
#include <GuiButton.au3>
#include <File.au3>
#include <String.au3>

$strSupportedFormats = "*.avi;*.mpg;*.mpeg;*.mp4;*.mkv;*.wmv"

$guiForm1 = GUICreate("Create Folders And Organize", 305, 356)
GUISetState(@SW_SHOW)
$guiDummy = GUICtrlCreateDummy()

$arrWinList = _GetListOfOpenExplorerWindows(1, 3) ; creates an array of windows that are open
_ArraySort($arrWinList, 0, 1)

If IsArray($arrWinList) Then ; List the open
    If $arrWinList[0][0] > 12 Then
        WinMove($guiForm1, "", @DesktopWidth/2 - 305, Default, 610, 378)
    EndIf
    For $i = 1 To $arrWinList[0][0]
        If $i < 13 Then
            $y = (($i - 1) * 28) + 8
            Assign("guiButton" & $i, GUICtrlCreateButton($arrWinList[$i][0], 8, $y , 283, 25, $WS_GROUP))
        ElseIf $i > 12 Then
            $y = (($i - 13) * 28) + 8
            Assign("guiButton" & $i, GUICtrlCreateButton($arrWinList[$i][0], 305, $y, 283, 25, $WS_GROUP))
            If $i = 24 Then
                ExitLoop
            EndIf
        EndIf
    Next
EndIf

GUIRegisterMsg($WM_COMMAND, "WM_COMMAND")

While 1
    $nMsg = GUIGetMsg()
    Switch $nMsg
        Case $GUI_EVENT_CLOSE
            Exit
        Case $guiDummy
            $strText = GUICtrlRead($guiDummy)
            GUICtrlSendToDummy($strText)
            $arrPos = _ArraySearch($arrWinList, $strText)
            _OrganizeFolder($arrWinList[$arrPos][1])
    EndSwitch
WEnd

Func WM_COMMAND($hWnd, $iMsg, $iwParam, $ilParam) ; if button is clicked, give popup confirming it.
    Local $hWndFrom, $iIDFrom, $iCode, $hWndEdit
    $hWndFrom = $ilParam
    $iIDFrom = _WinAPI_LoWord($iwParam)
    $iCode = _WinAPI_HiWord($iwParam)
    If $iCode = $BN_CLICKED Then
        $text = _GUICtrlButton_GetText ($hWndFrom)
        GUICtrlSendToDummy($guiDummy, $text)
    EndIf
EndFunc

Func _GetListOfOpenExplorerWindows($vIncludeArrayCount = 0, $vColumnReturn = 3) ; (0 = No, 1 = Yes), (1 = Short Names Only, 2 = Long Addresses, 3 or 1 + 2 = Return Both)
    Local $aWinList, $sGetText, $aWinText, $varStrLen, $sFormat, $i, $j, $k, $vRowsToDel
    $aWinList = WinList()
    Local $aOutput[0][2]
    $k = 0
    For $i = 1 To $aWinList[0][0]
        $sGetText = WinGetText($aWinList[$i][0])
        $aWinText = StringSplit($sGetText, @LF)
        For $j = 1 To $aWinText[0]
            If StringInStr($aWinText[$j], "Address:") Then
                $sFormat = StringReplace($aWinText[$j], "Address: ", "")
                If DirGetSize($sFormat) > -1 Then
                    ReDim $aOutput[UBound($aOutput) + 1][2]
                    $aOutput[$k][0] = $aWinList[$i][0]
                    $aOutput[$k][1] = $sFormat
                    $k += 1
                EndIf
            EndIf
        Next
    Next
    For $i = 1 To UBound($aOutput)
        $varStrLen = StringLen($aOutput[$i - 1][0])
        If $varStrLen = 0 Then
            $vRowsToDel = $vRowsToDel & $i - 1 & ";"
        EndIf
    Next
    $vRowsToDel = StringTrimRight($vRowsToDel, 1)
    _ArrayDelete($aOutput, $vRowsToDel)
    If $vIncludeArrayCount = 1 Then
        _ArrayInsert($aOutput, 0, UBound($aOutput))
    EndIf
    If $vColumnReturn = 1 Then
        _ArrayColDelete($aOutput, 1)
    ElseIf $vColumnReturn = 2 Then
        If $vIncludeArrayCount = 1 Then
            If UBound($aOutput) > 0 Then
                $aOutput[0][1] = UBound($aOutput)
            EndIf
        EndIf
        _ArrayColDelete($aOutput, 0)
    EndIf
    If UBound($aOutput) = 0 Then
        Return -1
    Else
        Return $aOutput
    EndIf
EndFunc

Func _OrganizeFolder($strFolderName) ; code to organize the content of the folder
    $arrFileList = _FileListToArrayRec($strFolderName & "\", $strSupportedFormats, 1, 0)
    For $i = 1 To $arrFileList[0]
        $strFileName = $arrFileList[$i]
        $arrSeason = StringRegExp($strFileName, "[S|s]\d\d[E|e]\d\d", 1)
        $arrSeasonx = StringRegExp($strFileName, "\d{1,2}[x|X]\d{1,2}", 1) ; If the show is formatted as 1x13 then convert to S01E14
        If IsArray($arrSeasonx) Then
            Dim $arrSeason[1]
            $arrSeason[0] = StringRegExpReplace($arrSeasonx[0], "\d{1,2}[x|X]\d{1,2}", _ConvertXToS($arrSeasonx[0]))
            $strFileNameOrig = $strFileName
            $strFileName = StringRegExpReplace($strFileName, "\d{1,2}[x|X]\d{1,2}", $arrSeason[0]) ; going back to the original file name, substitute 1x13 for S01E14.
            FileMove($strFolderName & "\" & $strFileNameOrig, $strFolderName & "\" & $strFileName)
        EndIf

        If IsArray($arrSeason) Then
            $arrShowName = StringRegExp($strFileName, ".+?(?=.[S]\d\d[E]\d\d)", 1)
            $strShowName = StringRegExpReplace($arrShowName[0], "\.", " ")
            $strFullPath = $strFolderName & "\" & _StringProper($strShowName) & "\" & _StringProper(StringLeft($arrSeason[0], 3))
            If FileExists($strFullPath) = 0 Then
                DirCreate($strFullPath)
            EndIf
            FileMove($strFolderName & "\" & $strFileName, $strFullPath & "\" & $strFileName)
        Endif
    Next
    Exit
EndFunc

Func _ConvertXToS($varExp)
    $varExp = StringLower($varExp) ; Force the x to be lowercase becasue stringsplit is case sensitive
    $arrStrBreakdown = StringSplit($varExp, "x", 2)
    $varStrReconstructed = "S" & StringFormat("%02d", $arrStrBreakdown[0]) & "E" & StringFormat("%02d", $arrStrBreakdown[1])
    Return $varStrReconstructed
EndFunc

I didn't put any safeguards because I only wrote it for myself and if a file doesn't match the season/episode specific expression, it ignores files. I didn't un-register the WM_Command because for this specific program, I will only ever run it one time and automatically quit. In the future (and for older programs that I keep running), I will make that change.

Thanks for your help once again!

Edited by abberration
Link to comment
Share on other sites

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
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...