Слушатель внутри цикла не позволит изменить внешний логический флаг

Следуя моему предыдущему сообщению здесь, я написал прослушиватель :

@Override
public void keyTyped(KeyEvent keyEvent) 
{
    PolygonFiller polyFiller = new PolygonFiller();
    char key = keyEvent.getKeyChar();

    final boolean runForever = true;

    switch(key)
    {
        /**
         *  Fill the polygons 
         */
        case FILL_POLYGON:      
        {
            if (greenLightForFilling == true)
            {

                while (runForever)
                {
                    fillPolygon(polyFiller);
                    KeyListener listener = new KeyListener()
                    {
                        public void keyPressed(KeyEvent keyEvent) 
                        {
                            char keyOther = keyEvent.getKeyChar();
                            if (keyOther == 'F' || keyOther == 'f')
                                // can't use break;
                                runForever = false;
                        }

                        @Override
                        public void keyReleased(KeyEvent arg0) {}

                        @Override
                        public void keyTyped(KeyEvent arg0) {}
                    };
                }
            }
            break;

        }  // end FILL_POLYGON


        case FILL_POLYGON_LOWERCASE:
        {
            if (greenLightForFilling == true)
            {
                fillPolygon(polyFiller);
            }
            break;              

        }

        /**
         *  save all polygons in a .scn file
         */
        case SAVE_POLYGONS :         
        {
            if (greenLightForFilling == true)
            {
                saveWorkspace();
            } 
            break;  
        }   // end SAVE_POLYGONS



        case SAVE_POLYGONS_LOWERCASE:
        {
            if (greenLightForFilling == true)
            {
                saveWorkspace();
            } 
            break;  
        }

        /**
         *  Delete everything & load all polygons from .scn file
         */
        case LOAD_POLYGONS:      
        {
            loadWorkspace();
            break;
        }   

        case LOAD_POLYGONS_LOWERCASE:
        {
            loadWorkspace();
            break;
        }

        default: break;  
    } // end switch


} 

Цель:

  1. выйти из цикла, когда я получаю второй f или F (я уже получил один f/F, когда я вошел в цикл).

  2. Нужно ли прикреплять второго слушателя к чему-то?

Но я не могу изменить runForever на false, так как он написан снаружи.

Даже если я изменю его на final , я также не смогу изменить его значение на false .

Как-нибудь обойти это?

Примечание: у меня уже есть прослушиватель ключей для входа в switch-case!!

ИЗМЕНИТЬ:

