简体   繁体   English

如何在不冻结JFrame并等待动作监听器完成的情况下从动作监听器调用方法?

[英]How do I call a method from an action listener without the JFrame freezing and waiting for the action listener to complete?

How do I set the text of a JTextArea while its JFrame is running, and refresh the JFrame to show the change, from another class? 如何在JTextArea的JFrame运行时设置其文本,并刷新JFrame以显示来自另一个类的更改?

I have a JFrame with a JTextArea which acts as a log, and the string it prints i update periodically with new activity from another class. 我有一个带有JTextArea的JFrame,它充当日志,并且它打印的字符串会随着来自另一个类的新活动而定期更新。 My JFrame class (EnablePage) looks like this: 我的JFrame类(EnablePage)如下所示:

package bot;
import java.awt.EventQueue;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
import javax.swing.JTextArea;
import javax.swing.JScrollPane;
import javax.swing.JButton;
import javax.swing.JLabel;
import java.awt.Font;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
public class EnablePage extends JFrame {
    public static String enablePane;
    private static JPanel contentPane;
    public static JTextArea txtrHello = new JTextArea();

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                try {
                    EnablePage frame = new EnablePage();
                    frame.setVisible(true);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }
    public EnablePage() {
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setBounds(100, 100, 594, 474);
        contentPane = new JPanel();
        contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
        setContentPane(contentPane);
        contentPane.setLayout(null);
        JScrollPane scrollPane = new JScrollPane();
        scrollPane.setToolTipText("");
        scrollPane.setBounds(6, 89, 582, 357);
        contentPane.add(scrollPane);
        txtrHello.setEditable(false);
        txtrHello.setText(enablePane);
        txtrHello.setWrapStyleWord(true);
        txtrHello.setLineWrap(true);
        scrollPane.setViewportView(txtrHello);

        JButton btnNewButton = new JButton("Enable");
        btnNewButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                try {
                    navigator.navigator();
                } catch (Exception e1) {
                    e1.printStackTrace();
                }
            }
        });
        btnNewButton.setBounds(59, 29, 117, 29);
        contentPane.add(btnNewButton);
    }
    public static void update(String x) {
        txtrHello.setText(enablePane+"\n"+x);

    }
}

And from my navigator class I've been trying to use this line of code to update the JtextArea, while it manipulates a website. 从导航器类中,我一直在尝试使用此行代码来更新JtextArea,同时它操纵网站。 This code I didn't include, but replaced here with "Thread.sleep(100000);" 我未包含此代码,但在此处替换为“ Thread.sleep(100000);”。 to illustrate the problem: 说明问题:

 package bot;


    import java.text.DateFormat;
    import java.text.SimpleDateFormat;
    import java.util.Date;

    import javax.swing.JOptionPane;

    public class navigator {

        public static DateFormat dateFormat = new SimpleDateFormat("MM-dd-yyyy(HH:mm:ss)");
        public static void navigator() throws Exception {
            Date date1 = new Date();
Thread.sleep(100000);


    EnablePage.update("Bot enabled: "+dateFormat.format(date1));
                 }
        }

However this is not updating the JFrame with the new text, because the EnablePage class is stuck waiting for the navigator() method to complete. 但是,这不会用新文本更新JFrame,因为EnablePage类被困在等待navigator()方法完成。 What ends up happening is the Enable button stays blue because the actionlistener method is never broken from, because the nagivator() method never finished. 最终发生的事情是“启用”按钮保持蓝色,这是因为从未监听过actionlistener方法,因为nagivator()方法从未完成。 What can I do to still call navigator() from the enable button but not have the EnablePage class freeze on this line? 我该怎么做,仍然从启用按钮调用navigator(),但在此行上没有冻结EnablePage类?

JTextArea#append will allow you to append text to the JTextArea , both setText and append are bound methods, this means that they will trigger an update when they are called so you shouldn't need to do anything more. JTextArea#append允许您将文本追加到JTextAreasetTextappend都是绑定方法,这意味着它们在被调用时将触发更新,因此您无需执行其他任何操作。 If it's not updating then it sounds like you have a reference issue. 如果未更新,则听起来您有参考问题。

You should consider providing a fully runnable example which demonstrates your problem. 您应该考虑提供一个完全可运行的示例来演示您的问题。 This will result in less confusion and better responses 这将减少混乱并改善响应

You should avoid the use of static , especially when associated with UI components, as this really begins to give you trouble with what you are referencing and what's on the screen. 您应该避免使用static ,尤其是在与UI组件关联时,因为它确实开始给您所引用的内容和屏幕上的内容带来麻烦。 static is NOT a cross communication mechanism for objects and shouldn't be used as such. static不是对象的交叉通信机制,因此不应如此使用。

If you can, you should define some kind of interface which describes the actions which be executed on your log frame (ie addLog(String) ), have your log frame implement this interface and then pass a reference of it to those classes that need it. 如果可以的话,您应该定义某种interface来描述在日志框架上执行的操作(即addLog(String) ),让日志框架实现此接口,然后将其引用传递给需要它的那些类。

Alternatively, you could use a singleton pattern to allow your log window to be accessed from any where in your application, personally, I'd be tempted to devise a queue of some kind, where other classes pushed log events onto this (singleton) queue and you had your frame either poll it or use some kind of blocking queue mechanism to monitor for changes to the queue. 另外,您可以使用单例模式来允许从应用程序中的任何位置访问日志窗口,就我个人而言,我很想设计某种队列,其他类将日志事件推送到此(单例)队列中并且您让帧轮询或使用某种阻塞队列机制来监视队列的更改。 This would require you to have a separate Thread (or SwingWorker ) which monitored the queue in the background so you don't block the Event Dispatching Thread. 这将需要您有一个单独的Thread (或SwingWorker )在后台监视队列,因此您不会阻塞事件调度线程。

