Java 当从单元格编辑器组件显示对话框时,JTable会失去焦点

Java 当从单元格编辑器组件显示对话框时,JTable会失去焦点,java,swing,jtable,jdialog,tablecelleditor,Java,Swing,Jtable,Jdialog,Tablecelleditor,在我的主应用程序中,当单元格编辑器组件显示对话框时,JTable会失去焦点 下面是我为您制作的一个简单SSCCE,让您看到问题所在 做这些简单的实验: 在第一个表格列中按F2开始编辑。然后将“列内容”更改为数字2,然后按ENTER键。表格将失去焦点,窗体中的第一个字段将失去焦点 在第一个表格列中按F2开始编辑。然后将“列内容”更改为数字2,然后按TAB键。表格将失去焦点,窗体中的第一个字段将失去焦点 表单中的第一个字段也是SearchField组件。因为它不在JTable中,所以当您将其内容

在我的主应用程序中,当单元格编辑器组件显示对话框时,JTable会失去焦点

下面是我为您制作的一个简单SSCCE,让您看到问题所在

做这些简单的实验:

  • 在第一个表格列中按F2开始编辑。然后将“列内容”更改为数字2,然后按ENTER键。表格将失去焦点,窗体中的第一个字段将失去焦点
  • 在第一个表格列中按F2开始编辑。然后将“列内容”更改为数字2,然后按TAB键。表格将失去焦点,窗体中的第一个字段将失去焦点
表单中的第一个字段也是SearchField组件。因为它不在JTable中,所以当您将其内容更改为数字2并提交编辑(使用ENTER或TAB)时,它的行为正常

那么,你认为这真的解决了问题还是有任何缺陷

马科斯

更新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建议后的另一种解决方案:

对于最后一个解决方案的意见和建议,我们仍将不胜感激。这是最终的最佳解决方案吗


Marcos

使用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);
    }
}

正如我已经评论过的:在编辑器中更改表的状态有点可疑,特别是当它与焦点相关时,即使在最好的情况下焦点也是脆弱的。所以我会竭尽全力避免它

这种错误行为感觉类似于错误实现的InputVerifier,它的verify和shouldYieldFocus都有副作用(比如抓取焦点),这是正确的:在这种情况下,focusManager会感到困惑,它会“忘记”之前最后一个自然的focusOwner

补救办法可能是让经理先完成自己的工作,并且只在完成后才显示消息。在您的示例代码中,可以通过包装到invokeLater中来实现:

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

        }
    });
}

在我的实际应用程序中,搜索字段只是在找不到实体时将值还原为null。因此,我并不是真的试图验证输入,因为不允许用户进入下一个组件(或表格单元格)。此外,对话框来自搜索组件,而不是表格单元格编辑器,这对我来说非常重要。这意味着我的应用程序中有很多代码需要更改。恐怕我不能用你的解决办法。我认为这可能是另一种不用在stopCellEditing方法中进行验证的方法(尽管很复杂)。我不知道解决方案(并且不得不承认我没有真正研究这个问题,现在已经太迟了:-),但不要在编辑器中更改表的状态(就像您请求将焦点放回它一样):这会带来比解决问题更多的问题。@kleopatra现在,改变表的状态似乎是唯一能让它工作的事情。我甚至可以将选择调用限制在显示对话框的情况下,从而最小化问题。我在
表.requestFocusInWindow
中看到,永久焦点所有者是
null
,因此我建议焦点管理器下一步必须聚焦哪个组件,否则它将选择它喜欢的,在本例中是表单中的第一个字段。无论如何,这个问题似乎很棘手。如果您有其他解决方案,我将非常感谢您的回复。谢谢。:)信不信由你,在我把问题贴在这里之前,我已经找到了这个确切的解决方案。我只是没有使用它,因为在显示对话框之前,我觉得在表中看到选择有点奇怪。我也没有把它贴在这里,以为人们不会试图找到更好的。但这也许是唯一合理的解决办法。我只是希望这个解决方案不会出现任何死角,而且它总是有效的。不过,我会更新我的帖子并给你学分。再次感谢您对我的帮助。好吧,您的基本问题仍然是您让编辑组件做的比编辑器应该做的更多:-)在另一端,您允许将不太有效的值提交给模型。这有点极端。。。站在你的立场上,我会尝试将通知负担从编辑组件中移开,首先对该需求进行仔细观察。关于提交给模型的无效值,在我的案例中这不是问题。模型中仅暂时接受无效值。模型只有在有效时才会发布到数据库。因此,在用户编辑时,可以使用暂时无效的模型。我真正的编辑器组件也必须在表之外工作。如果消息是出于它,我会复制代码传播,我强迫我的应用程序的其他部分为此付出代价。我仍然认为消息是编辑器的工作。如果消息不在其中,我认为它不应该直接耦合到组件中,而是应该是一个可重用的小型coin类,可以注入任何协作者;-)但是,维护遗留应用程序从来都不是件有趣的事……我甚至可以将搜索组件配置为在JTable中使用时不显示消息,但请注意,我们仍然存在与stopCellEditing方法中显示的消息对话框相同的问题(我不考虑在此处使用
SwingUtilities.invokeLater
)。唯一的解决方案是从stopCellEditing(@camickr solution)返回
false
,但由于他在帖子中提到的原因,我无法使用此解决方案。幸运的是,我的应用程序不是遗留的:)。
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);
    }
}
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);
    }
}
if (needsMessage()) {
    SwingUtilities.invokeLater(new Runnable() {
        public void run() {
            JOptionPane.showMessageDialog(null, "Not found: " +
                    newValue + ".", "Warning",
                    JOptionPane.WARNING_MESSAGE);

        }
    });
}