InputVerifier неправильно передает фокус и нуждается в совете по его использованию с другими методами ActionListener

У меня есть графический интерфейс, в котором пользователь вводит измерения в несколько полей и вычисляет результат на основе измерения. Я пытаюсь реализовать следующее для полей:

  1. Введенные значения должны быть в правильном диапазоне
  2. У меня есть диалоговое окно параметров, которое, среди прочего, устанавливает единицы измерения. Любое поле, в котором есть значение, должно быть обновлено до текущих единиц измерения.
  3. Когда значение в поле изменяется, мне нужно посмотреть, все ли измерения введены, и если да, то выполнить (или повторить) расчет.

Я сделал что-то подобное с таблицами (модель сохранила значения в «стандартных» единицах, а пользовательский рендерер и редактор ячеек обработали показ пользователю значений в текущих единицах и сохранение значений в «стандартных» единицах в модели ).

Я не верю, что у JTextField есть средство рендеринга, которое нужно переопределить, редактор документов выглядел немного устрашающе, а пользователю не понравился JFormattedTextField, поэтому я подумал, что создам собственный JTextField, в котором хранится «стандартное» значение, и использовать inputVerifier который я использовал ранее для таблицы.

Пример кода ниже (он почти работает). Я использовал JComboBox в качестве замены диалогового окна параметров и реализовал только одно текстовое поле.

Я мог бы использовать некоторые советы экспертов-

  1. Возможно, я неправильно понимаю setInputVerifier. Я думал, что его следует вызвать, если я попытаюсь изменить фокус с текстового поля и сохранить фокус в текстовом поле, если я сказал, что не уступаю фокус. Но если я введу «аа» в текстовое поле (не нажимая Enter), я смогу изменить значение в поле со списком. Моя отладка println говорит:

Значение громкости изменено (f) // мой прослушиватель фокуса сработал Обновление модели // от моего прослушивателя фокуса Проверка: 'aa' // от моего верификатора ввода Неверный номер // от моего верификатора ввода

текстовое поле приобретает красный контур, и я слышу звуковой сигнал, но поле со списком активно. Текстовое поле заканчивается пустым значением, так как прослушиватель действия со списком вызывается, когда я изменяю его значение. Почему мне разрешено изменять значение поля со списком? Как мне это остановить?

  1. Мое добавление InputVerifier, двух ActionListeners и FocusListener кажется неправильным. Мне нравится логическое разделение задач. Что я должен делать? Должен ли я расширить DoubleVerifier и переопределить actionPerformed, чтобы просто включить то, что в настоящее время находится в DoubleVerifier и что находится в VolumeValueListener?

Я хочу, чтобы текстовое поле проверялось, а представление базовых данных обновлялось либо когда пользователь вводит (CR) и остается в поле, либо когда он покидает поле. Вот почему действие и внимание слушателей.

Любые исправления или идеи приветствуются.

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() );
   }
}

Преобразование.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" );
      }
   }
}

person Jon Swanson    schedule 07.02.2013    source источник
comment
Боже, это много кода   -  person Dan    schedule 07.02.2013
comment
Извините, это была минимальная реализация функции для сохранения значения модели в текстовом поле, а затем отображения другого представления, а также выполнения проверки. Вам нужен пример, который просто показывает, что текстовое поле теряет фокус на поле со списком? Это было бы немного короче.   -  person Jon Swanson    schedule 07.02.2013
comment
В идеале да. Если вы знаете фрагмент кода, который, скорее всего, вызывает проблему (в данном случае это чисто Swing-код, хотя это может быть и не так), то лучше предоставить SSCCE — см. sscce.org для получения более подробной информации. Таким образом, проблема изолирована от того, что имеет значение, и вы получаете лучшие изменения в том, что вам помогают :)   -  person Dan    schedule 08.02.2013


Ответы (1)


Я понимаю часть своей проблемы из дополнительных исследований. InputVerifier касается только фокуса. Если ввод недействителен, то он не передаст фокус, однако позволит произойти событиям действия. Жалобы, которые я видел, были связаны с людьми, у которых была кнопка выхода, действие которой выполнялось, даже если данные в поле были недействительными. В моем случае у меня есть поле со списком, действие которого все еще может быть выполнено, даже если InputVerifier жалуется на недопустимые данные (текстовое поле получает красную рамку и издает звуковой сигнал). Так что в отношении этого аспекта проблемы я не верю, что есть хорошее решение. Одним из предложений была переменная, которую проверяли все прослушиватели действий перед выполнением действия, которое будет установлено с помощью InputVerifier. У меня есть мои (в идеале) многоразовые подпрограммы в отдельных файлах, поэтому у меня будут проблемы с этим решением.

Я все еще не уверен, как изящно справиться с ситуацией, когда у меня есть несколько различных общих действий (проверка ввода, преобразование единиц, обновление представления), где только некоторые из них будут необходимы для любого заданного поля, и я хочу назначить ActionListeners и FocusListeners в Последовательный порядок. Моя единственная мысль сейчас - иметь базовый слушатель, который, например, проверяет ввод, затем расширяет его и переопределяет методы actionPerformed, focusGained и focusLost, хотя, похоже, я в конечном итоге буду дублировать код для каждой комбинации.

person Jon Swanson    schedule 09.02.2013