Announcement

Collapse
No announcement yet.

The executable that can't be stopped

Collapse
X
  • Filter
  • Time
  • Show
Clear All
new posts

  • The executable that can't be stopped

    Hello folks,

    I was having a hard time terminating an executable that I wrote in PBWin. I finally figured out the answer, and I learned something new. I searched this forum for ideas and came across some posts that kinda sorta danced around the answer, but didn't really nail it down. So, I thought I would post here. Also, I think my 'fix' is a real hack. I'd like to know if there's a more elegant solution.

    The original code is too long and complex to post. But I reduced it down to its essence. In plain English, I created a DIALOG and used its CALLBACK to kick-off a lengthy job inside of a SUB. In this example, the SUB only needs 10 seconds. But in my working code, it's much longer. Often, I wanted to terminate the program early. To do that, I tried the most obvious thing: clicking on the red "X" in the DIALOG's upper-right corner. I soon realized from the Windows Task Manager that the program was still running even though the DIALOG was destroyed. I didn't realize the SUB would continue to run in its own thread. I tried various ways of inserting DIALOG END statements and sending %WM_SYSCOMMAND, %SC_CLOSE to the DIALOG. Nothing worked. I couldn't terminate the program in a graceful way. I had to use the Task Manager to kill the process.

    I do realize that the program won't end until it gets beyond the DIALOG SHOW MODAL statement in PBMAIN(). And I tried various ways to send the 'flow' there from the SUB. But the SUB just didn't want to give up. Also, inserting IF... THEN END SUB seems like it should work, but didn't.

    I confirmed it was the SUB's thread that kept the program alive. My fix, which is a real hack, is to check often (inside of a tight loop) if the DIALOG still exists, and force the SUB to give up early if the DIALOG has been destroyed. But this seems a little crazy. I can send DIALOG events to the CALLBACK, but I what I really need is to end the SUB based on the DIALOG destruction event. My problem goes around and around. I'm back to constantly scanning for events. My program design is more-or-less set. But could there have been a better way to design it for easy program termination?

    In my code below, note there's one line commented-out. Try it both ways. It proves conclusively that the SUB's thread stays alive even after the DIALOG has been destroyed.

    I feel like I'm re-inventing something Windows should already be doing. Any comments are welcome.

    Code:
    #COMPILER PBWIN
    #INCLUDE "win32api.inc"
    
    CALLBACK FUNCTION hCallback
        IF  CB.CTLMSG = %BN_CLICKED AND _
            CB.MSG    = %WM_COMMAND THEN
            IF CB.CTL = 1000 THEN CALL TheJobSub(CB.HNDL)
            IF CB.CTL = 1002 THEN DIALOG END CB.HNDL
        END IF
    END FUNCTION
    
    SUB TheJobSub(BYVAL hDlg AS DWORD)
        LOCAL n AS LONG
        LOCAL m AS LONG
        m = 1
        CONTROL SET TEXT hDlg, 1001, _
            "The job has started." + $CRLF + _
            "It will take 10 seconds." + $CRLF + _
            "Try to quit the application now." + $CRLF + _
            "Try the Quit button, Alt-F4, or the dialog's X button."
        ' move the button off-screen
        CONTROL SET LOC hDlg, 1000, 300, 200
        ' create a "Quit" button
        CONTROL ADD BUTTON, hDlg, 1002, "Quit", 10, 10, 28, 12
        ' this is the code for a lengthy job
        FOR n=1 TO 100
            SLEEP 100
            DIALOG DOEVENTS 0
            ' >>> try running this program with, then without, <<<
            ' >>> the following line commented-out: <<<<<<<<<<<<<<
            ' DIALOG GET SIZE hDlg TO m, m : if m = 0 then exit for
        NEXT n
        ' the job is done
        MSGBOX IIF$(m=0, "JobSub was forced to exit early.", _
             "JobSub has finished naturally."), _
             %MB_ICONINFORMATION, EXE.NAMEX$
    END SUB
    
    FUNCTION PBMAIN () AS LONG
        LOCAL hDlg AS DWORD
        DIALOG NEW 0, EXE.NAMEX$, 80, 80, 190, 66, _
              %WS_OVERLAPPEDWINDOW, 0 TO hDlg
        CONTROL ADD BUTTON, hDlg, 1000, "Start The Job", 43, 8, 100, 14
        CONTROL ADD LABEL, hDlg, 1001, "", 0, 23, 190, 34, %SS_CENTER
        DIALOG SHOW MODAL hDlg CALL hCallback
    END FUNCTION
    Christopher P. Becker
    signal engineer in the defense industry
    Abu Dhabi, United Arab Emirates

  • #2
    trigger an EXIT SUB inside the loop in the sub
    Code:
    #compile exe
    #dim all
    #compiler pbwin
    #include "win32api.inc"
    %ID_StartBtn  = 1000
    %ID_UnNameLbl = 1001
    %ID_QuitBtn   = 1002
    global gQuiting as long   '<-- added
    callback function hCallback
        if cb.ctlmsg = %bn_clicked and _
           cb.msg    = %wm_command then
          if cb.ctl = %ID_StartBtn then call TheJobSub(cb.hndl)
          if cb.ctl = %ID_QuitBtn then
            gQuiting = 1  '<--added
            dialog end cb.hndl
          end if
        end if
    end function
    
    sub TheJobSub(byval hDlg as dword)
        local n as long
        local m as long
        m = 1
        control set text hDlg, %ID_UnNameLbl, _
            "The job has started." + $crlf + _
            "It will take 10 seconds." + $crlf + _
            "Try to quit the application now." + $crlf + _
            "Try the Quit button, Alt-F4, or the dialog's X button."
        ' move the button off-screen
        control set loc hDlg, %ID_StartBtn, 300, 200
        ' create a "Quit" button
        control add button, hDlg, %ID_QuitBtn, "Quit", 10, 10, 28, 12
        ' this is the code for a lengthy job
        for n=1 to 1000
          if gQuiting = 1 then exit sub '<== added
            sleep 100
            dialog doevents 0
            ' >>> try running this program with, then without, <<<
            ' >>> the following line commented-out: <<<<<<<<<<<<<<
            ' DIALOG GET SIZE hDlg TO m, m : if m = 0 then exit for
        next n
        ' the job is done
        msgbox iif$(m=0, "JobSub was forced to exit early.", _
             "JobSub has finished naturally."), _
             %mb_iconinformation, exe.namex$
    end sub
    
    function pbmain () as long
        local hDlg as dword
        dialog new 0, exe.namex$, 80, 80, 190, 66, %ws_overlappedwindow, 0 to hDlg
        control add button, hDlg, %ID_StartBtn, "Start The Job", 43, 8, 100, 14
        control add label, hDlg, %ID_UnNameLbl, "", 0, 23, 190, 34, %ss_center
       ' control add button, hDlg, %ID_QuitBtn, "Quit", 10, 10, 28, 12
        dialog show modal hDlg call hCallback
    end function
    Cheers,
    Dale

    Comment


    • #3
      OK, I realized after posting that EXIT SUB would work whereas END SUB would not. But it still doesn't solve the fundamental problem. I'm still constantly checking for a condition. It slows things down. My example is simplified, showing only one loop. In reality, I have many sequential loops inside the SUB, some slow, some fast. My hack fix was to insert many IF... THEN EXIT SUB statements, but this seems very crude. I had hoped there was a magic bullet out there somewhere where I could start a monitor in another thread, let it watch for a Windows event (as opposed to IF... THEN inside a loop), and send a message to kill the SUB. Or something like that.
      Christopher P. Becker
      signal engineer in the defense industry
      Abu Dhabi, United Arab Emirates

      Comment


      • #4
        Replace
        Code:
        FOR n=1 TO 1000
              IF gQuiting = 1 THEN EXIT SUB '<== added
                SLEEP 100
                DIALOG DOEVENTS 0
                ' >>> try running this program with, then without, <<<
                ' >>> the following line commented-out: <<<<<<<<<<<<<<
                ' DIALOG GET SIZE hDlg TO m, m : if m = 0 then exit for
            NEXT n
        with
        Code:
         DO
              INCR n
              'IF gQuiting = 1 THEN EXIT SUB '<== added
              SLEEP 100
              DIALOG DOEVENTS 0
              ' >>> try running this program with, then without, <<<
              ' >>> the following line commented-out: <<<<<<<<<<<<<<
              ' DIALOG GET SIZE hDlg TO m, m : if m = 0 then exit for
            LOOP UNTIL n=1000 OR gQuiting=1
        Rod
        "To every unsung hero in the universe
        To those who roam the skies and those who roam the earth
        To all good men of reason may they never thirst " - from "Heaven Help the Devil" by G. Lightfoot

        Comment


        • #5
          Or even:
          Code:
          #COMPILE EXE
          #DIM ALL
          #COMPILER PBWIN
          #INCLUDE "win32api.inc"
          %ID_StartBtn  = 1000
          %ID_UnNameLbl = 1001
          %ID_QuitBtn   = 1002
          GLOBAL gQuiting,n AS LONG   '<-- added added more
          CALLBACK FUNCTION hCallback
              IF CB.CTLMSG = %BN_CLICKED AND _
                 CB.MSG    = %WM_COMMAND THEN
                IF CB.CTL = %ID_StartBtn THEN CALL TheJobSub(CB.HNDL)
                IF CB.CTL = %ID_QuitBtn THEN
                  n=gQuiting       '<--added
                  gQuiting = 1000  '<--added  < changed
                  DIALOG END CB.HNDL
                END IF
              END IF
          END FUNCTION
          
          SUB TheJobSub(BYVAL hDlg AS DWORD)
              'local n as long
              LOCAL m AS LONG
              m = 1
              CONTROL SET TEXT hDlg, %ID_UnNameLbl, _
                  "The job has started." + $CRLF + _
                  "It will take 10 seconds." + $CRLF + _
                  "Try to quit the application now." + $CRLF + _
                  "Try the Quit button, Alt-F4, or the dialog's X button."
              ' move the button off-screen
              CONTROL SET LOC hDlg, %ID_StartBtn, 300, 200
              ' create a "Quit" button
              CONTROL ADD BUTTON, hDlg, %ID_QuitBtn, "Quit", 10, 10, 28, 12
              ' this is the code for a lengthy job
              DO
                INCR gQuiting
                  SLEEP 100
                  DIALOG DOEVENTS 0
                  ' >>> try running this program with, then without, <<<
                  ' >>> the following line commented-out: <<<<<<<<<<<<<<
                  ' DIALOG GET SIZE hDlg TO m, m : if m = 0 then exit for
          
              LOOP UNTIL gQuiting=1000
              ' the job is done  ******* changed the following
              IF n>0 AND n<1000 THEN
                MSGBOX "JobSub was forced to exit early.", _
                   %MB_ICONINFORMATION, EXE.NAMEX$
              ELSE
                MSGBOX  "JobSub has finished naturally."), _
                   %MB_ICONINFORMATION, EXE.NAMEX$
              END IF
          END SUB
          
          FUNCTION PBMAIN () AS LONG
              LOCAL hDlg AS DWORD
              DIALOG NEW 0, EXE.NAMEX$, 80, 80, 190, 66, %WS_OVERLAPPEDWINDOW, 0 TO hDlg
              CONTROL ADD BUTTON, hDlg, %ID_StartBtn, "Start The Job", 43, 8, 100, 14
              CONTROL ADD LABEL, hDlg, %ID_UnNameLbl, "", 0, 23, 190, 34, %SS_CENTER
             ' control add button, hDlg, %ID_QuitBtn, "Quit", 10, 10, 28, 12
              DIALOG SHOW MODAL hDlg CALL hCallback
          END FUNCTION
          Rod
          "To every unsung hero in the universe
          To those who roam the skies and those who roam the earth
          To all good men of reason may they never thirst " - from "Heaven Help the Devil" by G. Lightfoot

          Comment


          • #6
            A global exit variable can be set under %WM_DESTROY in CALLBACK FUNCTION hCallback and then used for exiting the loop in SUB TheJobSub. No extra checks needed.

            Repeated DOEVENTS inside a tight loop can add to execution time. In such cases, something like IF n MOD 5 = 0 THEN DIALOG DOEVENTS can help (MOD 5 or 2 or 20, whatever gives best result).

            Comment


            • #7
              Check IsWin(hDlg) in your Sub - Exit Sub if IsFalse.

              Otherwise, if you are happy that your program can terminate as soon as the Dialog is closed (no open files etc) then add this to your callback..
              Code:
                  IF CB.MSG = %WM_DESTROY THEN
                    Local hProc, ProcessId As Dword
                      GetWindowThreadProcessId (Cb.Hndl, ProcessId)
                      hProc = OpenProcess(%PROCESS_TERMINATE, %false, ProcessId)
                      WinBeep 400,400 : WinBeep 200,400
                      TerminateProcess (hProc, 0)
                      closehandle hProc
                  END IF
              Rgds, Dave

              Comment


              • #8
                %WM_DESTROY does not kill the loop (FOR/NEXT in this case). ((sent after window removed)).
                https://docs.microsoft.com/en-us/win...msg/wm-destroy

                Could have been EXIT FOR instead of EXIT SUB.

                If you want to exit a loop prematurely you've got to check if the exit condition exists.

                Using the MOD to do the check less often probably takes longer than the IF for the check!

                I put the SEND %WM_DESTROY in place of the DIALOG -
                Code:
                #compile exe
                #dim all
                #compiler pbwin
                #include "win32api.inc"
                %ID_StartBtn  = 1000
                %ID_UnNameLbl = 1001
                %ID_QuitBtn   = 1002
                'global gQuiting as long   '<<-- now removed
                callback function hCallback
                    if cb.ctlmsg = %bn_clicked and _
                       cb.msg    = %wm_command then
                      if cb.ctl = %ID_StartBtn then call TheJobSub(cb.hndl)
                      if cb.ctl = %ID_QuitBtn then
                       ' gQuiting = 1  '<<-- now removed
                       dialog send cb.hndl, %wm_destroy , 0, 0
                      '  DIALOG END CB.HNDL '<<============== without this even dialog stays!!
                      end if
                    end if
                end function
                
                sub TheJobSub(byval hDlg as dword)
                    local n as long
                    local m as long
                    m = 1
                    control set text hDlg, %ID_UnNameLbl, _
                        "The job has started." + $crlf + _
                        "It will take 10 seconds." + $crlf + _
                        "Try to quit the application now." + $crlf + _
                        "Try the Quit button, Alt-F4, or the dialog's X button."
                    ' move the button off-screen
                    control set loc hDlg, %ID_StartBtn, 300, 200
                    ' create a "Quit" button
                    control add button, hDlg, %ID_QuitBtn, "Quit", 10, 10, 28, 12
                    ' this is the code for a lengthy job
                    for n=1 to 1000
                      ' if gQuiting = 1 then exit sub '<<-- now removed
                        sleep 100
                        dialog doevents 0
                        ' >>> try running this program with, then without, <<<
                        ' >>> the following line commented-out: <<<<<<<<<<<<<<
                        ' DIALOG GET SIZE hDlg TO m, m : if m = 0 then exit for
                    next n
                    ' the job is done
                    msgbox iif$(m=0, "JobSub was forced to exit early.", _
                         "JobSub has finished naturally."), _
                         %mb_iconinformation, exe.namex$
                end sub
                
                function pbmain () as long
                    local hDlg as dword
                    dialog new 0, exe.namex$, 80, 80, 190, 66, %ws_overlappedwindow, 0 to hDlg
                    control add button, hDlg, %ID_StartBtn, "Start The Job", 43, 8, 100, 14
                    control add label, hDlg, %ID_UnNameLbl, "", 0, 23, 190, 34, %ss_center
                   ' control add button, hDlg, %ID_QuitBtn, "Quit", 10, 10, 28, 12
                    dialog show modal hDlg call hCallback
                end function
                Dale

                Comment


                • #9
                  Dave posted while I was working. Looks more likely to work. Checking ...

                  added -
                  Check IsWin(hDlg) in your Sub - Exit Sub if IsFalse.
                  Is something different to check in loop in the sub.
                  Dale

                  Comment


                  • #10
                    Just adding the code Dave suggested to the callback works.

                    (But isn't the DOEVENTS in loop, and extra trip to the callback actually slower than checking one LONG variable?)

                    ((without the DOEVENTS adding the Exit button must move to PBMain (in this demo)))

                    Cheers,
                    Dale

                    Comment


                    • #11
                      Hi Christopher,


                      try this one

                      Code:
                      #COMPILER PBWIN
                      #INCLUDE "win32api.inc"
                      
                      
                      
                      CALLBACK FUNCTION hCallback
                      
                      static exitflag as dword
                      
                          if CB.MSG    = %WM_SYSCOMMAND THEN
                            if cb.wparam = %SC_Close then
                              exitflag = 1
                            end if
                          end if
                      
                          IF  CB.CTLMSG = %BN_CLICKED AND _
                              CB.MSG    = %WM_COMMAND THEN
                              IF CB.CTL = 1000 THEN CALL TheJobSub(CB.HNDL, exitflag)
                              IF CB.CTL = 1002 THEN exitflag = 1
                          END IF
                      END FUNCTION
                      
                      
                      SUB TheJobSub(BYVAL hDlg AS DWORD, exitflag as dword)
                          LOCAL n AS LONG
                      
                          CONTROL SET TEXT hDlg, 1001, _
                              "The job has started." + $CRLF + _
                              "It will take 10 seconds." + $CRLF + _
                              "Try to quit the application now." + $CRLF + _
                              "Try the Quit button, Alt-F4, or the dialog's X button."
                          ' move the button off-screen
                          CONTROL SET LOC hDlg, 1000, 300, 200
                          ' create a "Quit" button
                          CONTROL ADD BUTTON, hDlg, 1002, "Quit", 10, 10, 28, 12
                          ' this is the code for a lengthy job
                          FOR n=1 TO 100
                              if exitflag = 1 then exit for
                              DIALOG DOEVENTS 0
                      
                              SLEEP 100
                      
                          NEXT n
                          ' the job is done
                          MSGBOX IIF$(exitflag=1, "JobSub was forced to exit early.", _
                               "JobSub has finished naturally."), _
                               %MB_ICONINFORMATION, EXE.NAMEX$
                      
                      '    DIALOG END hdlg                                   'uncomment, if you want the dialog to close, when
                                                                            'the job is done either way
                      END SUB
                      
                      FUNCTION PBMAIN () AS LONG
                          LOCAL hDlg AS DWORD
                          DIALOG NEW 0, EXE.NAMEX$, 80, 80, 190, 66, _
                                %WS_OVERLAPPEDWINDOW, 0 TO hDlg
                          CONTROL ADD BUTTON, hDlg, 1000, "Start The Job", 43, 8, 100, 14
                          CONTROL ADD LABEL, hDlg, 1001, "", 0, 23, 190, 34, %SS_CENTER
                          DIALOG SHOW MODAL hDlg CALL hCallback
                      END FUNCTION

                      "END DIALOG", Alt + F4 or clicking the "Close" button try to end the message loop, but this doesn´t stop the "FOR ... NEXT" loop, so you program keeps running until this loop is done. If you want to end the "FOR ... NEXT" loop prematurely, you must check for an exit condition inside this loop, otherwise it will insist on running until it´s natural end. In other words: the message loop cannot end (even, if explicitly told to do so by "DIALOG END" , "DIALOG DOEVENTS") as long as a procedure called by it (message loop -> hCallback -> TheJobSub) didn´t return.


                      JK

                      Comment


                      • #12
                        I confirmed it was the SUB's thread that kept the program alive.
                        Thread for each job so dialog never waits.
                        Code:
                        #INCLUDE "win32api.inc"
                        %BUTTON1=1001
                        GLOBAL g() AS DWORD
                        
                        FUNCTION PBMAIN () AS LONG
                         DIM g(1 TO 1)
                         LOCAL hDlg AS DWORD
                         DIALOG NEW 0, EXE.NAMEX$, 80, 80, 190, 66,%WS_OVERLAPPEDWINDOW, 0 TO hDlg
                         CONTROL ADD BUTTON, hDlg, %BUTTON1, "Start job 1 thread", 43, 8, 100, 14
                         DIALOG SHOW MODAL hDlg CALL hCallback
                         DO UNTIL THREADCOUNT = 1
                          SLEEP 50
                         LOOP
                        END FUNCTION
                        
                        CALLBACK FUNCTION hCallback
                         SELECT CASE AS LONG CB.MSG
                          CASE %WM_COMMAND
                           SELECT CASE AS LONG CB.CTL
                            CASE %BUTTON1
                             IF (CB.CTLMSG = %BN_CLICKED OR CB.CTLMSG = 1) THEN
                              IF g(1) = 0 THEN 'thread not running
                               CONTROL SET TEXT CB.HNDL,%BUTTON1,"Quit job 1 thread"
                               THREAD CREATE TheJob(1) TO g(1) 'job to start
                               THREAD CLOSE g(1) TO g(1)
                              ELSE
                               g(1) = 0 'signal job 1 to finish
                               CONTROL SET TEXT CB.HNDL,%BUTTON1,"Start job 1 thread"
                              END IF
                             END IF
                           END SELECT
                          CASE %WM_SYSCOMMAND
                           IF (CB.WPARAM AND &HFFF0) = %SC_CLOSE THEN
                            g(1)=0 'signal job 1 to finish
                            DIALOG END CB.HNDL
                           END IF
                         END SELECT
                        END FUNCTION
                        
                        THREAD FUNCTION TheJob(BYVAL JobNumber AS LONG) AS LONG
                         STATIC running AS LONG
                         INCR running
                         IF running > 1 THEN EXIT FUNCTION 'only allow once
                        
                         SELECT CASE AS LONG JobNumber
                          CASE 1 'simulate job 1
                           DO WHILE g(1)
                            BEEP
                            SLEEP 1000
                           LOOP
                          CASE 2 'simulate job 2
                          CASE ELSE:? USING$("Job #",JobNumber)
                         END SELECT
                        
                         running = 0
                        END FUNCTION
                        https://duckduckgo.com instead of google

                        Comment


                        • #13
                          Correct, there are no additional threads in problem code. Disappearnce of dialog is being confused with ending of main thread which is running in a loop in a sub. Getting out of loop when dialog exits clears problem.

                          He does not need more threads, he wants existing main thread to stop (before it is done in loop) when Exit button pressed.
                          Dale

                          Comment


                          • #14

                            OK, I realized after posting that EXIT SUB would work whereas END SUB would not. But it still doesn't solve the fundamental problem. I'm still constantly checking for a condition. It slows things down. My example is simplified, showing only one loop. In reality, I have many sequential loops inside the SUB, some slow, some fast. My hack fix was to insert many IF... THEN EXIT SUB statements, but this seems very crude. I had hoped there was a magic bullet out there somewhere where I could start a monitor in another thread, let it watch for a Windows event (as opposed to IF... THEN inside a loop), and send a message to kill the SUB. Or something like that.
                            Christopher,
                            Put long running SUB in another thread.
                            No need for IF statements and DOEVENTS.
                            https://duckduckgo.com instead of google

                            Comment


                            • #15
                              Then you do the test for "quit early" somewhere else ... no gain.

                              as in CASE 1's next line is "DO WHILE g(1)"
                              it just doen't look like an IF/THEN
                              Dale

                              Comment


                              • #16
                                Dale,

                                The loop was used to keep the thread alive for the demonstration so clicking quit would have something to terminate.
                                DO/LOOP would not be in a real program.

                                CASE was used so only 1 thread function is needed with multiple jobs keeping the code in one place. It can also be eliminated.

                                Somewhere in the thread function a check for IF g(1) = 0 THEN EXIT FUNCTION is needed to early terminate.
                                https://duckduckgo.com instead of google

                                Comment


                                • #17
                                  Progress of job thread

                                  Also here is terminate thread by Michael Mattias example https://forum.powerbasic.com/forum/u...4788]Terminate

                                  Code:
                                  #INCLUDE "win32api.inc" 'threadsample.bas
                                  GLOBAL ghThread AS LONG
                                  %STARTJOB=1001
                                  %QUITJOB =1002
                                  %PROGRESSBAR=1003
                                  
                                  FUNCTION PBMAIN () AS LONG
                                   LOCAL hDlg AS DWORD
                                   DIALOG NEW 0, EXE.NAME$,,, 200, 30,%WS_OVERLAPPEDWINDOW, 0 TO hDlg
                                   CONTROL ADD BUTTON, hDlg, %STARTJOB,"Start job",0,0,50,12
                                   CONTROL DISABLE hDlg,%QUITJOB
                                   DIALOG SHOW MODAL hDlg CALL hCallback
                                   DO UNTIL THREADCOUNT=1:SLEEP 50:LOOP
                                  END FUNCTION
                                  
                                  CALLBACK FUNCTION hCallback
                                   LOCAL hThread AS LONG
                                   SELECT CASE AS LONG CB.MSG
                                    CASE %WM_COMMAND
                                     SELECT CASE AS LONG CB.CTL
                                      CASE %STARTJOB
                                       IF (CB.CTLMSG = %BN_CLICKED OR CB.CTLMSG = 1) THEN
                                         IF ghThread = 0 THEN
                                          CONTROL KILL CB.HNDL,%STARTJOB
                                          CONTROL ADD BUTTON, CB.HNDL, %QUITJOB,"Quit job",0,0,50,12
                                          CONTROL ADD PROGRESSBAR, CB.HNDL,%ProgressBar,"",60,0,100,12
                                          THREAD CREATE TheJob(CB.HNDL) TO ghThread
                                          THREAD CLOSE ghThread TO ghThread
                                         END IF
                                       END IF
                                      CASE %QUITJOB
                                        IF (CB.CTLMSG = %BN_CLICKED OR CB.CTLMSG = 1) THEN
                                         ghThread=0
                                         CONTROL KILL CB.HNDL,%QUITJOB
                                         CONTROL ADD BUTTON, CB.HNDL, %STARTJOB,"Start job",0,0,50,12
                                         CONTROL KILL CB.HNDL, %ProgressBar
                                        END IF
                                     END SELECT
                                    CASE %WM_SYSCOMMAND
                                     IF (CB.WPARAM AND &HFFF0) = %SC_CLOSE THEN
                                      ghThread=0
                                      DIALOG END CB.HNDL
                                     END IF
                                   END SELECT
                                  END FUNCTION
                                  
                                  THREAD FUNCTION TheJob(BYVAL hDlg AS DWORD) AS LONG
                                   CONTROL ADD PROGRESSBAR, hDlg,%ProgressBar,"",60,0,100,12
                                   LOCAL x AS LONG
                                  
                                   FOR x = 1 TO 10
                                    SLEEP 1000  'simulate processing
                                    IF ghThread = 0 THEN EXIT FOR 'quit or show progress
                                    PROGRESSBAR STEP hDlg,%PROGRESSBAR
                                    CONTROL SET TEXT hDlg,%QUITJOB,USING$("Quit (#%)",x*10)
                                   NEXT
                                  
                                   BEEP
                                   CONTROL SEND hDlg, %QuitJOB, %BM_CLICK, 0, 0
                                  
                                  END FUNCTION
                                  Last edited by Mike Doty; 7 Jan 2019, 05:26 PM.
                                  https://duckduckgo.com instead of google

                                  Comment


                                  • #18
                                    Christopher my choice is to eliminate the DIALOG DOEVENTS and replace it with something that provides better control for your needs. Try this...

                                    Code:
                                    #COMPILER PBWIN
                                    #INCLUDE "win32api.inc"
                                    
                                    GLOBAL ProgStat          AS LONG
                                    GLOBAL giCancelProgram   AS LONG
                                    GLOBAL giPRTDMessage     AS LONG  'monitors peek remove translate dispatch routine
                                    GLOBAL giPumpingMessages AS LONG  'monitors pump messages routine
                                    
                                    CALLBACK FUNCTION hCallback
                                        IF  CB.CTLMSG = %BN_CLICKED AND _
                                            CB.MSG    = %WM_COMMAND THEN
                                            IF CB.CTL = 1000 THEN CALL TheJobSub(CB.HNDL)
                                            IF CB.CTL = 1002 THEN
                                                giCancelProgram = 1
                                                DIALOG END CB.HNDL
                                            END IF
                                        END IF
                                    END FUNCTION
                                    
                                    SUB TheJobSub(BYVAL hDlg AS DWORD)
                                        LOCAL n AS LONG
                                        LOCAL m AS LONG
                                        m = 1
                                        CONTROL SET TEXT hDlg, 1001, _
                                            "The job has started." + $CRLF + _
                                            "It will take 10 seconds." + $CRLF + _
                                            "Try to quit the application now." + $CRLF + _
                                            "Try the Quit button, Alt-F4, or the dialog's X button."
                                        ' move the button off-screen
                                        CONTROL SET LOC hDlg, 1000, 300, 200
                                        ' create a "Quit" button
                                        CONTROL ADD BUTTON, hDlg, 1002, "Quit", 10, 10, 28, 12
                                        ' this is the code for a lengthy job
                                        FOR n=1 TO 100
                                            SLEEP 100
                                            CALL MyPumpMessages(10)
                                            IF GlobalVariableTest86 = 1 THEN EXIT FOR
                                            ' >>> try running this program with, then without, <<<
                                            ' >>> the following line commented-out: <<<<<<<<<<<<<<
                                            DIALOG GET SIZE hDlg TO m, m : IF m = 0 THEN EXIT FOR
                                        NEXT n
                                        ' the job is done
                                        MSGBOX IIF$(m=0, "JobSub was forced to exit early.", _
                                             "JobSub has finished naturally."), _
                                             %MB_ICONINFORMATION, EXE.NAMEX$
                                    END SUB
                                    
                                    FUNCTION PBMAIN () AS LONG
                                        LOCAL hDlg AS DWORD
                                        ProgStat = 1
                                    
                                        DIALOG NEW 0, EXE.NAMEX$, 80, 80, 190, 66, _
                                              %WS_OVERLAPPEDWINDOW, 0 TO hDlg
                                        CONTROL ADD BUTTON, hDlg, 1000, "Start The Job", 43, 8, 100, 14
                                        CONTROL ADD LABEL, hDlg, 1001, "", 0, 23, 190, 34, %SS_CENTER
                                        DIALOG SHOW MODAL hDlg CALL hCallback
                                    END FUNCTION
                                    
                                    '_________________________________________________________________
                                    '
                                    ' Processes pending Windows messages.                         xBot
                                    ' Call this procedure if you are performing a tight
                                    ' FOR/NEXT or DO/LOOP and need to allow
                                    ' your application to be responsive to user input.
                                    ' Modified version of AfxPumpMessages
                                    '_________________________________________________________________
                                    
                                    SUB MyPumpMessages(BYVAL xTimes AS LONG) EXPORT
                                      '/////////////////////////////////////////////////////////////
                                      'Do not want to remove the WM_QUIT message from message que.
                                      IF GlobalVariableTest86 = 1 THEN EXIT SUB
                                      '/////////////////////////////////////////////////////////////
                                    
                                      LOCAL Ix AS LONG
                                    
                                      IF giPumpingMessages = 1 THEN EXIT SUB
                                      giPumpingMessages = 1
                                    
                                      IF GlobalVariableTestOn THEN
                                          IF xTimes <= 0 THEN xTimes = 1
                                          FOR Ix = 1 TO xTimes
                                              CALL PeekRemoveTranslateDispatchMessage
                                              IF GlobalVariableTest86 = 1 THEN GOTO NoMoreNeeded
                                          NEXT Ix
                                      END IF
                                    
                                      NoMoreNeeded:
                                      giPumpingMessages = 0
                                    END SUB
                                    
                                    '_________________________________________________________________
                                    '
                                    '   SUB  PeekRemoveTranslateDispatchMessage
                                    '_________________________________________________________________
                                    
                                    SUB PeekRemoveTranslateDispatchMessage
                                      '/////////////////////////////////////////////////////////////
                                      'Do not want to remove the WM_QUIT message from message que.
                                      IF GlobalVariableTest86 = 1 THEN EXIT SUB
                                      '/////////////////////////////////////////////////////////////
                                      'Could use this to signal main message loop to close
                                      'PostThreadMessageA (GetCurrentThreadID, %WM_QUIT, 0, 0)   ' Signal Quit to Msg Loop
                                    
                                      IF giPRTDMessage = 1 THEN EXIT SUB
                                      giPRTDMessage = 1
                                    
                                      STATIC Msg AS tagMsg
                                      IF PeekMessageA(Msg, %NULL, %NULL, %NULL, %PM_REMOVE) THEN
                                          TranslateMessage Msg
                                          DispatchMessageA Msg
                                      END IF
                                    
                                      giPRTDMessage = 0
                                    END SUB
                                    
                                    '_________________________________________________________________
                                    '
                                    '   FUNCTION  GlobalVariableTest86                            xBot
                                    '_________________________________________________________________
                                    
                                    FUNCTION GlobalVariableTest86() AS LONG
                                        IF ProgStat = 0 OR giCancelProgram = 1 THEN
                                            FUNCTION = 1
                                        END IF
                                    END FUNCTION
                                    
                                    '_________________________________________________________________
                                    '
                                    '   FUNCTION  GlobalVariableTestOn                            xBot
                                    '_________________________________________________________________
                                    
                                    FUNCTION GlobalVariableTestOn() AS LONG
                                        IF ProgStat = 1 AND giCancelProgram = 0 THEN
                                            FUNCTION = 1
                                        END IF
                                    END FUNCTION

                                    Comment

                                    Working...
                                    X