Focus Order

How components are laid out on a window determines the focus order.  When tabbing through components on a window, the focus moves from left to right, then top to bottom. Focus will then cycle back to the first component. When determining order, the top left corner of the component is used. To see for yourself, drag several components into your window. Go to Preview Mode, and tab through the components.







Requesting Focus in the Window

You can programmatically request that focus be given to a component by calling the function requestFocusInWindow() on that function. This function is called on a component and will pull the focus to that component so it is selected and ready to use. It is best used with an input component, such as a Text Field, so the user can immediately begin typing into the component. You can use it on the internalFrameActivated event to bring focus to the component right when the window opens.  

The getComponetForPath Function

The example above references the function getComponentForPath. This function can be called from a window object, and allows you to specify the full path to a component inside of the window as a single string, using the following format:

getComponentForPath('Root Container.ComponentName')


When referencing a component from a window event handler, such as internalFrameActivated, clicking the Property Reference icon will use the getComponentForPath function. While this function is useful, you never have to use the getComponentForPath function. Instead you can use the component paths that are seen from event handlers on other components. Below is a comparison of using both getComponentForPath, as well as the more traditional 

# Both lines below will return a reference to the root container in the window that this script originates from.

print system.gui.getParentWindow(event).getComponentForPath('Root Container')
print system.gui.getParentWindow(event).getRootContainer()

# These lines will both reference the text property on a Label that is nested in a Group on the window.

print system.gui.getParentWindow(event).getComponentForPath('Root Container.Group.Label').text
print system.gui.getParentWindow(event).getRootContainer().getComponent('Group').getComponent('Label').text


system.gui.getParentWindow(event).getComponentForPath('Root Container.Text Field').requestFocusInWindow()



Controlling Focus Between Components in a Window

In some cases, you may wish to control which component gains focus when the user clicks on a different component, or tabs away, instead of using the default focus order. You can call requestFocusInWindow() from the focusLost event to control which component should gain focus next.

However, calling requestFocusInWindow() may cause some irregular behavior as shown below. Notice below how the "second" and "third" Text Fields both have a text cursor in the component, but the "first" text field has focus. This is because requestFocusInWindow() is being called on the focusLost event, which runs when one of our components loses focus. This means that while focus is being pulled to one component (the "second" Text Field), our script changes focus again to a different component. 


The solution to the problem above is to have the requestFocusInWindow() call occur as the last part of the event trigger. This can be accomplished in one of two ways: using Invoke Later under Advanced Settings, or the invokeLater() system function.

About Invoke Later

The concept of invoking some code later leads to a broader discussion on event handling and timing, which deviates from the purpose of this page: focus manipulation. The concept of "Invoke Later" simply means to wait for the current event to finish processing before running our focusLost script. In the scenario above, clicking from one component to another (or tabbing to a different component) natively calls a focus change event. A script on the focusLost event handler that uses requestFocusInWindow will also cause a focus change event, except it does so mid-execution of the native focus change event. 

The main issue then is that focus is being moved to two components simultaneously from within the same event stack. The solution then is to have them occur in sequential order instead, with our custom focusLost script occuring last, hence using Invoke Later.

Running a script with one of the invoke later approaches mentioned here is not required on most scripts in Ignition, but it is common when the script is attempting to set or interact with focus on a window. On a related matter, invoking a script later is not the same as adding a delay mid execution. For more information about scripting a delay, see the Adding a Delay to a Script page.

To resolve the focus manipulation issue mentioned above, we can take one of two approaches:

Script Editor Advanced Settings

The simplest approach would be to use the Invoke Later option under Advanced Settings in the Script Editor.


Using the invokeLater System Function

Alternatively, we could address this by using system.util.invokeLater to request focus at the end of the event.

# Create a function for invokeLater that requests focus. 
def focus():
	event.source.parent.getComponent('Text Field').requestFocusInWindow()

# Call the function once the event has executed. 
system.util.invokeLater(focus)



Text Areas and Focus Requests

Text Area components are slightly more complex when it comes to focus requests. Calling requestFocusInWindow() directly on the component will not allow the user to immediately start typing into the component. However, we can accomplish this by calling getViewport(), and then calling getView() first as shown in the code below.

# Create a reference to the Text Area. This step could be skipped and combined with the line below, but is segregated in this example for clarity.
textArea = event.source.parent.getComponent('Text Area')

# This line demonstrates how to request focus on a Text Area
textArea.getViewport().getView().requestFocusInWindow()