Skip to end of metadata
Go to start of metadata


Overview

In some cases, having a script execute after a delay is preferable. A common use case is waiting for some event elsewhere in the system to finish: a Tag change script executes that needs to wait for a new value from a separate Tag. One approach to this is to trigger our script, and then hold or wait until the other event occurs. On this page, we'll take a look at a couple of different approaches to this problem. 

It is important to note that pausing a script can cause your client to lock. It is often preferred to look for another event to trigger the script you need. For the example above where we are waiting on a Tag to change, you might be able to use the Tag change to fire your script instead of waiting in the original script. It is up to you to determine the best trigger based on what exactly your script does.

Using the system.util.invokeLater Function

The system.util.invokeLater function is a great way to add a delay mid-way through the script. Simply create a function that represents all of the work that should occur after the delay, and pass the function to invokeLater, along with a delay period. 

The example below calls two Message Boxes: once initially when pressed, and the other after a three second delay.

Python - Two Message Boxes
message = "All Done!"

# Create a function that will be called by invokeLater.
def runThisLater():
	system.gui.messageBox(message)
	
# Call invokeLater with a 3000ms delay. Note that our function will not run immediately 
# because invokeLater always executes once the rest of this script is complete.
system.util.invokeLater(runThisLater, 3000)

# Bring up another Message Box. This will appear before the "All Done!" message, because of invokeLater.
system.gui.messageBox("Waiting...")

On this page ...

One of the main limitations with invokeLater is that you can not pass parameters to the function that will be called. Parameters need to be initialized and determined elsewhere in your script, usually in the function definition. 


Using a Timer from Python's Threading Library

The Threading Library has a Timer function that works in a very similar fashion to invokeLater. The main difference is that you can pass parameters to the function parameter when calling the Timer. Take a look at Python's official documentation for more details on the Timer object.

The example below will again call two Message Boxes with a three second delay between them. However, the text that is defined in the second Message Box is specified when starting the Timer.

Python - Python Threading Timer
from threading import Timer

# Create a function that will be called by the Timer.
def runThisLater(param):
	system.gui.messageBox(str(param))
	
# Constructs a Timer object that runs a function after a specified interval of time has passed.
# Note the start() at the end: this is required to start the Timer. Don't forget this part! 
Timer(3.0, runThisLater, ["Stop Waiting, I'm done"]).start()

# Bring up another Message Box. This will appear before the "All Done!" message, because of the Timer
system.gui.messageBox("Waiting")

Another benefit to using the Timer, is you can cancel the execution of the Timer's action using the cancel() function, but it only works if the Timer is still in its waiting stage. 


Calling time.sleep from Python's Time Library

The simplest approach to pausing a script is to use the sleep() function in Python's Time Library:

Python - Sleeping the Code
from time import sleep

# This will pause execution of the script for 3 seconds. After that time, the script will continue.
sleep(3)

print "I'm awake!"

Using a While Loop

The While Loop is another simple approach to adding a delay: simply keep looping until the other event occurs. As always, you will want to take steps to ensure that an infinite loop never occurs: easiest by initializing a counter variable before iterating in the loop, and breaking out if the counter reaches a certain value.

Python - While Loop Safeguard
counter = 0

while not otherEvent:

	checkOtherEvent()
	counter += 1

	# Use this to break out if the event takes too long. You'll need the rest of your script to account for this possibility.
	if  counter >= 10000:
		print "Took too long. Leaving the While Loop"
		break	


Approaches to Avoid - Locking the Client

While the sleep() and while loop functions are simple to use, they can cause problems by locking up the client and because of this, we generally recommend avoiding them if possible. Typically, system.util.invokeLater on a delay can accomplish the same task. 

Note, that there are use cases for both approaches outlined below especially so in regard to the While Loop. However, neither function should be used to force a delay in a Client.

Using either of these methods to pause or delay a script can lock up the entire client, which may be very dangerous.

Reasons to Avoid time.sleep and While Loops

