【问题标题】:Breaking out of a loop with key bindings使用键绑定打破循环
【发布时间】:2017-12-02 01:22:43
【问题描述】:

我正在图像查看器应用程序中构建一个方法,该方法将创建幻灯片 - 在经过一段时间后,它将依次转到下一个图像。我希望这发生在无穷大,除非用户按下“esc”键。键绑定似乎是正确的解决方案,但我无法使用它们。到目前为止,这是我的代码:

private void slideshow(){
    JOptionPane.showMessageDialog(null, "Press 'esc' to stop the slideshow", "Slideshow", JOptionPane.INFORMATION_MESSAGE, slideshow);

    // sets the action listener that the timer triggers
    ActionListener slideshowPerformer = new ActionListener() {
        public void actionPerformed( ActionEvent event )
        {
            //goes to the next image every x seconds
            nextFile();
        }
    };

    Timer slideshowTimer = new Timer(slideshowTime, slideshowPerformer);

    while(true){
        //label is a JLabel
        label.getInputMap().//the key binding code
    }
}

尽管我已经在使用 MouseListener 浏览图像,但我愿意接受其他解决方案。

【问题讨论】:

  • 与键绑定关联的Action 应该停止Timer
  • @Kevin FYI KeyListeners 很少是一个好的选择,通常会引入更多的问题然后他们解决。 Swing 重度依赖键绑定 API 来解决 KeyListener 遭受的问题
  • 您不应该使用 while 循环。对于动画,您使用 Swing Timer。当计时器触发时,您将转到下一个图像。当您希望动画停止时,您可以停止计时器。

标签: java swing while-loop key-bindings


【解决方案1】:

与大多数 UI 框架一样,Swing 是事件驱动的。 Swing 和大多数 UI 框架一样,也是单线程的,不是线程安全的。

这意味着三件事......

  1. 您不应在事件调度线程的上下文中执行长时间运行或阻塞操作(如无限循环)
  2. 您不应从 EDT 上下文之外更新 UI 的状态
  3. 当某种状态发生变化时,您应该使用某种观察者模式来采取行动。

由于您已经在使用 Swing Timer 并提到使用键绑定,因此您已经走在正确的轨道上,您缺少的链接是,与键绑定关联的 Action 应该停止Timer.

或者,如本例所示,Action 应通知其他相关方(即观察者)Action 已被触发,观察者应采取适当的行动。

import com.sun.glass.events.KeyEvent;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Test {

    public static void main(String[] args) {
        new Test();
    }

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public interface Engine {
        public void stop();
        public void start();
    }

    public class TestPane extends JPanel implements Engine {

        private Timer timer;
        private JLabel label = new JLabel("Waiting");

        public TestPane() {
            setLayout(new GridBagLayout());
            add(label);
            timer = new Timer(1000, new ActionListener() {
                private int counter = 0;
                @Override
                public void actionPerformed(ActionEvent e) {
                    counter++;
                    label.setText(Integer.toString(counter));
                }
            });
            InputMap inputMap = getInputMap(WHEN_IN_FOCUSED_WINDOW);
            ActionMap actionMap = getActionMap();

            inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "stop");
            actionMap.put("stop", new StopAction(this));

            start();
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(200, 200);
        }

        @Override
        public void stop() {
            timer.stop();
            label.setText("Stopped");
        }

        @Override
        public void start() {
            timer.start();
        }

    }

    public class StopAction extends AbstractAction {

        private Engine engine;

        public StopAction(Engine engine) {
            this.engine = engine;
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            engine.stop();
        }

    }

}

【讨论】:

  • 谢谢!我最终走向了一个稍微不同的方向,但这只有在你们的帮助下才有可能!我摆脱了 while 循环(呃!我以前使用过摇摆计时器,不知道为什么我认为我需要一个循环!),Actions 绝对是有用的。
  • @DuncanAdkins:你应该接受这个答案,因为它让你走上了正确的道路。
【解决方案2】:

感谢大家的帮助!我完全被while循环放屁了,忘记了如何在热的一分钟内使用Swing计时器。这最终成为我的最终工作代码:

    ActionListener stopAction = new ActionListener(){
        public void actionPerformed(ActionEvent e){
            slideshowTimer.stop();
        }
    };

    // listens for the esc key to stop the slideshow
    label.getRootPane().registerKeyboardAction(stopAction,
        KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0),
        JComponent.WHEN_IN_FOCUSED_WINDOW);

    slideshowTimer.start();

【讨论】:

    【解决方案3】:

    我认为更好的解决方案是将计时器从 0 开始,然后进入一个 while 循环并在每次迭代时将其递增一。然后用一个数字取模,每次取模等于0,转到下一帧。这将永远运行,直到动作侦听器正在侦听要按下的 esc 键,您将在其中中断循环。

    总而言之,我认为只要使用一些低层次的想法,它会让你的问题更容易解决。

    【讨论】:

    • 我看不出同时拥有Timer 和循环的意义——因为 Swing 是单线程的并且不是线程安全的,所以您只是添加了需要解决的其他问题,即已经解决了仅使用Timer 本身来触发运动。在 OP 的问题的情况下,只需让 Key Binding 触发一些停止 Timer 的操作并执行需要执行的任何其他操作将是最好的解决方案 - 恕我直言
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-04-01
    • 1970-01-01
    • 1970-01-01
    • 2019-08-24
    • 1970-01-01
    • 1970-01-01
    • 2013-08-03
    相关资源
    最近更新 更多