Ben
Ben

Reputation: 31

How do I make a loop start and end with a key press and release?

Here is my code... How can I make it work so that it runs the loop while the user is holding a button and stops when the user releases the button?

public void nextPrimeNum()
{
    x = false;
    int b = 2;
    ArrayList<Integer> next = new ArrayList<Integer>();   
    while(x)
    {
        next = factors(b);
        if(next.size()==2)
        {
            System.out.println(b);
        }
        b++;
    }
    System.out.println("End");
}
public void keyPressed(KeyEvent e)
{
    if(e.getKeyCode() == 401)
    {
        x = true;
    }
}
public void keyRealesed(KeyEvent e)
{
    if(e.getKeyCode() == 402)
    {
        x = false;
    }
}

Upvotes: 0

Views: 591

Answers (2)

Thomas Fritsch
Thomas Fritsch

Reputation: 10147

GUI and multi-thread programming is inherently difficult.
So, this is as simple as it could be, without violating best practices too much.

You need several things:

  • A separate Thread for printing primes:
    Its run method loops for ever, but pauses when the Space key is not pressed.
    (see Defining and Starting a Thread for more info)
  • A KeyListener which will be called from AWT's event dispatch thread:
    The event handling methods are designed to finish fast, so that other events (like moving, resizing and closing the frame) still are handled fast.
    (see How to Write a Key Listener and The Event Dispatch Thread for more info)
  • A visible GUI component (JFrame) for adding the KeyListener
  • Some synchronization between the 2 threads (via synchronized, notify and wait) so that the prime-printing starts/continues on keyPressed and suspends on keyReleased
    (see Guarded Blocks for more info)
  • Initialize and start the whole GUI by invoking initGUI.
    (see Initial Threads for more info)

import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;

public class Main implements Runnable, KeyListener {

    public static void main(String[] args) {
        SwingUtilities.invokeLater(Main::initGUI);
    }

    private static void initGUI() {
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(new JLabel("Press SPACE key for printing primes"));
        frame.pack();
        frame.setLocationRelativeTo(null); // center on screen
        frame.setVisible(true);

        Main main = new Main();
        frame.addKeyListener(main);
        Thread thread = new Thread(main);
        thread.start();
    }

    private boolean spaceKeyPressed;

    private boolean isPrime(int n) {
        for (int i = 2; i < n; i++) {
            if (n % i == 0)
                return false;
        }
        return true;
    }

    @Override
    public void run() {
        for (int n = 2; /**/; n++) {
            while (!spaceKeyPressed) {
                synchronized (this) {
                    try {
                        wait(); // waits until notify()
                    } catch (InterruptedException e) {
                        // do nothing
                    }
                }
            }
            if (isPrime(n)) {
                System.out.println(n);
            }
        }
    }

    @Override
    public void keyTyped(KeyEvent e) {
        // do nothing
    }

    @Override
    public synchronized void keyPressed(KeyEvent e) {
        if (e.getKeyCode() == KeyEvent.VK_SPACE) {
            spaceKeyPressed = true;
            notifyAll(); // cause wait() to finish
        }
    }

    @Override
    public synchronized void keyReleased(KeyEvent e) {
        if (e.getKeyCode() == KeyEvent.VK_SPACE) {
            spaceKeyPressed = false;
            notifyAll(); // cause wait() to finish
        }
    }
}

Upvotes: 1

MadProgrammer
MadProgrammer

Reputation: 347314

So, the answer is - it's complicated. It covers broad topics such as concurrency (in general), GUI development, best practices with the specific API (Swing) which are better covered in more detail by reading through the various tutorials (and experimenting)

The example presents two ways to execute the "loop" (which is presented in the doInBackground method of the CalculateWorker class).

