Is a loading window possible?

In our program we have a section where you can edit employees in our databse, and it affects a lof of information in our db. It runs through through the table in the window, making a lot of queries per employee. I timed it at about 4 minutes.

Is there way to make it so I can get a loading screen? I tried system.gui.warningBox and placed it before the table loop, but that appears after the loop was done. I tried system.gui.messageBox, and that appeared first but then I had to click ok for the code to continue and then I’m at the same spot, the program just looks frozen and the user has no indication it’s working. Is it possible to just get a box to appear for the duration of the queries and then disappear, to load a graphic or anything really just to indicate that the program is working so the user doesn’t turn it off in the middle.

See the documentation for system.util.invokeAsynchronous and system.util.invokeLater. The heavy lifting can be run in the background (asynchronous), and can send progress to suitable graphics via invokeLater. And there’s various topics in the archives about these functions.

3 Likes

You could do something like:

event.source.parent.getComponent("LoadingBar").visible = True
def updateUI():
	event.source.parent.getComponent("LoadingBar").visible = False

def doLotsOfStuff():
	# paste your long script here	
	system.util.invokeLater(updateUI)


system.util.invokeAsynchronous(doLotsOfStuff)

An extension of what @pturmel was suggesting.

You can also make it fancy if you want to post out process updates.

4 Likes

I was able to get it to work going off of what you said but I had to do it slightly differently. I’ll post here in case anyone else has the same issue:

    def updateALotOfStuff(table = table, event = event):
        event.source.getComponent('Progress Bar').maximum = table.rowCount
        event.source.getComponent('Progress Bar').visible = True

        for i in range(table.rowCount):
            event.source.parent.getComponent('Progress Bar').value += 1
            # Do a lot of queries here
    
        system.nav.closeParentWindow(event)

system.util.invokeAsynchronous(updateMapRateInfo)

Obviously above my function I have a table and the event’s already there so they feed in fine. This was the way that worked for me. Trying to do invokeLater after the queries, the progress appeared only for a second right before the window closed anyways and wasn’t helpful. Maybe I was supposed to do it before the loop, but either way this works for me. Thanks for the help!

1 Like

You haven’t shown all of your code, but it looks like you are setting values on components from the background thread. That’s very bad. Lock up your system kind of bad. Queries go in the background. Assigning results and updating progress has to be in the foreground, in their own invokeLater functions.

2 Likes

Thanks for the warning. I was getting the progress bar to move like that but it did seem like I was misusing the async function. I now have something like this -

    def updateAlotOfStuff(tableData = tableData, event = event):
        def updateUI(tableData = tableData, event = event):
            event.source.parent.getComponent('Progress Bar').visible = True
            event.source.parent.getComponent('Progress Bar').maximum = tableData.rowCount

    system.util.invokeLater(updateUI)

    for i in range(tableData.rowCount):
        #lot of queries here
    system.nav.closeParentWindow(event)
system.util.invokeAsynchronous(updateMapRateInfo)

And with this I have a blank progress bar show up on screen and when the queries are done the whole thing closes which is fine. I was asked to do a progress bar, but I don’t think just having a blank one will necessarily cut it. Is there way to actually get it to the progress bar to move based on the queries without risking freezing up my system?

The only thing I could think of was to pass run the invoke later inside the for loop and also feed it the current iteration number, but that feels like I’m still just updating components from a background thread, is this safe or is that still bad practice?

My second solution, as this is not an operation we run very often, is that I’ve timed how long this query takes on average. I was going to add a solid minute to it and just throw in a timer in the invokelater function and have the progress bar load based on time, with no relation to the actual background thread. Again this has obvious problems, what if the queries are very slow today and it’s wrong and then the user goes to edit something else and its a race condition. But I am all out of ideas after that. Any suggestions?

Is it perhaps because you’ve missed the “r” on invokeLater?

     def updateAlotOfStuff(tableData = tableData, event = event):
        def updateUI(tableData = tableData, event = event):
            event.source.parent.getComponent('Progress Bar').visible = True
            event.source.parent.getComponent('Progress Bar').maximum = tableData.rowCount

    system.util.invokeLater(updateUI)

    for i in range(tableData.rowCount):
        #lot of queries here
    system.nav.closeParentWindow(event)
system.util.invokeAsynchronous(updateMapRateInfo)

Sorry I retyped my code from a VM to here not copy and pasted and made a typo, my code runs fine as is, it’s just not what I want. I’ll edit that typo though thanks.

I don’t see where you’re setting the value of the progress bar. So maybe try something like this?

    def updateAlotOfStuff(tableData = tableData, event = event):
        def updateUI(tableData = tableData, event = event):
            event.source.parent.getComponent('Progress Bar').visible = True
            event.source.parent.getComponent('Progress Bar').maximum = tableData.rowCount
            event.source.parent.getComponent('Progress Bar').value = newValue

    system.util.invokeLate(updateUI)

    for i in range(tableData.rowCount):
        #lot of queries here
    system.nav.closeParentWindow(event)
system.util.invokeAsynchronous(updateMapRateInfo)

Obviously, newValue isn’t defined here, but you’ll need some way of calculating how much progress has been achieved, perhaps a count of row’s you’ve processed.

That is what I want to do, but the issue is how do I get that newValue to the progress outside of just having the background thread actively updating it, which pturmel thankfully pointed out to me is a bad practice.

Your function names and indenting don’t match up. Where’s updateMapRateInfo ?

The point is that you have to call updateUI multiple times from the background to keep the progress bar moving. You may need various different updateUI functions at different points to update different things in the foreground. You almost certainly need different updateUI functions to assign results to the foreground components, separate from the progress bar updates.

Consider using the helpers from later.py, as described in this thread, to simplify your code. Your actionPerformed event would end up something like this:

def updateMapRateInfo(ev):
	progress = ev.source.parent.getComponent("Progess Bar")
	shared.later.assignLater(progress, "indeterminate", True)
	shared.later.assignLater(progress, "visible", True)

	# Do initial queries that will let you predict the maximum
	# you should use for the progress bar.  Then show it with:
	shared.later.assignLater(progress, "maximum", someMaximum)
	done = 0
	shared.later.assignLater(progress, "value", done)
	shared.later.assignLater(progress, "indeterminate", False)

	# Start doing your work, looping or whatever, with occassional
	# calls to show "done" in the the progress bar.  It can be
	# every time if maximum is a small number, or every ten or
	# hundred otherwise.  You don't want to spend more time
	# notifying the foreground than actually doing work.
	for row in pyds:
		done += 1
		if not (done % 10):
			shared.later.assignLater(progress, "value", done)
		# Do something with the row

	# When calculations are done, assign the results in the
	# foreground, and hide the progress bar:
	table = ev.source.parent.getComponent("Table")
	shared.later.assignLater(table, "data", newTableData)
	shared.later.assignLater(progress, "visible", False)

shared.later.callAsync(updateMapRateInfo, event)
3 Likes

Again, a typo thats why I should copy and paste haha. Thank you for the info this is exactly what I needed.