Java Swing: 焦点问题

6
我正在为我的游戏制作一个关卡编辑器。我有一个属性面板,可以修改所选对象的属性。我还有一个“保存”按钮,用于编写关卡xml。
当编辑器组件失去焦点或按下Enter键时,就会提交字段编辑(* )。这个功能非常棒,但唯一的问题是,当我执行以下操作序列时:
1.编辑字段 2.按下保存按钮
因为实际发生的情况是:
1.我编辑了字段 2.我按下了保存按钮 3.级别被保存 4.字段失去了焦点 5.编辑提交
如您所见,这是错误的顺序。当然,我希望该字段失去焦点,从而导致提交和然后保存级别。
是否有技巧、方法或解决方法可以让字段先失去焦点,然后执行保存按钮的操作监听器?
提前感谢。
(*提交=对对象属性进行的编辑也会提交给编辑器)
编辑:对于该字段,我使用了一个具有focusLost的FocusAdapter:
FocusAdapter focusAdapter = new FocusAdapter()
{

    @Override
    public void focusLost(FocusEvent e)
    {
        compProperties.setProperty(i, getColor());
        record(); // For undo-redo mechanism
    }
};

针对按钮,需要一个简单的ActionListeneractionPerformed

btnSave.addActionListener(new java.awt.event.ActionListener() {
     public void actionPerformed(java.awt.event.ActionEvent evt) {
         // Save the level
     }
});

1
不清楚你的代码如何工作,请在此处发布相关代码,因为还有其他选项,可以使用DocumentListener,也可以使用AncestorListener,或者只需将您的FocusHell包装到invokeLater中,并使用myTextField.setText(myTextField.getText); - mKorbel
@mKorbel:我尝试将保存过程包装到“invokeLater”中,但仍然是错误的顺序。 - Martijn Courteaux
1
请参阅此问答 - trashgod
1
ActionListenerFocusListener之间是否存在“无限循环”的并发问题,如果禁用Focus会发生什么?1)为ActionFocus创建单独的void,2)通过使用Boolean进行测试,如果有Focus开始,或者从JButton开始Action,3)通过尝试故障设置FocusAction事件的正确顺序,其中一个必须开始“firing events”并首先结束,不知道compProperties.setProperty(i, getColor());record();以及与“// Save the level”连接的内容是什么,我确定这是你的“A-Bomb”。 - mKorbel
2个回答

3

嗯...无法重现:在下面的代码片段中,无论我是单击按钮还是使用助记符,都会在actionPerfomed之前始终通知lost:

    final JTextField field = new JTextField("some text to change");
    FocusAdapter focus = new FocusAdapter() {

        @Override
        public void focusLost(FocusEvent e) {
            LOG.info("lost: " + field.getText());
        }

    };
    field.addFocusListener(focus);

    Action save = new AbstractAction("save") {

        @Override
        public void actionPerformed(ActionEvent e) {
            LOG.info("save: " + field.getText());
        }
    };
    save.putValue(Action.MNEMONIC_KEY, KeyEvent.VK_S);
    JButton button = new JButton(save);
    JComponent box = Box.createHorizontalBox();
    box.add(field);
    box.add(button);

另一方面,焦点是一个不可靠的属性,其顺序可能取决于系统(我的是win vista)。请检查代码片段在您的系统上的行为。

  • If you see the same sequence as I do, the problem is somewhere else
  • if you get the save before the lost, try to wrap the the save action into invokeLater (which puts it at the end of the EventQueue, so it's executed after all pending events)

    Action save = new AbstractAction("save") {
    
        @Override
        public void actionPerformed(ActionEvent e) {
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    LOG.info("save: " + field.getText());
                }
            });
        }
    };
    

问题出在其他地方,由OP添加了I/O流。1)将Focus和ActionListener从javax.swing.Action中移除,2)将I/O流重定向到Runnable#thread中,3)完成后再添加Focus和ActionListener,4)如果我将其分为两个invokeLater()步骤进行延迟,那么同样的方法也可以奏效,5)通过使用javax.swing.Timer和javax.swing.Action延迟ActionListener的事件,6)看起来Action与ActionListener有点不同,7)所有关于将焦点从JButton移动到JTexfield的操作都被包装在invokeLater() +1中。 - mKorbel

0
通常,将您的保存代码包装到SwingUtilities.invokeLater()中应该可以解决问题。正如您已经提到的,这不起作用?尝试这个:
private boolean editFocus = false;
FocusAdapter focusAdapter = new FocusAdapter()
{
   @Override
   public void focusGained(FocusEvent e){
        editFocus = true;
   }
   @Override
   public void focusLost(FocusEvent e){
       compProperties.setProperty(i, getColor());
       record(); // For undo-redo mechanism
       editFocus = false;
       if (saveRequested){
           save();               
       }
   }
};

而针对您的按钮:

private boolean saveRequested = false;

btnSave.addActionListener(new java.awt.event.ActionListener() {
    public void actionPerformed(java.awt.event.ActionEvent evt) {
         if (editFocus){
             saveRequested = true;
             return;
         } else {
             save();
         }
    }
});

然后是你的保存方法:

private void save(){
    // do your saving work
    saveRequested = false;
}

只有在按钮动作之后调用focusLost时,此代码才有效。如果突然顺序正确,则此代码将调用save()两次。

但是,再次使用您原始的方法包装您的save()代码应该可以工作,因为保存代码将在处理所有事件后执行。也就是在处理按钮单击和focusLost事件之后。因为focusLost代码立即执行(它没有被包装在invokeLater()中),所以focusLost代码应该始终在保存代码之前执行。这并不意味着事件顺序将正确!但与事件相关联的代码将按正确顺序执行。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接