Задержка перед добавлением обработчиков событий JFrame?

Я работаю над простым проектом Java Swing. Это код основного класса (имя изменено):

public class MainProg
{
    private static MainProg program; 

    //mainWin is a JFrame
    private MainWindow mainWin;

    //Event handler class which extends MouseAdapter
    private TrayManager trayMgr;


    public static void main(String[] args)
    {                
        program = new MainProg();
    }

    public MainProg()
    {
        mainWin = new MainWindow();
        trayMgr = new TrayManager();

        mainWin.startBtn.addMouseListener(trayMgr);

        mainWin.setVisible(true);
    }
}

Как видно, при запуске программа в main() создает новый экземпляр класса MainProg, который затем вызывает конструктор. В конструкторе он создает новый экземпляр JFrame mainWin. Затем он прикрепляет обработчик событий к кнопке на mainWin.

В классе обработчика событий trayMgr единственным методом является mouseClicked(), который ничего не делает, кроме System.out.println('Clicked');

Проблема в том, что когда я запускаю эту программу в Netbeans, сразу отображается JFrame, но мне, похоже, нужно нажать кнопку 2-3 раза, прежде чем сообщение будет напечатано в консоли.

Является ли это чем-то специфичным для Netbeans, или мне нужно что-то изменить, чтобы установить обработчик событий до того, как окно станет видимым?


person Ali    schedule 05.09.2012    source источник
comment
Можете ли вы включить SSCCE, демонстрирующий проблему?   -  person tenorsax    schedule 05.09.2012
comment
Netbeans ничего не меняет в том, как работает программа. Ваш код также неверен в том, что он вызывает Swing из потока, отличного от EDT. Это может быть причиной проблемы. Если вы покажете нам SSCCE, как сказал @Max, мы сможем помочь.   -  person Gene    schedule 05.09.2012
comment
@Gene Как мне создать SSCCE? Однако я уверен, что то, что вы упомянули о потоке, отличном от цикла событий, является проблемой. Можете ли вы уточнить это?   -  person Ali    schedule 05.09.2012
comment
Это определенно похоже на проблему с потоками, например, поток для отображения Jframe и настройки события выполняется отдельно.   -  person Ali    schedule 05.09.2012
comment
См. раздел основной поток и поток пользовательского интерфейса в Java.   -  person tenorsax    schedule 05.09.2012
comment
Почему вы все равно добавляете MouseListener в JButton? Вы почти никогда не должны делать этого.   -  person Hovercraft Full Of Eels    schedule 05.09.2012
comment
@HovercraftFullOfEels, почему бы и нет? Как еще я мог бы обрабатывать щелчок мыши? (Это первый раз, когда я делаю приложение для свинга, поэтому, пожалуйста, будьте суровы и поправьте меня, если я делаю что-то не так) :)   -  person Ali    schedule 05.09.2012
comment
ActionListener обрабатывает щелчок JButton. Если вы используете MouseListener, вы потенциально можете испортить работу кнопки. Вам нужно прочитать учебники, так как есть чему поучиться.   -  person Hovercraft Full Of Eels    schedule 05.09.2012
comment
@HovercraftFullOfEels Если вы хотите опубликовать, как я могу справиться с этой проблемой с потоками, а также с actionListener, и я приму ваш ответ   -  person Ali    schedule 05.09.2012
comment
@Click: никто не сказал, что это определенно проблема с потоками, и на самом деле, вероятно, это не так. Все, что было сказано, это то, что вы не соблюдаете правила потоков Swing при запуске графического интерфейса Swing. Поместите его в Runnable и вызовите его, используя SwingUtilities.invokeLater(...), как это рекомендуется делать в учебниках. Но вряд ли это решит вашу проблему. Я подозреваю, что у вас есть ошибка в коде, который не показан. Но снова избавьтесь от MouseListener и используйте ActionListeners только с вашим JButton.   -  person Hovercraft Full Of Eels    schedule 05.09.2012
comment
@HovercraftFullOfEels Я использую мастер графического интерфейса Netbeans, поэтому он генерирует для меня весь код для настройки компонентов пользовательского интерфейса. Таким образом, в классе JFrame метод initComponents вызывается из конструктора JFrame, который добавляет все элементы управления в JFrame. Как вы предлагаете мне реорганизовать его?   -  person Ali    schedule 05.09.2012
comment
Опять же, прежде всего, избавьтесь от этого MouseListener и используйте ActionListener. Пожалуйста, отчитайтесь, чтобы увидеть, если это что-то делает. Я лично против использования NetBeans для создания кода GUI, пока вы не научитесь кодировать Swing вручную. Это не простой инструмент «наведи и щелкни», и он может с треском провалиться, если вы сначала не знакомы с основами Swing.   -  person Hovercraft Full Of Eels    schedule 05.09.2012
comment
@HovercraftFullOfEels Использование ActionListener, похоже, устранило эту проблему с задержкой. Но все же, если я делаю что-то не так с потоками, я хотел бы это знать. Ваше здоровье   -  person Ali    schedule 05.09.2012


