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.

In the createStateDialog() this seems to work.

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

In draw() this does not seem to work.

void draw()
{
  background(0xFF123456);

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

I do have a workaround so I can continue exploring.

The draw method is responsible for rendering the G4P controls in their current state attempting to change that state during the rendering phase is not good practice.

Processing provides many event handlers e.g.

mouseMoved();
mouseClicked();
keyTyped();
...

and you will never see these called from the draw method. :smiling_face_with_sunglasses:

Fortunately Processing provides a number of methods that we can hook into its main event thread they are
"pre" "draw" "post" "mouseEvent" "keyEvent"
these are used extensively in G4P

Although aimed at the library developer it is perfectly acceptable to use in sketches.

In this example I have registered the “pre” method with
registerMethod("pre", this);
after this statement Processing will execute your pre method immediately before draw every frame but outside the rendering phase.

Try this out

void setup() {
  size(400, 200);
  registerMethod("pre", this);
  txf = new GTextField(this, 20, 30, 300, 25);
  txf.setText("I am waiting");
}

void pre() {
  if (millis() - startTime >= 4000) {
    startTime = millis();
    txf.setFocus(false);  // might not be needed but no harm done
    txf.setText("Ready for you!");
    txf.setFocus(true);
  }
}

void draw() {
  background(200, 240, 255);
}
2 Likes

And you know that because …? :smiley:

I did try to search the web and could not (immediately) find a reference to processing pre(). I could find registerMethod() at https://processing.github.io/processing-javadocs/core/processing/core/PApplet.html#registerMethod-java.lang.String-java.lang.Object- but that’s not what I usually visit :smiley: The normal reference should be sufficient.

But I did learn something new which is great.

Thanks for the example :+1: More stuff to study.

Not sure how I first discovered these but it was during the development of G4P which was first released in 2009.

They did not appear in the regular sketch user documentation but were described in documentation regarding development and release of contributed-libraries.

Anyway I have been to github and have found the relevant webpage on these methods. Although Processing describes them as “Library Methods” they are included in the official Processing API so can be legitimately and safely used inside sketches.

1 Like