1

ユーザーが に数値を入力JTextFieldsし、キーで進みTAB、最後にボタンをアクティブにして入力の処理を開始する直感的なユーザー インターフェイスを作成しようとしています。

最初はボタンが無効になっており、すべてのデータがテキスト フィールドに入力された場合にのみ有効にする必要があります。

小数点以下 4 桁までの正の数値javax.swing.InputVerifierのみの入力を制限するために使用していますが、これは正常に機能します。

3 つのフォーカス可能なオブジェクト、2 つのテキスト フィールド、およびボタンがあります。TAB(有効な) 数値をテキスト フィールドに入力した後にキーを押し、すべてのテキスト フィールドに有効な入力が含まれている場合は、ボタンが有効になります。それもうまくいきます。

問題は次のとおり
です。 最初のテキスト フィールドに有効なデータが既に含まれているときに、2 番目のテキスト フィールドに有効なデータを入力して を押すとTABボタンが本来のフォーカスを得られません。代わりに、フォーカスは、(再び) 最初のテキスト フィールドである、行内の次のフォーカス可能なオブジェクトに転送されます。

私は2つの異なるアプローチを使用しようとしました:

  1. ボタンのenabledプロパティは、FocusListenerオーバーライドされたfocusLost()メソッド内で変更されます
  2. ボタンのenabledプロパティは、オーバーライドされたshouldYieldFocus()メソッド内で変更されます

どちらの場合も、ボタンを有効にした直後にフォーカスがボタンをスキップします。ただし、その後+キーを使用してフォーカスを変更し続けると、ボタンは 2 番目のテキスト フィールドの直後にフォーカスを取得します。TABSHIFTTAB

ボタンを有効にする前にコンポーネントが事前に定義されてoppositeいるため、ボタンが有効になった後でもボタンがフォーカスされないように思えます。

ボタンを有効にした後にボタンを強制的にフォーカスさせようとしましたrequestFocusInWindow()が、それもうまくいきませんでした。問題は、レイアウトを強制的 LayoutFocusTraversalPolicy に再評価して、新しく導入されたボタンをすぐに考慮できるようにする方法です。無効になる前に?

私が試した両方のアプローチのコードは次のとおりです。

  1. ボタンのenabledプロパティは、FocusListener内部focusLost()メソッドを介して変更されます。
package verifiertest;

import java.awt.EventQueue;

import javax.swing.JFrame;
import javax.swing.JPanel;
import java.awt.BorderLayout;
import java.awt.Dimension;

import javax.swing.border.EmptyBorder;
import javax.swing.border.TitledBorder;
import javax.swing.text.JTextComponent;
import javax.swing.UIManager;
import java.awt.GridLayout;
import java.awt.Toolkit;
import java.math.BigDecimal;

import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.SwingConstants;
import javax.swing.JTextField;
import javax.swing.InputVerifier;
import javax.swing.JButton;
import javax.swing.JComponent;

import java.awt.FlowLayout;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.ActionEvent;

public class TestVerifier implements FocusListener {

    private JFrame frmInputverifierTest;
    private JTextField tfFirstNum;
    private JTextField tfSecondNum;
    private JLabel lblStatus;
    private JButton btnStart;
    private String statusText = "Input the numbers and press the \"Start!\" button...";

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    TestVerifier window = new TestVerifier();
                    window.frmInputverifierTest.setVisible(true);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    public TestVerifier() {
        initialize();
    }

