Provides user feedback and cancel option for long running plugins.
Requires: iuplua
Basic Template
Constructing a basic Progress Bar is very simple. Essentially, we have to do two things – define the properties of the window containing the bar, and those of the bar itself. For the window, it is common to remove the normal Windows close button and menu bar, define a suitable title, and set the size, as the IUP default is rather small. For the bar, its default range value is from 0 to 1, and this is ideal for presenting the fraction of a process that has been completed.
The example script below creates the progress bar with a suitable title using the StartProgressBar function. A simple loop counts up to 10, setting the position of the bar after each step. The bar title is then changed, and the process repeated counting back down. Finally, the bar is hidden once the process is completed.
A key point to remember when implementing a progress bar is that it will generally slow down the process you are monitoring, as it diverts processing to update the bar position. The optimum balance between speed and feedback can be achieved with a suitable updating frequency. For example, if a process has 100,000 steps, it is not sensible to update the progress bar after each one. Updating every thousand steps would probably be perfectly adequate.
In this basic template, the progress bar is controlled via global variables that are accessible from anywhere in the script. For most purposes, this is perfectly adequate, but if you want to squeeze out every last drop of performance (at the cost of increased script complexity) the following sections describe how to develop more sophisticated versions.
function StartProgressBar(BarTitle) gaugeProgress = iup.progressbar{expand='HORIZONTAL'} dlgProgress = iup.dialog{title = BarTitle, dialogframe = 'YES', border = 'YES', iup.vbox {gaugeProgress}} dlgProgress.rastersize = '400x70' dlgProgress.menubox = 'NO' -- Remove Windows close button and menu. dlgProgress:showxy(iup.CENTER, iup.CENTER) -- Put up Progress Display return dlgProgress end require('iuplua'); iup.SetGlobal('CUSTOMQUITMESSAGE','YES'); -- Display progress bar with initial title StartProgressBar('Counting up...') for i = 1, 10, 1 do gaugeProgress.value = i / 10 fhSleep(500) end dlgProgress.title = 'Counting down...' for i = 10, 1, -1 do gaugeProgress.value = i / 10 fhSleep(500) end dlgProgress.visible = 'NO' fhMessageBox('End of demo.', 'MB_OK')
Function Prototype Version
This version uses a Function Prototype Closure to encapsulate the Global variables of the Global Variable Version, making them Private and Local, which has several benefits:
- The variables cannot be accidentally accessed outside the function.
- The variables have the run time performance of Locals.
- The variables persist between function calls just like Globals.
Nevertheless, optionally some parameters can be held externally in a Global variable, without any loss of performance.
Function Prototype Closures are explained by Programming in Lua: Chapter 6.1 Closures.
Also this version offers several enhancements:
- Improved display of both percentage progress and elapsed time clock.
- Faster execution by updating the display only when necessary instead of every Step.
- Defences against accidental misuse such as missing parameters, etc.
-
--[[ @Title: Progress Bar (drop in) @Author: Jane Taubman / Mike Tate @LastUpdated: January 2013 @Description: Allows easy adding of a Progress Bar to any long running Plugin ]] -- Progress Bar Function Prototype -- function NewProgressBar(tblGauge) local tblGauge = tblGauge or {} -- Optional table of external parameters local strFont = tblGauge.Font or nil -- Font dialogue default is current font local strButton = tblGauge.Button or "255 0 0" -- Button colour default is red local strBehind = tblGauge.Behind or "255 255 255" -- Background colour default is white local intShowX = tblGauge.ShowX or iup.CENTER -- Show window default position is central local intShowY = tblGauge.ShowY or iup.CENTER local intMax, intVal, intPercent, intStart, intDelta, intScale, strClock, isBarStop local lblText, barGauge, lblDelta, btnStop, dlgGauge local function doFocus() -- Bring the Progress Bar window into Focus dlgGauge.bringfront="YES" -- If used too often, inhibits other windows scroll bars, etc end -- local function doFocus local function doUpdate() -- Update the Progress Gauge and the Delta % with clock barGauge.value = intVal lblDelta.title = string.format("%4d %% %s ",math.floor(intPercent),strClock) end -- local function doUpdate local function doReset() -- Reset all dialogue variables and Update display intVal = 0 -- Current value of Progress Bar intPercent = 0.01 -- Percentage of progress intStart = os.time() -- Start time of progress intDelta = 0 -- Delta time of progress intScale = math.ceil( intMax / 1000 ) -- Scale of percentage per second (this guess is corrected in Step function) strClock = "00 : 00 : 00" -- Clock delta time display isBarStop = false -- Stop button pressed signal doUpdate() doFocus() end -- local function doReset local tblProgressBar = { Start = function(strTitle,intMaximum) -- Create & start Progress Bar window if not dlgGauge then strTitle = strTitle or "" -- Dialogue and button title intMax = intMaximum or 100 -- Maximun range of Progress Bar, default is 100 local strSize = tostring( math.max( 100, string.len(" Stop "..strTitle) * 8 ) ).."x30" -- Adjust Stop button size to Title lblText = iup.label { title=" ", expand="YES", alignment="ACENTER", tip="Progress Message" } barGauge = iup.progressbar { rastersize="400x30", value=0, max=intMax, tip="Progress Bar" } lblDelta = iup.label { title=" ", expand="YES", alignment="ACENTER", tip="Percentage and Elapsed Time" } btnStop = iup.button { title=" Stop "..strTitle, rastersize=strSize, fgcolor=strButton, tip="Stop Progress Button", action=function() isBarStop = true end } -- Signal Stop button pressed return iup.CLOSE -- Often caused main GUI to close !!! dlgGauge = iup.dialog { title=strTitle.." Progress ", font=strFont, dialogframe="YES", background=strBehind, -- Remove Windows minimize/maximize menu iup.vbox{ alignment="ACENTER", gap="10", margin="10x10", lblText, barGauge, lblDelta, btnStop, }, move_cb = function(self,x,y) tblGauge.ShowX = x tblGauge.ShowY = y end, close_cb = btnStop.action, -- Windows Close button = Stop button } dlgGauge:showxy(intShowX,intShowY) -- Show the Progress Bar window doReset() -- Reset the Progress Bar display end end, Message = function(strText) -- Show the Progress Bar message if dlgGauge then lblText.title = strText end end, Step = function(intStep) -- Step the Progress Bar forward if dlgGauge then intVal = intVal + ( intStep or 1 ) -- Default step is 1 local intNew = math.ceil( intVal / intMax * 100 * intScale ) / intScale if intPercent ~= intNew then -- Update progress once per percent or per second, whichever is smaller intPercent = math.max( 0.1, intNew ) -- Ensure percentage is greater than zero if intVal > intMax then intVal = intMax intPercent = 100 end -- Ensure values do not exceed maximum intNew = os.difftime(os.time(),intStart) if intDelta < intNew then -- Update clock of elapsed time intDelta = intNew intScale = math.ceil( intDelta / intPercent ) -- Scale of seconds per percentage step local intHour = math.floor( intDelta / 3600 ) local intMins = math.floor( intDelta / 60 - intHour * 60 ) local intSecs = intDelta - intMins * 60 - intHour * 3600 strClock = string.format("%02d : %02d : %02d",intHour,intMins,intSecs) end doUpdate() -- Update the Progress Bar display end iup.LoopStep() end end, Focus = function() if dlgGauge then doFocus() end -- Bring the Progress Bar window to front end, Reset = function() -- Reset the Progress Bar display if dlgGauge then doReset() end end, Stop = function() -- Check if Stop button pressed iup.LoopStep() return isBarStop end, Close = function() -- Close the Progress Bar window isBarStop = false if dlgGauge then dlgGauge:destroy() dlgGauge = nil end end, } -- end newProgressBar return tblProgressBar end -- function NewProgressBar
Usage
Default Internal Parameters Example
-
local ProgressBar = NewProgressBar() ProgressBar.Start( "My Task" ) -- Defaults to 100 steps maximum for intStep = 1, 100 do ProgressBar.Message( intStep .. " %" ) fhSleep( 50, 40 ) -- Emulate performing the task ProgressBar.Step() -- Defaults to step by 1 if ProgressBar.Stop() then break end end ProgressBar.Close()
Advanced External Parameters Example
-
local tblGauge = { -- Pass parameters into Progress Bar function prototype Button = "0 0 255", -- Blue stop button ShowX = 100, -- Position window near top left ShowY = 100, Font = "Tahoma, Bold 10", -- Tahoma, bold 10 point font } local ProgressBar = NewProgressBar(tblGauge) ProgressBar.Start( "My Task", 300 ) -- Set maximum range to 300 for intStep = 1, 300 do ProgressBar.Message( "Step = " .. intStep ) fhSleep( 20, 20 ) -- Emulate performing the task ProgressBar.Step(1) if ProgressBar.Stop() then ProgressBar.Reset() fhSleep( 1500, 100 ) -- Emulate progress stopped action break end end ProgressBar.Close()
Reducing Reverse Progress Bar Example
-
local ProgressBar = NewProgressBar() ProgressBar.Start( "My Task", 100 ) ProgressBar.Step(100) -- Start with full bar for intStep = 1, 100 do ProgressBar.Message( 100 - intStep .. " %" ) fhSleep( 50, 40 ) -- Emulate performing the task ProgressBar.Step(-1) -- Step backwards if ProgressBar.Stop() then break end end ProgressBar.Close()
Global Variable Version
This version requires Global variables to communicate between the various functions.
-
--[[ @Title: Progress Display (drop in) @Author: Jane Taubman / Mike Tate @LastUpdated: May 2012 @Description: Allows easy adding of a Progress Bar to any long running Plugin ]] StrWhite = "255 255 255" ProgressDisplay = { Start = function(strTitle,intMax) -- Create and start the Progress Display window controls if not dlgProgress then cancelflag = false local cancelbutton = iup.button{ title="Cancel", rastersize="200x30", action = function() cancelflag = true -- Signal that Cancel button was pressed return iup.CLOSE end } gaugeProgress = iup.progressbar{ rastersize="400x30", max=intMax } -- Set progress bar maximum range messageline = iup.label{ title=" ", expand="YES", alignment="ACENTER" } dlgProgress = iup.dialog{ title=strTitle, dialogframe="YES", background=StrWhite, -- Remove Windows minimize/maximize menu iup.vbox{ alignment="ACENTER", gap="10", margin="10x10", messageline, gaugeProgress, cancelbutton } } dlgProgress.close_cb = cancelbutton.action -- Windows Close button acts as Cancel button dlgProgress:showxy(iup.CENTER, iup.CENTER) -- Show the Progress Display dialogue window end end, SetMessage = function(strMessage) -- Set the progress message if dlgProgress then messageline.title = strMessage end end, Step = function(iStep) -- Step the Progress Bar forward if dlgProgress then gaugeProgress.value = gaugeProgress.value + iStep local val = tonumber(gaugeProgress.value) local max = tonumber(gaugeProgress.max) if val > max then gaugeProgress.value = 0 end iup.LoopStep() end end, Reset = function() -- Reset progress bar if dlgProgress then gaugeProgress.value = 0 end end, Cancel = function() -- Check if Cancel button pressed return cancelflag end, Close = function() -- Close the dialogue window cancelflag = false if dlgProgress then dlgProgress:destroy() dlgProgress = nil end end, }
Usage
Note the iup.LoopStep()
function may also be needed in the main loop of the plugin.
-
ProgressDisplay.Start('This is my progress box',100) for i=1,100 do ProgressDisplay.SetMessage(i.." %") fhSleep(50,40) -- Emulate performing the task ProgressDisplay.Step(1) if ProgressDisplay.Cancel() then break end end ProgressDisplay.Reset() ProgressDisplay.Close()