Avoid using null layouts, pixel perfect layouts are an illusion within modern ui design. 避免使用null布局,像素完美布局是现代ui设计中的一种幻觉。 There are too many factors which affect the individual size of components, none of which you can control. 有太多因素会影响组件的单个大小,您无法控制。 Swing was designed to work with layout managers at the core, discarding these will lead to no end of issues and problems that you will spend more and more time trying to rectify Swing旨在与布局经理为核心一起工作,舍弃这些问题不会导致问题和问题的终结,您将花费越来越多的时间来尝试纠正

Updated 更新

Your runnable example works for me, more or less. 您可运行的示例或多或少对我有用。 Your reliance on static is worrying and Thread.sleep(100000); 您对static依赖令人担忧,并且Thread.sleep(100000); will block the Event Dispatching Thread, making your program look like it's hung (cause it has). 将阻止事件调度线程,使您的程序看起来像已挂起(因为已挂起)。 The following is reworked version of your example, without null layouts, without static and using a Swing Timer instead of Thread.sleep . 以下是示例的重做版本,没有null布局,没有static并且使用了Swing Timer而不是Thread.sleep The great thing about this, is once you press the "Enable" button, the timer will update the text area every second... 很棒的事情是,一旦按下“启用”按钮,计时器就会每秒更新一次文本区域...

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.Timer;

public class EnablePage extends JFrame {

    private JTextArea txtrHello = new JTextArea(10, 20);

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                try {
                    EnablePage frame = new EnablePage();
                    frame.pack();
                    frame.setLocationRelativeTo(null);
                    frame.setVisible(true);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    public EnablePage() {
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        setLayout(new BorderLayout());

        JScrollPane scrollPane = new JScrollPane(txtrHello);
        scrollPane.setToolTipText("");
        add(scrollPane);
        txtrHello.setEditable(false);
        txtrHello.setWrapStyleWord(true);
        txtrHello.setLineWrap(true);

        JButton btnNewButton = new JButton("Enable");
        btnNewButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                try {
                    Navigator.navigator(EnablePage.this);
                } catch (Exception e1) {
                    e1.printStackTrace();
                }
            }
        });
        add(btnNewButton, BorderLayout.NORTH);
    }

    public void update(String x) {
        System.out.println("Update " + x + "\n");
        txtrHello.append(x);

    }

    public static class Navigator {

        public static DateFormat dateFormat = new SimpleDateFormat("MM-dd-yyyy(HH:mm:ss)");

        public static void navigator(EnablePage page) throws Exception {

            Timer timer = new Timer(1000, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    Date date1 = new Date();
                    page.update("Bot enabled: " + dateFormat.format(date1));
                }
            });
            timer.start();
        }
    }
}

Here's a simple example. 这是一个简单的例子。 A clock JTextField is updated from a Thread. 时钟JTextField从线程更新。

As you can see, there are no update, validate, or invalidate method calls. 如您所见,没有更新,验证或使方法调用无效。

Edited to add: The calls to the SwingUtilities invokeLater method are important, to ensure that the Swing components are created and updated on the Event Dispatch thread (EDT) . 编辑添加:调用SwingUtilities invokeLater方法很重要,以确保在事件分发线程(EDT)上创建和更新Swing组件。

I also modified the Clock example to stop the Timer thread cleanly before disposing of the JFrame. 我还修改了Clock示例,以在处置JFrame之前彻底停止Timer线程。

package com.ggl.testing;

import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;

public class Clock implements Runnable {

    private JFrame frame;

    private JTextField clockDisplay;

    private Timer timer;

    @Override
    public void run() {
        frame = new JFrame("Clock");
        frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
        frame.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent event) {
                exitProcedure();
            }
        });

        JPanel panel = new JPanel();

        clockDisplay = new JTextField(12);
        clockDisplay.setEditable(false);
        clockDisplay.setHorizontalAlignment(JTextField.CENTER);

        panel.add(clockDisplay);

        frame.add(panel);
        frame.pack();
        frame.setLocationByPlatform(true);
        frame.setVisible(true);

        timer = new Timer(this);
        new Thread(timer).start();
    }

    public void exitProcedure() {
        timer.setRunning(false);
        frame.dispose();
        System.exit(0);
    }

    public void setText(String text) {
        clockDisplay.setText(text);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Clock());
    }

    public class Timer implements Runnable {

        private volatile boolean running;

        private Clock clock;

        private SimpleDateFormat timeFormat;

        public Timer(Clock clock) {
            this.clock = clock;
            this.running = true;
            this.timeFormat = new SimpleDateFormat("h:mm:ss a");
        }

        @Override
        public void run() {
            while (running) {
                displayTime();
                sleep();
            }

        }

        public void displayTime() {
            Calendar calendar = Calendar.getInstance();
            Date date = calendar.getTime();
            final String s = timeFormat.format(date);
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    clock.setText(s);
                }
            });
        }

        public void sleep() {
            try {
                Thread.sleep(200L);
            } catch (InterruptedException e) {
            }
        }

        public synchronized void setRunning(boolean running) {
            this.running = running;
        }

    }

}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM