У меня проблема с тем, что один из моих кадров не выглядит должным образом, когда он вызывается нажатием кнопки.
Кадр выглядит так, как если бы он был отображен неправильно, текст метки в нем сокращен, однако, когда я перемещаю ту же строку кода за пределы прослушивателя действия, он работает так, как должен.
У меня есть этакое главное меню, с двумя кнопками, на данный момент работает только меню «Создать», оно выглядит так:
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;
}
}
Спасибо за ваше время
Я вижу много разрозненного кода и говорю о MenuGenerator.generateTheMenu, но не вижу кода для MenuGenerator.generateTheMenu. Вы увидите множество вредных привычек, которые могут способствовать возникновению проблемы, но что действительно нужно, так это Минимальный, полный и проверяемый пример
К сожалению, без изменений. Я попытался добавить его как в setStatusbar, так и в createStatusBar, но без изменений. За исключением того, что при запуске paintImmediately out теперь отображается пустая строка состояния.
@MadProgrammer: Вредные привычки определенно есть, так как я недавно взял в руки java, не имея опыта программирования. Я добавлю недостающий код. Я постараюсь создать минимально полный и проверяемый пример.
При всем уважении, сегодня я уже достаточно гадал о проблемах людей. Вам действительно нужно предоставить Минимальный, полный и проверяемый пример, который демонстрирует вашу проблему, прежде чем кто-либо сможет приблизиться к тому, чтобы ответить на ваш вопрос.
Два момента: 1) Почему вы хотите использовать mf.dispose ()? 2) Есть ли в консоли следы стека исключений?
@keuleJ: Я избавляюсь от него, потому что он мне больше не нужен. И я не знаю никаких трассировок стека исключений.
@MadProgrammer: добавлен минимальный код, воспроизводящий это поведение.
Вызов TimeUnit.SECONDS.sleep(2); вместо контекста потока диспетчеризации событий является проблемой. setLayout(null) - это проблема, setPreferredSize - это проблема. Вы можете использовать statusFrame.setLocationRelativeTo(null) для центрирования окон на экране по умолчанию
@MadProgrammer Я согласен с тем, что использование сна - плохая замена, однако фрейм ведет себя точно так же, как и в исходном коде. Спасибо за statusFrame.setLocationRelativeTo (null), с этого момента я буду использовать его.
@GohanCZ Итак, проблема в том, что вы замораживаете поток диспетчеризации событий, который занимается обновлением макета и рисованием пользовательского интерфейса.
@MadProgrammer, почему это происходит только тогда, когда generateTheMenu (); находится внутри слушателя действий, и как его исправить?
actionPerformed выполняется из контекста потока диспетчеризации событий - в действительности остальная часть кода также выполняется из контекста EDT: P
И спасибо за работоспособный пример, он, по крайней мере, помог мне понять, в чем была наиболее вероятная причина проблемы;)
Что ж, это было последнее, что я мог сделать, так как это повысило бы мои шансы получить ответ, что вы и сделали, поэтому теперь мне нужно выяснить, как обойти проблему, чтобы я не запускал код на EDT или не узнавал, как использовать SwingWorker
@MadProgrammer: Небольшой уточняющий вопрос, в чем разница между done и addPropertyListener? Есть ли? Разве не избыточно использовать и то, и другое? И я также заметил, что мне не нужно публиковать, так как вызов StatusBar.setStatusBar (""); тоже работает. Обязательно ли использовать публикацию? У меня есть код для создания строки, которую я хочу установить в качестве заголовка StatusBar в другом классе, а не в generateTheMenu, поэтому мне удобнее просто вызвать .setStatusBar. Не минимальный код, который у меня есть, на самом деле выглядит примерно так: pastebin.com/tgk12XKz
@GohanCZ Я обновил ответ, так как набирал много текста: P




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 и замораживает его до тех пор, пока он не будет выполнен. Затем, когда это будет сделано, все снова отобразится правильно. Ваш код работает, однако, поскольку я не читал о параллелизме потоков или о чем-то подобном, я понятия не имею, на что я смотрю. Спасибо, что объяснили мне, ПОЧЕМУ это происходит, и за ссылку на параллелизм потоков
statusLabel.paintImmediately(statusLabel.getVisibleRect());- не требуется - лучше использоватьrevalidate, а затемrepaint