    private void initialize() {
        frmInputverifierTest = new JFrame();
        frmInputverifierTest.setTitle("InputVerifier Test");
        frmInputverifierTest.setBounds(100, 100, 500, 450);
        frmInputverifierTest.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        // center the window
        Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
        frmInputverifierTest.setLocation(dim.width/2 - frmInputverifierTest.getWidth()/2, dim.height/2 - frmInputverifierTest.getHeight()/2);

        JPanel panelContainer = new JPanel();
        panelContainer.setBorder(new EmptyBorder(5, 5, 5, 5));
        frmInputverifierTest.getContentPane().add(panelContainer, BorderLayout.CENTER);
        panelContainer.setLayout(new BorderLayout(0, 0));

        JPanel panelInput = new JPanel();
        panelInput.setBorder(new TitledBorder(null, "Input", TitledBorder.LEADING, TitledBorder.TOP, null, null));
        panelContainer.add(panelInput, BorderLayout.NORTH);
        panelInput.setLayout(new GridLayout(2, 2, 10, 4));

        JLabel lblFirstNum = new JLabel("Number #1:");
        lblFirstNum.setHorizontalAlignment(SwingConstants.TRAILING);
        panelInput.add(lblFirstNum);

        tfFirstNum = new JTextField();
        panelInput.add(tfFirstNum);
        tfFirstNum.setColumns(10);
        // setup the verifier
        MyTxtVerifier txtVerifier = new MyTxtVerifier();
        tfFirstNum.setInputVerifier(txtVerifier);
        // add focus listener
        tfFirstNum.addFocusListener(this);

        JLabel lblSecondNum = new JLabel("Number #2:");
        lblSecondNum.setHorizontalAlignment(SwingConstants.TRAILING);
        panelInput.add(lblSecondNum);

        tfSecondNum = new JTextField();
        panelInput.add(tfSecondNum);
        tfSecondNum.setColumns(10);
        // setup the verifier
        tfSecondNum.setInputVerifier(txtVerifier);
        // add focus listener
        tfSecondNum.addFocusListener(this);

        JPanel panelOutput = new JPanel();
        panelOutput.setBorder(new TitledBorder(UIManager.getBorder("TitledBorder.border"), "Output (not used at the moment)", TitledBorder.LEADING, TitledBorder.TOP, null, null));
        panelContainer.add(panelOutput, BorderLayout.CENTER);

        JPanel panelSouth = new JPanel();
        panelSouth.setBorder(null);
        panelContainer.add(panelSouth, BorderLayout.SOUTH);
        panelSouth.setLayout(new GridLayout(0, 1, 0, 0));

        JPanel panelStatus = new JPanel();
        FlowLayout flowLayout_1 = (FlowLayout) panelStatus.getLayout();
        flowLayout_1.setAlignment(FlowLayout.LEFT);
        panelStatus.setBorder(new TitledBorder(null, "Status", TitledBorder.LEADING, TitledBorder.TOP, null, null));
        panelSouth.add(panelStatus);

        lblStatus = new JLabel(statusText);
        panelStatus.add(lblStatus);

        JPanel panelActions = new JPanel();
        panelActions.setBorder(new TitledBorder(null, "Actions", TitledBorder.LEADING, TitledBorder.TOP, null, null));
        FlowLayout flowLayout = (FlowLayout) panelActions.getLayout();
        flowLayout.setAlignment(FlowLayout.RIGHT);
        panelSouth.add(panelActions);

        btnStart = new JButton("Start!");
        btnStart.setEnabled(false);
        btnStart.setVerifyInputWhenFocusTarget(true);
        btnStart.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                JOptionPane.showMessageDialog(frmInputverifierTest, "Start button pressed...", "Start", JOptionPane.PLAIN_MESSAGE);
            }
        });
        panelActions.add(btnStart);
    }

    // an inner class so it can access parent fields
    public class MyTxtVerifier extends InputVerifier {
        // This method should have no side effects
        @Override
        public boolean verify(JComponent input) {
            String text = ((JTextField)input).getText();
            // to allow changing focus when nothing is entered
            if(text.isEmpty())
                return true;
            try {
                BigDecimal value = new BigDecimal(text);
                if(value.floatValue() <= 0.0)
                    return false;
                return (value.scale() <= 4);
            } catch (Exception e) {
                return false;
            }
        }

        // This method can have side effects
        @Override
        public boolean shouldYieldFocus(JComponent input) {
            String statusOld, status;

            statusOld = statusText;         // remember the original text
            boolean isOK = verify(input);   // call overridden method
            if(isOK)
                status = statusOld;
            else {
                btnStart.setEnabled(false);
                status = "Error: The parameter should be a positive number up to 4 decimal places";
            }
            lblStatus.setText(status);
            // return super.shouldYieldFocus(input);
            return isOK;
        }
    }

    @Override
    public void focusGained(FocusEvent e) {
        // nothing to do on focus gained
    }

    @Override
    public void focusLost(FocusEvent e) {
        // in case we want to show a message box inside focusLost() - not to be fired twice
        if(e.isTemporary())
            return;
        final JTextComponent c = (JTextComponent)e.getSource();
        // in case there are more text fields but
        // we are validating only some of them
        if(c.equals(tfFirstNum) || c.equals(tfSecondNum)) {
            // are all text fields valid?
            if(c.getInputVerifier().verify(tfFirstNum) && c.getInputVerifier().verify(tfSecondNum) &&
                    !tfFirstNum.getText().isEmpty() && !tfSecondNum.getText().isEmpty())
                btnStart.setEnabled(true);
            else
                btnStart.setEnabled(false);
        }
    }
}
  1. ボタンのenabledプロパティは、オーバーライドされたshouldYieldFocus()メソッド内で変更されます。
