将代码从 ActionListener 移动到 main()
Moving code from ActionListener to main()
问题:
我有以下 java.awt.Button
代码:
Button btn = new Button("abcde");
btn.addActionListener((ActionEvent e) ->
{
String s = btn.getLabel().toUpperCase();
btn.setLabel(s);
});
我需要将 btn.addActionListener
中的代码移动到 public static void main(String[] args)
,如下所示(伪代码):
Button btn = new Button("abcde");
btn.addActionListener((ActionEvent e) ->
{
notify_main()_that_button_had_been_clicked();
});
public static void main(String[] args)
{
block_until_button_clicked();
String s = UI.getButton().getLabel();
s = s.toUpperCase();
UI.getButton().setLabel(s);
}
相关信息:
我知道 GUI 开发有更好的解决方案,但我仅限于将 AWT 用于 UI。
我无权更改以上内容,由于法律限制,我也无法提供有关真实代码的详细信息。
为了解决上述问题,我提交了以下 MVCE。请根据它回答:
import java.awt.Frame;
import java.awt.Button;
import java.awt.event.ActionEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
public class ResponsiveUI extends Frame
{
public final Button btn = new Button("abcde");
public ResponsiveUI()
{
add(btn);
btn.addActionListener((ActionEvent e) ->
{
String s = btn.getLabel().toUpperCase();
btn.setLabel(s);
});
}
public static void main(String[] args)
{
ResponsiveUI rui = new ResponsiveUI();
rui.addWindowListener(new WindowAdapter()
{
@Override
public void windowClosing(WindowEvent we)
{
System.exit(0);
}
});
rui.setSize(250, 150);
rui.setResizable(false);
rui.setVisible(true);
}
}
我为解决这个问题所做的努力:
我广泛使用了 Google,并且能够找到一些有用的链接。
- UI 将 运行 在单独的线程中,这将使它响应(不过我不知道如何正确
join()
它)。
- 对于信号机制,
wait()
和 notify()
似乎是正确的方法。
- 要设置按钮的文本,我可以使用
EventQueue.InvokeAndWait
。
- 要获取 Button 的文本,我不知道该怎么做,但我有一个丑陋的解决方法。
下面是修改后的 MVCE :
import java.awt.Frame;
import java.awt.Button;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.lang.reflect.InvocationTargetException;
public class ResponsiveUI extends Frame
{
public final Object lock = new Object(); // POINT #2 : signaling mechanism
public final Button btn = new Button("abcde");
public ResponsiveUI()
{
add(btn);
btn.addActionListener((ActionEvent e) ->
{
// POINT #2 : signal the main() thread that button is clicked
synchronized (lock)
{
lock.notify();
}
});
}
public static void main(String[] args)
{
ResponsiveUI rui = new ResponsiveUI();
// POINT #1: put UI into separate thread, so we can keep it responsive
// POINT #1: I still do not know how to properly join() (it works OK though)
Runnable r = () ->
{
rui.addWindowListener(new WindowAdapter()
{
@Override
public void windowClosing(WindowEvent we)
{
System.exit(0);
}
});
rui.setSize(250, 150);
rui.setResizable(false);
rui.setVisible(true);
};
EventQueue.invokeLater(r);
try
{
synchronized (rui.lock) // POINT #2
{ // POINT #2
rui.lock.wait(); // POINT #2 : wait for button press
final Button b = new Button(); // POINT #4 : EventQueue uses final local variables
// store text into temp button (ugly but works)
EventQueue.invokeAndWait(() -> // POINT #4
{
b.setLabel(rui.btn.getLabel());
});
// we could do all kind of things, but for illustrative purpose just transform text into upper case
EventQueue.invokeAndWait(() -> // POINT #3 :
{
rui.btn.setLabel(b.getLabel().toUpperCase());
});
}
}
catch (InterruptedException | InvocationTargetException ex)
{
System.out.println("Error : " + ex);
}
}
}
一般方法是您需要与 main
方法和 ActionListener
实现共享一些锁或信号量。一旦你有一个共享信号量,main
方法可以阻塞(等待)信号量并且 ActionListener
可以通知信号量。
在伪代码中:
Lock lock = new Lock();
Button btn = new Button("abcde");
btn.addActionListener((ActionEvent e) -> {
lock.notify();
});
public static void main(String[] args) {
lock.waitUntilNotified();
String s = UI.getButton().getLabel();
s = s.toUpperCase();
UI.getButton().setLabel(s);
}
我已将确切的信号量实现留给您,因为您的应用程序的性质将决定哪种机制最有效。例如,也许你想使用一个简单的对象并在其上调用 wait
和 notify
方法,或者如果你需要对加载到队列中的元素执行处理,你可以使用阻塞队列(即,项目排队,按下按钮表示现在应该处理排队的项目)。
您可以在此处找到有关如何实施 wait
和 notify
方法的信息:
共享锁可能很困难,因为将锁作为方法参数传递不是 main
方法中的一个选项。一种简单的方法(在这种情况下不会是首选)是将锁创建为 static
变量:
public class Application {
public static final Lock LOCK = new Lock();
public static void main(String[] args) {
LOCK.waitUntilNotified();
String s = UI.getButton().getLabel();
s = s.toUpperCase();
UI.getButton().setLabel(s);
}
}
其他一些class:
public class SomeOtherClass {
public void doSomething() {
Button btn = new Button("abcde");
btn.addActionListener((ActionEvent e) -> {
Application.LOCK.notify();
});
}
}
切记 Lock
只是伪代码,应替换为您决定使用的锁的任何实现。
我使用了 CountDownLatch 来简化所有锁定系统。
import java.awt.Frame;
import java.awt.Button;
import java.awt.EventQueue;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.lang.reflect.InvocationTargetException;
import java.util.concurrent.CountDownLatch;
public class ResponsiveUI extends Frame
{
private static final CountDownLatch LOCK = new CountDownLatch(1);
public final Button btn = new Button("abcde");
public ResponsiveUI()
{
add(btn);
btn.addActionListener(ae -> LOCK.countDown());
}
public static void main(String[] args)
{
ResponsiveUI rui = new ResponsiveUI();
rui.addWindowListener(new WindowAdapter()
{
@Override
public void windowClosing(WindowEvent we)
{
System.exit(0);
}
});
rui.setSize(250, 150);
rui.setResizable(false);
rui.setVisible(true);
try {
LOCK.await();
String s = rui.btn.getLabel().toUpperCase();
EventQueue.invokeAndWait(() ->
{
rui.btn.setLabel(s);
});
}
catch (InvocationTargetException | InterruptedException ex)
{
System.out.println("Error : " + ex);
}
}
}
变化:
public final Button btn = new Button("abcde");
至:
public static final Button btn = new Button("abcde");
并移动:
btn.addActionListener((ActionEvent evt) -> {
String s = btn.getLabel().toUpperCase();
btn.setLabel(s);
});
在主要方法中。
为什么首先要弄乱线程? AWT/Swing 应用程序中的线程有一些 "conventions" 而你似乎离他们很远。
为什么不使用 getter 来访问 main
层中的按钮和动作侦听器?
第一个解决方案。干净利落,大家都看得懂(组件实例中没有public static final
修饰符)":
public class ResponsiveUI extends Frame {
private Button button;
public ResponsiveUI() {
super("UI");
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.exit(1);
}
});
button = new Button("something");
add(button);
}
public Button getButton() {
return button;
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
ResponsiveUI ui = new ResponsiveUI();
Button button = ui.getButton();
button.addActionListener(e -> {
button.setLabel(button.getLabel().toUpperCase());
});
ui.setSize(250, 150);
ui.setResizable(false);
ui.setVisible(true);
});
}
}
第二种解决方案,使用某种依赖注入并避免按钮使用 getter:
public class ResponsiveUI extends Frame {
private Button button;
public ResponsiveUI(ActionListener buttonListener) {
super("UI");
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.exit(1);
}
});
button = new Button("something");
button.addActionListener(buttonListener);
add(button);
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
ActionListener changeLabelToUppercaseListener = e -> {
Object source = e.getSource();
if (source instanceof Button) {
Button b = (Button) source;
b.setLabel(b.getLabel().toUpperCase());
}
};
ResponsiveUI ui = new ResponsiveUI(changeLabelToUppercaseListener);
ui.setSize(250, 150);
ui.setResizable(false);
ui.setVisible(true);
});
}
}
据我了解您的问题,您希望在单击 [AWT] 按钮时通知主线程,并在收到该通知后更改该按钮标签的文本。
我从您在问题中发布的修改后的 minimal-reproducible-example 代码开始。
这是我的代码,后面有注释。
import java.awt.Button;
import java.awt.EventQueue;
import java.awt.Frame;
import java.awt.event.ActionEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
public class ResponsiveUI extends Frame {
private static String btnTxt;
public final Object lock = new Object();
public final Button btn = new Button("abcde");
public ResponsiveUI() {
add(btn);
btn.addActionListener((ActionEvent e) -> {
synchronized (lock) {
btnTxt = e.getActionCommand();
lock.notifyAll();
}
});
}
public static void main(String[] args) {
ResponsiveUI rui = new ResponsiveUI();
Runnable r = () -> {
rui.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent we) {
System.exit(0);
}
});
rui.setSize(250, 150);
rui.setResizable(false);
rui.setVisible(true);
};
EventQueue.invokeLater(r);
synchronized (rui.lock) {
try {
rui.lock.wait();
String newBtnTxt = btnTxt.toUpperCase();
EventQueue.invokeLater(() -> rui.btn.setLabel(newBtnTxt));
}
catch (InterruptedException x) {
x.printStackTrace();
}
}
}
}
为了让GUI线程通知主线程,我同意你的看法wait()
和notifyAll()
是使用的机制。我使用 notifyAll()
而不是 notify()
来确保通知 所有 等待线程。
我使用共享变量 btnTxt
在 GUI 线程和主线程之间传输按钮标签的文本。
默认情况下,ActionEvent
的 操作命令 是 [AWT] 按钮标签的文本。因此在我的 ActionListener
实现中使用方法 getActionCommand()
。
对 GUI 的所有更改都必须在 GUI 线程上执行,因此在主线程中为按钮标签创建新文本后,我使用 class 的方法 invokeLater()
java.awt.EventQueue
实际更改按钮的标签文本。
您应该能够按原样复制上面的代码,编译它并运行它。
问题:
我有以下 java.awt.Button
代码:
Button btn = new Button("abcde");
btn.addActionListener((ActionEvent e) ->
{
String s = btn.getLabel().toUpperCase();
btn.setLabel(s);
});
我需要将 btn.addActionListener
中的代码移动到 public static void main(String[] args)
,如下所示(伪代码):
Button btn = new Button("abcde");
btn.addActionListener((ActionEvent e) ->
{
notify_main()_that_button_had_been_clicked();
});
public static void main(String[] args)
{
block_until_button_clicked();
String s = UI.getButton().getLabel();
s = s.toUpperCase();
UI.getButton().setLabel(s);
}
相关信息:
我知道 GUI 开发有更好的解决方案,但我仅限于将 AWT 用于 UI。
我无权更改以上内容,由于法律限制,我也无法提供有关真实代码的详细信息。
为了解决上述问题,我提交了以下 MVCE。请根据它回答:
import java.awt.Frame;
import java.awt.Button;
import java.awt.event.ActionEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
public class ResponsiveUI extends Frame
{
public final Button btn = new Button("abcde");
public ResponsiveUI()
{
add(btn);
btn.addActionListener((ActionEvent e) ->
{
String s = btn.getLabel().toUpperCase();
btn.setLabel(s);
});
}
public static void main(String[] args)
{
ResponsiveUI rui = new ResponsiveUI();
rui.addWindowListener(new WindowAdapter()
{
@Override
public void windowClosing(WindowEvent we)
{
System.exit(0);
}
});
rui.setSize(250, 150);
rui.setResizable(false);
rui.setVisible(true);
}
}
我为解决这个问题所做的努力:
我广泛使用了 Google,并且能够找到一些有用的链接。
- UI 将 运行 在单独的线程中,这将使它响应(不过我不知道如何正确
join()
它)。 - 对于信号机制,
wait()
和notify()
似乎是正确的方法。 - 要设置按钮的文本,我可以使用
EventQueue.InvokeAndWait
。 - 要获取 Button 的文本,我不知道该怎么做,但我有一个丑陋的解决方法。
下面是修改后的 MVCE :
import java.awt.Frame;
import java.awt.Button;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.lang.reflect.InvocationTargetException;
public class ResponsiveUI extends Frame
{
public final Object lock = new Object(); // POINT #2 : signaling mechanism
public final Button btn = new Button("abcde");
public ResponsiveUI()
{
add(btn);
btn.addActionListener((ActionEvent e) ->
{
// POINT #2 : signal the main() thread that button is clicked
synchronized (lock)
{
lock.notify();
}
});
}
public static void main(String[] args)
{
ResponsiveUI rui = new ResponsiveUI();
// POINT #1: put UI into separate thread, so we can keep it responsive
// POINT #1: I still do not know how to properly join() (it works OK though)
Runnable r = () ->
{
rui.addWindowListener(new WindowAdapter()
{
@Override
public void windowClosing(WindowEvent we)
{
System.exit(0);
}
});
rui.setSize(250, 150);
rui.setResizable(false);
rui.setVisible(true);
};
EventQueue.invokeLater(r);
try
{
synchronized (rui.lock) // POINT #2
{ // POINT #2
rui.lock.wait(); // POINT #2 : wait for button press
final Button b = new Button(); // POINT #4 : EventQueue uses final local variables
// store text into temp button (ugly but works)
EventQueue.invokeAndWait(() -> // POINT #4
{
b.setLabel(rui.btn.getLabel());
});
// we could do all kind of things, but for illustrative purpose just transform text into upper case
EventQueue.invokeAndWait(() -> // POINT #3 :
{
rui.btn.setLabel(b.getLabel().toUpperCase());
});
}
}
catch (InterruptedException | InvocationTargetException ex)
{
System.out.println("Error : " + ex);
}
}
}
一般方法是您需要与 main
方法和 ActionListener
实现共享一些锁或信号量。一旦你有一个共享信号量,main
方法可以阻塞(等待)信号量并且 ActionListener
可以通知信号量。
在伪代码中:
Lock lock = new Lock();
Button btn = new Button("abcde");
btn.addActionListener((ActionEvent e) -> {
lock.notify();
});
public static void main(String[] args) {
lock.waitUntilNotified();
String s = UI.getButton().getLabel();
s = s.toUpperCase();
UI.getButton().setLabel(s);
}
我已将确切的信号量实现留给您,因为您的应用程序的性质将决定哪种机制最有效。例如,也许你想使用一个简单的对象并在其上调用 wait
和 notify
方法,或者如果你需要对加载到队列中的元素执行处理,你可以使用阻塞队列(即,项目排队,按下按钮表示现在应该处理排队的项目)。
您可以在此处找到有关如何实施 wait
和 notify
方法的信息:
共享锁可能很困难,因为将锁作为方法参数传递不是 main
方法中的一个选项。一种简单的方法(在这种情况下不会是首选)是将锁创建为 static
变量:
public class Application {
public static final Lock LOCK = new Lock();
public static void main(String[] args) {
LOCK.waitUntilNotified();
String s = UI.getButton().getLabel();
s = s.toUpperCase();
UI.getButton().setLabel(s);
}
}
其他一些class:
public class SomeOtherClass {
public void doSomething() {
Button btn = new Button("abcde");
btn.addActionListener((ActionEvent e) -> {
Application.LOCK.notify();
});
}
}
切记 Lock
只是伪代码,应替换为您决定使用的锁的任何实现。
我使用了 CountDownLatch 来简化所有锁定系统。
import java.awt.Frame;
import java.awt.Button;
import java.awt.EventQueue;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.lang.reflect.InvocationTargetException;
import java.util.concurrent.CountDownLatch;
public class ResponsiveUI extends Frame
{
private static final CountDownLatch LOCK = new CountDownLatch(1);
public final Button btn = new Button("abcde");
public ResponsiveUI()
{
add(btn);
btn.addActionListener(ae -> LOCK.countDown());
}
public static void main(String[] args)
{
ResponsiveUI rui = new ResponsiveUI();
rui.addWindowListener(new WindowAdapter()
{
@Override
public void windowClosing(WindowEvent we)
{
System.exit(0);
}
});
rui.setSize(250, 150);
rui.setResizable(false);
rui.setVisible(true);
try {
LOCK.await();
String s = rui.btn.getLabel().toUpperCase();
EventQueue.invokeAndWait(() ->
{
rui.btn.setLabel(s);
});
}
catch (InvocationTargetException | InterruptedException ex)
{
System.out.println("Error : " + ex);
}
}
}
变化:
public final Button btn = new Button("abcde");
至:
public static final Button btn = new Button("abcde");
并移动:
btn.addActionListener((ActionEvent evt) -> {
String s = btn.getLabel().toUpperCase();
btn.setLabel(s);
});
在主要方法中。
为什么首先要弄乱线程? AWT/Swing 应用程序中的线程有一些 "conventions" 而你似乎离他们很远。
为什么不使用 getter 来访问 main
层中的按钮和动作侦听器?
第一个解决方案。干净利落,大家都看得懂(组件实例中没有public static final
修饰符)":
public class ResponsiveUI extends Frame {
private Button button;
public ResponsiveUI() {
super("UI");
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.exit(1);
}
});
button = new Button("something");
add(button);
}
public Button getButton() {
return button;
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
ResponsiveUI ui = new ResponsiveUI();
Button button = ui.getButton();
button.addActionListener(e -> {
button.setLabel(button.getLabel().toUpperCase());
});
ui.setSize(250, 150);
ui.setResizable(false);
ui.setVisible(true);
});
}
}
第二种解决方案,使用某种依赖注入并避免按钮使用 getter:
public class ResponsiveUI extends Frame {
private Button button;
public ResponsiveUI(ActionListener buttonListener) {
super("UI");
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.exit(1);
}
});
button = new Button("something");
button.addActionListener(buttonListener);
add(button);
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
ActionListener changeLabelToUppercaseListener = e -> {
Object source = e.getSource();
if (source instanceof Button) {
Button b = (Button) source;
b.setLabel(b.getLabel().toUpperCase());
}
};
ResponsiveUI ui = new ResponsiveUI(changeLabelToUppercaseListener);
ui.setSize(250, 150);
ui.setResizable(false);
ui.setVisible(true);
});
}
}
据我了解您的问题,您希望在单击 [AWT] 按钮时通知主线程,并在收到该通知后更改该按钮标签的文本。
我从您在问题中发布的修改后的 minimal-reproducible-example 代码开始。
这是我的代码,后面有注释。
import java.awt.Button;
import java.awt.EventQueue;
import java.awt.Frame;
import java.awt.event.ActionEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
public class ResponsiveUI extends Frame {
private static String btnTxt;
public final Object lock = new Object();
public final Button btn = new Button("abcde");
public ResponsiveUI() {
add(btn);
btn.addActionListener((ActionEvent e) -> {
synchronized (lock) {
btnTxt = e.getActionCommand();
lock.notifyAll();
}
});
}
public static void main(String[] args) {
ResponsiveUI rui = new ResponsiveUI();
Runnable r = () -> {
rui.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent we) {
System.exit(0);
}
});
rui.setSize(250, 150);
rui.setResizable(false);
rui.setVisible(true);
};
EventQueue.invokeLater(r);
synchronized (rui.lock) {
try {
rui.lock.wait();
String newBtnTxt = btnTxt.toUpperCase();
EventQueue.invokeLater(() -> rui.btn.setLabel(newBtnTxt));
}
catch (InterruptedException x) {
x.printStackTrace();
}
}
}
}
为了让GUI线程通知主线程,我同意你的看法wait()
和notifyAll()
是使用的机制。我使用 notifyAll()
而不是 notify()
来确保通知 所有 等待线程。
我使用共享变量 btnTxt
在 GUI 线程和主线程之间传输按钮标签的文本。
默认情况下,ActionEvent
的 操作命令 是 [AWT] 按钮标签的文本。因此在我的 ActionListener
实现中使用方法 getActionCommand()
。
对 GUI 的所有更改都必须在 GUI 线程上执行,因此在主线程中为按钮标签创建新文本后,我使用 class 的方法 invokeLater()
java.awt.EventQueue
实际更改按钮的标签文本。
您应该能够按原样复制上面的代码,编译它并运行它。