7

私はSwingを初めて使用し、状況があります。xmlファイルinput(meta-data)に基づいてGUIコンポーネントを動的にレンダリングするアプリケーションを設計しています。現在、ほとんどのJTextFieldには、検証のためにInputVerifierが設定されています。無効な入力があると、入力ベリファイアがJOptionPaneをポップアップします。

これで、ユーザーが無効なデータを入力して先に進み、パネルのボタンをクリックすると、ダイアログがポップアップし、ユーザーはそれに応答する必要があります。しかしその後もボタンは状態を解放するためにペイントされません。まだ押されているように見えましたが、実際はそうではありません。コード全体がかなり乱雑なので、私は問題のシナリオを以下のコードに入れています:-

JButtonが押されていないように見えるようにするにはどうすればよいですか?論理も説明していただければ幸いです。

前もって感謝します。

package test;

import java.awt.BorderLayout;
import java.awt.Frame;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import javax.swing.InputVerifier;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JTextField;

public class VerifierTest extends JFrame {

    private static final long serialVersionUID = 1L;

    public VerifierTest() {
        JTextField tf;
        tf = new JTextField("TextField1");

        getContentPane().add(tf, BorderLayout.NORTH);
        tf.setInputVerifier(new PassVerifier());

        final JButton b = new JButton("Button");
        b.setVerifyInputWhenFocusTarget(true);
        getContentPane().add(b, BorderLayout.EAST);
        b.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if (b.hasFocus())
                    System.out.println("Button clicked");
            }
        });

        addWindowListener(new MyWAdapter());
    }

    public static void main(String[] args) {
        Frame frame = new VerifierTest();
        frame.setSize(400, 200);
        frame.setVisible(true);
        //frame.pack();
    }

    class MyWAdapter extends WindowAdapter {

        public void windowClosing(WindowEvent event) {
            System.exit(0);
        }
    }

    class PassVerifier extends InputVerifier {

        public boolean verify(JComponent input) {
            JTextField tf = (JTextField) input;
            String pass = tf.getText();
            if (pass.equals("Manish"))
                return true;
            else {
                String message = "illegal value: " + tf.getText();
                JOptionPane.showMessageDialog(tf.getParent(), message,
                        "Illegal Value", JOptionPane.ERROR_MESSAGE);

                return false;
            }
        }
    }
}
4

5 に答える 5

3

このメソッドverifyは、実際にはJOptionPaneを開くのに適した場所ではありません。

問題を解決するために検討できるいくつかのアプローチがあります。

  1. このJOptionPaneは、テキストフィールドがフォーカスを失い、入力が正しくない場合に表示されるようにします。JTextFieldでFocusListenerを使用し、適切なイベントに対応します。
  2. このJOptionPaneは、ボタンが押されるたびに表示されるようにします。入力が正しくない場合は、ActionListenerを使用して表示します。

後者のオプションの小さな抜粋を次に示します。

import java.awt.BorderLayout;
import java.awt.Frame;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.InputVerifier;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JTextField;

public class VerifierTest extends JFrame {

    private static final long serialVersionUID = 1L;

    public VerifierTest() {
        final JTextField tf = new JTextField("TextField1");

        getContentPane().add(tf, BorderLayout.NORTH);
        tf.setInputVerifier(new PassVerifier());

        final JButton b = new JButton("Button");
        b.setVerifyInputWhenFocusTarget(true);
        getContentPane().add(b, BorderLayout.EAST);
        b.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                if (!tf.getInputVerifier().verify(tf)) {
                    JOptionPane.showMessageDialog(tf.getParent(), "illegal value: " + tf.getText(), "Illegal Value",
                            JOptionPane.ERROR_MESSAGE);
                }
                if (b.hasFocus()) {
                    System.out.println("Button clicked");
                }
            }
        });
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }

    public static void main(String[] args) {
        Frame frame = new VerifierTest();
        frame.setSize(400, 200);
        frame.setVisible(true);
    }

    class PassVerifier extends InputVerifier {

        @Override
        public boolean verify(JComponent input) {
            final JTextField tf = (JTextField) input;
            String pass = tf.getText();
            return pass.equals("Manish");
        }
    }
}

また、ウィンドウリスナーを追加する代わりに、JFrameのデフォルトのクローズ操作を設定することを検討してください(ただし、ユーザーにアプリケーションを終了するかどうかを尋ねるダイアログをポップアップする場合は、WindowListenerを使用することをお勧めします)。

于 2012-09-19T18:55:24.863 に答える
1

SwingUtilitiesGUIがイベントスレッド上にあることを確認するための呼び出しを追加し、 Frameへの参照を削除しました。

GUIはWindowsXPで動作します。

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import javax.swing.InputVerifier;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;