package verifiertest;

import java.awt.EventQueue;

import javax.swing.JFrame;
import javax.swing.JPanel;
import java.awt.BorderLayout;
import java.awt.Dimension;

import javax.swing.border.EmptyBorder;
import javax.swing.border.TitledBorder;
import javax.swing.UIManager;
import java.awt.GridLayout;
import java.awt.Toolkit;
import java.math.BigDecimal;

import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.SwingConstants;
import javax.swing.JTextField;
import javax.swing.InputVerifier;
import javax.swing.JButton;
import javax.swing.JComponent;

import java.awt.FlowLayout;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;

public class TestVerifier {

    private JFrame frmInputverifierTest;
    private JTextField tfFirstNum;
    private JTextField tfSecondNum;
    private JLabel lblStatus;
    private JButton btnStart;
    private String statusText = "Input the numbers and press the \"Start!\" button...";

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    TestVerifier window = new TestVerifier();
                    window.frmInputverifierTest.setVisible(true);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    public TestVerifier() {
        initialize();
    }

    private void initialize() {
        frmInputverifierTest = new JFrame();
        frmInputverifierTest.setTitle("InputVerifier Test");
        frmInputverifierTest.setBounds(100, 100, 500, 450);
        frmInputverifierTest.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        // center the window
        Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
        frmInputverifierTest.setLocation(dim.width/2 - frmInputverifierTest.getWidth()/2, dim.height/2 - frmInputverifierTest.getHeight()/2);

        JPanel panelContainer = new JPanel();
        panelContainer.setBorder(new EmptyBorder(5, 5, 5, 5));
        frmInputverifierTest.getContentPane().add(panelContainer, BorderLayout.CENTER);
        panelContainer.setLayout(new BorderLayout(0, 0));

        JPanel panelInput = new JPanel();
        panelInput.setBorder(new TitledBorder(null, "Input", TitledBorder.LEADING, TitledBorder.TOP, null, null));
        panelContainer.add(panelInput, BorderLayout.NORTH);
        panelInput.setLayout(new GridLayout(2, 2, 10, 4));

        JLabel lblFirstNum = new JLabel("Number #1:");
        lblFirstNum.setHorizontalAlignment(SwingConstants.TRAILING);
        panelInput.add(lblFirstNum);

        tfFirstNum = new JTextField();
        panelInput.add(tfFirstNum);
        tfFirstNum.setColumns(10);
        // setup the verifier
        MyTxtVerifier txtVerifier = new MyTxtVerifier();
        tfFirstNum.setInputVerifier(txtVerifier);

        JLabel lblSecondNum = new JLabel("Number #2:");
        lblSecondNum.setHorizontalAlignment(SwingConstants.TRAILING);
        panelInput.add(lblSecondNum);

        tfSecondNum = new JTextField();
        panelInput.add(tfSecondNum);
        tfSecondNum.setColumns(10);
        // setup the verifier
        tfSecondNum.setInputVerifier(txtVerifier);

        JPanel panelOutput = new JPanel();
        panelOutput.setBorder(new TitledBorder(UIManager.getBorder("TitledBorder.border"), "Output (not used at the moment)", TitledBorder.LEADING, TitledBorder.TOP, null, null));
        panelContainer.add(panelOutput, BorderLayout.CENTER);

        JPanel panelSouth = new JPanel();
        panelSouth.setBorder(null);
        panelContainer.add(panelSouth, BorderLayout.SOUTH);
        panelSouth.setLayout(new GridLayout(0, 1, 0, 0));

        JPanel panelStatus = new JPanel();
        FlowLayout flowLayout_1 = (FlowLayout) panelStatus.getLayout();
        flowLayout_1.setAlignment(FlowLayout.LEFT);
        panelStatus.setBorder(new TitledBorder(null, "Status", TitledBorder.LEADING, TitledBorder.TOP, null, null));
        panelSouth.add(panelStatus);

        lblStatus = new JLabel(statusText);
        panelStatus.add(lblStatus);

        JPanel panelActions = new JPanel();
        panelActions.setBorder(new TitledBorder(null, "Actions", TitledBorder.LEADING, TitledBorder.TOP, null, null));
        FlowLayout flowLayout = (FlowLayout) panelActions.getLayout();
        flowLayout.setAlignment(FlowLayout.RIGHT);
        panelSouth.add(panelActions);

        btnStart = new JButton("Start!");
        btnStart.setEnabled(false);
        btnStart.setVerifyInputWhenFocusTarget(true);
        btnStart.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                JOptionPane.showMessageDialog(frmInputverifierTest, "Start button pressed...", "Start", JOptionPane.PLAIN_MESSAGE);
            }
        });
        panelActions.add(btnStart);
    }

    // an inner class so it can access parent fields
    public class MyTxtVerifier extends InputVerifier {
        // This method should have no side effects
        @Override
        public boolean verify(JComponent input) {
            String text = ((JTextField)input).getText();
            // to allow changing focus when nothing is entered
            if(text.isEmpty())
                return true;
            try {
                BigDecimal value = new BigDecimal(text);
                if(value.floatValue() <= 0.0)
                    return false;
                return (value.scale() <= 4);
            } catch (Exception e) {
                return false;
            }
        }

        // This method can have side effects
        @Override
        public boolean shouldYieldFocus(JComponent input) {
            String statusOld, status;

            statusOld = statusText;         // remember the original text
            boolean isOK = verify(input);   // call overridden method
            if(isOK)
                status = statusOld;
            else {
                status = "Error: The parameter should be a positive number up to 4 decimal places";
            }
            lblStatus.setText(status);
            setBtnState(input);             // enable or disable the button
            //btnStart.requestFocusInWindow();  //  <-- does not help
            // return super.shouldYieldFocus(input);
            return isOK;
        }
    }

    private void setBtnState(JComponent input) {
        if (input.equals(tfFirstNum) || input.equals(tfSecondNum)) {
            // are all text fields valid?
            if (input.getInputVerifier().verify(tfFirstNum) && input.getInputVerifier().verify(tfSecondNum)
                    && !tfFirstNum.getText().isEmpty() && !tfSecondNum.getText().isEmpty())
                btnStart.setEnabled(true);
            else
                btnStart.setEnabled(false);
        }
    }
}

