G4P GTextField looses focus after setText

I have been working with G4P for four days.

I have a simple form to enter or modify data and I’m experiencing a problem with the two GTextField controls. The data will be saved to and read from a file.

The code that generated this is

GGroup gbxStateDialog;
GPanel pnlStateDialog;
GLabel lblStateIndex;
GTextField txtStateIndex;
GLabel lblStateName;
GTextField txtStateName;
GLabel lblStateDescription;
GTextArea txtStateDescription;
GLabel lblInitialState;
GCheckbox cbxInitialState;
GButton btnOK;
GButton btnCancel;
GTabManager tt;

void createStateDialog()
{
  // create a group that can hold a panel. Note that a group does not have a size
  gbxStateDialog = new GGroup(this);
  gbxStateDialog.setVisible(true);

  int pX1 = 2, pY1 = 2, pHeight1 = 240, pWidth1 = 400;
  pnlStateDialog = new GPanel(this, pX1, pY1, pWidth1, pHeight1, "Information (drag to move : click to open/close)");
  pnlStateDialog.setLocalColor(4, color(0xC0, 0xC0, 0xC0));  // changes the background (4)
  pnlStateDialog.setLocalColor(2, color(0x00, 0x00, 0xC0));  // panel text (2); this is the title
  pnlStateDialog.setFont(new java.awt.Font("Arial", java.awt.Font.PLAIN, 18));
  gbxStateDialog.addControl(pnlStateDialog);

  lblStateIndex = new GLabel(this, 10, 30, 150, 20, "Index");
  lblStateIndex.setTextAlign(GAlign.LEFT, null);
  lblStateIndex.setOpaque(true);
  lblStateIndex.setFont(new java.awt.Font("Arial", java.awt.Font.PLAIN, 16));
  pnlStateDialog.addControl(lblStateIndex);

  txtStateIndex = new GTextField(this, 170, 30, 200, 20);
  txtStateIndex.tag = "txtStateIndex";
  txtStateIndex.setPromptText("State index");
  txtStateIndex.setFont(new java.awt.Font("Arial", java.awt.Font.PLAIN, 16));
  //txtStateIndex.setFont(new java.awt.Font("Courier New", java.awt.Font.PLAIN, 16));
  pnlStateDialog.addControl(txtStateIndex);

  lblStateName = new GLabel(this, 10, 55, 150, 20, "State name");
  lblStateName.setTextAlign(GAlign.LEFT, null);
  lblStateName.setOpaque(true);
  lblStateName.setFont(new java.awt.Font("Arial", java.awt.Font.PLAIN, 16));
  pnlStateDialog.addControl(lblStateName);

  txtStateName = new GTextField(this, 170, 55, 200, 20);
  txtStateName.tag = "txtStateName";
  txtStateName.setPromptText("State name");
  txtStateName.setFont(new java.awt.Font("Arial", java.awt.Font.PLAIN, 16));
  //txtStateName.setFont(new java.awt.Font("Courier New", java.awt.Font.PLAIN, 16));
  pnlStateDialog.addControl(txtStateName);

  lblStateDescription = new GLabel(this, 10, 80, 150, 20, "State description");
  lblStateDescription.setTextAlign(GAlign.LEFT, null);
  lblStateDescription.setOpaque(true);
  lblStateDescription.setFont(new java.awt.Font("Arial", java.awt.Font.PLAIN, 16));
  pnlStateDialog.addControl(lblStateDescription);

  txtStateDescription = new GTextArea(this, 170, 80, 200, 90, G4P.SCROLLBARS_VERTICAL_ONLY );
  txtStateDescription.tag = "txtStateDescription";
  txtStateDescription.setPromptText("State description");
  txtStateDescription.setFont(new java.awt.Font("Arial", java.awt.Font.PLAIN, 16));
  //txtStateDescription.setFont(new java.awt.Font("Courier New", java.awt.Font.PLAIN, 16));
  pnlStateDialog.addControl(txtStateDescription);

  lblInitialState = new GLabel(this, 10, 180, 150, 20, "Initial state");
  lblInitialState.setFont(new java.awt.Font("Arial", java.awt.Font.PLAIN, 16));
  pnlStateDialog.addControl(lblInitialState);

  cbxInitialState = new GCheckbox(this, 170, 180, 20, 20);
  cbxInitialState.tag = "cbxInitialState";
  pnlStateDialog.addControl(cbxInitialState);

  btnOK = new GButton(this, 10, 205, 80, 25, "OK");
  btnOK.setFont(new java.awt.Font("Arial", java.awt.Font.PLAIN, 16));
  btnOK.tag = "StateDialogOK";
  btnCancel = new GButton(this, 100, 205, 80, 25, "Cancel");
  btnCancel.setFont(new java.awt.Font("Arial", java.awt.Font.PLAIN, 16));
  btnCancel.tag = "StateDialogCancel";
  pnlStateDialog.addControls(btnOK, btnCancel);

  // force focus on first control
  txtStateIndex.setFocus(true);
  //txtStateIndex.setText("-1");  // crashes the application when field has focus and and a character is entered

  tt = new GTabManager();
  tt.addControls(txtStateIndex, txtStateName, txtStateDescription);

  // bugfix
  //txtStateName.setFocus(true);
  //txtStateIndex.setFocus(true);
}

