Jump to content
tk1

ExitLoop in a function

Recommended Posts

tk1

Hello, I've been away for a long time but working on fixing some things in a script I wrote a long time ago to monitor changes in the external IP address of my Internet modem.

Here's some sample code that my question is based on:

;GUI create code to create buttons, labels, etc.
;Part of the GUI has the following button:
Global $btnRefresh = GUICtrlCreateButton("Refresh", 88, 112, 75, 29, $BS_CENTER, -1)
GUICtrlSetOnEvent(-1, "fuRefresh")
GUICtrlSetFont(-1, 10, 400, 0, "Arial")
GUICtrlSetTip(-1, "Click to refresh the last known IP and last time it was checked.")
;More GUI code
;End of GUI code, begin of the rest of the script.

fuRefresh()

Func fuRefresh() ;Refresh the labels in the GUI.
    ;code that gets info and makes updates
    fuWaitForChange()
EndFunc

Func fuWaitForChange()
    While 1
        ;Code that checks for a change, then sleeps 60 times for 1 second each time.
    WEnd
EndFunc

I hope that's not too stripped down that it prevents you from understanding my question.

Steps:

1.  I start the script, the GUI comes up, displays data, and periodically checks for changes.

2.  At some point, I want to manually update the data, so I click the Refresh button.

3.  The data is refreshed on the GUI and the script continues to run and make periodic updates, however now all buttons and the red X (to close the GUI)  will not respond.

 

At this point, reclicking the refresh button does nothing and the only way to close the GUI is to end task it (I have an Exit button that also ceases to function).

The function "fuWaitForChange()" has a While loop in it.  I'm thinking when I click the Refresh button, it again calls the fuWaitForChange() function creating another instance of a While loop inside the first.  Is this correct, and if so is this the problem causing the lack of response to clicks (the rest of the script continues to run --checking for changes, updating labels, etc.)?

When I tried putting just an ExitLoop in the beginning of the fuRefresh() function, I get a syntax error because it's 'outside a loop'.  I gamed it by creating a simple While 1 loop inside the beginning of the fuRefresh() function, with only an ExitLoop 2 inside that While loop (surprisingly I don't get a syntax error when I do a syntax check!).

Func fuRefresh() ;Refresh the labels in the GUI.
    While 1
        ExitLoop 2
    WEnd
    ;code that gets info and makes updates
    fuWaitForChange()
EndFunc

That just doesn't feel right, though.

 

I'd appreciate any help with this.

Thanks,

tk1

Share this post


Link to post
Share on other sites
careca

Did you ever heard of adlib function? could be good for that.


Spoiler

Paster - Main function is to paste text, but has more functions. (No longer mantained, switched to String Trigger)

Renamer - Rename files and folders, remove portions of text from the filename etc.

GPO Tool - Export/Import Group policy settings.

MirrorDir - Synchronize/Backup/Mirror Folders

BeatsPlayer - Music player.

Params Tool - Right click an exe to see it's parameters or execute them.

String Trigger - Triggers pasting text or applications or internet links on specific strings.

Inconspicuous - Hide files in plain sight, not fully encrypted.

Regedit Control - Registry browsing history, quickly jump into any saved key.

Time4Shutdown - Write the time for shutdown in minutes.

Power Profiles Tool - Set a profile as active, delete, duplicate, export and import.

Firefox Profile Backup - Backup/restore previously saved profile.

Finished Task Shutdown - Shuts down pc when specified window/Wndl/process closes.

NetworkSpeedShutdown - Shuts down pc if download speed goes under "X" Kb/s.

IUIAutomation - Topic with framework and examples

Au3Record.exe

Share this post


Link to post
Share on other sites
czardas
Posted (edited)

It may be acceptable in some languages, including AutoIt, to code the ExitLoop instruction inside a second function. Unfortunately AU3Check cannot determine that a function was called from within a loop without running (interpreting) the code. I personally always call ExitLoop directly from within the loop depending on a condition of some sort: it makes it easier to understand the code. There are many ways to set and check a condition. I give one simple example using ByRef. You could also set a global flag, or return a value from the second function, to trigger such an action. I hope this helps.

