2

メイン アプリケーションで、セル エディタ コンポーネントからダイアログが表示されると、JTable がフォーカスを失います。

以下は、問題を確認するために作成した簡単な SSCCE です。

これらの単純な実験を行います:

  • テーブルの最初の列で F2 を押して、編集を開始します。次に、列の内容を数字の 2 に変更し、ENTER キーを押します。テーブルはフォーカスを失い、フォームの最初のフィールドはフォーカスを取得します。
  • テーブルの最初の列で F2 を押して、編集を開始します。次に、列の内容を数字の 2 に変更し、TAB キーを押します。テーブルはフォーカスを失い、フォームの最初のフィールドはフォーカスを取得します。

フォームの最初のフィールドも SearchField コンポーネントです。これは JTable にないため、内容を数値 2 に変更して編集をコミットすると (ENTER または TAB を使用)、適切に動作します。

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.Objects;

import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.DefaultCellEditor;
import javax.swing.JFormattedTextField;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.table.AbstractTableModel;
import javax.swing.text.DefaultFormatterFactory;
import javax.swing.text.NumberFormatter;

public class SSCCE extends JPanel
{
    private SSCCE()
    {
        setLayout(new BorderLayout());
        setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));

        JPanel pnlFields = new JPanel();
        pnlFields.setLayout(new BoxLayout(pnlFields, BoxLayout.PAGE_AXIS));
        pnlFields.setBorder(BorderFactory.createEmptyBorder(0, 0, 10, 0));

        SearchField field1 = new SearchField();
        configureField(field1);
        pnlFields.add(field1);

        pnlFields.add(Box.createRigidArea(new Dimension(0, 3)));

        JTextField field2 = new JTextField();
        configureField(field2);
        pnlFields.add(field2);

        add(pnlFields, BorderLayout.PAGE_START);
        add(new JScrollPane(createTable()), BorderLayout.CENTER);
    }

    private void configureField(JTextField field)
    {
        field.setPreferredSize(new Dimension(150, field.getPreferredSize().height));
        field.setMaximumSize(field.getPreferredSize());
        field.setAlignmentX(LEFT_ALIGNMENT);
    }

    private JTable createTable()
    {
        JTable table = new JTable(new CustomTableModel());

        table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        table.setCellSelectionEnabled(true);
        table.getTableHeader().setReorderingAllowed(false);
        table.setPreferredScrollableViewportSize(new Dimension(500, 170));

        table.setDefaultEditor(Integer.class, new SearchFieldCellEditor(new SearchField()));

        return table;
    }

    private static void createAndShowGUI()
    {
        JFrame frame = new JFrame("SSCCE (JTable Loses Focus)");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(new SSCCE());
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    public static void main(String[] args)
    {
        SwingUtilities.invokeLater(
            new Runnable()
            {
                @Override
                public void run()
                {
                    createAndShowGUI();
                }
            }
        );
    }
}

class CustomTableModel extends AbstractTableModel
{
    private String[] columnNames = {"Column1 (Search Field)", "Column 2"};
    private Class<?>[] columnTypes = {Integer.class, String.class};
    private Object[][] data = {{1, ""}, {3, ""}, {4, ""}, {5, ""}, {6, ""}};

    @Override
    public int getColumnCount()
    {
        return columnNames.length;
    }

    @Override
    public int getRowCount()
    {
        return data.length;
    }

    @Override
    public String getColumnName(int col)
    {
        return columnNames[col];
    }

    @Override
    public Object getValueAt(int row, int col)
    {
        return data[row][col];
    }

    @Override
    public Class<?> getColumnClass(int c)
    {
        return columnTypes[c];
    }

    @Override
    public boolean isCellEditable(int rowIndex, int columnIndex)
    {
        return true;
    }

    @Override
    public void setValueAt(Object value, int row, int col)
    {
        data[row][col] = value;
        fireTableCellUpdated(row, col);
    }
}

class SearchFieldCellEditor extends DefaultCellEditor
{
    SearchFieldCellEditor(final SearchField searchField)
    {
        super(searchField);
        searchField.removeActionListener(delegate);
        delegate = new EditorDelegate()
        {
            @Override
            public void setValue(Object value)
            {
                searchField.setValue(value);
            }

            @Override
            public Object getCellEditorValue()
            {
                return searchField.getValue();
            }
        };
        searchField.addActionListener(delegate);
    }

