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.
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:
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.
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.
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.
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.
We can now run our query in between user keyboard activity.