CallingFunc()

Func CallingFunc()
    Local $iCondition = 0

    While 1
        ModifyAction($iCondition)
        If $iCondition = 5 Then ExitLoop
    WEnd
EndFunc

Func ModifyAction(ByRef $iCondition)
    $iCondition += 1
    ConsoleWrite($iCondition & @LF)
EndFunc

 

Edited by czardas

Share this post


Link to post
Share on other sites
tk1
5 hours ago, careca said:

Did you ever heard of adlib function? could be good for that.

Hi careca.  I had not heard or nor used adlib.  From the help, it says to keep it simple since it runs so often and has the potential to create CPU load.  The actual code is not as simple as I made it in my example above.  It's an interesting function, though, and I'll have to keep in mind for future projects.  Thank you for pointing it out!

 

4 hours ago, czardas said:

It may be acceptable in some languages, including AutoIt, to code the ExitLoop instruction inside a second function. Unfortunately AU3Check cannot determine that a function was called from within a loop without running (interpreting) the code. I personally always call ExitLoop directly from within the loop depending on a condition of some sort: it makes it easier to understand the code. There are many ways to set and check a condition. I give one simple example using ByRef. You could also set a global flag, or return a value from the second function, to trigger such an action. I hope this helps.

CallingFunc()

Func CallingFunc()
    Local $iCondition = 0

    While 1
        ModifyAction($iCondition)
        If $iCondition = 5 Then ExitLoop
    WEnd
EndFunc

Func ModifyAction(ByRef $iCondition)
    $iCondition += 1
    ConsoleWrite($iCondition & @LF)
EndFunc

 

I'm sorry czardas, I don't see how this would help in my situation.  In your example, you're calling one function from within the While loop of another.  In my situation/example, the only time the Refresh is called another time is when the user clicks it, so it is random if at all.  Maybe I'm missing something in your suggestion?

 

Thank you both of you,

tk1

Share this post


Link to post
Share on other sites
Subz

Here is an example using AdlibRegister and Button, remember when using a While Loop you need to force an exit, other

#include <Inet.au3>
#include <GUIConstantsEx.au3>
;~ You could change the "192.168.1.1" to _GetIp(), but wanted to give you a visual example
Global $idIpAddress, $sIpAddress = "192.168.1.1"
Example()

Func Example()
    ; Create a GUI with various controls.
    Local $hGUI = GUICreate("Example", 250, 50)
    $idIpAddress = GUICtrlCreateLabel($sIpAddress, 10, 10, 145, 30)
        GUICtrlSetFont(-1, 14, 800, 0, "Arial")
    Local $idRefresh = GUICtrlCreateButton("Refresh", 155, 10, 85, 25)
        GUICtrlSetFont(-1, 10, 400, 0, "Arial")
        GUICtrlSetTip(-1, "Click to refresh the last known IP and last time it was checked.")
    GUISetState(@SW_SHOW, $hGUI)
    AdlibRegister("_GetIpAddress", 3000) ;~ Check IP Address every 3 seconds
    While 1
        Switch GUIGetMsg()
            Case $GUI_EVENT_CLOSE
                ExitLoop
            Case $idRefresh
                _GetIpAddress()
        EndSwitch
    WEnd
    GUIDelete($hGUI)
EndFunc   ;==>Example

Func _GetIpAddress()
    $sIpAddress = _GetIP()
    If $sIpAddress = GUICtrlRead($idIpAddress) Then Return
    GUICtrlSetData($idIpAddress, $sIpAddress)
EndFunc