which is called from setup(). The focus is forced on the first GTextField control.

The fun started when I tried to set a default value for the first GTextField with txtStateIndex.setText("-1"); at the end of the function. I did notice that the text cursor disappeared. Pressing the tab key to move to the next field (or any other key) resulted in the following exception

NullPointerException
NullPointerException
java.lang.NullPointerException: Cannot read field "startCharIndex" because "this.endTLHI.tli" is null
	at g4p_controls.GTextField.keyEvent(Unknown Source)
	at g4p_controls.GWindowImpl.sendKeyEvent(Unknown Source)
	at g4p_controls.GWindowImpl.keyEvent(Unknown Source)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:568)
	at processing.core.PApplet$RegisteredMethods.handle(PApplet.java:1313)
	at processing.core.PApplet.handleMethods(PApplet.java:1460)
	at processing.core.PApplet.handleKeyEvent(PApplet.java:2663)
	at processing.core.PApplet.dequeueEvents(PApplet.java:2270)
	at processing.core.PApplet.handleDraw(PApplet.java:2112)
	at processing.javafx.PSurfaceFX$1.handle(Unknown Source)
	at processing.javafx.PSurfaceFX$1.handle(Unknown Source)
	at javafx.graphics/com.sun.scenario.animation.shared.TimelineClipCore.visitKeyFrame(TimelineClipCore.java:239)
	at javafx.graphics/com.sun.scenario.animation.shared.TimelineClipCore.playTo(TimelineClipCore.java:197)
	at javafx.graphics/javafx.animation.Timeline.doPlayTo(Timeline.java:172)
	at javafx.graphics/javafx.animation.AnimationAccessorImpl.playTo(AnimationAccessorImpl.java:39)
	at javafx.graphics/com.sun.scenario.animation.shared.InfiniteClipEnvelope.timePulse(InfiniteClipEnvelope.java:120)
	at javafx.graphics/javafx.animation.Animation.doTimePulse(Animation.java:1189)
	at javafx.graphics/javafx.animation.Animation$1.lambda$timePulse$0(Animation.java:207)
	at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
	at javafx.graphics/javafx.animation.Animation$1.timePulse(Animation.java:206)
	at javafx.graphics/com.sun.scenario.animation.AbstractPrimaryTimer.timePulseImpl(AbstractPrimaryTimer.java:343)
	at javafx.graphics/com.sun.scenario.animation.AbstractPrimaryTimer$MainLoop.run(AbstractPrimaryTimer.java:266)
	at javafx.graphics/com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:571)
	at javafx.graphics/com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:555)
	at javafx.graphics/com.sun.javafx.tk.quantum.QuantumToolkit.pulseFromQueue(QuantumToolkit.java:548)
	at javafx.graphics/com.sun.javafx.tk.quantum.QuantumToolkit.lambda$runToolkit$11(QuantumToolkit.java:353)
	at javafx.graphics/com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:96)
	at javafx.graphics/com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
	at javafx.graphics/com.sun.glass.ui.win.WinApplication.lambda$runLoop$3(WinApplication.java:184)
	at java.base/java.lang.Thread.run(Thread.java:833)
