cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

Uncharted

Choose Language Hide Translation Bar
Craige_Hales
Super User
Progress Bar with Cancel Button

Progress Bar  showed how to make a progress bar. @abmayfield  question in "not responding" vs. "still thinking"  prompted me to revisit this. Here's another take, including a cancel button.

not cancelednot canceled

Cancel button pressed.Cancel button pressed.

progressBarWidth = 500;
progressUpdatesSeconds = 1 / 4; // 1/2 sec updates -> 1300ips,  1/30 -> 1000ips (1/4 compromise speed vs appearance)
cancel = 0; // clear the cancel flag
New Window( "Progressbar demo", //
	V List Box( //
		cats = Text Box( "" ), // output from the work function
		t = Text Box( "", <<setwrap( 500 ) ), // status of the progressbar
		H List Box( // Duct tape has a light side and a dark side. So does the progressbar.
			left = Spacer Box( size( 0, 10 ), color( "light green" ) ), //
			right = Spacer Box( size( progressBarWidth, 10 ), color( "dark green" ) ), //
			<<padding( 5, 5, 5, 5 ), // gray wrapper
			<<backgroundcolor( "dark gray" ) //
		), //
		cancelButton = Button Box( "Cancel", cancel = 1 ) // sets the cancel flag
	)
);
Wait( 0 ); // allow the window to open, otherwise the updates are not visible

// progressbar variables
startMicroSeconds = HP Time();
workCalls = 0;
recentUpdateMicroSeconds = HP Time();
runSeconds = (recentUpdateMicroSeconds - startMicroSeconds) / 1e6; // so the loop can start
limitSeconds = 10; // time to run the demo

updateProgress = Function( {fractionComplete},
	leftsize = Round( progressBarWidth * fractionComplete );
	rightsize = progressBarWidth - leftsize;
	left << width( leftsize );
	right << width( rightsize );
	t << settext( Eval Insert( "^workCalls^ iterations in ^char(runSeconds,6,3)^ seconds -> ^workCalls/runSeconds^ ips" ) );
	t << updatewindow; // this works without the wait	
);

totalCats = 0;
dowork = Function( {},
	x = "";
	For( i = 1, i <= 1000, i += 1, x = x || "." );
	totalCats += 1000;
	cats << settext( Eval Insert( "total concatenations=^totalCats^" ) );
);

While( runSeconds < limitSeconds & !cancel, // change this to represent the work you are doing
	
	// do some work, simulated by concatenating strings...
	dowork();
	
	// update the progressbar
	workCalls += 1;
	nowMicroSeconds = HP Time();
	// how often does it need to update on the screen?
	If( (nowMicroSeconds - recentUpdateMicroSeconds) / 1e6 > progressUpdatesSeconds, 
		// in this example I'm using a fixed time duration of "limitSeconds" to represent the
		// amount of work to be done. And "runSeconds" to represent the amount of work done so far.
		// Usually you'll have something other than time: number of rows in a table perhaps, or
		// number of categories to make reports for. As long as you know how many total and how
		// many completed, you can compute this fraction...
		updateProgress( runSeconds / limitSeconds );  // change this to represent the fraction of work finished
		recentUpdateMicroSeconds = nowMicroSeconds;
		Wait( 0 ); // this wait is needed to let the cancel button work
	);
	runSeconds = (nowMicroSeconds - startMicroSeconds) / 1e6;
);

If( cancel, // test the cancel flag
	Beep(); // audio flag
	left << color( "dark red" ); // visual flag something is awry
	right << color( "red" );
, //
	updateProgress( 1.0 ); // people like to see the bar go to 100%
);
cancelButton << setbuttonname( "Close" ); // visual flag the button function is changed
cancelButton << setscript( (cancelButton << closewindow) ); // and what to do if the button is pressed

 

Testing between JMP 15 and 16, I noticed 16 is a lot faster on this string concatenation work load! Nice work!

 

Edit: if JMP is running a platform that does not have its own cancelable progress bar, and it runs for a long time, this is not going to help. Tech Support would be a good place to request the platform support for a progress bar. Without it, there is no way (short of forcibly closing JMP) to stop it until it is done.

Last Modified: Nov 5, 2021 11:04 AM
Comments
jthi
Super User

Thanks @Craige_Hales ! I happened to need progress bar (or something similar) to one of my scripts, so I took this and modified it a bit to fit my needs. I turned it into a class ('m very beginner with JMP's classes and classes in general) for practice and because I felt like it could fit into one. I also added some documentation which can be parsed with Natural Docs.

Code:

 