If used on a component, these approaches could lock up the Client or Designer. Scripts called from a component run in the same thread that the Client and Designer use to refresh the screen. Whenever a script on a component triggers, the screen is unable to refresh until the script finishes. In most cases, this is so fast that no one notices. If your script is intentionally calling sleep() or using a long running While Loop, then the Client will lock up for the duration.

Additionally, these approaches run an extended period of time, and block other component based scripts from executing. They do not yield to other scripts while waiting. 

Because of these two reasons, the sleep() and While Loop functions can cause your clients to appear unresponsive, when really they are just running a script that prevents the screen from refreshing. 

Recommended Alternative

As mentioned earlier, the system.util.invokeLater function (also mentioned on this page) can provide the same functionality. If a sleep() or While Loop function must be used, then they should be called from system.util.invokeAsynchronous to prevent blocking any other scripts.


Demonstration - Executing a Delay Between User Keyboard Input

In many cases, you may need to show your users a large number of entries on a Table or Template Repeater. As more entries are added to the system, adding a search field that can filter results becomes more appealing. Furthermore, being able to filter as the user types (as opposed to forcing them to hit enter every time) adds some polish to the window. However, if the entries are backed by the database, you will not want to run a query every time a user presses a key. Instead, it would be preferable to wait for a delay in input, and run one query to limit strain on the database. 

The following is not a traditional example that you would find here in the User Manual, and assumes you are comfortable with many concepts in Ignition. 

Workflow for a Delay Using Two Scripts 

One approach to adding a delay involves two scripts:

  • Pre-Delay Script: This script fires before the delay. In this case, we will use a script on the Text Field's keyReleased event. This will call the script everytime a new key is entered into the Text Field, and calls the Post-Delay Script on a delay. It also passes the text that is currently in the Text Field, and increments an edit count (could be configured as a custom property on the same component).

  • Post-Delay Script: This script runs after the delay, so it needs to check if there were any new keystrokes that occurred during the delay. If there were no new keystrokes, then it can run the query. In this demonstration, we will use a Custom Method not the Text Field component. 



Additionally, we will need some criteria that our script can use to determine if it is safe to run our query. You can have multiple criteria here, but for the sake of simplicity we will use the Text Property on the Text Field. This way we will know what the user typed before the delay, and then can compare it back to the Text property after the delay. If the Text on the component after the delay is different, then we don't want to run the query as the user may still be typing. 

The keyReleased script is shown below. It is using a .05 second delay, but could of course can be modified. We're creating a new Timer object every time a key is released, so there will be many of these scripts firing as the user types. However, they are fairly lightweight, and don't directly interact with the database, so having many executions of this script isn't taxing on the system.

Python - Pre-Delay Script
# We're using the Timer object for this because we want to pass parameters to the function that will run after the delay.
from threading import Timer 

# Ignoring arrow keys.
if (event.keyCode < 37 or event.keyCode > 40):

	# Grab the text in the component currently.
	currentText = event.source.text

	# Calls the function after 0.5 seconds, and pass the currentText.
	Timer (0.5, event.source.sendingQuery,[currentText]).start()


Below is the sendingQuery script. As mentioned, this script will determine if the user ceased keyboard activity before running a query against the database. This example is using a Custom Method on the Text Field, so it uses the self argument to reference the source component. 

Python - Post-Delay Script
def sendingQuery (self, oldText):

	# Read the value of the text property, since it may have changed during the delay.
	currentSearchText = self.text

	# If the text before the delay is the same as the after the delay, there has been a pause in keyboard activity, so we should run the query.
	if (currentSearchText == oldText):

		# If the text field isn't empty, then we need to filter the results with a WHERE clause and our criteria.
		if(currentSearchText != ""):
			newQuery = "SELECT * FROM employees WHERE CONCAT(firstname, ' ', lastname) like '%" + currentSearchText + "%'"
		# If the Text Field Is blank, then we should run the query again, but this time without a filter, so all results appear.
		else:
			newQuery = "SELECT * FROM employees"

We can now run our query in between user keyboard activity.



  • No labels