是否可以避免保留对听众的引用?

Is it possible to avoid keeping a reference to the listeners?

我正在尝试创建一个“被动视图”的想法,其中用户操作会触发侦听器但应用程序本身不会。

考虑我需要监听 Co​​mponentResized 事件的情况。当用户调整 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() 之前删除事件侦听器。之后再加回来。