Где находится аналог Java Swing цикла "GetMessage()"?

Я занимался программированием Win32 GUI несколько лет назад. Сейчас я использую Java Swing.

Просто из любопытства, где аналог Swing логики цикла сообщений Win32? В Win32 это достигается с помощью API GetMessage (). Я думаю, это должно быть где-то глубоко завернуто.

1 ответ

Решение

обзор

Следующая диаграмма широко иллюстрирует, как Swing/AWT работает на платформе Windows:

       Our Listeners
             ▲
             │ (Events dispatched to our code by EDT)
 ╭ ◀─────────┴───────────╮
 │ Event Dispatch Thread │
 ╰───────────▲─────────▶ ╯
             │ (Events pulled from the queue by EDT)
             │
        Event Queue
             ▲
             │ (Events posted to the queue by WToolkit)
 ╭ ◀─────────┴───────────╮
 │    WToolkit Thread    │
 ╰───────────▲─────────▶ ╯
             │ (Messages pulled by WToolkit via PeekMessage)
             │
        Windows API

Эта архитектура почти полностью скрыта от нас управляемой событиями абстракцией. Мы только взаимодействуем с самым верхним концом, когда события инициируются (actionPerformed, paintComponent и т. д.) и время от времени публикуем события сами (invokeLater, repaint, так далее.).

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

Поток рассылки событий

EDT - это поток обработки событий Swing, и все программы Swing работают преимущественно в этом потоке. По большей части, это просто система AWT, и она находится в java.awt.EventDispatchThread,

Система диспетчеризации событий довольно разбросана, поэтому я расскажу о конкретном примере, предполагая, что JButton была нажата.

Чтобы начать выяснять, что происходит, мы могли бы взглянуть на трассировку стека.

class ClickStack {
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame();
                JButton button = new JButton("Click for stack trace");

                button.addActionListener(new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent ae) {
                        new Error().printStackTrace(System.out);
                    }
                });

                frame.add(button);
                frame.pack();
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setVisible(true);
            }
        });
    }
}

Эта программа получает нам стек вызовов, как показано ниже:

at sscce.ClickStack$1$1.actionPerformed
at javax.swing.AbstractButton.fireActionPerformed
...
at javax.swing.DefaultButtonModel.setPressed
at javax.swing.plaf.basic.BasicButtonListener.mouseReleased
at java.awt.Component.processMouseEvent
...
at java.awt.Component.processEvent
...
at java.awt.Component.dispatchEventImpl
...
at java.awt.Component.dispatchEvent
at java.awt.EventQueue.dispatchEventImpl
...
at java.awt.EventQueue.dispatchEvent
at java.awt.EventDispatchThread.pumpOneEventForFilters
at java.awt.EventDispatchThread.pumpEventsForFilter
...
at java.awt.EventDispatchThread.pumpEvents
at java.awt.EventDispatchThread.run

Если мы посмотрим на EventDispatchThread.run Метод, мы видим:

public void run() {
    try {
        pumpEvents(...);
    } finally {
        ...
    }
}

EventDispatchThread.pumpEvents берет нас к EventDispatchThread.pumpEventsForFilter который содержит логику внешнего цикла:

void pumpEventsForFilter(...) {
    ...
    while(doDispatch && ...) {
        pumpOneEventForFilters(...);
    }
    ...
}

Затем событие извлекается из очереди и отправляется для отправки в EventDispatchThread.pumpOneEventForFilters:

void pumpOneEventForFilters(...) {
    AWTEvent event = null;
    ...
    try {
        ...
        EventQueue eq = getEventQueue();
        ...
        event = eq.getNextEvent();
        ...
        eq.dispatchEvent(event);
        ...
    } catch(...) {
        ...
    } ...
}

java.awt.EventQueue содержит логику, в которой тип события сужается, а событие отправляется дальше. EventQueue.dispatchEvent звонки EventQueue.dispatchEventImpl где мы видим следующую структуру решения:

if (event instanceof ActiveEvent) {
    ...
    ((ActiveEvent)event).dispatch();
} else if (src instanceof Component) {
    ((Component)src).dispatchEvent(event);
    ...
} else if (src instanceof MenuComponent) {
    ((MenuComponent)src).dispatchEvent(event);
} else if (src instanceof TrayIcon) {
    ((TrayIcon)src).dispatchEvent(event);
} else if (src instanceof AWTAutoShutdown) {
    ...
    dispatchThread.stopDispatching();
} else {
    ...
}

Большинство событий, с которыми мы знакомы, проходят через Component дорожка.

Component.dispatchEvent звонки Component.dispatchEventImpl который для большинства событий типа слушателя вызывает Component.processEvent где событие сужается и пересылается снова:

/**
 * Processes events occurring on this component. By default this
 * method calls the appropriate process<event type>Event
 * method for the given class of event.
 * ...
 */