public class VerifierTest implements Runnable {

    private static final long serialVersionUID = 1L;

    public VerifierTest() {

    }

    @Override
    public void run() {
        JFrame frame = new JFrame();
        frame.setSize(400, 200);

        JTextField tf;
        tf = new JTextField("TextField1");
        tf.setInputVerifier(new PassVerifier());
        frame.getContentPane().add(tf, BorderLayout.NORTH);

        final JButton b = new JButton("Button");
        b.setVerifyInputWhenFocusTarget(true);
        frame.getContentPane().add(b, BorderLayout.EAST);
        b.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if (b.hasFocus())
                    System.out.println("Button clicked");
            }
        });

        frame.addWindowListener(new MyWAdapter());
        frame.setVisible(true);
    }

    public static void main(String[] args) {
       SwingUtilities.invokeLater(new VerifierTest());
    }

    class MyWAdapter extends WindowAdapter {
        @Override
        public void windowClosing(WindowEvent event) {
            System.exit(0);
        }
    }

    class PassVerifier extends InputVerifier {
        @Override
        public boolean verify(JComponent input) {
            JTextField tf = (JTextField) input;
            String pass = tf.getText();
            if (pass.equals("Manish"))
                return true;
            else {
                String message = "illegal value: " + tf.getText();
                JOptionPane.showMessageDialog(tf.getParent(), message,
                        "Illegal Value", JOptionPane.ERROR_MESSAGE);

                return false;
            }
        }
    }
}
于 2012-09-19T18:56:29.157 に答える
1

以下のようにボタンに新しいマウスリスナーを追加しましたが、今は問題なく動作しているようですが、ボタンの選択状態を修正する良い方法かどうかはわかりません。

package test;

import java.awt.BorderLayout;
import java.awt.Frame;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import javax.swing.InputVerifier;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JTextField;
import javax.swing.plaf.basic.BasicButtonListener;

public class VerifierTest extends JFrame {

    private static final long serialVersionUID = 1L;

    public VerifierTest() {
        JTextField tf;
        tf = new JTextField("TextField1");

        getContentPane().add(tf, BorderLayout.NORTH);
        tf.setInputVerifier(new PassVerifier());

        final JButton b = new JButton("Button");
        b.setVerifyInputWhenFocusTarget(true);
        getContentPane().add(b, BorderLayout.EAST);
        b.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if (b.hasFocus())
                    System.out.println("Button clicked");
            }
        });

        b.addMouseListener(new BasicButtonListener(b) {
            @Override
            public void mouseExited(MouseEvent e) {
                ((JButton)e.getSource()).getModel().setArmed(false);
                ((JButton)e.getSource()).getModel().setPressed(false);
            }

        });

        addWindowListener(new MyWAdapter());
    }

    public static void main(String[] args) {
        Frame frame = new VerifierTest();
        frame.setSize(400, 200);
        frame.setVisible(true);
        // frame.pack();
    }

    class MyWAdapter extends WindowAdapter {

        public void windowClosing(WindowEvent event) {
            System.exit(0);
        }
    }

    class PassVerifier extends InputVerifier {

        public boolean verify(JComponent input) {
            JTextField tf = (JTextField) input;
            String pass = tf.getText();
            if (pass.equals("Manish"))
                return true;
            else {
                final String message = "illegal value: " + tf.getText();
                        JOptionPane.showMessageDialog(null, message,
                                "Illegal Value", JOptionPane.ERROR_MESSAGE);

                return false;
            }
        }
    }
}
于 2012-09-19T20:33:25.737 に答える
1

まず、verify()でダイアログを開くInputVerifierのすべての実装が無効です。彼らは彼らの契約に違反しました、APIドキュメント:

この方法には副作用はありません。

「すべき」は本当に「してはいけない」という意味です。副作用の正しい場所はshouldYieldFocusです。

2番目:副作用(メッセージダイアログを表示)をshouldYieldFocusに正しく移動しても機能しません...バグ(機能リクエストと呼ばれます;-)のため、10年以上前のトップ10にありますRFE

ハックアラウンドバグであるため、@ dareurdremのmouseListenerは、実行可能なハックで得られるものと同じくらい優れています:-)

アップデート

バグをハックするためのさまざまなオプションを少し試した後、別のハックがあります-すべてのハックと同じくらい壊れやすいです(LAFトグルに耐えられないため、動的な切り替えが必要な場合は再インストールする必要があります)

マウスの動作をハッキングするための基本的なアプローチは、UIによってインストールされたリスナーにフックすることです。

  • オリジナルを探す
  • ほとんどのイベントを元のイベントに直接委任するカスタムリスナーを実装する
  • 押されたイベントの場合、最初にフォーカスを要求します。元のデリゲートに譲られた場合、何もしない場合

