Search code examples
javaswingjtextfield

Using JTextField with Model (on focusLost) and run Actions with model data


I have a Java Swing application, that has many JTextFields and a datamodel.

When leaving the textfields (focus lost), the text is written to the Model. So far, so good.

There are JMenu-Actions that read data from the model and send it to the server.

The Problem is, that the "focus lost" is not fired when running the Menu Action by it's accelerator. So the Actions reads and transmit the old value from the datamodel. .

There may be many ways to fix this...? Do you have suggestions how to solve this?

What doesn't work for me:

  • Write to model on every key press (via Document Listener): not usable, should only write on leaving textfield (focus lost). The text is (expensively) evaluated after writing it to the model - can not run after every key press.
  • Handling the writing to model in every action. There ca. 500 Textfields and ca. 100 actions. Diffucult to match without forgetting anything.

Runnable Demo Code:

package swingmodel;

import java.awt.FlowLayout;
import java.awt.event.*;

import javax.swing.*;

/**
 * Simple Demo Problem. Enter a Text in the first Textfield and press ALT-T. The
 * focus listener did no run, therefore the old value from model is displayed.
 */
public class TextDemoOnMenu extends JPanel {
  private Model model;

  public TextDemoOnMenu() {
    super(new FlowLayout());

    model = new Model();
    MyTextField textField = new MyTextField(20, model);
    add(textField);
    add(new JTextField(5));

  }

  class MyTextField extends JTextField {

    private Model model;

    public MyTextField(int arg, Model model) {
      super(arg);
      this.model = model;
      addFocusListener(new FocusAdapter() {
        @Override
        public void focusLost(FocusEvent e) {
          System.out.println("focus lost");
          writeToModel();
        }
      });
    }

    public void writeToModel() {
      this.model.setText(getText());
    }
  }

  class ShowModelTextAction extends AbstractAction {

    public ShowModelTextAction(String string) {
      super(string);
    }

    @Override
    public void actionPerformed(ActionEvent e) {
      String message = "text is: " + model.getText();
      JOptionPane.showMessageDialog(TextDemoOnMenu.this, message);
    }
  }

  class Model {
    private String text;

    void setText(String t) {
      text = t;
    }

    public String getText() {
      return text;
    }
  }

  private void createAndShowGUI() {
    // Create and set up the window.
    JFrame frame = new JFrame("TextDemo");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    // Add contents to the window.
    frame.add(this);

    Action action = new ShowModelTextAction("show text");
    JMenuBar menuBar = new JMenuBar();
    JMenu menu = new JMenu("My Menu");
    menuBar.add(menu);
    JMenuItem menuItem = new JMenuItem("show text");
    menuItem.setAction(action);
    menuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_T, ActionEvent.ALT_MASK));
    menu.add(menuItem);
    frame.setJMenuBar(menuBar);

    // Display the window.
    frame.setSize(400, 200);
    frame.setVisible(true);
  }


  public static void main(String[] args) {
    SwingUtilities.invokeLater(new Runnable() {
      public void run() {
        new TextDemoOnMenu().createAndShowGUI();
      }
    });
  }

}

Solution

  • You could modify all your Actions to use the KeyboardFocusManager. You would get the current component that has focus. If it is one of your custom text fields, then you can force an update to the model before processing the Action.

    The text is (expensively) evaluated after writing it to the model

    Also, it sounds like you should be handling focusGained as well. Then can save the original text and do a comparison on the text on focus lost before automatically updating the model. (ie. what if somebody just tabs through all the text fields?).