是否可以避免保留对听众的引用?
Is it possible to avoid keeping a reference to the listeners?
我正在尝试创建一个“被动视图”的想法,其中用户操作会触发侦听器但应用程序本身不会。
考虑我需要监听 ComponentResized 事件的情况。当用户调整 window 大小时,我会做一些事情。但是一键按下也会调用该组件的 setSize()
方法。当从程序调用 setSize
时,我不希望触发侦听器。但是当它来自我想要的用户操作时。
public class Example extends JFrame {
static boolean stopResizing = false;
public Example() {
super("");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout(new FlowLayout());
ComponentAdapter listener = new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent e) {
if (stopResizing == true)
return;
System.out.println("RESIZED");
}
};
addComponentListener(listener);
JButton changeSizeButton = new JButton("Change size");
changeSizeButton.addActionListener(e -> {
stopResizing = true;
setSize(getSize().width + 15, getSize().height);
stopResizing = false;
});
add(changeSizeButton);
pack();
setLocationByPlatform(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
new Example().setVisible(true);
});
}
}
在上面的示例中,当 window 调整大小时,将打印“resized”。但是当按下按钮时,它也会被打印出来。我知道我可以 remove
并重新 add
组件侦听器,但这可以避免吗?在注册了多个侦听器的“大”视图中,这会很痛苦。
正如我通过 setSize
的调用层次结构看到的那样,事件发布在 EventQueue
中。这就是布尔标志不起作用的原因。在触发事件之前,标志变为 true
。因此,也许问题可以导出为“我可以操纵(safe/trust-able 可能是怎样)EventQueue 吗?”。通过在发布火灾事件之前添加 stopResizing = false
来操纵它。
另一种选择可能是创建一个静态方法来迭代所有侦听器,删除它们,运行 Runnable
(包含 setSize
),然后该方法将它们重新添加。但据我所知,从组件中粗暴地删除所有侦听器,也会删除 Swing 的内部侦听器,并且组件会出现意外行为。也许有一种方法可以在不保留引用的情况下将自定义(由我添加的)侦听器与 Swing 的侦听器分开?
我试图将 stopResizing = false
添加到 invokeLater
调用中,但它也不起作用。
请记住,这不仅仅是关于 ComponentListeners 的。它适用于任何类型的听众,适用于任何类型的组件。因此,我想让它“概括”它。
更新
即使我删除了侦听器,也会打印“调整大小”。
public class Example extends JFrame {
public Example() {
super("");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout(new FlowLayout());
ComponentAdapter listener = new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent e) {
System.out.println("RESIZED");
}
};
addComponentListener(listener);
JButton changeSizeButton = new JButton("Change size");
changeSizeButton.addActionListener(e -> {
removeComponentListener(listener);
setSize(getSize().width + 15, getSize().height);
addComponentListener(listener);
});
add(changeSizeButton);
pack();
setLocationByPlatform(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
new Example().setVisible(true);
});
}
}
我想避免 invokeLater 的原因是因为代码可能隐含地看起来像:
removeComponentListener(listener);
setSize(getSize().width + 15, getSize().height); //I dont want to fire the listener
addComponentListener(listener);
setSize(getSize().width + 15, getSize().height); // I Want to fire the listener
I tried to add the stopResizing = false into a invokeLater call, but it does not work either.
确实有效。我不知道你是怎么尝试的,但这行得通:
changeSizeButton.addActionListener(e -> {
stopResizing = true;
setSize(getSize().width + 15, getSize().height);
SwingUtilities.invokeLater(() -> {
stopResizing = false;
});
});
花了 2 天时间多次阅读 EventQueue class,我想我已经解决了。解决方案似乎是 SecondaryLoop,而后台线程等待所有事件被分派(在我们的例子中包括组件事件)。
public class Example extends JFrame {
public Example() {
super("");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout(new FlowLayout());
ComponentAdapter listener = new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent e) {
System.out.println("RESIZED");
}
};
addComponentListener(listener);
JButton changeSizeButton = new JButton("Change size");
changeSizeButton.addActionListener(e -> {
removeComponentListener(listener);
setSize(new Dimension(getSize().width + 1, getSize().height));
waitUntilAllEventsAreDispatched();
addComponentListener(listener);
setSize(new Dimension(getSize().width + 1, getSize().height)); //I want here to print
});
add(changeSizeButton);
pack();
setLocationByPlatform(true);
}
private EventQueue eventQueue() {
return Toolkit.getDefaultToolkit().getSystemEventQueue();
}
private void waitUntilAllEventsAreDispatched() {
SecondaryLoop secondaryLoop = eventQueue().createSecondaryLoop();
new Thread(() -> {
while (eventQueue().peekEvent() != null)
;
secondaryLoop.exit();
}).start();
secondaryLoop.enter();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
new Example().setVisible(true);
});
}
}
这个例子只会打印一次“RESIZED”。
正如预期的那样,它也适用于布尔标志:
ComponentAdapter listener = new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent e) {
if (allListenersDisabled)
return;
System.out.println("RESIZED");
}
};
addComponentListener(listener);
JButton changeSizeButton = new JButton("Change size");
changeSizeButton.addActionListener(e -> {
allListenersDisabled = true;
setSize(new Dimension(getSize().width + 1, getSize().height));
waitUntilAllEventsAreDispatched();
allListenersDisabled = false;
setSize(new Dimension(getSize().width + 1, getSize().height)); //I want here to print
});
一种解决方案是在调用 setSize()
之前删除事件侦听器。之后再加回来。
我正在尝试创建一个“被动视图”的想法,其中用户操作会触发侦听器但应用程序本身不会。
考虑我需要监听 ComponentResized 事件的情况。当用户调整 window 大小时,我会做一些事情。但是一键按下也会调用该组件的 setSize()
方法。当从程序调用 setSize
时,我不希望触发侦听器。但是当它来自我想要的用户操作时。
public class Example extends JFrame {
static boolean stopResizing = false;
public Example() {
super("");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout(new FlowLayout());
ComponentAdapter listener = new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent e) {
if (stopResizing == true)
return;
System.out.println("RESIZED");
}
};
addComponentListener(listener);
JButton changeSizeButton = new JButton("Change size");
changeSizeButton.addActionListener(e -> {
stopResizing = true;
setSize(getSize().width + 15, getSize().height);
stopResizing = false;
});
add(changeSizeButton);
pack();
setLocationByPlatform(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
new Example().setVisible(true);
});
}
}
在上面的示例中,当 window 调整大小时,将打印“resized”。但是当按下按钮时,它也会被打印出来。我知道我可以 remove
并重新 add
组件侦听器,但这可以避免吗?在注册了多个侦听器的“大”视图中,这会很痛苦。
正如我通过 setSize
的调用层次结构看到的那样,事件发布在 EventQueue
中。这就是布尔标志不起作用的原因。在触发事件之前,标志变为 true
。因此,也许问题可以导出为“我可以操纵(safe/trust-able 可能是怎样)EventQueue 吗?”。通过在发布火灾事件之前添加 stopResizing = false
来操纵它。
另一种选择可能是创建一个静态方法来迭代所有侦听器,删除它们,运行 Runnable
(包含 setSize
),然后该方法将它们重新添加。但据我所知,从组件中粗暴地删除所有侦听器,也会删除 Swing 的内部侦听器,并且组件会出现意外行为。也许有一种方法可以在不保留引用的情况下将自定义(由我添加的)侦听器与 Swing 的侦听器分开?
我试图将 stopResizing = false
添加到 invokeLater
调用中,但它也不起作用。
请记住,这不仅仅是关于 ComponentListeners 的。它适用于任何类型的听众,适用于任何类型的组件。因此,我想让它“概括”它。
更新 即使我删除了侦听器,也会打印“调整大小”。
public class Example extends JFrame {
public Example() {
super("");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout(new FlowLayout());
ComponentAdapter listener = new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent e) {
System.out.println("RESIZED");
}
};
addComponentListener(listener);
JButton changeSizeButton = new JButton("Change size");
changeSizeButton.addActionListener(e -> {
removeComponentListener(listener);
setSize(getSize().width + 15, getSize().height);
addComponentListener(listener);
});
add(changeSizeButton);
pack();
setLocationByPlatform(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
new Example().setVisible(true);
});
}
}
我想避免 invokeLater 的原因是因为代码可能隐含地看起来像:
removeComponentListener(listener);
setSize(getSize().width + 15, getSize().height); //I dont want to fire the listener
addComponentListener(listener);
setSize(getSize().width + 15, getSize().height); // I Want to fire the listener
I tried to add the stopResizing = false into a invokeLater call, but it does not work either.
确实有效。我不知道你是怎么尝试的,但这行得通:
changeSizeButton.addActionListener(e -> {
stopResizing = true;
setSize(getSize().width + 15, getSize().height);
SwingUtilities.invokeLater(() -> {
stopResizing = false;
});
});
花了 2 天时间多次阅读 EventQueue class,我想我已经解决了。解决方案似乎是 SecondaryLoop,而后台线程等待所有事件被分派(在我们的例子中包括组件事件)。
public class Example extends JFrame {
public Example() {
super("");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout(new FlowLayout());
ComponentAdapter listener = new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent e) {
System.out.println("RESIZED");
}
};
addComponentListener(listener);
JButton changeSizeButton = new JButton("Change size");
changeSizeButton.addActionListener(e -> {
removeComponentListener(listener);
setSize(new Dimension(getSize().width + 1, getSize().height));
waitUntilAllEventsAreDispatched();
addComponentListener(listener);
setSize(new Dimension(getSize().width + 1, getSize().height)); //I want here to print
});
add(changeSizeButton);
pack();
setLocationByPlatform(true);
}
private EventQueue eventQueue() {
return Toolkit.getDefaultToolkit().getSystemEventQueue();
}
private void waitUntilAllEventsAreDispatched() {
SecondaryLoop secondaryLoop = eventQueue().createSecondaryLoop();
new Thread(() -> {
while (eventQueue().peekEvent() != null)
;
secondaryLoop.exit();
}).start();
secondaryLoop.enter();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
new Example().setVisible(true);
});
}
}
这个例子只会打印一次“RESIZED”。
正如预期的那样,它也适用于布尔标志:
ComponentAdapter listener = new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent e) {
if (allListenersDisabled)
return;
System.out.println("RESIZED");
}
};
addComponentListener(listener);
JButton changeSizeButton = new JButton("Change size");
changeSizeButton.addActionListener(e -> {
allListenersDisabled = true;
setSize(new Dimension(getSize().width + 1, getSize().height));
waitUntilAllEventsAreDispatched();
allListenersDisabled = false;
setSize(new Dimension(getSize().width + 1, getSize().height)); //I want here to print
});
一种解决方案是在调用 setSize()
之前删除事件侦听器。之后再加回来。