Printing multiple reports at once (report viewer)

Hi,

I have made a vision application consisting of a report viewer, a table and a print button.
The report viewer consist of a label with some text, a Code 128 barcode and a QR code, five parameters altogether.

The table is getting its content from a database and the idea is that the user can choose one or more rows and push the print button and the label printer prints them one by one.
This all works great when one or two rows are selected but when three or more are selected the first row is printed ok but all the other prints consist of the data from the last row.

The problem (I think) is that when the user select rows in the table the report viewer is updated with the information from the first row (lowest index) so when the print button is pushed that label prints ok.
But when the parameters are to be updated with the data from the next row and get printed, all rest of the selected rows are processed before the second print has begun thus the parameters will contain data from the last row when printing label #2 and all the following labels.
So if I e.g. select 80 rows in the table the first label will contain the data from the first row (ok) but the rest 79 labels will all contain data from the last row (not ok).

I’ve found a script in Inductive documentation that should be handling this by using invokeAsynchronous and reportviewer.reportLoading but it doesn’t help at all.

How can I make sure that each row is processed (report finished loading) and printed before the next row is processed?

I did find the same problem mentioned in this forum in 2018 but didn’t really se any solution for the problem.

My code:

# How many rows are selected in the table?
rowCount = event.source.parent.getComponent('tblMultiResult').getSelectedRowCount()
i = 0

# Creates an array with the row indexes of the selected rows
SelectedRows = event.source.parent.getComponent('tblMultiResult').getSelectedRows()
label = event.source.parent.getComponent('rvMROLabel')

# Custom property (string) with the name of the printer
selectedPrinter = event.source.parent.selectedPrinter

# Function for printing the label.
def printLabel():
	event.source.parent.getComponent('rvMROLabel').print(selectedPrinter, False) # Prints the label on a Zebra label printer

# Function for pausing the script while the report viewer is loading the new values
def waiting():
	while label.reportLoading:
		pass
	system.util.invokeLater(printLabel)

# Cycles through the selected rows and filling the labels report with corresponding data and prints it
while i < rowCount:

	row = SelectedRows[i]
	label.Description = event.source.parent.getComponent('tblMultiResult').data.getValueAt(row, 2)
	label.Efecto = event.source.parent.getComponent('tblMultiResult').data.getValueAt(row, 1)
	label.Part_No = event.source.parent.getComponent('tblMultiResult').data.getValueAt(row, 3)
	label.SAP_No = event.source.parent.getComponent('tblMultiResult').data.getValueAt(row, 0)
	label.Storage_Bin = event.source.parent.getComponent('tblMultiResult').data.getValueAt(row, 4)

	system.util.invokeAsynchronous(waiting) # Calls the wait function in a seperate thread.
	i += 1

Thanks

I’m surprised that waiting() function doesn’t lock up the entire client–it is an infinite loop in the GUI thread.

Well I suppose that the idea is that when the report viewer has finished loading the .reportLoading will turn False and the while loop will exit.

Copied the structure from here:
https://docs.inductiveautomation.com/display/DOC79/Report+Viewer

Yeah, but reportLoading is a component property that shouldn’t be able to change while waiting() is tying up the GUI thread. Ah, it is launched asynchronously. So it spins tying up another CPU but not the GUI thread. But it accesses reportLoading from the background–that’s not safe in Swing. What a terrible example.

Oh, really. Well playing around with the script trying to get the threads finished before they are printed doesn’t really give me the result I’m expecting so I don’t think that I fully understand what’s going around in the background.

In a parallel universe we’d have updating system.print.createPrintJob to accept byte arrays, so you could avoid this whole process. Unfortunately, for now, something a bit ‘hokey’ in the client is the best you can do, I think.
You’d have to do basically what you’re currently doing, but entirely on the EDT, using system.util.invokeLater to continue ‘pushing’ your execution to the end of the stack if things aren’t done yet.

I would probably do something like this (Phil will surely step in with better advice :slight_smile: ) -

  1. Put a custom property on the root container, printsRemaining, that’s an integer.
  2. When the user clicks the button to start the printing process, set printsRemaining to whatever it needs to be.
  3. Bind the visibility of some ‘loading’ overlay component to printsRemaining > 0. That way operators get an immediate cue ‘oh, something is happening’, and don’t try to hit the print button multiple times (you could also disable the print button if printsRemaining > 0).
  4. Within the same script that set printsRemaining, launch a task (using invokeLater) with a moderate delay - say half a second or whatever arbitrary value. In that async task, you’re going to check printsRemaining and the status of reportLoading. If no report is loading, but printsRemaining is greater than 0, you’re going to decrement printsRemaining, start the print job, then launch the same invokeLater task again.