NullPointerException

To solve the issue I initially added an additional txtStateIndex.setFocus(true); at the end of the above function but that did not solve the issue. What solved the issue was first setting the focus to another control and next back to the one that hade the focus; e.g.

  txtStateName.setFocus(true);
  txtStateIndex.setFocus(true);

For further debugging I extended the basic draw() with an update of the GTextField every 4 seconds. Each time txtStateIndex.setText(String.valueOf(cnt)); was executed the issue showed.

int startTime = millis();
int cnt = 1;
void draw()
{
  background(0xFF123456);

  if (millis() - startTime >= 4000)
  {
    startTime = millis();
    txtStateIndex.setText(String.valueOf(cnt));
    cnt++;
    // bugfix
    //txtStateName.setFocus(true);
    //txtStateIndex.setFocus(true);
  }
}

The problem only shows with GTextField. I did modify the code to update the second GTextField resulting in the same issue; I also modified the code to update the GTextArea but no problem was experienced.

Full code (with bugfix)

import processing.javafx.*;
import g4p_controls.*;

void settings()
{
  System.setProperty("glass.win.uiScale", "1");
  size(600, 300, FX2D);
  smooth();
}

void setup()
{
  createStateDialog();
}

int startTime = millis();
int cnt = 1;
void draw()
{
  background(0xFF123456);

  if (millis() - startTime >= 4000)
  {
    startTime = millis();
    txtStateIndex.setText(String.valueOf(cnt));
    cnt++;
    // bugfix
    txtStateName.setFocus(true);
    txtStateIndex.setFocus(true);
  }
}

GGroup gbxStateDialog;
GPanel pnlStateDialog;
GLabel lblStateIndex;
GTextField txtStateIndex;
GLabel lblStateName;
GTextField txtStateName;
GLabel lblStateDescription;
GTextArea txtStateDescription;
GLabel lblInitialState;
GCheckbox cbxInitialState;
GButton btnOK;
GButton btnCancel;
GTabManager tt;