// Hot-keys hit by the user - used for keyboard listening
private static final char FILL_POLYGON = 'F';
private static final char SAVE_POLYGONS = 'S';
private static final char LOAD_POLYGONS = 'L';
private static final char FILL_POLYGON_LOWERCASE = 'f';
private static final char SAVE_POLYGONS_LOWERCASE = 's';
private static final char LOAD_POLYGONS_LOWERCASE = 'l';


    @Override
    public void keyTyped(KeyEvent keyEvent) 
    {
        PolygonFiller polyFiller = new PolygonFiller();
        char key = keyEvent.getKeyChar();

        final Settable runForever = new Settable();

        switch(key)
        {
            /**
             *  Fill the polygons 
             */
            case FILL_POLYGON:      
            {
                if (greenLightForFilling == true)
                {

                        KeyListener listener = new KeyListener()
                        {
                            public void keyPressed(KeyEvent keyEvent) 
                            {
                                char keyOther = keyEvent.getKeyChar();
                                if (keyOther == 'F' || keyOther == 'f')
                                    runForever.set(false);
                            }

                            @Override
                            public void keyReleased(KeyEvent arg0) {}

                            @Override
                            public void keyTyped(KeyEvent arg0) {}
                        };

                        this.addKeyListener(listener);


                        while (runForever.get())
                        {
                            fillPolygon(polyFiller);
                        }
                }
                break;

            }  // end FILL_POLYGON

...
}

person JAN    schedule 02.04.2013    source источник


Ответы (3)


Конечные переменные не могут быть изменены, но если они представляют изменяемый объект, вы можете изменить содержимое этого объекта.

Например, если вы создаете класс Settable и используете его экземпляр вместо boolean, вы можете сделать это:

class Settable {
    private boolean flag;
    public boolean get() {return flag;}
    public boolean set(boolean val) { flag = val; }
}

Теперь используйте final экземпляр Settable в своем коде; используйте set(...) вместо присваивания и get() вместо прямого доступа:

final Settable runForever = new Settable();
runForever.set(true);
...
while (runForever.get()) {
    ...
    if (keyOther == 'F' || keyOther == 'f') runForever.set(false);
    ...
}
person Sergey Kalinichenko    schedule 02.04.2013
comment
Вы можете использовать для этого AtomicBoolean вместо создания нового класса. - person JeroenWarmerdam; 02.04.2013
comment
@JeroenWarmerdam Я бы не хотел использовать AtomicBoolean или массив с одним значением boolean, потому что они предназначались для чего-то действительно другого. Использование AtomicBoolean намекает читателям вашего кода на параллелизм там, где его нет. Это может ввести ваших читателей в заблуждение, что для меня хуже, чем написание дополнительных пяти строк кода для нового класса. - person Sergey Kalinichenko; 02.04.2013
comment
Почему вы считаете, что это неправильное использование AtomicBoolean? - person JeroenWarmerdam; 02.04.2013
comment
@dasblinkenlight: Отлично, спасибо. Еще одна вещь, как я могу активировать этот слушатель сейчас? поскольку, когда я снова нажимаю F/f, слушатель не отвечает, я думаю, он не активирован. - person JAN; 02.04.2013
comment
@JeroenWarmerdam Я не утверждаю, что это неправильно или даже неоптимально, просто читатели могут запутаться. Когда я вижу Atomic<anything>, я думаю о параллелизме; код OP асинхронный, но не параллельный. Я считаю, что избежание путаницы у читателя почти так же важно, как и правильность кода. Однако использование AtomicBoolean или даже boolean runForever[1] технически правильно. - person Sergey Kalinichenko; 02.04.2013
comment
@ron Вам нужно добавить свой listener к тому же элементу ввода, который принимает взаимодействие с клавиатурой (текстовая область?). Вы должны добавить его вне цикла и удалить его, как только цикл закончится. - person Sergey Kalinichenko; 02.04.2013
comment
@dasblinkenlight: у меня есть (см. отредактированный пост), но второй F не отвечает. Я ничего не оставил? - person JAN; 02.04.2013
comment
@ron Вы забыли установить runForever на true перед циклом. Вам также нужно вызвать this.removeKeyListener(listener) после цикла. - person Sergey Kalinichenko; 02.04.2013
comment
@dasblinkenlight: я изменил его на true внутри класса, но упомянутые вами модификации действительно сработали ... он все еще зависает и не отвечает ни на какие f/F . Спасибо . - person JAN; 02.04.2013
comment
@dasblinkenlight Спасибо за объяснение. Я думал, что ваши мыслительные процессы могут быть так же полезны для ОП, как и сам ответ. - person JeroenWarmerdam; 02.04.2013
comment
@dasblinkenlight: Нет, private static final char FILL_POLYGON = 'F'; - person JAN; 02.04.2013
comment
@ron Вы нажимаете прописную F или строчную f во второй раз? Я думаю, вам понадобится редизайн, потому что оба слушателя потребляют F. Что еще более важно, цикл занятости в потоке пользовательского интерфейса не позволяет ни одному из ваших слушателей отвечать - никогда. Это самая большая проблема с вашим текущим дизайном: вы должны перенести заполнение прямоугольника на другой поток. - person Sergey Kalinichenko; 02.04.2013
comment
@dasblinkenlight хороший ответ для базовых классов Java, пожалуйста, прочитайте комментарии в предыдущем посте OP, и чтобы увидеть мой ответ здесь, по умолчанию этот ответ может быть проголосован против :-) - person mKorbel; 02.04.2013
comment
@mKorbel Что ж, ответ решает заявленную проблему связи между внутренним классом и окружающим его методом. Хотя более серьезная проблема запуска цикла занятости в потоке пользовательского интерфейса остается (см. мой последний комментарий), нецелесообразно перефразировать исходный вопрос, чтобы спросить о решении этой другой проблемы: я думаю, что выгрузка цикла занятости в отдельный поток заслуживает отдельного вопроса. - person Sergey Kalinichenko; 02.04.2013
comment
@dasblinkenlight не нападает на ваш пост здесь, так как я упомянул хороший ответ для Essential Classes :-), ничто не решило его идею, OP вернется сюда с несколькими побочными эффектами, вызванными KeyListener и Swing JComponents, удаленный комментарий (@Guillaume Polet ) речь о том простом и очень понятном - person mKorbel; 02.04.2013

  • пожалуйста, вы прочитали комментарий @kleopatra в своем предыдущем посте, не стоит его игнорировать

  • не никогда, не в неправильном направлении, используйте KeyBindings и с Swing Action вместо KeyListener

  • принятый ответ @dasblinkenlight ничего не решил в Swing, Swing и Java2D, используйте KeyBindings и Swing Action,

Например

import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;

public class FullScreen {

    private static final long serialVersionUID = 1L;
    private JButton button = new JButton("Close Meeee");
    private JPanel myPanel = new JPanel();
    private JFrame frame = new JFrame();

    public FullScreen() {
        button.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                System.exit(0);
            }
        });
        myPanel = new JPanel() {
            @Override
            public Dimension getPreferredSize() {
                return new Dimension(400, 300);
            }
        };
        myPanel.setFocusable(true);
        myPanel.add(button);
        frame.add(myPanel);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        //frame.setUndecorated(true);
        frame.getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
                KeyStroke.getKeyStroke("ENTER"), "clickENTER");
        frame.getRootPane().getActionMap().put("clickENTER", updateBol());
        frame.pack();
        frame.setVisible(true);
        updateCol().setEnabled(false);
        updateDol().setEnabled(false);
    }

    private Action updateBol() {
        return new AbstractAction("updateBol") {
            private static final long serialVersionUID = 1L;

            @Override
            public void actionPerformed(ActionEvent e) {
                System.out.println("updateCol is " + updateBol().isEnabled());
                System.out.println("updateBol is " + updateCol().isEnabled());
                System.out.println("updateDol is " + updateDol().isEnabled());
                updateCol().actionPerformed(e);
                updateDol().actionPerformed(e);
            }
        };
    }

    private Action updateCol() {
        return new AbstractAction("updateCol") {
            private static final long serialVersionUID = 1L;
            private Boolean _enabled = false;

            @Override
            public void actionPerformed(ActionEvent e) {
                System.out.println("updateCol is " + updateCol().isEnabled());
                if (updateCol().isEnabled()) {
                } else {
                }
            }

            public void setEnabled(Boolean bol) {
                _enabled = bol;
            }

            @Override
            public boolean isEnabled() {
                return _enabled;
            }
        };
    }

    private Action updateDol() {
        return new AbstractAction("updateDol") {
            private static final long serialVersionUID = 1L;
            private Boolean _enabled = false;

            @Override
            public void actionPerformed(ActionEvent e) {
                System.out.println("updateDol is " + updateDol().isEnabled());
                if (updateCol().isEnabled()) {
                } else {
                }
            }

            public void setEnabled(Boolean bol) {
                _enabled = bol;
            }

            @Override
            public boolean isEnabled() {
                return _enabled;
            }
        };
    }

    public static void main(String[] args) {
        Runnable doRun = new Runnable() {
            @Override
            public void run() {
                FullScreen fullScreen = new FullScreen();
            }
        };
        SwingUtilities.invokeLater(doRun);
    }
}
person mKorbel    schedule 02.04.2013

Вы можете сделать окончательный массив логических значений размера 1 для хранения вашего флага. Даже если массив является окончательным, его элементы не являются таковыми, поэтому вы можете изменять элементы массива из анонимных классов.

person infthi    schedule 02.04.2013