protected void processEvent(AWTEvent e) {
    if (e instanceof FocusEvent) {
        processFocusEvent((FocusEvent)e);
    } else if (e instanceof MouseEvent) {
        switch(e.getID()) {
          case MouseEvent.MOUSE_PRESSED:
          case MouseEvent.MOUSE_RELEASED:
          case MouseEvent.MOUSE_CLICKED:
          case MouseEvent.MOUSE_ENTERED:
          case MouseEvent.MOUSE_EXITED:
              processMouseEvent((MouseEvent)e);
              break;
          case ...:
              ...
        }
    } else if (e instanceof KeyEvent) {
        processKeyEvent((KeyEvent)e);
    } else if (e instanceof ComponentEvent) {
        processComponentEvent((ComponentEvent)e);
    } else if (...) {
        ...
    } ...
}

Для JButton нажмите, мы следуем за MouseEvent,

Эти события низкого уровня, в конечном счете, имеют один внутренний обработчик Component, Так, например, мы могли бы взглянуть на javax.swing.plaf.BasicButtonListener который реализует ряд интерфейсов слушателя.

BasicButtonListener использует события мыши для изменения нажатого состояния модели кнопки. Наконец, модель кнопки определяет, была ли она нажата в DefaultButtonModel.setPressed увольняет ActionEvent и наш слушатель actionPerformed вызывается.

Native Messaging

То, как реализовано собственное собственное окно, конечно, зависит от платформы, но я могу немного пройтись по платформе Windows, так как это то, о чем вы спрашивали. Вы найдете материал для платформы Windows в следующих каталогах:

  • Джава: src/windows/classes/sun/awt/windows
  • Родные: src/windows/native/sun/windows

Реализация Windows java.awt.Toolkit, который sun.awt.windows.WToolkit, запускает отдельный поток для фактического цикла сообщений. WToolkit.run вызывает метод JNI eventLoop, Комментарий в исходном файле объясняет, что:

/*
 * eventLoop() begins the native message pump which retrieves and processes
 * native events.
 * ...

Это приводит нас к C++ AwtToolkit класс, расположенный в awt_Toolkit.h а также awt_Toolkit.cpp (другие классы следуют тому же соглашению об имени файла).

Нативная реализация eventLoop звонки AwtToolkit::MessageLoop:

AwtToolkit::GetInstance().MessageLoop(AwtToolkit::PrimaryIdleFunc,
                                      AwtToolkit::CommonPeekMessageFunc);

(AwtToolkit::CommonPeekMessageFunc звонки PeekMessage, который является неблокирующим альтер-эго GetMessage.)

Вот где находится внешний цикл:

UINT
AwtToolkit::MessageLoop(IDLEPROC lpIdleFunc,
                        PEEKMESSAGEPROC lpPeekMessageFunc)
{
    ...

    m_messageLoopResult = 0;
    while (!m_breakMessageLoop) {

        (*lpIdleFunc)();

        PumpWaitingMessages(lpPeekMessageFunc); /* pumps waiting messages */
        ...
    }
    ...
}

AwtToolkit::PumpWaitingMessages на самом деле имеет знакомый цикл сообщений, который вызывает TranslateMessage а также DispatchMessage:

/*
 * Called by the message loop to pump the message queue when there are
 * messages waiting. Can also be called anywhere to pump messages.
 */
BOOL AwtToolkit::PumpWaitingMessages(PEEKMESSAGEPROC lpPeekMessageFunc)
{
    MSG  msg;
    BOOL foundOne = FALSE;
    ...

    while (!m_breakMessageLoop && (*lpPeekMessageFunc)(msg)) {
        foundOne = TRUE;
        ProcessMsg(msg); // calls TranslateMessage & DispatchMessage (below)
    }
    return foundOne;
}

void AwtToolkit::ProcessMsg(MSG& msg)
{
    if (msg.message == WM_QUIT) {
        ...
    }
    else if (msg.message != WM_NULL) {
        ...

        ::TranslateMessage(&msg);
        ::DispatchMessage(&msg);
    }
}

(И напомним, что DispatchMessage вызывает WindowProc Перезвоните.)

Собственное окно обернуто объектом C++, который имеет специфические для платформы вещи, а также свободную параллель некоторых API, которые мы имеем в коде Java.

Там, кажется, есть пара WindowProc функции. Один из них используется внутри инструментария, AwtToolkit::WndProc вместе с пустым окном.

WindowProc функция, которую мы на самом деле заинтересованы в том, AwtComponent::WndProc, WndProc вызывает виртуальную функцию под названием AwtComponent::WindowProc, Переопределение некоторых подклассов WindowProc (например AwtFrame::WindowProc), но большинство сообщений обрабатываются AwtComponent::WindowProc, Например, он содержит следующий регистр переключения:

case WM_LBUTTONDOWN:
case WM_LBUTTONDBLCLK:
    mr = WmMouseDown(static_cast<UINT>(wParam), myPos.x, myPos.y,
                     LEFT_BUTTON);
    break;

AwtComponent::WmMouseDown начинается серия звонков, которые публикует java.awt.MouseEvent к EventQueue в Java:

SendMouseEvent(java_awt_event_MouseEvent_MOUSE_PRESSED, now, x, y,
               GetJavaModifiers(), clickCount, JNI_FALSE,
               GetButton(button), &msg);

После того, как событие опубликовано, мы в конечном счете возвращаемся к вершине, где событие видно на EDT.

Другие вопросы по тегам