Слушайте, когда компонент отображается в первый раз

Я пытаюсь захватить первый момент, когда компонент отображается на экране, не используя "грязные" решения, как с использованием таймера. В принципе, я хочу знать момент, когда я могу с уверенностью начать использовать getLocationOnScreen() метод на компоненте.

Я думал, что слушатель компонентов может помочь, но не повезло. Я застрял до сих пор и не знаю, какой слушатель использовать для этого. Любые предложения?

Вот пример кода, который показывает, что сбой слушателя компонента.

import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class CompListenerTest
{
    static ComponentListener cL = new ComponentAdapter()
    {
        @Override
        public void componentShown(ComponentEvent e)
        {
            super.componentShown(e);
            System.out.println("componentShown");
        }
    };

    static MouseListener mL = new MouseAdapter() 
    {

        @Override
        public void mousePressed(MouseEvent e)
        {
            super.mousePressed(e);
            JComponent c = (JComponent) e.getSource();
            System.out.println("mousePressed c="+c.isShowing());
        }

    };

    public static void main(String[] args)
    {
        JPanel p = new JPanel();
        p.setPreferredSize(new Dimension(300, 400));
        p.setBackground(Color.GREEN);
        p.addComponentListener(cL);
        p.addMouseListener(mL);

        System.out.println("initial test p="+p.isShowing());
        JPanel contentPane = new JPanel();
        contentPane.setBackground(Color.RED);
        contentPane.add(p);
        JFrame f = new JFrame();
        f.setContentPane(contentPane);
        f.setSize(800, 600);
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.setVisible(true);
    }
}

Спасибо заранее.

Ответы

Ответ 1

Причина, по которой компонент ComponentListener не работает, заключается в том, что он сообщает об изменениях в видимом свойстве - и по умолчанию это правда, даже не являясь частью иерархии компонентов.

Чтобы быть надежно уведомленным, используйте HierarchyListener


Редактируйте (размышляя о моей эволюции знаний в отношении этого вопроса/ответов, не уверен, что должен сказать сетевой этикет об этом... просто наведите меня, если это неправильный путь: -)

Во-первых: вопрос, заданный в теме, не обязательно связан с реальной проблемой (как прокомментировал Боро ниже - каким-либо образом ссылаться на комментарий?): нет необходимости держать какой-то локальный флаг, чтобы решить, или нет, это безопасно отправить getLocationOnScreen в компонент, просто спросите сам компонент. Learn-item 1 для себя:-)

Второе: вопрос, который задают, довольно интересен. Пять экспертов (включая меня, самопровозглашенных), пять разных ответов. Который вызвал немного копания с моей стороны.

Моя гипотеза: ComponentEvents не полезны для уведомления (first-) показов. Я знал, что componentShown бесполезен, потому что это своего рода свойство. Измените уведомление о видимом свойстве компонента (который редко изменяется). Однако недоумевая о рекомендуемой полезности перемещенного/измененного размера.

Построение прецедента: полностью подготовьте фрейм в примере и сохраните его для последующего отображения, типичный подход для улучшения воспринимаемой производительности. Мое предсказание, основанное на моей гипотезе: изменение размера/перемещение было произведено во время подготовки, ничто в шоу-времени (примечание: isShowing - это то, что мы делаем, это последнее). Фрагмент, который нужно добавить в примере OP:

    final JFrame f = new JFrame();
    f.setContentPane(contentPane);
    f.setSize(800, 600);
    //        f.pack(); 

    JFrame controller = new JFrame("opener");
    controller.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    Action open = new AbstractAction("open/hide second") {

        @Override
        public void actionPerformed(ActionEvent e) {
            f.setVisible(!f.isVisible());
        }

    };
    controller.add(new JButton(open));
    controller.pack();
    controller.setVisible(true);

Разочарование: никаких уведомлений на подготовительном этапе, уведомление на шоу-времени, по мере необходимости, моя гипотеза казалась неправильной;-) Последний шанс: замените setSize для пакета... и voila, уведомление на этапе подготовки, без уведомления в шоу-время, снова рад. Игра немного больше: похоже, что компонент ComponentEvents запущен, если компонент отображается, что может или не может быть полезным в некоторых контекстах, но если показ - это состояние, за которым мы после.

Новые императивные правила (черновик):
Не используйте ComponentListener для уведомления о "показе". Это осталось от AWT-возраста.
Используйте AncestorListener. Это, похоже, замена Swing, слегка неверное уведомление о "добавлении", которое на самом деле означает "показ"
Используйте HierarchyListener только в том случае, если действительно заинтересованы в изменениях мелкозернистого состояния.

Ответ 2

Я использую AncestorListener и обрабатываю событие ancestorAdded.

Ответ 3

Как ни странно, ComponentListener отлично работает при применении к JFrame. Вот измененный источник, где я видел, как он работает.

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class CompListenerTest
{
    static ComponentListener cL = new ComponentAdapter()
    {
        @Override
        public void componentShown(ComponentEvent e)
        {
            super.componentShown(e);
            System.out.println("componentShown");
        }
    };

    public static void main(String[] args)
    {
        JPanel p = new JPanel();
        p.setPreferredSize(new Dimension(300, 400));
        p.setBackground(Color.GREEN);

        System.out.println("initial test p="+p.isShowing());
        JPanel contentPane = new JPanel();
        contentPane.setBackground(Color.RED);
        contentPane.add(p);
        JFrame f = new JFrame();
        f.addComponentListener(cL);
        f.setContentPane(contentPane);
        f.setSize(800, 600);
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.setVisible(true);
    }
}

Ответ 4

Использование AncestorListener и ancestorAdded работало для меня. Здесь пример кода:

    addAncestorListener(new AncestorListener()
    {
        @Override
        public void ancestorRemoved(AncestorEvent event) {}

        @Override
        public void ancestorMoved(AncestorEvent event) {}

        @Override
        public void ancestorAdded(AncestorEvent event)
        {
            // component is shown here
        }
    });

Ответ 5

Для большинства целей вы можете пойти с первым вызовом ComponentListener.componentMoved (или если вас также интересует размер ComponentListener.componentResized). Они вызываются всякий раз, когда изменяется положение/размер компонента.

Ответ 6

static ComponentListener cL = new ComponentAdapter() {
        @Override
        public void componentResized(ComponentEvent e) {
            super.componentResized(e);
            System.out.println("componentShown = "+e.getComponent().isDisplayable());
        }
    };