    @Override
    public boolean stopCellEditing()
    {
        try
        {
            ((SearchField) getComponent()).commitEdit();
        }
        catch (ParseException ex)
        {
            ex.printStackTrace();
        }
        return super.stopCellEditing();
    }
}

class SearchField extends JFormattedTextField implements PropertyChangeListener
{
    private Object _oldValue;

    SearchField()
    {
        setupFormatter();
        addPropertyChangeListener("value", this);
    }

    private void setupFormatter()
    {
        NumberFormat integerFormat = NumberFormat.getIntegerInstance();
        integerFormat.setGroupingUsed(false);

        NumberFormatter integerFormatter =
            new NumberFormatter(integerFormat)
            {
                @Override
                public Object stringToValue(String text) throws ParseException
                {
                    return text.isEmpty() ? null : super.stringToValue(text);
                }
            };
        integerFormatter.setValueClass(Integer.class);
        integerFormatter.setMinimum(Integer.MIN_VALUE);
        integerFormatter.setMaximum(Integer.MAX_VALUE);

        setFormatterFactory(new DefaultFormatterFactory(integerFormatter));
    }

    @Override
    public void propertyChange(PropertyChangeEvent evt)
    {
        Object newValue = evt.getNewValue();
        if (!Objects.equals(newValue, _oldValue))
        {
            _oldValue = newValue;
            // Suppose that a value of 2 means that the data wasn't found.
            // So we display a message to the user.
            if (new Integer(2).equals(newValue))
            {
                JOptionPane.showMessageDialog(
                    null, "Not found: " + newValue + ".", "Warning",
                    JOptionPane.WARNING_MESSAGE);
            }
        }
    }
}

それで、この問題を解決する方法はありますか?この問題の解決策は私にとって非常に重要です。

ありがとうございました。

マルコス

*更新*

私は解決策を見つけたと思いますが、それが本当に信頼できる解決策であるかどうか、あなたの意見を聞きたいです.

stopCellEditingメソッドを次のように変更し、SSCCE を再度テストします。

@Override
public boolean stopCellEditing()
{
    SearchField searchField = (SearchField) getComponent();

    try
    {
        searchField.commitEdit();
    }
    catch (ParseException ex)
    {
        ex.printStackTrace();
    }

    Component table = searchField.getParent();
    table.requestFocusInWindow();

    return super.stopCellEditing();
}

それで、これは本当に問題を解決すると思いますか、それとも何か欠陥があると思いますか?

マルコス

更新 2

私は小さな欠陥を見つけました。これらの変更により修正されます。

class SearchFieldCellEditor extends DefaultCellEditor
{
    SearchFieldCellEditor(final SearchField searchField)
    {
        super(searchField);
        searchField.removeActionListener(delegate);
        delegate = new EditorDelegate()
        {
            @Override
            public void setValue(Object value)
            {
                searchField.setValue(value);
            }

            @Override
            public Object getCellEditorValue()
            {
                return searchField.getValue();
            }
        };
        searchField.addActionListener(delegate);
    }

    @Override
    public Component getTableCellEditorComponent(
        JTable table, Object value, boolean isSelected, int row, int column)
    {
        SearchField searchField = (SearchField) getComponent();
        searchField.setPreparingForEdit(true);
        try
        {
            return super.getTableCellEditorComponent(
                table, value, isSelected, row, column);
        }
        finally
        {
            searchField.setPreparingForEdit(false);
        }
    }

    @Override
    public boolean stopCellEditing()
    {
        SearchField searchField = (SearchField) getComponent();

        try
        {
            searchField.commitEdit();
        }
        catch (ParseException ex)
        {
            ex.printStackTrace();
        }

        Component table = searchField.getParent();
        table.requestFocusInWindow();

        return super.stopCellEditing();
    }
}

class SearchField extends JFormattedTextField implements PropertyChangeListener
{
    private boolean _isPreparingForEdit;
    private Object _oldValue;