フォーカスイベントは非同期である可能性があるため、最後の箇条書きは少し複雑です。そのため、フォーカスされているかどうかのチェックを呼び出す必要があります。次に、呼び出しには、誰も反対しなかった場合に備えてリリースを送信する必要があります。

もう1つの癖は、rootPaneの押されたアクション(defaultButtonの場合)です。これは、無条件にdoClickを呼び出すことにより、inputVerifiersを尊重せずに実行されます。これは、mouseListenerにフックするのと同じパターンに従って、アクションにフックすることでハッキングできます。

  • rootPaneの押されたアクションを見つける
  • 拒否する可能性のあるinputVerifierをチェックするカスタムアクションを実装します。そうでない場合は元のアクションに委任し、それ以外の場合は何もしません。

これらの線に沿って変更された例:

public class VerifierTest implements Runnable {

    private static final long serialVersionUID = 1L;

    @Override
    public void run() {
        InteractiveTestCase.setLAF("Win");
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(400, 200);

        JTextField tf = new JTextField("TextField1");
        tf.setInputVerifier(new PassVerifier());
        frame.add(tf, BorderLayout.NORTH);

        final JButton b = new JButton("Button");
        frame.add(b);
        b.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
               System.out.println("Button clicked");
            }
        });
        // hook into the mouse listener
        replaceBasicButtonListener(b);
        frame.add(new JTextField("not validating, something else to focus"),
                BorderLayout.SOUTH);
        frame.getRootPane().setDefaultButton(b);
        // hook into the default button action
        Action pressDefault = frame.getRootPane().getActionMap().get("press");
        frame.getRootPane().getActionMap().put("press", new DefaultButtonAction(pressDefault));
        frame.setVisible(true);
    }

    protected void replaceBasicButtonListener(AbstractButton b) {
        final BasicButtonListener original = getButtonListener(b);
        if (original == null) return;
        Hacker l = new Hacker(original);
        b.removeMouseListener(original);
        b.addMouseListener(l);
    }

    public static class Hacker implements MouseListener {
        private BasicButtonListener original;

        /**
         * @param original the listener to delegate to.
         */
        public Hacker(BasicButtonListener original) {
            this.original = original;
        }

        /**
         * Hook into the mousePressed: first request focus and
         * check its success before handling it.
         */
        @Override
        public void mousePressed(final MouseEvent e) {
            if (SwingUtilities.isLeftMouseButton(e)) {
                if(e.getComponent().contains(e.getX(), e.getY())) {
                    // check if we can get the focus
                    e.getComponent().requestFocus();
                    invokeHandleEvent(e);
                    return;
                }
            }
            original.mousePressed(e);
        }

        /**
         * Handle the pressed only if we are focusOwner.
         */
        protected void handlePressed(final MouseEvent e) {
            if (!e.getComponent().hasFocus())  {
                // something vetoed the focus transfer
                // do nothing
                return;
            } else {
                original.mousePressed(e);
                // need a fake released now: the one from the
                // original cycle might never has reached us
                MouseEvent released = new MouseEvent(e.getComponent(), MouseEvent.MOUSE_RELEASED,
                        e.getWhen(), e.getModifiers(), 
                        e.getX(), e.getY(), e.getClickCount(), e.isPopupTrigger()
                        );
                original.mouseReleased(released);
            }
        }


        /**
         * focus requests might be handled
         * asynchronously. So wrap the check 
         * wrap the block into an invokeLater.
         */
        protected void invokeHandleEvent(final MouseEvent e) {
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    handlePressed(e);
                }
            });
        }

        @Override
        public void mouseClicked(MouseEvent e) {
            original.mouseClicked(e);
        }

        @Override
        public void mouseReleased(MouseEvent e) {
            original.mouseReleased(e);
        }

        @Override
        public void mouseEntered(MouseEvent e) {
            original.mouseEntered(e);
        }

        @Override
        public void mouseExited(MouseEvent e) {
            original.mouseExited(e);
        }
    }
    public static class DefaultButtonAction extends AbstractAction {

        private Action original;

        /**
         * @param original
         */
        public DefaultButtonAction(Action original) {
            this.original = original;
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            JRootPane root = (JRootPane) e.getSource();
            JButton owner = root.getDefaultButton();
            if (owner != null && owner.getVerifyInputWhenFocusTarget()) {
                Component c = KeyboardFocusManager
                        .getCurrentKeyboardFocusManager()
                         .getFocusOwner();
                if (c instanceof JComponent && ((JComponent) c).getInputVerifier() != null) {
                    if (!((JComponent) c).getInputVerifier().shouldYieldFocus((JComponent) c)) return;
                }


            }
            original.actionPerformed(e);
        }

    }
    /**
     * Returns the ButtonListener for the passed in Button, or null if one
     * could not be found.
     */
    private BasicButtonListener getButtonListener(AbstractButton b) {
        MouseMotionListener[] listeners = b.getMouseMotionListeners();

        if (listeners != null) {
            for (MouseMotionListener listener : listeners) {
                if (listener instanceof BasicButtonListener) {
                    return (BasicButtonListener) listener;
                }
            }
        }
        return null;
    }

    public static void main(String[] args) {
       SwingUtilities.invokeLater(new VerifierTest());
    }


    public static class PassVerifier extends InputVerifier {
        /**
         * Decide whether or not the input is valid without
         * side-effects.
         */
        @Override
        public boolean verify(JComponent input) {
            final JTextField tf = (JTextField) input;
            String pass = tf.getText();
            if (pass.equals("Manish"))
                return true;
            return false;
        }

        /**
         * Implemented to ask the user what to do if the input isn't valid.
         * Note: not necessarily the best usability, it's mainly to
         * demonstrate the different effects on not/agreeing with
         * yielding focus transfer.
         */
        @Override
        public boolean shouldYieldFocus(final JComponent input) {
            boolean valid = super.shouldYieldFocus(input);
            if (!valid) {
                String message = "illegal value: " + ((JTextField) input).getText();
                int goAnyWay = JOptionPane.showConfirmDialog(input, "invalid value: " +
                        message + " - go ahead anyway?");
                valid = goAnyWay == JOptionPane.OK_OPTION;
            }
            return valid;
        }
    }
}
于 2012-09-22T10:10:13.287 に答える
0

