JAVA - JFrame не выглядит должным образом при вызове из слушателя действий

У меня проблема с тем, что один из моих кадров не выглядит должным образом, когда он вызывается нажатием кнопки.

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

У меня есть этакое главное меню, с двумя кнопками, на данный момент работает только меню «Создать», оно выглядит так:

https://i.imgur.com/k1Ne5v9.png

Код для слушателя действия:

    runMenuButt.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
            System.out.println("Generate Menu pressed");
            mF.dispose();
            MenuGenerator.generateTheMenu();
        }
    });

Результат выглядит неверным: https://i.imgur.com/n86y4CD.png Фрейм также не отвечает, нажатие X - нет, но оно должно закрыть фрейм и приложение.

Однако изменение кода на:

    runMenuButt.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
            System.out.println("Generate Menu pressed");
            //mF.dispose();

        }
    });
    MenuGenerator.generateTheMenu();

Выдает правильный вид: https://i.imgur.com/TFbkmAO.png

Код для «Главного меню»

public static void openMainMenu() {
    Font menuFont = new Font("Courier",Font.BOLD,16);
    Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
    mF.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    mF.setSize(465,230);
    mF.setLocation(dim.width/2-mF.getSize().width/2, dim.height/2-mF.getSize().height/2);
    mF.getContentPane().setBackground(Color.WHITE);

    Color blueSteel = new Color(70,107,176);
    JPanel p = new JPanel();
    p.setSize(600,50);
    p.setLayout(new GridBagLayout());
    GridBagConstraints gbc = new GridBagConstraints();
    p.setLocation((mF.getWidth() - p.getWidth()) /2, 20);
    p.setBackground(blueSteel);
    JLabel l = new JLabel("Welcome to the menu GENERATORRRR");
    l.setFont(menuFont);
    l.setForeground(Color.WHITE);
    p.add(l, gbc);

    JButton runMenuButt = new JButton("Generate Menu");
    runMenuButt.setLocation(20 , 90);
    JButton manageRecipButt = new JButton("Manage Recipients");
    manageRecipButt.setLocation(240 , 90);
    menuUtilities.formatButton(runMenuButt);
    menuUtilities.formatButton(manageRecipButt);

    mF.setResizable(false);
    mF.setLayout(null);
    mF.add(runMenuButt);
    mF.add(manageRecipButt);
    mF.add(p);
    mF.setVisible(true);

    runMenuButt.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
            System.out.println("Generate Menu pressed");
            //mF.dispose();

        }
    });
    MenuGenerator.generateTheMenu();

    manageRecipButt.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
            JOptionPane.showMessageDialog(null, "Not supported yet", "Function not yet available",JOptionPane.INFORMATION_MESSAGE);
        }
    });
    //System.out.println(mF.getContentPane().getSize());
}

И строка состояния:

public class StatusBar {
private static JLabel statusLabel= new JLabel("Starting");
private static JFrame statusFrame = new JFrame("Generation Status");

public static void createStatusBar() {
    Font menuFont = new Font(Font.MONOSPACED,Font.BOLD,20);
    Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();

    statusFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    statusFrame.setSize(700,100);

    JPanel p = new JPanel();
    p.setPreferredSize(new Dimension(100,100));
    statusLabel.setFont(menuFont);
    p.add(statusLabel);

    statusFrame.add(p,BorderLayout.CENTER);
    statusFrame.setLocation(dim.width/2-statusFrame.getSize().width/2, dim.height/2-statusFrame.getSize().height/2);
    statusFrame.setVisible(true);
}

public static void setStatusBar(String statusText) {
    statusLabel.setText(statusText);
    statusLabel.paintImmediately(statusLabel.getVisibleRect());
    statusLabel.revalidate();
}

public static void closeStatusBar(){
    statusFrame.dispose();
}
}

Я создаю панель с этой строкой: StatusBar.createStatusBar ();

Почему строка состояния не отображается должным образом, когда MenuGenerator.generateTheMenu (); вызывается из слушателя действия?

Вот минимальный код, который воспроизводит это поведение для всех, кто хочет его протестировать: он также использует класс для StatusBar, который уже опубликован.

public class MinimalClass {
private static JFrame mF = new JFrame("Main Menu");

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

public static void openMainMenu() {
    Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
    mF.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    mF.setSize(465,230);
    mF.setLocation(dim.width/2-mF.getSize().width/2, dim.height/2-mF.getSize().height/2);
    mF.getContentPane().setBackground(Color.WHITE);

    JButton runMenuButt = new JButton("Generate Menu");
    runMenuButt.setLocation(20 , 90);
    runMenuButt.setSize(200 , 85);

    mF.setResizable(false);
    mF.setLayout(null);
    mF.add(runMenuButt);
    mF.setVisible(true);

    runMenuButt.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
            System.out.println("Generate Menu pressed");
            mF.dispose();
            generateTheMenu();
        }
    });
}

public static void generateTheMenu() {
    System.setProperty("sun.java2d.cmm", "sun.java2d.cmm.kcms.KcmsServiceProvider");
    String rawMenuOutput = "";

    try {
        rawMenuOutput= getMenuInJavaNow();
    } catch (Exception e){
        System.out.println("Something went terribly wrong");
    }
    System.out.println(rawMenuOutput);
}

public static String getMenuInJavaNow() throws IOException {

    String rawMenuOutput = "Restaurant Menu" ;
    rawMenuOutput = rawMenuOutput + "Test line";
    String []menuOtpArr = new String [3];

    try {
        StatusBar.createStatusBar();
        TimeUnit.SECONDS.sleep(2);
        StatusBar.setStatusBar("Test1");
        TimeUnit.SECONDS.sleep(2);
        menuOtpArr[0] = "Test line";
        StatusBar.setStatusBar("Test2");
        TimeUnit.SECONDS.sleep(2);
        menuOtpArr[1] = "Test line";
        StatusBar.setStatusBar("Test3");
        TimeUnit.SECONDS.sleep(2);
        menuOtpArr[2] = "Test line";
        StatusBar.setStatusBar("Test4");
        TimeUnit.SECONDS.sleep(2);
        StatusBar.closeStatusBar();
    } catch (Exception e) {
    }

    for (int i=0;i < menuOtpArr.length;i++) {
        rawMenuOutput = rawMenuOutput + "\n\n" +menuOtpArr[i];
    }
    return rawMenuOutput;
}
}

Спасибо за ваше время

statusLabel.paintImmediately(statusLabel.getVisibleRect()); - не требуется - лучше использовать revalidate, а затем repaint
MadProgrammer 17.05.2018 11:36

Я вижу много разрозненного кода и говорю о MenuGenerator.generateTheMenu, но не вижу кода для MenuGenerator.generateTheMenu. Вы увидите множество вредных привычек, которые могут способствовать возникновению проблемы, но что действительно нужно, так это Минимальный, полный и проверяемый пример

MadProgrammer 17.05.2018 11:43

К сожалению, без изменений. Я попытался добавить его как в setStatusbar, так и в createStatusBar, но без изменений. За исключением того, что при запуске paintImmediately out теперь отображается пустая строка состояния.

GohanCZ 17.05.2018 11:45

@MadProgrammer: Вредные привычки определенно есть, так как я недавно взял в руки java, не имея опыта программирования. Я добавлю недостающий код. Я постараюсь создать минимально полный и проверяемый пример.

GohanCZ 17.05.2018 11:47

При всем уважении, сегодня я уже достаточно гадал о проблемах людей. Вам действительно нужно предоставить Минимальный, полный и проверяемый пример, который демонстрирует вашу проблему, прежде чем кто-либо сможет приблизиться к тому, чтобы ответить на ваш вопрос.

MadProgrammer 17.05.2018 11:47

Два момента: 1) Почему вы хотите использовать mf.dispose ()? 2) Есть ли в консоли следы стека исключений?

keuleJ 17.05.2018 11:53

@keuleJ: Я избавляюсь от него, потому что он мне больше не нужен. И я не знаю никаких трассировок стека исключений.

GohanCZ 17.05.2018 12:00

@MadProgrammer: добавлен минимальный код, воспроизводящий это поведение.

GohanCZ 17.05.2018 12:19

Вызов TimeUnit.SECONDS.sleep(2); вместо контекста потока диспетчеризации событий является проблемой. setLayout(null) - это проблема, setPreferredSize - это проблема. Вы можете использовать statusFrame.setLocationRelativeTo(null) для центрирования окон на экране по умолчанию

MadProgrammer 17.05.2018 12:41

@MadProgrammer Я согласен с тем, что использование сна - плохая замена, однако фрейм ведет себя точно так же, как и в исходном коде. Спасибо за statusFrame.setLocationRelativeTo (null), с этого момента я буду использовать его.

GohanCZ 17.05.2018 12:45

@GohanCZ Итак, проблема в том, что вы замораживаете поток диспетчеризации событий, который занимается обновлением макета и рисованием пользовательского интерфейса.

MadProgrammer 17.05.2018 12:46

@MadProgrammer, почему это происходит только тогда, когда generateTheMenu (); находится внутри слушателя действий, и как его исправить?

GohanCZ 17.05.2018 12:47
"только когда generateTheMenu (); находится внутри слушателя действия" - поскольку actionPerformed выполняется из контекста потока диспетчеризации событий - в действительности остальная часть кода также выполняется из контекста EDT: P
MadProgrammer 17.05.2018 12:58

И спасибо за работоспособный пример, он, по крайней мере, помог мне понять, в чем была наиболее вероятная причина проблемы;)

MadProgrammer 17.05.2018 13:03

Что ж, это было последнее, что я мог сделать, так как это повысило бы мои шансы получить ответ, что вы и сделали, поэтому теперь мне нужно выяснить, как обойти проблему, чтобы я не запускал код на EDT или не узнавал, как использовать SwingWorker

GohanCZ 17.05.2018 13:11

@MadProgrammer: Небольшой уточняющий вопрос, в чем разница между done и addPropertyListener? Есть ли? Разве не избыточно использовать и то, и другое? И я также заметил, что мне не нужно публиковать, так как вызов StatusBar.setStatusBar (""); тоже работает. Обязательно ли использовать публикацию? У меня есть код для создания строки, которую я хочу установить в качестве заголовка StatusBar в другом классе, а не в generateTheMenu, поэтому мне удобнее просто вызвать .setStatusBar. Не минимальный код, который у меня есть, на самом деле выглядит примерно так: pastebin.com/tgk12XKz

GohanCZ 17.05.2018 15:51

@GohanCZ Я обновил ответ, так как набирал много текста: P

MadProgrammer 17.05.2018 21:45
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
17
64
1

Ответы 1

statusLabel.paintImmediately(statusLabel.getVisibleRect());, кажется, маскирует более серьезную проблему.

Проблема в том, что Swing является однопоточным (и НЕ потокобезопасным). Это означает, что когда вы вызываете TimeUnit.SECONDS.sleep(2); изнутри getMenuInJavaNow, который вызывается generateTheMenu, который вызывается ActionListener, он вызывается в контексте потока диспетчеризации событий.

Это переводит EDT в спящий режим, что означает, что он не обрабатывает запросы макета или рисования (должным образом).

Начните с чтения Параллелизм в Swing для получения более подробной информации.

Теперь у вас есть более серьезная проблема: как ее решить. Для ответа на этот вопрос нам требуется гораздо больше контекста, чем доступно в настоящее время.

Похоже, что getMenuInJavaNow возвращает некоторые значения, но я не уверен, с какой целью.

Решение "А" - использовать SwingWorker (подробнее см. Рабочие потоки и SwingWorker). Он обеспечивает возможность выполнять длительные задачи в фоновом режиме, но также предоставляет средства для синхронизации обновлений с пользовательским интерфейсом, например ...

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingWorker;

public class MinimalClass {

    private static JFrame mF = new JFrame("Main Menu");

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                openMainMenu();
            }
        });
    }

    public static void openMainMenu() {
        Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
        mF.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        mF.setLocationRelativeTo(null);
        mF.getContentPane().setBackground(Color.WHITE);

        JButton runMenuButt = new JButton("Generate Menu");
        runMenuButt.setMargin(new Insets(25, 25, 25, 25));

        JPanel buttons = new JPanel(new GridLayout(0, 2));
        buttons.add(runMenuButt);

        mF.add(buttons);
        mF.pack();
        mF.setVisible(true);

        runMenuButt.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                System.out.println("Generate Menu pressed");
                mF.dispose();
                generateTheMenu();
            }
        });
    }

    public static void generateTheMenu() {
        System.setProperty("sun.java2d.cmm", "sun.java2d.cmm.kcms.KcmsServiceProvider");

        StatusBar.createStatusBar();

        SwingWorker<String, String> worker = new SwingWorker<String, String>() {
            @Override
            protected String doInBackground() throws Exception {
                String rawMenuOutput = "Restaurant Menu";
                rawMenuOutput = rawMenuOutput + "Test line";
                String[] menuOtpArr = new String[3];

                try {
                    TimeUnit.SECONDS.sleep(2);
                    publish("1234567890123456789012345678901234567890");
                    TimeUnit.SECONDS.sleep(2);
                    publish("This is a test");
                    TimeUnit.SECONDS.sleep(2);
                    publish("More testing");
                    TimeUnit.SECONDS.sleep(2);
                    publish("Still testing");
                    TimeUnit.SECONDS.sleep(2);
                } catch (Exception e) {
                }

                for (int i = 0; i < menuOtpArr.length; i++) {
                    rawMenuOutput = rawMenuOutput + "\n\n" + menuOtpArr[i];
                }
                return rawMenuOutput;
            }

            @Override
            protected void done() {
                StatusBar.closeStatusBar();
            }

            @Override
            protected void process(List<String> chunks) {
                StatusBar.setStatusBar(chunks.get(chunks.size() - 1));
            }

        };

        worker.addPropertyChangeListener(new PropertyChangeListener() {
            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                if (worker.getState() == SwingWorker.StateValue.DONE) {
                    try {
                        String result = worker.get();
                        System.out.println(result);
                        StatusBar.closeStatusBar();
                    } catch (InterruptedException | ExecutionException ex) {
                        ex.printStackTrace();
                    }
                }
            }
        });
        worker.execute();
    }

    public static class StatusBar {

        private static JLabel statusLabel = new JLabel("Starting");
        private static JFrame statusFrame = new JFrame("Generation Status");

        public static void createStatusBar() {
            Font menuFont = new Font(Font.MONOSPACED, Font.BOLD, 20);
            Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();

            statusFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            statusFrame.setSize(700, 100);

            JPanel p = new JPanel();
            p.setBackground(Color.RED);
            //          p.setPreferredSize(new Dimension(100, 100));
            statusLabel.setFont(menuFont);
            p.add(statusLabel);

            statusFrame.add(p, BorderLayout.CENTER);
            statusFrame.setLocationRelativeTo(null);
            statusFrame.setVisible(true);
        }

        public static void setStatusBar(String statusText) {
            statusLabel.setText(statusText);
        }

        public static void closeStatusBar() {
            statusFrame.dispose();
        }
    }
}

Наблюдения ...

  • static вам не друг, особенно в таких случаях. Вам действительно, очень, очень нужно научиться жить без этого.
  • setLayout(null) не делает вам одолжений, особенно в долгосрочной перспективе. Найдите время, чтобы пройти Размещение компонентов в контейнере и начать правильно использовать менеджеры компоновки, они могут показаться "сложными", но они избавят вас от большого количества выпадения волос.
  • По возможности избегайте использования setPreferred/Minimum/MaximumSize, вы лишаете компонент возможности предоставлять полезные подсказки рендеринга, которые могут меняться на разных платформах и конвейерах рендеринга.

Just a quick follow up question, what is the difference between done and addPropertyListener ? Is there any? Isnt it redundant to use both?

Пример здесь довольно простой, для меня я использовал done для обработки того, что SwingWorker «знает», что нужно сделать, но он не знает, что делать с результатом.

Вместо этого я использовал PropertyChangeListener, чтобы справиться с этим - суть - это пример.

And I also noticed, that I dont need to actually publish, as calling StatusBar.setStatusBar(""); works as well. Is it necessary to use publish?

Одним словом ДА. Swing НЕ является потокобезопасным, вызов StatusBar.setStatusBar("") напрямую может привести к некоторым странным и неожиданным результатам. publish отправляет вызов в поток диспетчеризации событий, что делает безопасным обновление пользовательского интерфейса изнутри.

I have the code for generating the String I want to set as the StatusBar Title in another class, not in the generateTheMenu, therefore it is more convenient for me to simply call .setStatusBar. The not minimal code I have is actually something like this

Здесь действительно пригодятся такие вещи, как interface. Класс, генерирующий «строку», «может» либо вернуть результирующий текст, либо передать ссылку на реализацию interface, которая используется для его «отображения». Таким образом, ваш SwingWorker может действовать как потребитель для String и передавать его через метод publish.

Необходимо понять ряд действительно важных концепций.

  • Вы хотите отделить свой код. Это упрощает изменение определенных частей кода, не затрагивая другие части.
  • Вы хотите иметь возможность «кодировать интерфейс, а не реализацию». Это идет рука об руку с первым комментарием. По сути, вы хотите «скрыть» детали реализации в максимально возможной степени - для этого есть множество разных причин, но это помогает сохранить ваш код компактным, помогает сделать последующие шаги более понятными и предотвращает доступ одной части кода к другой, которая у нее действительно есть. не несет ответственности за это (действительно ли генерация строки отвечает за обновление строки состояния? ИМХО - не совсем)
  • Также есть шаблоны проектирования, который упрощает решение проблем. Я уже упоминал понятие «продукт / потребитель», но это лишь один

"Поток диспетчеризации событий"

Я понимаю, потому что я вызываю большой кусок кода из ActionListener, поэтому EDT, на котором он работает, выполняет код на EDT и замораживает его до тех пор, пока он не будет выполнен. Затем, когда это будет сделано, все снова отобразится правильно. Ваш код работает, однако, поскольку я не читал о параллелизме потоков или о чем-то подобном, я понятия не имею, на что я смотрю. Спасибо, что объяснили мне, ПОЧЕМУ это происходит, и за ссылку на параллелизм потоков

GohanCZ 17.05.2018 13:05

Другие вопросы по теме