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 -
):
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

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)

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

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.