Ответы (2)


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

public void main(String[] args) {
  SwingUtilities.invokeLater(new Runnable(
    public void run() {
      program = new MainProg();
    }
  ));
}

Кто-то еще рекомендовал использовать invokeAndWait(...) вместо invokeLater(...), но это может быть рискованно, особенно если вы непреднамеренно сделаете этот вызов из самого потока событий Swing. В вашей ситуации вам лучше использовать invokeLater(...).

Но опять же, я думаю, что основная проблема с кодом, который вы показали, заключалась в неправильном использовании MouseListener там, где должен был использоваться ActionListener. Научиться кодировать любую библиотеку GUI может быть довольно сложно, и по этой причине вы не можете ничего предполагать. Ознакомьтесь с учебными пособиями и учитесь у экспертов. Кроме того, если вы планируете программировать Swing в долгосрочной перспективе, рассмотрите возможность отказа от утилит генерации кода NetBean и сначала научитесь программировать Swing вручную. Вы не пожалеете, если сделаете это.

person Hovercraft Full Of Eels    schedule 05.09.2012
comment
Вопрос. Итак, когда этот код запускается, он будет выглядеть следующим образом: MainProg() -> JFrame() -> Jframe.initComponents() (здесь все компоненты пользовательского интерфейса настраиваются и добавляются в Jframe). Вы так задумали? - person Ali; 05.09.2012
comment
Я должен поддержать предложение, чтобы вы сначала научились кодировать Swing вручную. У меня много работы, и я только начинаю изучать, как использовать NetBeans GUI Builder. Многие из моих недавних вопросов по Java здесь, в Stackoverflow, касались использования GUI Builder. В общем, я нахожу его громоздким и, скорее всего, вообще откажусь от его использования в будущих проектах. - person Code-Apprentice; 05.09.2012
comment
@ClickUpvote Из-за проблем с потоками стек вызовов немного сложнее, чем вы заявляете. В конце концов вызывается run(), который затем вызывает конструктор MainProg() и так далее. - person Code-Apprentice; 05.09.2012
comment
@Click: я не уверен на 100%, что вы спрашиваете, но я думаю, что да, это правильно. Все, что я могу сказать наверняка, это то, что все построение графического интерфейса должно выполняться в потоке событий. - person Hovercraft Full Of Eels; 05.09.2012
comment
@HovercraftFullOfEels И под потоком событий вы имеете в виду новый поток, который вы предоставляете SwingUtilities.invokeLater выше? - person Ali; 05.09.2012
comment
@Click: нет, код, который я разместил, не запускает напрямую какой-либо поток. Вместо этого я имею в виду поток, созданный Swing, который отвечает за обработку всех пользовательских взаимодействий и всей графики. Swing создает этот поток при запуске Swing. Код, который я показал ниже, ставит Runnable в очередь только в этом потоке, если он существует. Если он еще не существует, я уверен, что он предлагает Swing запустить его. Пожалуйста, прочитайте об параллелизме в Swing, но не пока не беспокойтесь об использовании SwingWorkers. - person Hovercraft Full Of Eels; 05.09.2012
comment
@HovercraftFullOfEels Спасибо за вашу помощь, но у меня есть еще один вопрос. Прямо сейчас я использую только один JFrame, но скажем, я хочу загрузить новый JFrame, когда пользователь нажимает кнопку (например, он нажимает «Показать подробности», и я хочу открыть новый JFrame с деталями). Нужно ли мне делать что-то конкретное для этого с точки зрения многопоточности, или я могу просто настроить JFrame и добавить в него компоненты, а затем показать его, как только программа запустится через SwingUtilties.invokeLater в main()? - person Ali; 05.09.2012
comment
@Click: во-первых, вы не захотите показывать второй JFrame. Если вы хотите отобразить второе зависимое окно, вы, вероятно, будете использовать JDialog или обмениваться представлениями через CardLayout. Скорее всего, код для создания и отображения диалогового окна будет находиться в потоке событий Swing, поскольку он, вероятно, будет вызываться из прослушивателя событий, который всегда находится в потоке событий. Тогда нет, вам не нужно будет этого делать. - person Hovercraft Full Of Eels; 05.09.2012
comment
@HovercraftFullOfEels Верно, так что, как только у меня будет этот SwingUtilities.invokeLater в main (), я могу забыть об этом и запрограммировать все остальное, как обычно? - person Ali; 05.09.2012
comment
@Click: если вы не создаете и не используете фоновые потоки, то да, я считаю, что это правильно, но если ваше приложение Swing представляет собой нечто большее, чем простое, вам, вероятно, в какой-то момент понадобится выполнить фоновые потоки. - person Hovercraft Full Of Eels; 05.09.2012
comment
@HovercraftFullOfEels Спасибо, последний вопрос .. так что, если я выполняю какие-либо фоновые потоки, то в каждом потоке мне нужно будет поместить фактический код в SwingUtilities.invokeLater, как в main ()? - person Ali; 05.09.2012
comment
@Click: не обязательно. Если вы используете SwingWorker, то он сделает это за вас в определенных ситуациях. - person Hovercraft Full Of Eels; 05.09.2012