    SearchField()
    {
        setupFormatter();
        addPropertyChangeListener("value", this);
    }

    void setPreparingForEdit(boolean isPreparingForEdit)
    {
        _isPreparingForEdit = isPreparingForEdit;
    }

    private void setupFormatter()
    {
        NumberFormat integerFormat = NumberFormat.getIntegerInstance();
        integerFormat.setGroupingUsed(false);

        NumberFormatter integerFormatter =
            new NumberFormatter(integerFormat)
            {
                @Override
                public Object stringToValue(String text) throws ParseException
                {
                    return text.isEmpty() ? null : super.stringToValue(text);
                }
            };
        integerFormatter.setValueClass(Integer.class);
        integerFormatter.setMinimum(Integer.MIN_VALUE);
        integerFormatter.setMaximum(Integer.MAX_VALUE);

        setFormatterFactory(new DefaultFormatterFactory(integerFormatter));
    }

    @Override
    public void propertyChange(PropertyChangeEvent evt)
    {
        final Object newValue = evt.getNewValue();
        if (!Objects.equals(newValue, _oldValue))
        {
            _oldValue = newValue;
            // Suppose that a value of 2 means that the data wasn't found.
            // So we display a message to the user.
            if (new Integer(2).equals(newValue) && !_isPreparingForEdit)
            {
                JOptionPane.showMessageDialog(null, "Not found: " + newValue + ".", "Warning",
                    JOptionPane.WARNING_MESSAGE);
            }
        }
    }
}

他にも欠陥は見つかりましたか?レビューをいただきたいです。

マルコス

更新 3

kleopatraによる提案後の別の解決策:

class SearchFieldCellEditor extends DefaultCellEditor
{
    SearchFieldCellEditor(final SearchField searchField)
    {
        super(searchField);
        searchField.setShowMessageAsynchronously(true);
        searchField.removeActionListener(delegate);
        delegate = new EditorDelegate()
        {
            @Override
            public void setValue(Object value)
            {
                searchField.setValue(value);
            }

            @Override
            public Object getCellEditorValue()
            {
                return searchField.getValue();
            }
        };
        searchField.addActionListener(delegate);
    }

    @Override
    public Component getTableCellEditorComponent(
        JTable table, Object value, boolean isSelected, int row, int column)
    {
        SearchField searchField = (SearchField) getComponent();
        searchField.setPreparingForEdit(true);
        try
        {
            return super.getTableCellEditorComponent(
                table, value, isSelected, row, column);
        }
        finally
        {
            searchField.setPreparingForEdit(false);
        }
    }

    @Override
    public boolean stopCellEditing()
    {
        SearchField searchField = (SearchField) getComponent();

        try
        {
            searchField.commitEdit();
        }
        catch (ParseException ex)
        {
            ex.printStackTrace();
        }

        return super.stopCellEditing();
    }
}

class SearchField extends JFormattedTextField implements PropertyChangeListener
{
    private boolean _showMessageAsynchronously;
    private boolean _isPreparingForEdit;
    private Object _oldValue;

    SearchField()
    {
        setupFormatter();
        addPropertyChangeListener("value", this);
    }

    public boolean isShowMessageAsynchronously()
    {
        return _showMessageAsynchronously;
    }

    public void setShowMessageAsynchronously(boolean showMessageAsynchronously)
    {
        _showMessageAsynchronously = showMessageAsynchronously;
    }

    void setPreparingForEdit(boolean isPreparingForEdit)
    {
        _isPreparingForEdit = isPreparingForEdit;
    }

    private void setupFormatter()
    {
        NumberFormat integerFormat = NumberFormat.getIntegerInstance();
        integerFormat.setGroupingUsed(false);

        NumberFormatter integerFormatter =
            new NumberFormatter(integerFormat)
            {
                @Override
                public Object stringToValue(String text) throws ParseException
                {
                    return text.isEmpty() ? null : super.stringToValue(text);
                }
            };
        integerFormatter.setValueClass(Integer.class);
        integerFormatter.setMinimum(Integer.MIN_VALUE);
        integerFormatter.setMaximum(Integer.MAX_VALUE);

        setFormatterFactory(new DefaultFormatterFactory(integerFormatter));
    }

