简体   繁体   中英

InputVerifier incorrectly yielding focus and need advice using it with other ActionListener methods

I have a GUI in which the user enters measurements into a number of fields and calculates a result based on the measurement. I am trying to implement the following for the fields-

  1. The values entered must be in the proper range
  2. I have an option dialog that among other things, sets the units. Any field that has a value in it, must be updated to the current units
  3. When a value in a field changes, I need to see if all the measurements are entered, and if so, do (or redo) the calculation.

I've done something like this with tables (the model retained the values in 'standard' units and a custom renderer and cell editor handled showing the user the values in the current units and storing the values in the 'standard' units in the model).

I don't believe JTextField has a renderer to override, the Document editor looked a bit daunting, and the user didn't like the JFormattedTextField, so I thought I would create a custom JTextField that stored a 'standard' value and use the inputVerifier that I used previously for the table.

Example code is below (it almost works). I used a JComboBox as a stand-in for the option dialog and implemented only one text field.

I could use some expert advice-

  1. I may be misunderstanding setInputVerifier. I thought it should be invoked if I attempted to change focus from the text field and keep focus in the text field if I said don't yield focus. But if I put 'aa' in the text field (without hitting enter) I can change the value in the combobox. My debugging println say-

Volume value changed (f) // my focus listener fired off Updating model // from my focus listener Verifying: 'aa' // from my input verifier Invalid number // from my input verifier

the text box gets a red outline and I hear a beep, but the combobox is active. The text field ends up with an empty value, since the combobox action listener is called when I change its value. Why am I allowed to change the combox value? How do I stop that?

  1. My adding an InputVerifier, two ActionListeners and a FocusListener seems wrong. I do like the logical separation of tasks. What should I be doing? Should I extend DoubleVerifier and override the actionPerformed to just include what's currently in DoubleVerifier and what is in the VolumeValueListener?

I want the text field to be validated and the view of the underlying data updated either when the user enters (CR) and stays in the field or when they leaves the field. Which is why the action and focus listeners.

Any corrections or insights are most welcome.

UnitsTextField.java

import java.util.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

class UnitsTextField extends JTextField
{
   Double modelValue = null;
   Double viewValue  = null;

   UnitsTextField( int cols )
   {
      super( cols );
   }

   public void updateModel() throws Exception
   {
      System.out.println( "Updating model" );
      modelValue = Conversion.modelValue( this.getText() );
   }

   public void refreshView()
   {
      this.setText( Conversion.viewString( modelValue ) );
   }

   public Double getModelValue()
   {
      return modelValue;
   }
} 

UnitsLabel.java

import java.util.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

class UnitsLabel extends JLabel
{
   public void refreshView()
   {
      super.setText( Conversion.viewLabel() );
   }
}

Conversion.java

import java.util.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

class Conversion
{
   public  static enum  UNITS {CC, L, GAL};

   public  static Map<String,UNITS> unitTypes = 
                                       new HashMap<String, UNITS>()
   {
      {
         put( "Cubic centimeters", UNITS.CC  );
         put( "Liters",            UNITS.L   );
         put( "Gallons",           UNITS.GAL );
      }
   };

   public  static Map<UNITS,Double> unitConversions =
                                       new HashMap<UNITS, Double>()
   {
      {
         put( UNITS.CC,     1.0 );
         put( UNITS.L,   1000.0 );
         put( UNITS.GAL, 4404.9 );
      }
   };

   private static UNITS unitType = UNITS.CC;

   public static void   setUnitType( UNITS unit )
   {
      unitType = unit;
   }

   public static void   setUnitType( String unitString )
   {
      unitType = unitTypes.get(unitString);
   }

   public static String[] getUnitNames()
   {
      return (unitTypes.keySet()).toArray(new String[0]);
   }

   public static String viewLabel()
   {
      return unitType.toString();
   }

   public static Double modelValue( String viewString ) throws Exception
   {
      Double value = null;

      if (viewString != null && viewString.length() > 0)
      {
         value = Double.parseDouble( viewString );
         value = value * unitConversions.get(unitType);
      }
      return value;
   }

   public static String viewString( Double modelValue )
   {
      Double value = null;

      if (modelValue != null)
      {
         value = modelValue / unitConversions.get(unitType);
      }
      return (value == null) ? "" : value.toString();
   }
}

DoubleVerifier.java

import java.util.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.*;
import java.text.NumberFormat;
import java.awt.Toolkit;

