Есть ли простой способ изменить поведение элемента управления Java/Swing, когда он получает фокус?

Для большинства графических интерфейсов, которые я использовал, когда элемент управления, содержащий текст, получает фокус, выделяется все содержимое элемента управления. Это означает, что если вы только начнете печатать, вы полностью замените предыдущее содержимое.

Пример: у вас есть управление вращением, которое инициализировано нулевым значением. Вы переходите к нему и вводите «1». Значение в элементе управления теперь равно 1.

С Swing этого не происходит. Текст в элементе управления не выделен, и точка появляется на том или ином конце существующего текста. Продолжая приведенный выше пример:

С Swing JSpinner, когда вы переходите к элементу управления вращением, карат находится слева. Вы вводите «1», и значение в элементе управления теперь равно 10.

Это приводит меня (и моих пользователей) в отчаяние, и я хотел бы это изменить. Что еще более важно, я хотел бы изменить его глобально, чтобы новое поведение применялось к JTextField, JPasswordField, JFormattedTextField, JTextArea, JComboBox, JSpinner и т. д. Единственный способ, который я нашел для этого, — добавить FocusAdapter к каждому элементу управления и переопределить метод focusGained(), чтобы делать правильные вещи [tm].

Должен быть более простой и менее хрупкий способ. Пожалуйста?

РЕДАКТИРОВАТЬ: еще одна дополнительная информация для этого конкретного случая. Форма, с которой я работаю, была создана с помощью конструктора форм Idea. Это означает, что обычно я не пишу код для создания компонентов. Можно сказать Idea, что вы хотите создать их самостоятельно, но я хотел бы избежать хлопот.

Девиз: Все хорошие программисты в основном ленивы.


person Dale Wilson    schedule 15.09.2008    source источник
comment
Я нашел решение, которое удовлетворяет мои потребности. Он опубликован как ответ.   -  person Dale Wilson    schedule 17.09.2008


Ответы (5)


Когда мне это понадобилось в прошлом, я создал подклассы компонентов, которые я также хотел добавить к функциям «автоматической очистки». например:

public class AutoClearingTextField extends JTextField {
   final FocusListener AUTO_CLEARING_LISTENER = new FocusListener(){
      @Override
      public void focusLost(FocusEvent e) {
         //onFocusLost(e);
      }

      @Override
      public void focusGained(FocusEvent e) {
         selectAll();
      }
   };

   public AutoClearingTextField(String string) {
      super(string);
      addListener();
   }

   private void addListener() {
      addFocusListener(AUTO_CLEARING_LISTENER);      
   }
}

Самая большая проблема в том, что я не нашел "хорошего" способа получить все стандартные конструкторы без написания переопределений. Добавление их и принудительный вызов addListener — самый общий подход, который я нашел.

Другой вариант — отслеживать ContainerEvents в контейнере верхнего уровня с помощью ContainerListeer, чтобы обнаруживать наличие новых виджетов и добавлять соответствующий прослушиватель фокуса на основе добавленных виджетов. (например: если событие контейнера вызвано добавлением TextField, добавьте прослушиватель фокуса, который знает, как выбрать весь текст в TextField, и т. д.) Если добавлен Container, вам нужно рекурсивно добавить ContainerListener к этому новому подконтейнеру.

В любом случае, вам не нужно будет возиться со слушателями фокуса в реальном коде пользовательского интерфейса — обо всем этом позаботятся на более высоком уровне.

person rcreswick    schedule 15.09.2008

Я не пробовал это сам (только баловался этим некоторое время назад), но вы, вероятно, можете получить текущий сфокусированный компонент, используя: KeyboardFocusManager (есть статический метод getCurrentKeyboardFocusManager()) и добавив к нему PropertyChangeListener. Оттуда вы можете узнать, является ли компонент JTextComponent, и выбрать весь текст.

person Avrom    schedule 15.09.2008
comment
+1 По моему опыту, это лучшее решение, если у вас много элементов управления и/или вы используете визуальный конструктор форм. - person Daniel Rikowski; 05.11.2013

Можно написать отдельный класс, который прикрепляет FocusListener к нужному текстовому полю. Все, что может сделать слушатель фокуса, это вызвать selectAll() для текстового виджета, когда он получит фокус.

public class SelectAllListener implements FocusListener {
  private static INSTANCE = new SelectAllListener();

  public void focusLost(FocusEvent e) { }

  public void focusGained(FocusEvent e) {
    if (e.getSource() instanceof JTextComponent) {  
      ((JTextComponent)e.getSource()).selectAll();
    }
  };

  public static void addSelectAllListener(JTextComponent tc) {
    tc.addFocusListener(INSTANCE);
  }

  public static void removeSelectAllListener(JTextComponent tc) {
    tc.removeFocusListener(INSTANCE);
  }
}

Принимая JTextComponent в качестве аргумента, это поведение можно добавить непосредственно в JTextArea, JPasswordField и все остальные компоненты редактирования текста. Это также позволяет классу добавлять выбор всех в редактируемые поля со списком и JSpinners, где ваш контроль над компонентом текстового редактора может быть более ограниченным. Можно добавить удобные методы:

public static void addSelectAllListener(JSpinner spin) {
  if (spin.getEditor() instanceof JTextComponent) {
    addSelectAllListener((JTextComponent)spin.getEditor());
  }
}

public static void addSelectAllListener(JComboBox combo) {
  JComponent editor = combo.getEditor().getEditorComponent();
  if (editor instanceof JTextComponent) {
    addSelectAllListener((JTextComponent)editor);
  }
}

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

person shemnon    schedule 16.09.2008

Прочитав ответы до сих пор (Спасибо!) Я передал самую внешнюю JPanel следующему методу:

void addTextFocusSelect(JComponent component){
    if(component instanceof JTextComponent){
        component.addFocusListener(new FocusAdapter() {
                @Override
                public void focusGained(FocusEvent event) {
                    super.focusGained(event);
                    JTextComponent component = (JTextComponent)event.getComponent();
                    // a trick I found on JavaRanch.com
                    // Without this, some components don't honor selectAll
                    component.setText(component.getText());
                    component.selectAll();
                }
            });

    }
    else
    {
        for(Component child: component.getComponents()){
            if(child instanceof JComponent){
                addTextFocusSelect((JComponent) child);
            }
        }
    }
}

Оно работает!

person Dale Wilson    schedule 17.09.2008
comment
+1 за трюк setText(getText()); Все остальное я придумал для себя, но оно все еще не работало! - person Huw Walters; 07.02.2016

Единственный известный мне способ — создать FocusListener и прикрепить его к вашему компоненту. Если вы хотите, чтобы этот FocusListener был глобальным для всех компонентов вашего приложения, вы можете рассмотреть возможность использования аспектно-ориентированного программирования (АОП). С помощью АОП можно закодировать его один раз и применить прослушиватель фокуса ко всем компонентам, созданным в вашем приложении, без необходимости копировать и вставлять код component.addFocusListener(listener) в приложение.

Ваш аспект должен будет перехватить создание JComponent (или подклассов, к которым вы хотите добавить это поведение) и добавить прослушиватель фокуса к вновь созданному экземпляру. Подход АОП лучше, чем копирование и вставка FocusListener во весь код, потому что вы сохраняете все это в одном фрагменте кода и не создаете кошмар обслуживания, как только вы решите изменить свое глобальное поведение, например, удалив прослушиватель для JSpinners.

Существует множество фреймворков АОП на выбор. Мне нравится JBossAOP, так как это на 100 % чистая Java, но вы можете взглянуть на AspectJ.

person Alexandre Brasil    schedule 15.09.2008