I have a Jframe that includes a Jspinner. When the JSpinner is increased a new JTextField is created in my panel headerPanel. The script creates the textboxes with an integer attached to their variable name (tFrame0, tFrame1, and so on).
Original code and all old edits moved to This pastebin.
Linking the pastebin just in case it will help anyone in the future, and to not clutter the currently relevant code.
This was solved with the help of TreffnonX! Thanks for being very patient with me in the comments and in chat.
Here is the working code in case anybody stumbles across this kind of issue later.
Global Variables
private JPanel headerPanel;
private JSpinner spinner;
public List<JTextField> findTextFields() {
List<JTextField> fields = new LinkedList<>();
Component[] children = headerPanel.getComponents();
for (Component child : children) {
if (child instanceof JTextField) {
JTextField childField = (JTextField) child;
// check, if the name is prefixed correctly.
String name = childField.getName();
if (name.startsWith(nameTField)) {
fields.add(childField);
}
}
}
return fields;
}
JSpinner
spinner = new JSpinner();
spinner.setModel(new SpinnerNumberModel(1, 1, 100, 1));
spinner.addContainerListener(new ContainerAdapter() {
@Override
public void componentAdded(ContainerEvent arg0) {
adaptBoxes();
}
});
spinner.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent arg0) {
int spinnerValue = (Integer) spinner.getValue();
if (spinnerValue == headerPanel.getComponentCount()) {
System.out.println("Error, spinner shouldn't change to same alue");
}
adaptBoxes();
frame.revalidate();
frame.repaint();
}
});
adaptBoxes();
adaptBoxes(); Method
public void adaptBoxes() {
// Find value of spinner.
int spinnerValue = (Integer) spinner.getValue();
List<JTextField> textFields = findTextFields();
int numTextFields = textFields.size();
if (numTextFields > spinnerValue) {
// if we have too many fields.
for (JTextField textField : textFields) {
String name = textField.getName();
Matcher matcher = POSTFIX_PATTERN.matcher(name);
if (matcher.matches()) {
String strPostfix = matcher.group(1);
int postFixNumeric = Integer.parseInt(strPostfix);
System.out.println("for postFix = " + postFixNumeric + ": " + textField.getText());
if (postFixNumeric >= spinnerValue) {
System.out.println("PFN: " + postFixNumeric);
System.out.println("FTF: " + numTextFields);
headerPanel.remove(textField);
}
}
}
} else {
while (numTextFields < spinnerValue) {
// if we have too few fields.
int hp = headerPanel.getComponentCount();
JTextField tField = new JTextField();
tField.setName(nameTField + hp);
tField.getDocument().addDocumentListener(new TextFieldDocumentListener());
headerPanel.add(tField);
textFields = findTextFields();
numTextFields = textFields.size();
}
}
}
JTextField Document Listener
/**
* Inner class
*/
private final class TextFieldDocumentListener implements DocumentListener {
@Override
public void changedUpdate(DocumentEvent e) {
warn();
}
@Override
public void removeUpdate(DocumentEvent e) {
warn();
}
@Override
public void insertUpdate(DocumentEvent e) {
warn();
}
/**
* Actual sysout to inform the user...
*/
public void warn() {
List<JTextField> textFields = findTextFields();
for (JTextField textField : textFields) {
String name = textField.getName();
Matcher matcher = POSTFIX_PATTERN.matcher(name);
System.out.println(name);
if (matcher.matches()) {
String strPostfix = matcher.group(1);
int postfixNumeric = Integer.parseInt(strPostfix);
System.out.println("for postfix = " + postfixNumeric + ": " + textField.getText());
} else {
System.out.println("matcher error: " + matcher);
}
}
}
}
You cannot 'append my for loop int i to a variable name', unless you want to use reflections (https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/package-summary.html). Reflections would allow you to search a java-Object
for it's fields, and based on their name (if exposed) access them.
Luckiely, this is not what you actually need here. You are actually looking for a way to identify the children (Component
s) of your J?Panel both by type and name, and then deduce the index, you formerly appended. My suggestion is to do the following:
// This pattern can be anywhere in the Class-file:
public static final Pattern POSTFIX_PATTERN = Pattern.compile("tFrame(\\d+)");
// This should be in your exposed GUI class (supposedly the Panel), but can be anywhere it can access headerPanel.
public List<JTextField> findTextFields() {
List<JTextField> fields = new LinkedList<>();
Component[] children = headerPanel.getComponents();
// this is your 'search-loop':
for (Component child : children) {
if (child instanceof JTextField) {
JTextField childField = (JTextField) child;
String name = childField.getName();
if (name.startsWith("tFrame")) {
fields.add(childField);
}
}
}
return fields;
}
(This could also be done as with Stream
, but the above code is simpler).
The above code 'finds' your existing components, and you can work from there, for example by sorting them, based on their names, and then doing a print.
The problem why you receive only part of your text fields seem to react to the modifications is because you (probably) don't pass the same inputData
-array to the different DocumentListener
s you create. if you set a breakpoint and check, I bet the inputData-Arrays have a different hash. That said, I can't deduce this 100%, since the surrounding code is incomplete.
I'd suggest to change:
if (tField.getText() != null) {
Object[] currentData = new Object[getFieldNum + 1];
currentData[getFieldNum] = tField.getText();
inputData[getFieldNum] = tField.getText();
for (int i = 0; i < inputData.length; i++) {
if (inputData[i] != null) {
System.out.println("for i = " + i + ": " + inputData[i]);
}
}
}
to
for (JTextField textField : findTextFields()) {
String name = textField.getName();
Matcher matcher = POSTFIX_PATTERN.matcher(name);
if (matcher.matches()) {
String strPostfix = matcher.group(1);
int postfixNumeric = Integer.parseInt(strPostfix);
System.out.println("for postfix = " + postfixNumeric + ": " + textField.getText());
}
}
This way, you get the components at the point in time when the user actually made the change you want to react to.
Also on a side note: Do you actually want to replace all existing text fields and their current state, once you reduce or increase the number on the JSpinner?
Maybe consider to remove the elements that are postfixNumeric >= getTextFields().size()
, or increase the number of elements without removing those that are less than the previous number on the JSpinner. That can be done with the loop you got.
The problem turned out to originate in a multitude of instance-fields used instead of local variables. Since the code was only partially posted, this was hard to impossible to determin from the question alone.