public class DoubleVerifier extends InputVerifier implements ActionListener
{
   public boolean shouldYieldFocus(JComponent input)
   {
      JTextField tf   = (JTextField) input;
      boolean inputOK = verify(input);

      if (inputOK)
      {
         tf.setBorder( new LineBorder( Color.black ) );
         return true;
      }
      else
      {
         tf.setBorder( new LineBorder( Color.red ) );
         Toolkit.getDefaultToolkit().beep();
         return false;
      }
   }

   public boolean verify(JComponent input)
   {
      JTextField tf  = (JTextField) input;
      String     txt = tf.getText();
      double     n;

      System.out.println( "Verifying: '" + txt + "'" );
      if (txt.length() != 0)
      {
         try
         {
            n = Double.parseDouble(txt);
         }
         catch (NumberFormatException nf)
         {
            System.out.println( "Invalid number" );
            return false;
         }
      }
      return true;
   }

   public void actionPerformed(ActionEvent e)
   {
      System.out.println( "Input verification" );
      JTextField source = (JTextField) e.getSource();
      shouldYieldFocus(source);
   }
}

VolumeTextFieldTest.java

import java.util.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.*;

class VolumeTextFieldTest extends JFrame
{
   private JComboBox      volumeCombo;
   private UnitsLabel     volumeLabel;
   private UnitsTextField volumeField;

   public VolumeTextFieldTest()
   {
      setSize(300, 100);
      setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      volumeCombo   = new JComboBox( Conversion.getUnitNames() );
      volumeCombo.addActionListener( new VolumeListener() );
      volumeCombo.addFocusListener( new VolumeListener() );
      volumeLabel   = new UnitsLabel();
      volumeLabel.refreshView();
      volumeField   = new UnitsTextField(8);
      DoubleVerifier dVerify = new DoubleVerifier();
      volumeField.setInputVerifier(  dVerify );
      volumeField.addActionListener( dVerify );
      volumeField.addActionListener( new VolumeValueListener() );
      volumeField.addFocusListener(  new VolumeValueListener() );
      JPanel myPane = new JPanel();
      myPane.add(volumeCombo);
      myPane.add(volumeField);
      myPane.add(volumeLabel);
      getContentPane().add(myPane);
      setVisible(true);
   }

   public class VolumeListener implements ActionListener, FocusListener
   {
      @Override
      public void actionPerformed( ActionEvent ae )
      {
          System.out.println( "Volume type changed" );
     Conversion.setUnitType( (String) volumeCombo.getSelectedItem() );
     volumeLabel.refreshView();
          volumeField.refreshView();
      }
      @Override
      public void focusGained( FocusEvent fg )
      {
      }
      @Override
      public void focusLost( FocusEvent fl )
      {
          System.out.println( "Volume type changed" );
     Conversion.setUnitType( (String) volumeCombo.getSelectedItem() );
     volumeLabel.refreshView();
          volumeField.refreshView();
      }
   }

   public class VolumeValueListener implements ActionListener, FocusListener
   {
      @Override
      public void actionPerformed( ActionEvent ae )
      {
         System.out.println( "Volume value changed (a)" );
         try
         {
        volumeField.updateModel();
        volumeField.refreshView();
         }
         catch (Exception e)
         {}
      }
      @Override
      public void focusGained( FocusEvent fg )
      {
      }
      @Override
      public void focusLost( FocusEvent fl )
      {
         System.out.println( "Volume value changed (f)" );
         try
         {
        volumeField.updateModel();
        volumeField.refreshView();
         }
         catch (Exception e)
         {}
      }
   }

   public static void main(String[] args)
   {
      try
      {
         SwingUtilities.invokeLater( new Runnable()
         {
            public void run ()
            {
               VolumeTextFieldTest runme = new VolumeTextFieldTest();
            }
         });
      }
      catch (Exception e)
      {
         System.out.println( "GUI did not start" );
      }
   }
}

I understand part of my problem from additional research. InputVerifier is only concerned with focus. If the input is invalid, then it will not transfer focus, however, it will allow action events to occur. The complaints I have seen have been related to people who had an exit button whose action would be performed even if the data in a field was invalid. In my case, I have a combobox whose action can still be performed even though the InputVerifier is complaining about the invalid data (text field gets a red border and beeps). So in respect to that aspect of the problem, I don't believe there is a good solution. One suggestion was a variable that all the action listeners checked before performing the action, which would be set by the InputVerifier. I've got my (ideally) reusable routines in separate files, so will have some problems with this solution.

I'm still unsure of how to gracefully handle the situation where I have a several distinct generic actions (verify input, convert units, update a view) where only some will be needed for any given field and I want to assign ActionListeners and FocusListeners in sequential order. My only thought right now is to have a base listener, that for example verifies input, then extend it and override the actionPerformed, focusGained and focusLost methods, though it seems like I will end up duplicating code for every combination.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM