Overview
The following diagram broadly illustrates how Swing/AWT works on the Windows platform:
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
This architecture is almost entirely hidden from us by the event-driven abstraction. We only interact with the top-most end when events are triggered (actionPerformed
, paintComponent
, etc.) and by occasionally posting events ourselves (invokeLater
, repaint
, etc.).
Official documentation on the subject tends to be very general so I'm going to use (very paraphrased) excerpts from the source code.
Event Dispatch Thread
The EDT is the Swing event processing thread and all Swing programs run primarily on this thread. For the most part, this is just the AWT system and it's located in java.awt.EventDispatchThread
.
The event dispatching system is pretty dispersed, so I'll walk through a specific example supposing a JButton
has been clicked.
To begin figuring out what's going on, we might look at a stack trace.
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);
}
});
}
}
This program gets us a call stack like the following:
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
If we take a look at the EventDispatchThread.run
method, we see:
public void run() {
try {
pumpEvents(...);
} finally {
...
}
}
EventDispatchThread.pumpEvents
takes us to EventDispatchThread.pumpEventsForFilter
which contains the outer loop logic:
void pumpEventsForFilter(...) {
...
while(doDispatch && ...) {
pumpOneEventForFilters(...);
}
...
}
An event is then pulled off the queue and sent off for dispatch in EventDispatchThread.pumpOneEventForFilters
:
void pumpOneEventForFilters(...) {
AWTEvent event = null;
...
try {
...
EventQueue eq = getEventQueue();
...
event = eq.getNextEvent();
...
eq.dispatchEvent(event);
...
} catch(...) {
...
} ...
}
java.awt.EventQueue
contains logic where the type of event is narrowed and the event is further dispatched. EventQueue.dispatchEvent
calls EventQueue.dispatchEventImpl
where we see the following decision structure:
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 {
...
}
Most events we are familiar with go through the Component
path.
Component.dispatchEvent
calls Component.dispatchEventImpl
which, for most listener-type events, calls Component.processEvent
where the event is narrowed down and forwarded again:
/**
* 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 (...) {
...
} ...
}
For a JButton
click, we're following a MouseEvent
.
These low level events ultimately have a single handler internal to the Component
. So for example, we might have a look at javax.swing.plaf.BasicButtonListener
which implements a number of listener interfaces.
BasicButtonListener
uses the mouse events to change the pressed state of the button model. Finally, the button model determines if it's been clicked in DefaultButtonModel.setPressed
, fires an ActionEvent
and our listener's actionPerformed
gets called.
Native Messaging
How the actual native window is implemented is of course platform-specific but I can go through the Windows platform a bit since it's what you asked about. You'll find the Windows platform stuff in the following directories:
The Windows implementation of java.awt.Toolkit
, which is sun.awt.windows.WToolkit
, starts a separate thread for the actual message loop. WToolkit.run
calls a JNI method eventLoop
. A comment in the source file explains that:
/*
* eventLoop() begins the native message pump which retrieves and processes
* native events.
* ...
This leads us to the C++ AwtToolkit
class, located in awt_Toolkit.h
and awt_Toolkit.cpp
(other classes follow the same file name convention).
The native implementation of eventLoop
calls AwtToolkit::MessageLoop
:
AwtToolkit::GetInstance().MessageLoop(AwtToolkit::PrimaryIdleFunc,
AwtToolkit::CommonPeekMessageFunc);
(AwtToolkit::CommonPeekMessageFunc
calls PeekMessage
, which is the non-blocking alter-ego of GetMessage
.)
This is where the outer loop is located:
UINT
AwtToolkit::MessageLoop(IDLEPROC lpIdleFunc,
PEEKMESSAGEPROC lpPeekMessageFunc)
{
...
m_messageLoopResult = 0;
while (!m_breakMessageLoop) {
(*lpIdleFunc)();
PumpWaitingMessages(lpPeekMessageFunc); /* pumps waiting messages */
...
}
...
}
AwtToolkit::PumpWaitingMessages
actually has a familiar-looking message loop, which calls TranslateMessage
and 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);
}
}
(And recall that DispatchMessage
calls the WindowProc
callback.)
The native window is wrapped by a C++ object which has platform-specific stuff, as well as a loose parallel of some of the API we have in Java code.
There seem to be a couple of WindowProc
functions. One is just used internally by the toolkit, AwtToolkit::WndProc
, along with an empty window.
The WindowProc
function we're actually interested in is AwtComponent::WndProc
. WndProc
calls a virtual function called AwtComponent::WindowProc
. Some subclasses override WindowProc
(e.g. AwtFrame::WindowProc
), but a majority of messages are handled by AwtComponent::WindowProc
. For example, it contains the following switch case:
case WM_LBUTTONDOWN:
case WM_LBUTTONDBLCLK:
mr = WmMouseDown(static_cast<UINT>(wParam), myPos.x, myPos.y,
LEFT_BUTTON);
break;
AwtComponent::WmMouseDown
begins a series of calls that posts a java.awt.MouseEvent
to the EventQueue
in Java:
SendMouseEvent(java_awt_event_MouseEvent_MOUSE_PRESSED, now, x, y,
GetJavaModifiers(), clickCount, JNI_FALSE,
GetButton(button), &msg);
After the event is posted, we are ultimately led back to the top where the event is seen on the EDT.
Toolkit
and theEventQueue
– Bookish