/* ProgressBar and ProgressDlg classes. ProgressBar modified from Alaska sample program to assure that the threads are released!!! Basicly I removed the thread from the Class definition and initialize in an ivar. Also made sure the ivar was NILed out on destroy. This seems to have done the trick. This should work any where you have used the ProgressBar class. You can also use the ProgressDlg class to simplify creation of quick dialogs to show progress Also there are two functions to simplify things further StartProgress(),IncProgress() James W. Loughner jwrl@charter.net */ #include "Gra.ch" #include "Xbp.ch" #include "Common.ch" #include "Appevent.ch" #include "Font.ch" #include "Appbrow.ch" #include "Dmlb.ch" #include "dll.ch" #include "inkey.ch" #xtrans CenterPos( , ) => ; { Int( (\[1] - \[1]) / 2 ) ; , Int( (\[2] - \[2]) / 2 ) } // remark out the following line to run in a real program // unremark to compile as a stand alone test #define TESTPROGRESS #ifdef TESTPROGRESS PROCEDURE APPSYS() RETURN PROCEDURE MAIN() LOCAL oProgDlg,nColor,aSize LOCAL I,J FOR J = 1 TO 10 // test for release of thread IF J%2 = 0 // alternate horizontal vertical aSize := {50,300} // vertical ELSE aSize := {300,50} // horizontal ENDIF nColor := J // change color oProgDlg := StartProgress(Appdesktop(),aSize,1,2500,nColor) FOR I = 1 TO 2500 IncProgress(oProgDlg) SLEEP(1) NEXT I oProgDlg:Destroy() oProgDlg:= NIL SLEEP(100) NEXT J RETURN #endif FUNCTION StartProgress(oParent,aSize,nMin,nMax,nColor) LOCAL oDlg IF EMPTY(aSize) aSize := {300,55} ENDIF oDlg := ProgressDlg():NEW(oParent,,CenterPos(aSize,oParent:CurrentSize()),aSize) oDlg:ProgressBar:minimum := nMin oDlg:ProgressBar:maximum := nMax IF nColor != NIL oDlg:ProgressBar:Color := nColor ENDIF oDlg:create() RETURN oDlg PROCEDURE IncProgress(oProgDlg) oProgDlg:ProgressBar:increment() RETURN CLASS ProgressDlg FROM XbpDialog EXPORTED: VAR ProgressBar,Percentage INLINE METHOD INIT(oParent, oOwner, aPos, aSize, aPP, lVisible ) ::XbpDialog:Init(oParent, oOwner, aPos, aSize, aPP, lVisible ) ::TitleBar := .F. IF aSize[1]>aSize[2] // wide use horizontal ::ProgressBar:= ProgressBar():New(::XbpDialog,,{10,aSize[2]/2-INT(0.3*aSize[2])},{aSize[1]-20,INT(0.3*aSize[2])}) IF aSize[2] >= 50 ::Percentage := XbpStatic():New(::XbpDialog,,{10,aSize[2]-INT(0.5*aSize[2])},{aSize[1]-20,INT(0.3*aSize[2])}) ::Percentage:Type := XBPSTATIC_TYPE_TEXT ::Percentage:Options := XBPSTATIC_TEXT_CENTER+XBPSTATIC_TEXT_VCENTER ::ProgressBar:Percent := ::Percentage ENDIF ELSE ::ProgressBar:= ProgressBar():New(::XbpDialog,,{aSize[1]/2+INT(0.3*aSize[1])/4,10},{INT(0.3*aSize[1]),aSize[2]-20}) IF aSize[1] >= 50 ::Percentage := XbpStatic():New(::XbpDialog,,{0,10},{INT(0.5*aSize[1]),aSize[2]-20}) ::Percentage:Type := XBPSTATIC_TYPE_TEXT ::Percentage:Options := XBPSTATIC_TEXT_CENTER+XBPSTATIC_TEXT_VCENTER ::ProgressBar:Percent := ::Percentage ENDIF ENDIF RETURN Self INLINE METHOD Create(oParent, oOwner, aPos, aSize, aPP, lVisible ) ::XbpDialog:Create(oParent, oOwner, aPos, aSize, aPP, lVisible ) ::ProgressBar:Create() IF !EMPTY(::Percentage) ::Percentage:Create() ENDIF RETURN Self INLINE METHOD Destroy() ::ProgressBar:Destroy() ::ProgressBar := NIL ::XbpDialog:Destroy() RETURN Self ENDCLASS CLASS ProgressBar FROM XbpStatic PROTECTED: VAR squares, every, _current METHOD displayHoriz, displayVert EXPORTED: VAR maxWait, color,Thread,sig VAR minimum, current, maximum VAR Percent ASSIGN METHOD minimum, current, maximum METHOD init , create , destroy , setSize METHOD display, execute, increment ENDCLASS /* * Initialize the object and set Thread:interval() to zero. This way, * method :execute() is automatically repeated. */ METHOD ProgressBar:init( oParent, oOwner, aPos, aSize, aPP, lVisible ) ::Thread := Thread():New() // don't inherhit // ::Thread:init() ::Thread:setInterval( 1 ) // maybe the problem is with interval(0)-nope ::Thread:atStart := {|| ::xbpStatic:show() } ::Sig := Signal():New() // don't inherhit // ::Signal:init() ::xbpStatic:init( oParent, oOwner, aPos, aSize, aPP, lVisible ) ::xbpStatic:type := XBPSTATIC_TYPE_RAISEDBOX ::xbpStatic:paint := {|| ::display() } ::color := GRA_CLR_BLUE ::squares := 1 ::current := 0 ::every := 1 ::maxWait := 100 ::minimum := 0 ::maximum := 100 RETURN /* * Request system resources; calculate the number or squares which * fit into the progress bar and start the thread. */ METHOD ProgressBar:create( oParent, oOwner, aPos, aSize, aPP, lVisible ) ::xbpStatic:create( oParent, oOwner, aPos, aSize, aPP, lVisible ) aSize := ::currentSize() ::squares := Int( aSize[1] / (aSize[2]+1) ) ::Thread:start({||::execute()}) RETURN /* * Stop the thread of ProgressBar and release system resources */ METHOD ProgressBar:destroy /* * Turn off automatic repetition of :execute(). */ ::thread:setInterVal( NIL ) IF ::thread:active /* * Thread is still active. * Signal thread to leave its :wait() state */ ::Sig:signal() ENDIF IF ThreadObject() <> ::thread /* * The current thread is not the thread of ProgressBar (self). * Therefore, the current thread must wait for the end of self:thread */ ::thread:synchronize(0) ENDIF /* * System resources are released when self:thread has terminated */ ::thread := NIL // bingo this does release the thread!!!! ::xbpStatic:destroy() RETURN self /* * Change the size of ProgressBar. Before the size is changed, * everything is overpainted with the background color. */ METHOD ProgressBar:setSize( aSize ) LOCAL oPS, aAttr[ GRA_AA_COUNT ], _aSize oPS := ::lockPS() _aSize := ::currentSize() _aSize[1] -= 2 _aSize[2] -= 2 aAttr [ GRA_AA_COLOR ] := GRA_CLR_BACKGROUND GraSetAttrArea( oPS, aAttr ) GraBox( oPS, {1,1}, _aSize, GRA_FILL ) ::unlockPS( oPS ) ::xbpStatic:setSize( aSize ) RETURN self /* * ASSIGN method for :minimum */ METHOD ProgressBar:minimum( nMinimum ) IF ::maximum <> NIL .AND. nMinimum > ::maximum ::minimum := ::maximum ::maximum := nMinimum ELSE ::minimum := nMinimum ENDIF ::current := ::minimum RETURN self /* * ASSIGN method for :current */ METHOD ProgressBar:current( nCurrent ) IF Valtype( nCurrent ) == "N" ::current := nCurrent IF Valtype( ::maximum ) + Valtype( ::minimum ) == "NN" ::every := Int( ( ::maximum - ::minimum ) / ::squares ) ::_current := ::current ENDIF ENDIF RETURN ::current /* * ASSIGN method for :maximum */ METHOD ProgressBar:maximum( nMaximum ) IF ::minimum <> NIL .AND. nMaximum < ::minimum ::maximum := ::minimum ::minimum := nMaximum ELSE ::maximum := nMaximum ENDIF ::current := ::minimum RETURN self /* * Increment the current value and refresh display if necessary */ METHOD ProgressBar:increment( nIncrement ) IF Valtype( nIncrement ) <> "N" nIncrement := 1 ENDIF /* * While a progress is displayed, PROTECTED VAR :_current is incremented * to avoid the overhead of the ASSIGN method :current() */ ::_current += nIncrement IF Int( ::_current % ::every ) == 0 /* * This interrupts the ::wait( ::maxWait ) method in :execute(). * The progress bar is then refreshed immediately in its own thread. * Since the display occurs in a separate thread, it does not * slow down the actual process whose progress is visualized. * Index creation, for example, does not update the display, * but only signals self:thread */ ::Sig:signal() ENDIF RETURN /* * Refresh progress bar automatically every ::maxWait / 100 seconds * This method runs in self:thread and is automatically restarted * due to :setInterval(0) */ METHOD ProgressBar:execute ::Sig:wait( ::maxWait ) ::display() RETURN self /* * Visualize the current state of a process */ METHOD ProgressBar:display LOCAL oPS := ::lockPS() LOCAL aSize := ::currentSize() LOCAL aAttr [ GRA_AA_COUNT ] aSize[1] -= 2 aSize[2] -= 2 IF aSize[1] > aSize[2] ::displayHoriz( oPS, aSize, aAttr ) ELSE ::displayVert ( oPS, aSize, aAttr ) ENDIF IF !EMPTY(::Percent) ::Percent:SetCaption(STR(INT(100*::_current/(::maximum-::minimum)),3)+"%") ENDIF ::unlockPS( oPS ) RETURN self /* * Display squares from left to right (horizontal display) */ METHOD ProgressBar:displayHoriz( oPS, aSize, aAttr ) LOCAL nX, aPos1, aPos2, nCenter /* * Max. x coordinate for squares */ nX := aSize[1] * ::_current / ( ::maximum - ::minimum ) nX := Min( nX, aSize[1] ) /* * Fill the area to the right of the squares with background color */ aAttr [ GRA_AA_COLOR ] := GRA_CLR_BACKGROUND GraSetAttrArea( oPS, aAttr ) GraBox( oPS, {1+nX,1}, {aSize[1],aSize[2]}, GRA_FILL ) /* * Define fill color for squares */ aAttr [ GRA_AA_COLOR ] := ::color GraSetAttrArea( oPS, aAttr ) /* * Calculate position for leftmost square (starting position) */ aPos1 := { 2, 2 } ::squares := Int( aSize[1] / (aSize[2]+1) ) nCenter := 2 + ( aSize[1] - (::squares * (aSize[2]+1)) ) / 2 aPos1[1] := Max( 2, nCenter ) aPos2 := { aPos1[1]+aSize[2]-2 , aSize[2]-1 } /* * Draw the squares */ DO WHILE aPos2[1] < nX GraBox( oPS, aPos1, aPos2, GRA_FILL ) aPos1[1] += aSize[2]+1 aPos2[1] += aSize[2]+1 ENDDO IF aPos2[1] < aSize[1] GraBox( oPS, aPos1, aPos2, GRA_FILL ) ENDIF RETURN self /* * Display squares from bottom to top (vertical display) */ METHOD ProgressBar:displayVert( oPS, aSize, aAttr ) LOCAL nY, aPos1, aPos2, nCenter /* * Max. y coordinate for squares */ nY := aSize[2] * ::_current / ( ::maximum - ::minimum ) nY := Min( nY, aSize[2] ) /* * Fill the area above the squares with background color */ aAttr [ GRA_AA_COLOR ] := GRA_CLR_BACKGROUND GraSetAttrArea( oPS, aAttr ) GraBox( oPS, {1,nY}, {aSize[1],aSize[2]}, GRA_FILL ) /* * Define fill color for squares */ aAttr [ GRA_AA_COLOR ] := ::color GraSetAttrArea( oPS, aAttr ) /* * Calculate position for lowest square (starting position) */ aPos1 := { 2, 2 } ::squares := Int( aSize[2] / (aSize[1]+1) ) nCenter := 2 + (aSize[2] - (::squares * (aSize[1]+1)) ) / 2 aPos1[2] := Max( 2, nCenter ) aPos2 := { aSize[1]-1, aPos1[2]+aSize[1]-2 } /* * Draw the squares */ DO WHILE aPos2[2] < nY GraBox( oPS, aPos1, aPos2, GRA_FILL ) aPos1[2] += aSize[1]+1 aPos2[2] += aSize[1]+1 ENDDO IF aPos2[2] < aSize[2] GraBox( oPS, aPos1, aPos2, GRA_FILL ) ENDIF RETURN self