javaconstructorcallbackfocuslistener

FocusListener callback method doesn't work for separately instantiated objects from same variable?


I'm currently working on the GUI part of our group's first semester exam project, and I ran into a curious issue regarding callbacks and event handling.

A bit off topic: I have googled this SO hard, but have really failed to find any concrete examples of using callbacks in Java, so from what i've gathered... By definition... What i'm doing here is actually a callback (Would be awesome if you could explain why, or point me to a page that explains it thoroughly)

Here's the code:

private FocusListener callback = new FocusListener(){
    @Override public void focusGained(FocusEvent e){
        if(hasBeenSet){}else{tfield.setText("");hasBeenSet=true;}
    } @Override public void focusLost(FocusEvent e){}};

    ...

    tfield = new JTextField("Insert CPR number", 8);
    constraint.gridx = 0;
    constraint.gridy = 1;
    constraint.gridwidth = 2;
    panel.add(tfield, constraint);

            tfield.addFocusListener(callback);

    tfield = new JTextField("Type new password", 8);
    constraint.gridx = 0;
    constraint.gridy = 2;
    panel.add(tfield, constraint);

            tfield.addFocusListener(callback);

    tfield = new JTextField("Retype new password", 8);
    constraint.gridx = 0;
    constraint.gridy = 3;
    panel.add(tfield, constraint);

            tfield.addFocusListener(callback);

When I start up the GUI, it has these three (3) textfields, and the idea is to remove the text when the user focuses on the textfield. This should work for all three textfields, but apparently, whatever textfield you click on, only the last textfield gets set to an empty string. I'm most curious about this since each object is instantiated individually.

The obvious workaround here, is to just rename tfield to like "tfield[1-3]", but would be great if you could humor me with this :)

Also: Please note that all the GUI code is actually inside the constructor. Yes, I know this is completely ridiculous, but our design suggests that all logic and data handling will happen after the constructor has completed... So there shouldn't be any threats here per say, but I don't know if this would somehow conflict the callback method for FocusListener.

Thank you. :)

P.S. For the "Re/type new password" fields, I do acknowledge the JComponent JPasswordField, and that will be changed after this problem is fixed. So no need for the security warning :)


Solution

  • The tfield variable holds the reference to the last instance of JTextField. The way to do what you want is this:

    private FocusListener callback = new FocusListener() {
        @Override public void focusGained(FocusEvent e){
            JTextField jtf = (JTextField) e.getSource();
            if(hasBeenSet){}else{jtf.setText("");hasBeenSet=true;}
        }
        ...
    

    Note: as your code reads at the moment, hasBeenSet will be shared across all 3 text fields.

    Update:

    Java has no support for closures, so when the focusGained runs, it sees the last value of tfield, not the value tfield had when the listerner was installed.

    It looks like hasBeenSet is defined as a member of the outer class and as such focusGained is checking the same variable for all 3 textfields. Here is a way of handling what I think you're trying to do:

    tfield = new JTextField("Insert CPR number", 8);
    tfield.putClientProperty("originalText", tfield.getText());
    

    Then in the focusGained:

        @Override public void focusGained(FocusEvent e){
            JTextField jtf = (JTextField) e.getSource();
            if(jtf.getClientProperty("originalText").equals(jtf.getText())){
                 jtf.setText("");
            }
        }
    

    The putClientProperty/getClientProperty methods are defined in JComponent class and so these are available in every Swing GUI component that inherits from JComponent. They store/retrieve an Object given a string. In this case the string "originalText" holds the value originally used to initialize the JTextField. Upon gaining focus, if the field still contains that value, it is set to blank. Likewise, you can perform a similar operation in focusLost where if the field is blank you set it to the value retrieved for "originalText".