views:

37

answers:

2

In a master/detail view I have a series of text fields (and one or two other controls) that all pertain to the detail of the currently selected item. They all share the same DocumentListener so if you change any of them a pair of "save"/"discard" buttons become enabled. The buttons invoke a method and I can happily save/discard items.

However, when I use InputMap and ActionMap to attach a shared saveAction to the enter key and a shared discardAction to the escape key the discardAction only works on some fields (saveAction works for them all).

When logging I can see that for the fields that work the discardAction is triggered first, followed by the appropriate combination of removeUpdate and insertUpdate.

For the fields that don't work the discardAction is never triggered. Enough chitter, chatter - here is the pertinent code (copy and paste, not paraphrase):

docChangeListener = new DocumentListener() {
    public void insertUpdate(DocumentEvent de) {
        System.out.println("\t insertUpdate just got triggered");
        memberDetailsChanged(de);
    }
    public void removeUpdate(DocumentEvent de) {
        System.out.println("\t removeUpdate just got triggered");
        memberDetailsChanged(de);
    }
    public void changedUpdate(DocumentEvent de) {
        // Not a styled document, safely ignore
    }
};

saveAction = new AbstractAction() {
    public void actionPerformed(ActionEvent ae) {
        System.out.println("\t saveAction just got triggered");
        saveChanges();
    }
};
discardAction = new AbstractAction() {
    public void actionPerformed(ActionEvent ae) {
        System.out.println("\t discardAction just got triggered");
        discardChanges();
    }
};

private void registerDetailField(final JTextField field) {
    field.getDocument().putProperty("field", field);
    field.getInputMap().put(KeyStroke.getKeyStroke("ENTER"), "saveActionKey");
    field.getActionMap().put("saveActionKey", saveAction);
    field.getInputMap().put(KeyStroke.getKeyStroke("ESCAPE"), "discardActionKey");
    field.getActionMap().put("discardActionKey", discardAction);
    field.getDocument().addDocumentListener(docChangeListener);
}

All the text fields are registered the same way (using registerDetailField()). They also have putClientProperty called on them to allocate them a type for validation (see below).

The ONLY difference between the fields that work and the fields that don't is the actual validation process. I'll cut it down because it is so long but I feel I have to include it. discardAction SEEMS to fire first for the fields that work but the fields that don't work all have custom validation in common.

private void verifyField(final JTextField field) {
    int fieldType = ((Integer)field.getClientProperty("type")).intValue();
    String fieldValue = field.getText();

    switch (fieldType) {
        case STANDARD_FIELD:
            return; // No validation at the moment
        case MEMBER_NUMBER_FIELD:
            if (fieldValue.length() == 0) { // Field is required
                field.setBackground(REQUIRED_COLOUR);
                field.setToolTipText("This is a required field");
                invalidFields.add(field);
                return;
            }
            // Check proposed value is valid
            if (customTableModel.memberNumStringIsValid(fieldValue,
                                     selectedMember.getMemberNumber())) {
                field.setBackground(NORMAL_COLOUR);
                field.setToolTipText(null);
                invalidFields.remove(field);
            } else {
                field.setBackground(ERROR_COLOUR);
                field.setToolTipText("This value must be a unique,
                                     positive number");
                invalidFields.add(field);
            }
            return;
/* SNIP */
        default:
            return;
    }
}

Hopefully it's a simple problem with my verifyField method that I'm overlooking due to lack of sleep but at the moment I'm completely stumped.

A: 

I wold suggest to NOT attach actions to each text field. Attach them to the parent container and when retrieving input map from it - use WHEN_ANCESTOR_OF_FOCUSED_COMPONENT condition.

This way whenever this container is in focus actions will be available

eugener
@eugener Thanks for the advice! The reason I didn't do that originally is that there are other components in the same panel that aren't related to the details view. Although writing this now I realise it would be fairly simple to group the details view components into their own panel... Thanks for the tip!
Matt
A: 

The problem you are having is with the setting of the tool tip text. Once you do that, the ToolTipManager is replacing your discard action key in the inputMap with its own hideTip key stroke, also VK_ESCAE.

akf