If you want use GUIOnEventMode (don't mix the two modes) you could use something like:

#include <Inet.au3>
#include <GUIConstantsEx.au3>
#include <MsgBoxConstants.au3>

Global $idIpAddress, $sIpAddress

Opt("GUIOnEventMode", 1)

GUICreate("Hello World", 200, 100)
    GUISetOnEvent($GUI_EVENT_CLOSE, "CLOSEButton")
$idIpAddress = GUICtrlCreateLabel("192.168.1.1", 30, 10)
Local $iOKButton = GUICtrlCreateButton("OK", 70, 50, 60)
    GUICtrlSetOnEvent($iOKButton, "OKButton")
GUISetState(@SW_SHOW)
While 1
    Sleep(100)
WEnd

Func OKButton()
    While 1
        $sIpAddress = _GetIP()
        If $sIpAddress = GUICtrlRead($idIpAddress) Then ExitLoop
        GUICtrlSetData($idIpAddress, $sIpAddress)
        Sleep(100)
    WEnd
EndFunc

Func CLOSEButton()
    MsgBox($MB_OK, "GUI Event", "You selected CLOSE! Exiting...")
    Exit
EndFunc

 

Share this post


Link to post
Share on other sites
czardas
Posted (edited)
6 hours ago, tk1 said:

I'm sorry czardas, I don't see how this would help in my situation.  In your example, you're calling one function from within the While loop of another.  In my situation/example, the only time the Refresh is called another time is when the user clicks it, so it is random if at all.  Maybe I'm missing something in your suggestion?

I assume the function will be called more than once and I can only guess what code might have been stripped away. In your attempted patch you wrote ExitLoop 2. That means that you exit two loops, one of which does not exist within the function. For you to have written this implies that you probably need some clarification regarding syntax.

12 hours ago, tk1 said:

When I tried putting just an ExitLoop in the beginning of the fuRefresh() function, I get a syntax error because it's 'outside a loop'.

Func fuRefresh() ;Refresh the labels in the GUI.
    While 1
        ExitLoop 2
    WEnd
    ;code that gets info and makes updates
    fuWaitForChange()
EndFunc

 

Understanding basic syntax should help you to solve this and other problems in the future, which is why I gave the example above (3rd post): to try and help clarify things for you. From your original description of this problem, it sounds like you got stuck inside an infinite loop somewhere. You need to find it and get out when the right condition has been met.

Edited by czardas

Share this post


Link to post
Share on other sites
careca
15 hours ago, tk1 said:

From the help, it says to keep it simple since it runs so often and has the potential to create CPU load.

That really depends on the timer you put in place, of course, if the time is too low it keeps calling the function, but if it's more than a second you'll be fine.


Spoiler

Paster - Main function is to paste text, but has more functions. (No longer mantained, switched to String Trigger)

Renamer - Rename files and folders, remove portions of text from the filename etc.

GPO Tool - Export/Import Group policy settings.

MirrorDir - Synchronize/Backup/Mirror Folders

BeatsPlayer - Music player.

Params Tool - Right click an exe to see it's parameters or execute them.

String Trigger - Triggers pasting text or applications or internet links on specific strings.

Inconspicuous - Hide files in plain sight, not fully encrypted.

Regedit Control - Registry browsing history, quickly jump into any saved key.

Time4Shutdown - Write the time for shutdown in minutes.

Power Profiles Tool - Set a profile as active, delete, duplicate, export and import.

Firefox Profile Backup - Backup/restore previously saved profile.

Finished Task Shutdown - Shuts down pc when specified window/Wndl/process closes.

NetworkSpeedShutdown - Shuts down pc if download speed goes under "X" Kb/s.

IUIAutomation - Topic with framework and examples

Au3Record.exe

Share this post


Link to post
Share on other sites
tk1
Posted (edited)

Ok, apparently I'm not making myself clear with the example I gave so I'll go ahead and put the whole code here so you can see what exactly I'm doing.  I apologize for not doing that from the get-go.  I agree that I seem to be creating an infinite loop, but I'm just not sure how to prevent it in this situation (I'm sure there's a way, there always is).

 

BTW, some of the comments in the code are just notes to myself.

 

#Region ;**** Directives created by AutoIt3Wrapper_GUI ****
#AutoIt3Wrapper_Icon=IP1.ico
#AutoIt3Wrapper_Res_Fileversion=0.0.3.6
#EndRegion ;**** Directives created by AutoIt3Wrapper_GUI ****

;   To Do:
;       Backup/clear history
;       Create file for web
;       Upload ip information to website
;       Check for hung ftp process
;       Implement ini - use for folder locations, ftp profile, etc.
;       if no ini, create one with default settings and locations (search and discover)
;
;   Required extras:
;       PuTTY
;       SFTPEx.au3

#include <Inet.au3>
#include <StaticConstants.au3>
#include <GUIConstantsEx.au3>
#include <GuiButton.au3>
#include <ProgressConstants.au3>
#include <EditConstants.au3>
#include <WindowsConstants.au3>
#include <FileConstants.au3>
#include <SFTPEx.au3> ;Not included in normal AutoIt install
#include <Constants.au3>
#include <WMI_PingPlus.au3> ;Not included in normal AutoIt install, I modified WMI_Ping.au3

Global $sExtIPFullPath = "C:\Data\extIP\extIP.txt"
Global $sExtIPHistoryFullPath = "C:\Data\extIP\extIPhist.txt"
Global $sIPChangedTime
Global $sWINIPcurr
Global $sWANIPlast
Global $sPingCheck = "C:\Data\extIP\PingCheck.txt"
Global $sAppVersion = FileGetVersion(@ScriptFullPath)

Opt("GUIOnEventMode", 1) ;Change to OnEvent mode

Global $extIP = GUICreate("Ext IP Mon   " & $sAppVersion, 254, 150, 285, 264, -1, -1)
GUISetOnEvent($GUI_EVENT_CLOSE, "fuClose")
GUICtrlCreateLabel("Last known IP:", 14, 16, 90, 15, $SS_LEFT, -1)
; Include StaticConstants.au3, Left-aligns text in a simple rectangle.
GUICtrlSetFont(-1, 10, 400, 0, "Arial")
GUICtrlSetBkColor(-1, "-2")
Global $sLastKnownIP = GUICtrlCreateLabel("0.0.0.0", 128, 16, 110, 15, $ES_RIGHT)
; Include EditConstants.au3, Right-aligns text in a multiline edit control.
GUICtrlSetFont(-1, 10, 600, 0, "Arial")
GUICtrlCreateLabel("Changed:  ", 14, 42, 90, 23)
GUICtrlSetFont(-1, 10, 400, 0, "Arial")
Global $sLastChanged = GUICtrlCreateLabel($sIPChangedTime, 105, 42, 132, 23, $ES_RIGHT)
GUICtrlSetFont(-1, 10, 400, 0, "Arial")
GUICtrlCreateLabel("Verified:", 14, 69, 90, 15, -1, -1)
GUICtrlSetFont(-1, 10, 400, 0, "Arial")
GUICtrlSetBkColor(-1, "-2")
Global $sTimeChecked = GUICtrlCreateLabel("", 105, 69, 132, 23, $ES_RIGHT)
GUICtrlSetFont(-1, 10, 400, 0, "Arial")
GUICtrlSetBkColor(-1, "-2")
GUICtrlSetTip(-1, "This is the last time the known IP was checked.")
Global $sStatus = GUICtrlCreateLabel("--> Begin <--", 85, 90, 150, 15, -1, -1)
GUICtrlSetBkColor(-1, "-2")
Global $oMinuteBar = GUICtrlCreateProgress(85, 106, 73, 1, $pbs_smooth)
GUICtrlSetBkColor(-1, "-2")
Global $btnShowHist = GUICtrlCreateButton("History", 8, 112, 75, 29, $BS_CENTER, -1)
; Include ButtonConstants.au3, Centers text horizontally in the button rectangle.
GUICtrlSetOnEvent(-1, "fuShowHist")
GUICtrlSetFont(-1, 10, 400, 0, "Arial")
GUICtrlSetTip(-1, "Click to open the history file in Notepad.")
Global $btnRefresh = GUICtrlCreateButton("Refresh", 88, 112, 75, 29, $BS_CENTER, -1)
GUICtrlSetOnEvent(-1, "fuRefresh")
GUICtrlSetFont(-1, 10, 400, 0, "Arial")
GUICtrlSetTip(-1, "Click to refresh the last known IP and last time it was checked.")
Global $btnExit = GUICtrlCreateButton("Exit", 168, 112, 75, 29, -1, -1)
GUICtrlSetOnEvent(-1, "fuClose")
GUICtrlSetFont(-1, 10, 400, 0, "Arial")

GUISetState(@SW_SHOW, $extIP)

$sWANIPlast = fuReadPubIP() ;Get the last known IP from the file.

While 1
fuRefresh()
WEnd

Func fuRefresh() ;Refresh the labels in the GUI.
    GUICtrlSetData($sStatus, "Getting current ext. IP.")
    fuGetWANIP() ;Get current external IP.
    fuCheckWANIP() ;Verify external IP is valid.
    GUICtrlSetData($sStatus, "Last known vs. current.")
    fuUpdateIP() ;Updates the current external IP address.
    GUICtrlSetData($sStatus, "Begin ping cycle.")
    fuWaitForIPChange() ;Ping current IP, until unpingable.
EndFunc   ;==>fuRefresh


Func fuReadPubIP() ;Get the last known IP from the file.
    GUISetCursor(1) ;Set the cursor to APPSTARTING
    Local $sIPaddr = FileReadLine($sExtIPFullPath, 1) ;Reads the first line of the file.
    GUICtrlSetData($sLastKnownIP, $sIPaddr) ;Updates IP on the GUI.
    GUICtrlSetData($sTimeChecked, @YEAR & "." & @MON & "." & @MDAY & "   " & @HOUR & ":" & @MIN & ":" & @SEC)
    $sIPChangedTime = FileReadLine($sExtIPFullPath, 3) & "   " & FileReadLine($sExtIPFullPath, 4)
    GUICtrlSetData($sLastChanged, $sIPChangedTime)
    GUISetCursor(2) ;Set the cursor to ARROW
    If StringInStr($sIPaddr, ".") = "0" Or $sIPaddr = "-1" Then
        Return "86"
    Else
        Return $sIPaddr
    EndIf
EndFunc   ;==>fuReadPubIP

Func fuGetWANIP() ;Get current external IP.
    GUISetCursor(1) ;Set the cursor to APPSTARTING
    $sWINIPcurr = _GetIP() ;Call to the external function in Inet.au3
    GUISetCursor(2) ;Set the cursor to ARROW
EndFunc   ;==>fuGetWANIP

Func fuCheckWANIP() ;Verify $sWINIPcurr is a valid external IP.
    While $sWINIPcurr = "" Or $sWINIPcurr = "-1" Or $sWINIPcurr = "0" Or StringLeft($sWINIPcurr, 7) = "192.168"
        Sleep(20000)
        fuGetWANIP()
    WEnd
EndFunc   ;==>fuCheckWANIP

Func fuUpdateIP() ;Updates the current external IP address.
    GUISetCursor(1) ;Set the cursor to APPSTARTING.

    ;Get the IP if it has changed or is missing.
    If $sWANIPlast = "" Or $sWANIPlast <> $sWINIPcurr Then
        FileDelete($sExtIPFullPath)
        FileWriteLine($sExtIPFullPath, $sWINIPcurr) ;Creates the current IP file, & writes the new IP to it.
        Local $sDateChanged = @YEAR & "." & @MON & "." & @MDAY
        Local $sTimeChanged = @HOUR & ":" & @MIN & ":" & @SEC
        ;Add the date and time to the current IP file.
        FileWriteLine($sExtIPFullPath, @CRLF & $sDateChanged & @CRLF & $sTimeChanged)
        ;Update the history file with the date and time.
        FileWriteLine($sExtIPHistoryFullPath, $sDateChanged & @TAB & $sTimeChanged & @TAB & $sWINIPcurr)
        $sWANIPlast = fuReadPubIP()
    EndIf
    GUICtrlSetData($sStatus, "Update ext. IP.")

    GUISetCursor(2) ;Set the cursor to ARROW
EndFunc   ;==>fuUpdateIP

Func fuWaitForIPChange() ;Ping current IP, until unpingable.
    Local $sPingTime
    Local $sResult

    GUICtrlSetData($sStatus, $sWANIPlast)

    While 1
        $sResult = WMI_Ping($sWANIPlast, 2)
        If Not @error Then
            GUISetCursor(1) ;Set the cursor to APPSTARTING.
            For $i = 1 To 5
                GUICtrlSetData($sStatus, "Minute " & $i & " of 5.")
                $sPingTime = @HOUR & ":" & @MIN & ":" & @SEC & @TAB & @YEAR & "." & @MON & "." & @MDAY & _
                        @TAB & "Pinged OK"
                FileWriteLine($sPingCheck, $sPingTime)
                fuMinuteBarUpdate() ;Wait for 1 minute.
                $sWANIPlast = fuReadPubIP()
            Next
            GUICtrlSetData($sStatus, "Publish to web.")
            ; RETHINK:  Is this the correct place to publish to web?
            fuPubToWeb()
        Else
            GUICtrlSetData($sStatus, $sWANIPlast & " is unpingable.")
            GUISetCursor(2) ;Set the cursor to ARROW
            ExitLoop
        EndIf
    WEnd
    GUICtrlSetData($sStatus, "Restart IP check cycle.")
EndFunc   ;==>fuWaitForIPChange

Func fuPubToWeb() ;Publish to the website.
    ;_SFTP_Open("C:\PROGRA~1\Internet\PuTTY\putty.exe")

    ; publish the IP address, and the date/time of the last ping.
    FileDelete($sPingCheck) ;****  WHEN READY, MOVE TO fuWaitForIPChange ****

EndFunc   ;==>fuPubToWeb

Func fuMinuteBarUpdate()
    For $u = 1 To 60
        ;increment status bar by 1/60
        GUICtrlSetData($oMinuteBar, (100 - ($u / 60) * 100))
        Sleep(1000)
    Next
EndFunc   ;==>fuMinuteBarUpdate

Func fuShowHist() ;Open the history file in Notepad.
    Run("notepad.exe " & $sExtIPHistoryFullPath)
EndFunc   ;==>fuShowHist

Func fuClose()
    Exit
EndFunc   ;==>fuClose

I also attached my modified WMI_PingPlus.au3.

WMI_PingPlus.au3

Edited by tk1

Share this post


Link to post
Share on other sites
Subz

You use the following query which always appears to fail, I'm not sure what you're expecting.

SELECT * FROM Win32_PingStatus WHERE address = '86' AND TimeToLive = 2 AND BufferSize = 32 AND NoFragmentation = False AND RecordRoute = 0 AND ResolveAddressNames = False AND SourceRoute = ''

Your Gui is freezing because of the call to fuMinuteBarUpdate it won't continue until its finished the loop, you need to use adlibregister or some other function to remove that loop., 

Share this post


Link to post
Share on other sites
tk1
47 minutes ago, Subz said:

You use the following query which always appears to fail, I'm not sure what you're expecting.

SELECT * FROM Win32_PingStatus WHERE address = '86' AND TimeToLive = 2 AND BufferSize = 32 AND NoFragmentation = False AND RecordRoute = 0 AND ResolveAddressNames = False AND SourceRoute = ''

Your Gui is freezing because of the call to fuMinuteBarUpdate it won't continue until its finished the loop, you need to use adlibregister or some other function to remove that loop., 

So if the IP is anything but a good, real, external IP address (e.g. "86") it is supposed to fail.  That is the desired outcome, which will then trigger attempts to get the new valid external IP of the modem.  Sometimes I was getting a successful ping on a valid external IP because the ISP had already reassigned that IP to another node, which for my purposes, was undesirable.  To fix that, I added the TTL to the WMI_Ping so I could stop the ping at the WAN port of the modem.

Your last statement caused me to realize the While loop in my fuWaitForIPChange() function is not pinging as often as I thought it was (once every 5 minutes vs. once a minute).

 

Thanks,

tk1

Share this post


Link to post
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

×