首页 > 解决方案 > 是否可以避免保留对听众的引用?

问题描述

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

考虑一个我需要监听 Co​​mponentResized 事件的情况。当用户调整窗口大小时,我会做一些事情。但是一键按下也会调用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);
        });
    }

}

在上面的示例中,当窗口调整大小时,会打印“resized”。但是当按钮被按下时,它也会被打印出来。我知道我可以remove并且重新add成为组件侦听器,但这可以避免吗?在注册了多个听众的“大”视图中,这将是痛苦的。

正如我通过遵循 的调用层次结构所看到的那样setSize,事件发布在EventQueue. 这就是布尔标志不起作用的原因。标志true在事件触发之前变为。所以,也许这个问题可以派生为“我可以操纵(多么安全/可信)EventQueue?”。stopResizing = false通过在发布火灾事件之前添加来操作它。

另一种选择可能是创建一个静态方法来迭代所有侦听器,删除它们,运行 a Runnable(包含setSize),然后该方法将它们重新添加上。但据我所知,粗暴地从组件中删除所有侦听器,也会删除 Swing 的内部侦听器,并且组件会出现意外行为。也许有一种方法可以在不保留引用的情况下将自定义(由我添加)侦听器与 Swing 的侦听器分开?

我试图将其添加stopResizing = falseinvokeLater通话中,但它也不起作用。

请记住,这不仅仅是关于 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

标签: javaswingawt

解决方案


在花了 2 天时间阅读了多次 EventQueue 课程后,我想我解决了。解决方案似乎是一个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
});

推荐阅读