    @Override
    public void propertyChange(PropertyChangeEvent evt)
    {
        final Object newValue = evt.getNewValue();
        if (!Objects.equals(newValue, _oldValue))
        {
            _oldValue = newValue;
            // Suppose that a value of 2 means that the data wasn't found.
            // So we display a message to the user.
            if (new Integer(2).equals(newValue) && !_isPreparingForEdit)
            {
                if (_showMessageAsynchronously)
                {
                    SwingUtilities.invokeLater(
                        new Runnable()
                        {
                            @Override
                            public void run()
                            {
                                showMessage(newValue);
                            }
                        }
                    );
                }
                else
                {
                    showMessage(newValue);
                }
            }
        }
    }

    private void showMessage(Object value)
    {
        JOptionPane.showMessageDialog(null, "Not found: " + value + ".",
            "Warning", JOptionPane.WARNING_MESSAGE);
    }
}

この最後の解決策に関するコメントや提案は引き続き歓迎されます。これは究極かつ最適なソリューションですか?

マルコス

4

2 に答える 2

1

編集は stopCellEditing() メソッドで行います。

この例では、5 文字の文字列を入力する必要があります。

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.text.*;
import javax.swing.event.*;
import javax.swing.border.*;
import javax.swing.table.*;

public class TableEdit extends JFrame
{
    TableEdit()
    {
        JTable table = new JTable(5,5);
        table.setPreferredScrollableViewportSize(table.getPreferredSize());

        JScrollPane scrollpane = new JScrollPane(table);
        add(scrollpane);

        //  Use a custom editor

        TableCellEditor fce = new FiveCharacterEditor();
        table.setDefaultEditor(Object.class, fce);

        add(new JTextField(), BorderLayout.NORTH);
    }

    class FiveCharacterEditor extends DefaultCellEditor
    {
        FiveCharacterEditor()
        {
            super( new JTextField() );
        }

        public boolean stopCellEditing()
        {
            JTable table = (JTable)getComponent().getParent();

            try
            {
                System.out.println(getCellEditorValue().getClass());
                String editingValue = (String)getCellEditorValue();

                if(editingValue.length() != 5)
                {
                    JTextField textField = (JTextField)getComponent();
                    textField.setBorder(new LineBorder(Color.red));
                    textField.selectAll();
                    textField.requestFocusInWindow();

                    JOptionPane.showMessageDialog(
                        null,
                        "Please enter string with 5 letters.",
                        "Alert!",JOptionPane.ERROR_MESSAGE);
                    return false;
                }
            }
            catch(ClassCastException exception)
            {
                return false;
            }

            return super.stopCellEditing();
        }

        public Component getTableCellEditorComponent(
            JTable table, Object value, boolean isSelected, int row, int column)
        {
            Component c = super.getTableCellEditorComponent(
                table, value, isSelected, row, column);
            ((JComponent)c).setBorder(new LineBorder(Color.black));

            return c;
        }

    }

    public static void main(String [] args)
    {
        JFrame frame = new TableEdit();
        frame.setDefaultCloseOperation(EXIT_ON_CLOSE);
        frame.pack();
        frame.setLocationRelativeTo( null );
        frame.setVisible(true);
    }
}
于 2013-04-05T16:45:48.430 に答える
1

すでにコメントしたように、エディターでテーブルの状態を変更するのは少し怪しいです。特に、最高の状態でも脆いフォーカスに関連している場合はなおさらです。だから私はそれを避けるために多大な努力をします。

誤った動作は、間違って実装された InputVerifier に似ています。これは、verify と shouldYieldFocus に副作用 (フォーカスを取得するなど) があります。自然な last-focusOwner-before。

対処法としては、マネージャーに最初に仕事を任せて、それが終わったときにのみメッセージを表示することです。あなたのコード例では、invokeLater にラップすることで実現できます。

if (needsMessage()) {
    SwingUtilities.invokeLater(new Runnable() {
        public void run() {
            JOptionPane.showMessageDialog(null, "Not found: " +
                    newValue + ".", "Warning",
                    JOptionPane.WARNING_MESSAGE);

        }
    });
}
于 2013-04-06T09:43:36.350 に答える