void createStateDialog()
{
  // create a group that can hold a panel. Note that a group does not have a size
  gbxStateDialog = new GGroup(this);
  gbxStateDialog.setVisible(true);

  int pX1 = 2, pY1 = 2, pHeight1 = 240, pWidth1 = 400;
  pnlStateDialog = new GPanel(this, pX1, pY1, pWidth1, pHeight1, "Information (drag to move : click to open/close)");
  pnlStateDialog.setLocalColor(4, color(0xC0, 0xC0, 0xC0));  // changes the background (4)
  pnlStateDialog.setLocalColor(2, color(0x00, 0x00, 0xC0));  // panel text (2); this is the title
  pnlStateDialog.setFont(new java.awt.Font("Arial", java.awt.Font.PLAIN, 18));
  gbxStateDialog.addControl(pnlStateDialog);

  lblStateIndex = new GLabel(this, 10, 30, 150, 20, "Index");
  lblStateIndex.setTextAlign(GAlign.LEFT, null);
  lblStateIndex.setOpaque(true);
  lblStateIndex.setFont(new java.awt.Font("Arial", java.awt.Font.PLAIN, 16));
  pnlStateDialog.addControl(lblStateIndex);

  txtStateIndex = new GTextField(this, 170, 30, 200, 20);
  txtStateIndex.tag = "txtStateIndex";
  txtStateIndex.setPromptText("State index");
  txtStateIndex.setFont(new java.awt.Font("Arial", java.awt.Font.PLAIN, 16));
  //txtStateIndex.setFont(new java.awt.Font("Courier New", java.awt.Font.PLAIN, 16));
  pnlStateDialog.addControl(txtStateIndex);

  lblStateName = new GLabel(this, 10, 55, 150, 20, "State name");
  lblStateName.setTextAlign(GAlign.LEFT, null);
  lblStateName.setOpaque(true);
  lblStateName.setFont(new java.awt.Font("Arial", java.awt.Font.PLAIN, 16));
  pnlStateDialog.addControl(lblStateName);

  txtStateName = new GTextField(this, 170, 55, 200, 20);
  txtStateName.tag = "txtStateName";
  txtStateName.setPromptText("State name");
  txtStateName.setFont(new java.awt.Font("Arial", java.awt.Font.PLAIN, 16));
  //txtStateName.setFont(new java.awt.Font("Courier New", java.awt.Font.PLAIN, 16));
  pnlStateDialog.addControl(txtStateName);

  lblStateDescription = new GLabel(this, 10, 80, 150, 20, "State description");
  lblStateDescription.setTextAlign(GAlign.LEFT, null);
  lblStateDescription.setOpaque(true);
  lblStateDescription.setFont(new java.awt.Font("Arial", java.awt.Font.PLAIN, 16));
  pnlStateDialog.addControl(lblStateDescription);

  txtStateDescription = new GTextArea(this, 170, 80, 200, 90, G4P.SCROLLBARS_VERTICAL_ONLY );
  txtStateDescription.tag = "txtStateDescription";
  txtStateDescription.setPromptText("State description");
  txtStateDescription.setFont(new java.awt.Font("Arial", java.awt.Font.PLAIN, 16));
  //txtStateDescription.setFont(new java.awt.Font("Courier New", java.awt.Font.PLAIN, 16));
  pnlStateDialog.addControl(txtStateDescription);

  lblInitialState = new GLabel(this, 10, 180, 150, 20, "Initial state");
  lblInitialState.setFont(new java.awt.Font("Arial", java.awt.Font.PLAIN, 16));
  pnlStateDialog.addControl(lblInitialState);

  cbxInitialState = new GCheckbox(this, 170, 180, 20, 20);
  cbxInitialState.tag = "cbxInitialState";
  pnlStateDialog.addControl(cbxInitialState);

  btnOK = new GButton(this, 10, 205, 80, 25, "OK");
  btnOK.setFont(new java.awt.Font("Arial", java.awt.Font.PLAIN, 16));
  btnOK.tag = "StateDialogOK";
  btnCancel = new GButton(this, 100, 205, 80, 25, "Cancel");
  btnCancel.setFont(new java.awt.Font("Arial", java.awt.Font.PLAIN, 16));
  btnCancel.tag = "StateDialogCancel";
  pnlStateDialog.addControls(btnOK, btnCancel);

  // force focus on first control
  txtStateIndex.setFocus(true);
  txtStateIndex.setText("-1");  // crashes the application when field has focus and and a character is entered

  tt = new GTabManager();
  tt.addControls(txtStateIndex, txtStateName, txtStateDescription);

  // bugfix
  txtStateName.setFocus(true);
  txtStateIndex.setFocus(true);
}

It still needs a bit of polishing.


PDE version 4.5.0, operating system Windows 11 home.
G4P version (reported) V4.3.11

You might try setting the default text before forcing it to have focus.

  // force focus on first control after setting
  txtStateIndex.setText("-1");
  txtStateIndex.setFocus(true);

When you click on a text field or use setFocus(true) the text field becomes active and it responds to keyboard events.

In this active state the text field has to keep track of many things for instance the text insert position, text alignment, text style, text font etc. and apply these to the currently displayed text so using setText(…) to change the currently displayed text makes all this information invalid, screws up the control which then has hissy-fits, sorry I mean throws exceptions :laughing:

The general rule is

when the text field or area is responding to keyboard events do not programmatically change the text

Hope this helps.

1 Like

Thanks for the reply.

Quite sure that I did try that but will try tomorrow again.

That is not always possible. In the scenarios below it’s necessary to programmatically set the content of the text fields.

  1. If I click on an object representing a state (there can be hundreds of them) the information in the dialog needs to reflect that state’s information.
  2. Initial information might be read from file.