Поскольку вы спросили, код, который я разместил здесь, представляет собой Java SSCCE на другом тема. invokeLater — это способ выполнения вычислений в EDT. (Есть также invokeAndWait, который здесь отлично сработает, но при некоторых других условиях может привести к тупиковой ситуации.)

На самом деле этот пример, возможно, несколько чрезмерно консервативен. В некоторых ссылках говорится, что вы можете запустить Swing из основного потока вызовом show() или setVisible(). Однако у меня есть программа, которая плохо себя ведет под Java 7, когда я пытаюсь это сделать.

person Gene    schedule 05.09.2012
comment
Нет, вы почти никогда не должны использовать invokeAndWait(...). Гораздо лучше использовать invokeLater(...). Единственное исключение, о котором я знаю, возможно, при запуске JApplets, но это все. OP запускает JFrame, поэтому эта рекомендация неверна. Пожалуйста, измените его. - person Hovercraft Full Of Eels; 05.09.2012
comment
Если дадите мне ссылку в поддержку этого утверждения, сделаю. Я никогда не видел ничего в долгом поиске документации Swing. Здесь invokeLater блокирует основной поток, который в любом случае не может выполнять дальнейшую работу с пользовательским интерфейсом. В примере, который я разместил, invokeLater позволит основному потоку выполнить еще несколько инструкций, чтобы зависнуть в ожидании выхода из EDT. Существенной разницы в путях выполнения нет. Но с другой стороны, я очень рад, что меня опровергает более чем просто утверждение выделено жирным шрифтом. - person Gene; 06.09.2012
comment
Основная проблема, которую я знаю, заключается в том, что вы можете вызывать invokeLater в любом потоке, но не можете вызывать invokeAndWait в EDT. Если вам нужна документация по этому вопросу, просто проверьте SwingUtilities API. - person Hovercraft Full Of Eels; 06.09.2012
comment
Существует также риск взаимоблокировки с помощью invokeAndWait, и вы должны убедиться, что поток, который вызывает invokeAndWait, не содержит никаких блокировок, которые могут потребоваться другим во время выполнения вызова. Подробнее об этом читайте в Threads и Swing. Ганс Мюллер и Кэти Уолрат, двое из команды, которая помогла создать Swing и его механику многопоточности. - person Hovercraft Full Of Eels; 06.09.2012
comment
OP запускал простой код инициализации Swing в основном потоке, и мое предложение InvokeAndWait было в этом контексте. Я согласен, что кто-то может вырвать это из контекста и попасть в беду, поэтому я изменю это. Спасибо. - person Gene; 06.09.2012
comment
Человек, которого я беспокоюсь о том, чтобы вырвать это из контекста, это оригинальный постер и вы. Укоренившиеся привычки трудно изживаются, и если он сейчас научится использовать invokeAndWait, он легко может столкнуться с неприятностями позже, когда столкнется с одной из проблем, перечисленных выше. Вы не пристегиваете ремни безопасности сегодня, потому что знаете, что сегодня попадете в автомобильную аварию, вы пристегиваете их, потому что это снижает ваши шансы получить непредсказуемый вред. То же самое и с хорошей практикой программирования. 1+ за редактирование вашего поста. Спасибо. - person Hovercraft Full Of Eels; 06.09.2012
comment
Я тоже волнуюсь за тебя, так что мы квиты... ;-) - person Gene; 06.09.2012