You can press and hold the JButton or press and hold the [kbd]Space[kbd] bar, both will cause the "main loop" to run, updating the JTextArea with the results...

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.KeyStroke;
import javax.swing.SwingWorker;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

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 class TestPane extends JPanel {

        private JTextArea ta;
        private CalculateWorker worker;

        public TestPane() {
            setLayout(new BorderLayout());

            ta = new JTextArea(20, 20);
            ta.setEditable(false);
            add(new JScrollPane(ta));

            worker = new CalculateWorker(ta);

            JButton btn = new JButton("Press");
            btn.getModel().addChangeListener(new ChangeListener() {
                @Override
                public void stateChanged(ChangeEvent e) {
                    System.out.println("...isRunning = " + worker.isRunning());
                    if (!worker.isRunning()) {
                        return;
                    }
                    System.out.println("...isPressed = " + btn.getModel().isPressed());
                    System.out.println("...isPaused = " + worker.isPaused());
                    if (btn.getModel().isPressed()) {
                        worker.pause(false);
                    } else {
                        worker.pause(true);
                    }
                }
            });

            add(btn, BorderLayout.SOUTH);
            worker.execute();

            InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
            ActionMap am = getActionMap();

            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0, false), "Space.released");
            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0, true), "Space.pressed");

            am.put("Space.released", new CalculateAction(false, worker));
            am.put("Space.pressed", new CalculateAction(true, worker));
        }

        public class CalculateWorker extends SwingWorker<List<String>, String> {

            private AtomicBoolean run = new AtomicBoolean(true);
            private AtomicBoolean paused = new AtomicBoolean(false);

            private ReentrantLock pausedLocked = new ReentrantLock();
            private Condition pausedCondition = pausedLocked.newCondition();

            private JTextArea ta;

            public CalculateWorker(JTextArea ta) {
                this.ta = ta;
                pause(true);
            }

            public void stop() {
                run.set(false);
                pausedLocked.lock();
                pausedCondition.signalAll();
                pausedLocked.unlock();
            }

            public void pause(boolean pause) {
                paused.set(pause);
                pausedLocked.lock();
                pausedCondition.signalAll();
                pausedLocked.unlock();
            }

            public boolean isPaused() {
                return paused.get();
            }

            public boolean isRunning() {
                return run.get();
            }

            @Override
            protected List<String> doInBackground() throws Exception {
                List<String> values = new ArrayList<>(256);
                long value = 0;
                System.out.println("!! Start running");
                while (run.get()) {
                    while (paused.get()) {
                        System.out.println("!! I'm paused");
                        pausedLocked.lock();
                        try {
                            pausedCondition.await();
                        } finally {
                            pausedLocked.unlock();
                        }
                    }
                    System.out.println("!! Start loop");
                    while (!paused.get() && run.get()) {
                        value++;
                        values.add(Long.toString(value));
                        publish(Long.toString(value));
                        Thread.sleep(5);
                    }
                    System.out.println("!! Main loop over");
                }
                System.out.println("!! Run is over");
                return values;
            }

            @Override
            protected void process(List<String> chunks) {
                for (String value : chunks) {
                    ta.append(value);
                    ta.append("\n");
                }
                ta.setCaretPosition(ta.getText().length());
            }

        }

        public class CalculateAction extends AbstractAction {

            private boolean start;
            private CalculateWorker worker;

            public CalculateAction(boolean start, CalculateWorker worker) {
                putValue(NAME, "Calculate");
                this.start = start;
                this.worker = worker;
            }

            @Override
            public void actionPerformed(ActionEvent e) {
                worker.pause(start);
            }

        }

    }

}

Is there a simpler solution?

Of course, I always go for the most difficult, hard to understand solutions first (sarcasm)

While it "might" be possible to reduce the complexity, the example presents a number of "best practice" concepts which you would do well to learn and understand.

The solution could also be done differently depending on the API used, so, it's the "simplest" solution for the specific API choice.

I wanted to do it from the console!

Java can't do that - it's console support is rudimentary at best and doesn't support a concept of "key pressed/released" actions (since it's running in a single thread, it would be impossible for it to do otherwise).

There "are" solutions you might try, but they would require a third party library linked to native binaries to implement, which would (possibly) reduce the number of platforms it would run on

Upvotes: 0

Related Questions