実際の本当の問題は、フォーカスシステムとawtリスナーがどのように相互作用するかです。Javaで宣言されているバグがいくつかあり、開発者は誰が責任を負っているのかを行ったり来たりしています。マウスリスナーは:processMouseEventを実行し、そのロジック内で、現在のFocusOwnerはFocusを生成するように求められます。失敗します。ただし、イベントの半分はすでに処理されているため、ボタンは武装し、フォーカスはフィールドに残ります。

私はついに開発者のコ​​メントを1つ見ました。フィールドがフォーカスを失うことが許可されていない場合は、リスナーを先に進めないでください。

例:100未満の値のみを許可するように編集してJTextfieldを定義します。フォーカスを失うとメッセージがポップアップ表示されます。ベースのJButtonクラスのprocessMouseEvent(MouseEvent e)を次のコードでオーバーライドしました。

protected void processMouseEvent(MouseEvent e) {
    if ( e.getComponent() != null && e.getComponent().isEnabled() ) { //should not be processing mouse events if it's disabled.
            if (e.getID() == MouseEvent.MOUSE_RELEASED && e.getClickCount() == 1) {
                // The mouse button is being released as per normal, and it's the first click. Process it as per normal.
                super.processMouseEvent(e);

                // If the release occured within the bounds of this component, we want to simulate a click as well
                if (this.contains(e.getX(), e.getY())) {
                    super.processMouseEvent(new MouseEvent(e.getComponent(),
                                                            MouseEvent.MOUSE_CLICKED,
                                                            e.getWhen(),
                                                            e.getModifiers(),
                                                            e.getX(),
                                                            e.getY(),
                                                            e.getClickCount(),
                                                            e.isPopupTrigger(),
                                                            e.getButton()));
                }
            }
            else if (e.getID() == MouseEvent.MOUSE_CLICKED && e.getClickCount() == 1) {
                // Normal clicks are ignored to prevent duplicate events from normal, non-moved events
            }
            else if (e.getID() == MouseEvent.MOUSE_PRESSED && e.getComponent() != null && (e.getComponent().isFocusOwner() || e.getComponent().requestFocusInWindow())) {// if already focus owner process mouse event
                super.processMouseEvent(e); 
            }
            else {
                // Otherwise, just process as per normal.
                if (e.getID() != MouseEvent.MOUSE_PRESSED) {
                    super.processMouseEvent(e); 
                }
            }
        }
}

この論理の根底には、単純な質問があります。ボタン:あなたはすでに所有者に焦点を合わせていますか?そうでない場合:can you(Button)おそらくGAINフォーカス(覚えておいてください-shouldYieldFocus()はrequestFocusInWindow()呼び出し内の現在のフォーカスホルダーで呼び出され、有効でない場合は常にfalseを返します)

これには、エラーダイアログがポップアップするという副作用もあります。

このロジックは、フォーカスシステムがイベントの完了を停止している間、JavaライブラリのprocessMouseEventロジックがイベントの半分を処理するのを停止します。

明らかに、クリックでアクションを実行するすべての異なるJComponentにこのタイプのロジックが必要になります。

于 2013-04-10T13:43:31.613 に答える