Page 1 of 2

Example of StatusbarProgress Update

Posted: Tue Mar 10, 2020 10:37 pm
by Columbo
Can anyone give me a explanation or, preferably, a quick commented example, of how you update a StatusbarProgress gadget when copying a file source to destination? Creating the gadget, according to the Purebasic documentation looks simple enough but I can’t figure out how use it to show the progress when using CopyFile().

Thanks

Re: Example of StatusbarProgress Update

Posted: Tue Mar 10, 2020 11:05 pm
by microdevweb
Hi Columbo,
a small example

Code: Select all

; example of status bar
Global max = 1000 ; max value of status bar
Enumeration 
  #MAIN_FORM
  #STATUS_BAR
  #TEXT
EndEnumeration
Procedure THR_increment(*para)
  Protected n
  Repeat
    n + 1
    SetGadgetText(#TEXT,"progress "+Str(n)+" on "+Str(max))
    StatusBarProgress(#STATuS_BAR,0,n,#PB_StatusBar_BorderLess,0,max)
    Delay(20) 
  Until n > =max
  
  MessageRequester("Information","The work is done",#PB_MessageRequester_Info)
  CloseWindow(#MAIN_FORM)
  End
EndProcedure
Procedure start()
  OpenWindow(#MAIN_FORM,0,0,200,50,"Working",#PB_Window_ScreenCentered)
  TextGadget(#TEXT,10,10,180,30,"")
  CreateStatusBar(#STATUS_BAR,WindowID(#MAIN_FORM))
  AddStatusBarField(#PB_Ignore) ; take all size
  CreateThread(@THR_increment(),0)
EndProcedure

start()

Repeat
  WaitWindowEvent()
ForEver 

Re: Example of StatusbarProgress Update

Posted: Wed Mar 11, 2020 3:33 am
by Rinzwind
Dont update gui from thread. Crash prone.
Use postevent to instruct main event loop to update gui.

Re: Example of StatusbarProgress Update

Posted: Wed Mar 11, 2020 4:00 am
by Columbo
Thank you microdevweb. There are no comments in the code but I see how you are creating the StatusBarProgress Gadget. I'm just not sure how to connect it to the copying of a file using CopyFile()? I am assuming that it should know how large the file is and somehow determine how much of the total file has been copied and update the StatusBarProgress Gadget accordingly but that is where I am lost.

Re: Example of StatusbarProgress Update

Posted: Wed Mar 11, 2020 7:48 am
by TI-994A
Columbo wrote:...not sure how to connect it to the copying of a file using CopyFile()? ... somehow determine how much of the total file has been copied and update the StatusBarProgress Gadget accordingly...
Hi John.

In my experience, the CopyFile() function tends to be a little blocking and provides no updates in its process. An ideal alternative would be to manually copy the file with the ReadData() / WriteData() functions, allowing greater control of the process and progress. Here's an example which performs quite comparably to the system function, complete with progress updates (with detailed comments as requested - :lol: ):

Code: Select all

CompilerIf Not #PB_Compiler_Thread
  compiler_error.s = "Please enable threadsafe option in menu:" + #CRLF$ + 
                     "Compiler > Compiler Options > [X] Create threadsafe executable"
  MessageRequester("File Copy", compiler_error)
  End  
CompilerEndIf

; values for custom post events should 
; start from #PB_Event_FirstCustomValue
Enumeration #PB_Event_FirstCustomValue
  
  ; events to be posted from copy thread to
  ; update main thread of states & progress
  #File_Copy_Started
  #File_Copy_Progress
  #File_Copy_Complete  
  #File_Copy_Incomplete
  #File_Copy_Error
  
  ; gadget & window identifiers
  #mainWindow = 0
  
  #progressLabel = 0
  #progressBar
  #startCopyButton
  #abortCopyButton  
  
  ; file handles
  #sourceFile = 0
  #destinationFile
  
  ; number of bytes to read & write at a time
  #copyChunkSize = 1048576
EndEnumeration

; global flags to facilitate thread termination
Global copyThread, threadQuit

; files are selected outside the thread, so they are 
; declared as globals to be accessible by the thread
Global.s sourceFile, destinationFile

; drop-in file copy procedure with progress
Procedure FileCopyProgress(threadData)
  
  ; open source file for reading      
  If ReadFile(#sourceFile, sourceFile)
    
    ; capture source file length to calculate progress
    sourceFileLen = Lof(#sourceFile)
    destinationFileLen = sourceFileLen
    
    ; create the destination file or overwrite if exists
    If CreateFile(#destinationFile, destinationFile)                    
      
      ; create a memory buffer to hold the read data
      *fileCopyBuffer = AllocateMemory(#copyChunkSize)
      
      ; notify main thread that copy process has started
      PostEvent(#File_Copy_Started)
      
      ; loop until copied data is zero or thread termination flag is set
      While destinationFileLen > 0 And Not threadQuit
        
        ; read #copyChunkSize data into memory buffer
        ReadData(#sourceFile, *fileCopyBuffer, #copyChunkSize)
        
        ; write #copyChunkSize data into destination file
        WriteData(#destinationFile, *fileCopyBuffer, #copyChunkSize)            
        
        ; decrease remaining data to be written
        destinationFileLen - #copyChunkSize      
        
        ; calculate the percentage of the progress (basic math)
        progress.d = ((sourceFileLen - destinationFileLen) / sourceFileLen) * 100            
        
        ; notify main thread of progress percentage (rounded-off integer value only)
        PostEvent(#File_Copy_Progress, 0, 0, 0, Int(progress))
        
      Wend
      
      ; free allocated memory & close destination file
      FreeMemory(*fileCopyBuffer)
      CloseFile(#destinationFile)
      
      ; notify main thread of completion state based on percentage
      If progress < 100
        PostEvent(#File_Copy_Incomplete)
      Else            
        PostEvent(#File_Copy_Complete)
      EndIf
      
    Else
      
      ; notify main thread of destination file-creation error
      PostEvent(#File_Copy_Error, 0, 0, 0, #destinationFile)
      
    EndIf
    
    ; close source file
    CloseFile(#sourceFile)
    
  Else
    
    ; notify main thread of source file-opening error
    PostEvent(#File_Copy_Error, 0, 0, 0, #sourceFile)
    
  EndIf                   
  
EndProcedure

; selecting files before starting the copy thread
Procedure StartFileCopy() 
  
  ; start the copy process if the thread is not active
  If Not IsThread(copyThread)
    
    ; select source & destination files  
    sourceFile = OpenFileRequester("Select file to copy", "", "*.*", 0)            
    If sourceFile <> ""             
      
      destinationFile = SaveFileRequester("Select destination path & filename", "", "*.*", 0)            
      If destinationFile <> ""          
        
        threadQuit = #False
        copyThread = CreateThread(@FileCopyProgress(), 0)
        
      EndIf  
      
    EndIf             
    
  EndIf         
  
EndProcedure

; show/hide gadgets (repetitive calls)
Procedure DisplayProgress(show)
  
  HideGadget(#startCopyButton, show)
  HideGadget(#progressBar, show ! #True)
  HideGadget(#progressLabel, show ! #True)
  HideGadget(#abortCopyButton, show ! #True)  
  SetGadgetState(#progressBar, 0)
  
EndProcedure

wFlags = #PB_Window_SystemMenu | #PB_Window_ScreenCentered 
OpenWindow(#mainWindow, 0, 0, 400, 200, "Copy File Progress", wFlags)
TextGadget(#progressLabel, 0, 30, 400, 40, "Copying 0%...", #PB_Text_Center)
ProgressBarGadget(#progressBar, 10, 70, 380, 40, 0, 100)
ButtonGadget(#startCopyButton, 100, 80, 200, 40, "SELECT FILE TO COPY")
ButtonGadget(#abortCopyButton, 100, 130, 200, 40, "ABORT")

DisplayProgress(#False)

Repeat
  
  Select WaitWindowEvent()
      
    Case #PB_Event_CloseWindow      
      If IsThread(copyThread)
        
        ; if thread is active terminate thread
        threadQuit = #True
        
      Else
        
        ; if thread is not active terminate app
        appQuit = #True
        
      EndIf     
            
    ; thread notifies that copy process has started  
    Case #File_Copy_Started      
      DisplayProgress(#True)
      
    ; thread notifies of copy progress
    Case #File_Copy_Progress
      
      ; get the progress percentage sent by the thread
      progress = EventData()
      
      ; update UI only if percentage is different from last notification
      If progress <> lastProgress 
        lastProgress = progress
        SetGadgetState(#progressBar, progress)
        SetGadgetText(#progressLabel, "Copying " + Str(progress) + "%...")      
      EndIf     
      
    ; thread notifies that copy has completed
    Case #File_Copy_Complete
      message.s = sourceFile + #CRLF$ + " copied to " + #CRLF$ + destinationFile
      MessageRequester("File Copy", message)     
      DisplayProgress(#False)
      
    ; thread notifies that copy was aborted
    Case #File_Copy_Incomplete
      MessageRequester("File Copy", "File copy aborted!")
      DisplayProgress(#False)
      
    ; thread notifies of copy error  
    Case #File_Copy_Error
      
      ; get the copy error sent by the thread
      error = EventData()            
      
      If error = #sourceFile
        errorMessage$ = "Error opening source file!"
      Else        
        errorMessage$ = "Error creating destination file!"
      EndIf
      
      MessageRequester("File Copy", errorMessage$)           
      
    ; gadget handlers
    Case #PB_Event_Gadget
      Select EventGadget()
          
        ; start the file copy process  
        Case #startCopyButton                              
          StartFileCopy()
          
        ; abort copy process and terminate the thread  
        Case #abortCopyButton                    
          If IsThread(copyThread)
            threadQuit = #True
          EndIf     
          
      EndSelect      
      
  EndSelect
  
Until appQuit
EDITS:
1. Added the threadsafe check as suggested by Marc56us.
2. Removed the file selection from the thread as it was crashing on MacOS, as highlighted by Mindphazer.

Re: Example of StatusbarProgress Update

Posted: Wed Mar 11, 2020 8:30 am
by netmaestro
I made one too. You don't need it since TI-994A already did it but I worked on it so you get it.
Replace a$ with the path of a large file on your drive and b$ with its copied name to test:

Code: Select all

#UpdateStatus = #PB_Event_FirstCustomValue

Global a$, b$, threadfinished.i=#False, tid.i=0

a$ = "d:\movies\broadcast news.mp4"
b$ = "c:\movies\broadcast news.mp4"

Procedure Thread(void)
  threadfinished=#False
  If FileSize(b$) = 0 Or FileSize(b$) = -1
    If OpenFile(0, a$)
      If CreateFile(1, b$)
        totalbytes.q = FileSize(a$)
        chunksize.i  = totalbytes/100
        lastchunk.i  = totalbytes%100
        *chunkholder = AllocateMemory(chunksize)
        For i=1 To 100
          ReadData(0, *chunkholder, chunksize)
          WriteData(1, *chunkholder, chunksize)
          PostEvent(#UpdateStatus,#PB_Ignore,#PB_Ignore,#PB_Ignore, i)
          Delay(1)
        Next
        If lastchunk
          ReadData(0, *chunkholder, lastchunk)
          WriteData(1, *chunkholder, lastchunk)
        EndIf
        threadfinished=#True
        CloseFile(0)
        CloseFile(1)
        MessageRequester("", "Copy Finished!")
        PostEvent(#UpdateStatus,#PB_Ignore,#PB_Ignore,#PB_Ignore, 0)
      EndIf
    EndIf
  Else
    MessageRequester("", "File exists!")
    PostEvent(#UpdateStatus,#PB_Ignore,#PB_Ignore,#PB_Ignore, 0)
  EndIf
  
EndProcedure

Procedure ButtonHandler()
  If Not IsThread(tid)
    tid = CreateThread(@Thread(), 0)
    DisableGadget(1, 1)
  EndIf
EndProcedure

Procedure UpdateStatus()
  StatusBarProgress(0,0, EventData())
  If EventData()=0
    DisableGadget(1, 0)
  EndIf
EndProcedure

OpenWindow(0,0,0,640,480,"",#PB_Window_SystemMenu|#PB_Window_ScreenCentered)
CreateStatusBar(0, WindowID(0))
AddStatusBarField(640)
StatusBarProgress(0,0,0,#PB_StatusBar_BorderLess,0,100)
BindEvent(#UpdateStatus, @UpdateStatus())
ButtonGadget(1, 270, 250, 100, 20, "Copy Now")
BindGadgetEvent(1, @ButtonHandler())

Repeat:Until WaitWindowEvent() = #PB_Event_CloseWindow


Re: Example of StatusbarProgress Update

Posted: Wed Mar 11, 2020 9:31 am
by microdevweb
Rinzwind wrote:Dont update gui from thread. Crash prone.
Use postevent to instruct main event loop to update gui.
That right with another language like Java, but with Pb i never don't have any problem. Just active the thread option of compilator

Re: Example of StatusbarProgress Update

Posted: Wed Mar 11, 2020 10:06 am
by TI-994A
microdevweb wrote:...active the thread option of compilator
That reminds me; make sure to select the Create threadsafe executable option under the Compiler > Compiler Options menu whenever using threads, like in theses example.

Re: Example of StatusbarProgress Update

Posted: Wed Mar 11, 2020 10:23 am
by Marc56us
:idea: Add in Templates list for threadsafe code

Code: Select all

EnableExplicit

CompilerIf Not #PB_Compiler_Thread
  CompilerError "Need Compiler Options > [X] Create threadsafe executable"
CompilerEndIf
@Columbo you can also check destination filesize while copying in thread.
However, with SSD disks you often won't have time to see anything unless you have a huge file.

There is also an API that displays the Windows copy window with a progress bar and optionally a flow chart.
(but I don't know where)

:wink:

Re: Example of StatusbarProgress Update

Posted: Wed Mar 11, 2020 12:11 pm
by Mindphazer
microdevweb wrote:
Rinzwind wrote:Dont update gui from thread. Crash prone.
Use postevent to instruct main event loop to update gui.
That right with another language like Java, but with Pb i never don't have any problem. Just active the thread option of compilator
Hi microdevweb

Your code crashes on MacOS (even with thread safe activated); it does not on Windows

Re: Example of StatusbarProgress Update

Posted: Wed Mar 11, 2020 4:55 pm
by Columbo
Thanks Syed and netmaestro. Very much appreciated. I'll look at both examples.

Cheers!

Re: Example of StatusbarProgress Update

Posted: Wed Mar 11, 2020 5:40 pm
by mk-soft
The Module ThreadToGUI is for this... See Signatur :wink:

Re: Example of StatusbarProgress Update

Posted: Wed Mar 11, 2020 7:47 pm
by Columbo
Well commented Syed, thank you so much. Just one clarification,... Can you explain the "Thread" stuff? I know what a thread is in a forum post but what is it in a Purebasic program?

Thanks.

Re: Example of StatusbarProgress Update

Posted: Wed Mar 11, 2020 9:20 pm
by Sicro
@Columbo:

About threads in PureBasic:
https://www.purebasic.com/documentation ... index.html

Here I have also a module from the forum member @ts-soft: CopyDirEx.pbi

Re: Example of StatusbarProgress Update

Posted: Wed Mar 11, 2020 9:51 pm
by netmaestro
Lieutenant, a thread works like this: Say you have a procedure called Doit() that you call from your main loop. Your main loop stops executing until that procedure has finished and control is returned to it from the procedure. No good for long jobs. A thread is a procedure that executes alongside your main loop, running at the same time so your main loop can keep going, not stopping to wait for the procedure to finish. You often have a loop in a thread procedure and if you do it's wise to make sure there's a Delay(1) in it otherwise it'll hog the whole cpu. That's basically it in a nutshell, it's not complicated or difficult at all.