テスト アプリケーションのスクリーンショットは次のとおりです。

テスト アプリケーションのスクリーンショット

注:このコードは、別のトピックである、以前に尋ねた質問に
含まれるコードに関連しています。

編集:受け入れられた回答の作成者によって提案された提案(を実行するために
使用)を試してみると、概念実証として役立つコードは次のとおりです。invokeLater()requestFocusInWindow()

@Override
public void focusLost(FocusEvent e) {
    // in case we want to show a message box inside focusLost() - not to be fired twice
    if(e.isTemporary())
        return;
    final JTextComponent c = (JTextComponent)e.getSource();
    // in case there are more text fields but
    // we are validating only some of them
    if(c.equals(tfFirstNum) || c.equals(tfSecondNum)) {
        // are all text fields valid?
        if(c.getInputVerifier().verify(tfFirstNum) && c.getInputVerifier().verify(tfSecondNum) &&
                !tfFirstNum.getText().isEmpty() && !tfSecondNum.getText().isEmpty())
            btnStart.setEnabled(true);
        else
            btnStart.setEnabled(false);
    }
    if (btnStart.isEnabled() && e.getOppositeComponent()==tfFirstNum) {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                btnStart.requestFocusInWindow();
            }
        });
    }
}

focusLost()これは、 に関連するメソッドの変更にすぎませんapproach #01。-で使用する同様の解決策があるかapproach #02どうかはわかりません。oppositeshouldYieldFocus()FocusListener

注:
このソリューションを使用すると、2 番目の数字を入力してTABボタンを押した後、フォーカスが最初に (一時的に) 最初のテキスト フィールドにジャンプしてから、ボタンに移動することがはっきりとわかります。

4

1 に答える 1

3

を使用せずInputVerifier、代わりに を使用することをお勧めしますDocumentListener

を使用する利点はDocumentListener、文字を入力するたびにテキスト フィールドを編集できるため、ユーザーはすぐにフィードバックを受けることができることです。次に、最初の数字を入力するとすぐに、ボタンを有効にすることができます (編集基準を満たした場合)。

ユーザーが Tab キーを入力しようとする前にボタンが有効になるため、フォーカスの問題は発生しません。

開始するための基本的な例を次に示します。

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

public class DataEntered implements DocumentListener
{
    private JButton button;
    private List<JTextField> textFields = new ArrayList<JTextField>();

    public DataEntered(JButton button)
    {
        this.button = button;
    }

    public void addTextField(JTextField textField)
    {
        textFields.add( textField );
        textField.getDocument().addDocumentListener( this );
    }

    public boolean isDataEntered()
    {
        for (JTextField textField : textFields)
        {
            if (textField.getText().trim().length() == 0)
                return false;
        }

        return true;
    }

    @Override
    public void insertUpdate(DocumentEvent e)
    {
        checkData();
    }

    @Override
    public void removeUpdate(DocumentEvent e)
    {
        checkData();
    }

    @Override
    public void changedUpdate(DocumentEvent e) {}

    private void checkData()
    {
        button.setEnabled( isDataEntered() );
    }

    private static void createAndShowUI()
    {
        JButton submit = new JButton( "Submit" );
        submit.setEnabled( false );

        JTextField textField1 = new JTextField(10);
        JTextField textField2 = new JTextField(10);

        DataEntered de = new DataEntered( submit );
        de.addTextField( textField1 );
        de.addTextField( textField2 );

        JFrame frame = new JFrame("SSCCE");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(textField1, BorderLayout.WEST);
        frame.add(textField2, BorderLayout.EAST);
        frame.add(submit, BorderLayout.SOUTH);
        frame.pack();
        frame.setLocationByPlatform( true );
        frame.setVisible( true );
    }

    public static void main(String[] args)
    {
        EventQueue.invokeLater(new Runnable()
        {
            public void run()
            {
                createAndShowUI();
            }
        });
    }
}

基本コードは、テキストが入力されるたびにボタンを有効にします。dataEntered()編集条件を適用するには、メソッドを変更する必要があります。

編集:

API を使用して目的の操作を行う方法がわかりません。以下はハッキングの可能性です。

私が理解しているように、問題は2つの状況です。

  1. フォーカスがフォームの最後のフィールドにあり、Tab を使用する場合
  2. フォーカスがフォームの最初のフィールドにあり、Shift-Tab を使用する場合

したがって、できることは、最初と最後のコンポーネントの 2 つのパラメーターを使用して InputVerifier を作成することです。次に、FocusListener を使用すると、

  1. 現在のフォーカスは最初のコンポーネントにあり、反対側のコンポーネントは最後のコンポーネントです
  2. 現在のフォーカスは最後のコンポーネントにあり、反対のコンポーネントは最初のコンポーネントにあります

フォームをラップしていることがわかります。これら 2 つの状況では、[保存] ボタンにフォーカスを配置する必要があるため、[保存] ボタンにフォーカスを手動で要求する必要があります。したがって、次を使用するだけでこれを行うことができます。

saveButton.requestFocusInWindow();

フォーカスは、最初に反対側のコンポーネントに移動し、次にボタンに移動することに注意してください。そのコードを でラップする必要がある場合もありますSwingUtilities.invokeLater()

于 2016-08-05T00:47:55.090 に答える