Nah, you got this.

(I'm on a startup, peeking at the forum in my scattered thumb-twiddling time.)

Ok, well after changing the flow of the code and some hours with trials and errors I think I have a code that actually works and physically prints what I want in the correct order.
I had to create two custom parameters, one for keeping track of the total number of prints (acc. to your suggestion) and one for getting the correct order of the prints (descending). Keep in mind that a user selection in the table could be a CTRL selection i.e. I must use the selectedRows array in order to update the report viewer with the correct data.
I also had problem with the first label already been loaded after selecting rows in the table but before the user pushes the print button, therefor I need to begin with printing in the waiting function before updating label parameters otherwise I got the first label printed twice, except when printing to the console, then it looked OK even if I did it the other way around.

The working code looks like this anyhow:

# Activates graphics for "Printing in progress"
event.source.parent.getComponent('tmrLoadingPrint').running = True
event.source.parent.getComponent('imgLoadingPrint').visible = True

# How many rows are selected in the table?
rowCount = event.source.parent.getComponent('tblMultiResult').getSelectedRowCount()
i = 0

# Creates an array with the row indexes of the selected rows
SelectedRows = event.source.parent.getComponent('tblMultiResult').getSelectedRows()
label = event.source.parent.getComponent('rvMROLabel')

selectedPrinter = event.source.parent.selectedPrinter
event.source.parent.printsRemaining = rowCount
event.source.parent.printPosition = 0

# Function for pausing the script while the report viewer is loading incl. generating barcodes
def waiting():
	# If the report viewer is loading, do nothing
	while label.reportLoading:
		pass
	# How many prints are left to be printed?
	rem = event.source.parent.printsRemaining
		
	if rem > 0:
		# Since the first print is already loaded in the report viewer we start with printing it
		event.source.parent.getComponent('rvMROLabel').print(selectedPrinter, False)
		#print label.Description + " " + str(event.source.parent.printsRemaining) # For error testing

		event.source.parent.printsRemaining -= 1	# One less print to print
		event.source.parent.printPosition += 1		# Increase print position, used when getting the row index from "selectedRows"

		# Check again if there is any more to print after decreasing the parameter. Important for avoiding out of index errors
		if event.source.parent.printsRemaining > 0:
			pos = event.source.parent.printPosition
			row = SelectedRows[pos]
			
			# We finish of by start the next load so that the waiting function will work in the next iteration
			label.Description = event.source.parent.getComponent('tblMultiResult').data.getValueAt(row, 2)
			label.Efecto = event.source.parent.getComponent('tblMultiResult').data.getValueAt(row, 1)
			label.Part_No = event.source.parent.getComponent('tblMultiResult').data.getValueAt(row, 3)
			label.SAP_No = event.source.parent.getComponent('tblMultiResult').data.getValueAt(row, 0)
			label.Storage_Bin = event.source.parent.getComponent('tblMultiResult').data.getValueAt(row, 4)
			
			# Wait a second then restastart the process
			system.util.invokeLater(waiting, 1000)
		else:
			# Stops and hides graphics for "Printing in progress"
			event.source.parent.getComponent('imgLoadingPrint').visible = False
			event.source.parent.getComponent('tmrLoadingPrint').running = False

# Calls the waiting function in a separate thread
system.util.invokeLater(waiting)

Thanks for the help!

1 Like

You still have an 100% CPU loop on the GUI thread. Use the delay option to invokeLater to not have an do-nothing loop. (As Paul suggested.)

That’s true, I replaced

while label.reportLoading:
		pass

with

if label.reportLoading:
		system.util.invokeLater(waiting, 500)

Hope this will ease of the CPU load a bit, must also confirm that it still works with the printer (works fine with the output console).

2 Likes

Probably should be like so:

if label.reportLoading:
	system.util.invokeLater(waiting, 500)
	return

You don’t want the rest of the function to run when you are deferring.

1 Like

Ok, I’ve added that too.
Thanks!