View more...
Names Default To Here(1);
//Modified from https://community.jmp.com/t5/Uncharted/Progress-Bar-with-Cancel-Button/ba-p/433560
Define Class(
/************************************************************************************************
	Class: ProgressBar
		---Prototype---
		ProgressBar(int max_iteration = 1, int ith_iter_to_print = 5)
		---------------
		Creates brogress bar with cancel button. Pressing cancel will return 1 from 
		update(current_iteration) method this can be used to break out of loop.
	
	Prototype:
		> pb = New Object(ProgressBar(<max_iteration = 1>, <ith_iter_to_print = 5>)
		> pb << update(current_iteration)
		> pb << close()

	Parameters:
		max_iteration - Maximum iterations for the progress
		ith_iter_to_print - ith values to print. Modulo(current_iteration, ith_iter_to_print)
	
	Examples:
		----------JSL-----------
		pb = New Object(ProgressBar(10, 1));
		For(i = 1, i <= 10, i++,
			cancel_pressed = pb << update(i);
			If(cancel_pressed == 1,
				break();
			);
			wait(1);
		);
		pb << Close;
		pb << Delete Class;
		------------------------
************************************************************************************************/
	"ProgressBar",
	progressBarWidth = 500;
	cancel = 0;
	
	_init_ = Method({max_iteration = 1, ith_iter_to_print = 5},
		this:cancel = 0;
		this:max_iteration = max_iteration;
		this:window_ref = this:create();
		this:i_to_print = ith_iter_to_print;
		this:start_time = HP Time();
	);
	
	create = Method({},
		aa_nw = Associative Array();
		nw = New Window("Progress Bar", //
			V List Box(
				Align("center"), 
		//		cats = Text Box(""), // output from the work function
				t = Text Box("", <<setwrap(500)), // status of the progressbar
				H List Box( // Duct tape has a light side and a dark side. So does the progressbar.
					left = Spacer Box(size(0, 10), color("light green")), //
					right = Spacer Box(size(progressBarWidth, 10), color("dark green")), //
					<<padding(5, 5, 5, 5), // gray wrapper
					<<backgroundcolor("dark gray") //
				), //
				cancelButton = Button Box("Cancel", <<Style("Toggle")) // sets the cancel flag
			)
		);
		aa_nw["nw"] = nw;
		aa_nw["t"] = t;
		aa_nw["left"] = left;
		aa_nw["right"] = right;
		aa_nw["cancelButton"] = cancelButton;
		Return(aa_nw);
	);
	
	get_cancel_status = Method({},
		Return(this:window_ref["cancelButton"] << get)
	);
	
	update = Method({current_iteration},
		If(Modulo(current_iteration, this:i_to_print) == 0 | current_iteration == 1,
			Try(
				leftsize = Round(progressBarWidth * (current_iteration / this:max_iteration));
				rightsize = progressBarWidth - leftsize;
				this:window_ref["left"] << width(leftsize);
				this:window_ref["right"] << width(rightsize);
				cur_time = Round((HP Time() - this:start_time) / 10^6, 3); //could use formatting to keep same length
				this:window_ref["t"] << settext(Eval Insert("Iteration ^current_iteration^ of ^this:max_iteration^. Total runtime: ^cur_time^s"));
		//		this:window_ref["t"] << settext(Eval Insert("^current_iteration^ iterations in ^char(runSeconds,6,3)^ seconds -> ^workCalls/runSeconds^ ips"));
				this:window_ref["t"] << updatewindow; // this works without the wait
				cancel_status = this:get_cancel_status();
			, //lazy handling with catch
				cancel_status = 1;
				show(exception_msg);
			);
		);
/*		If(cancel_status == 1,
			this:window_ref["left"] << color("dark red");
			this:window_ref["right"] << color("red");
		);
*/
		Return(cancel_status);
	);
	
	close = Method({},
		Try(
			this:window_ref["nw"] << Close Window
		)
	);
);

pb = New Object(ProgressBar(10, 1));
For(i = 1, i <= 10, i++,
	cancel_pressed = pb << update(i);
	If(cancel_pressed == 1,
		break();
	);
	wait(1);
);
pb << Close;
pb << Delete Class;

Write();

Natural docs will look something like this:

 

jthi_0-1636986289331.png

Craige_Hales
Super User

It does want to be packaged, thanks!

 

In your example, pb<<Delete Class may not be what you want.  pb=0 is enough to destroy the object but leave the class available to create another progress bar object.

 

You've also pointed out something I missed in How to use Define Class :  I thought I needed to lay out the class members explicitly in the define class prototype, but this: can create them too.

 

Define Class("bubble",
//not needed:	m_xpos = .; 
	_init_ = Method( {x}, 
//because using explicit this: ... this:m_xpos = x;