@@ -34,6 +34,7 @@ public class ComponentEvent<T extends Component> extends EventObject {
3434
3535 private boolean fromClient = false ;
3636 private Command unregisterListenerCommand = null ;
37+ private UI ui ;
3738
3839 /**
3940 * Creates a new event using the given source and indicator whether the
@@ -50,36 +51,78 @@ public ComponentEvent(T source, boolean fromClient) {
5051 this .fromClient = fromClient ;
5152 }
5253
54+ /**
55+ * Creates a new event using the given source, indicator whether the event
56+ * originated from the client side or the server side, and an explicit UI to
57+ * associate with the event.
58+ * <p>
59+ * Use this constructor when the source component may not be attached to a
60+ * UI at the time the event is fired, but the UI is still known (for
61+ * example, when the event is dispatched from code that has access to the
62+ * current UI). The supplied UI is then returned by {@link #getUI()} without
63+ * relying on the source's attachment state.
64+ *
65+ * @param source
66+ * the source component
67+ * @param fromClient
68+ * <code>true</code> if the event originated from the client
69+ * side, <code>false</code> otherwise
70+ * @param ui
71+ * the UI associated with the event, or <code>null</code> if not
72+ * available
73+ */
74+ public ComponentEvent (T source , boolean fromClient , UI ui ) {
75+ super (source );
76+ this .fromClient = fromClient ;
77+ this .ui = ui ;
78+ }
79+
5380 @ SuppressWarnings ("unchecked" )
5481 @ Override
5582 public T getSource () {
5683 return (T ) super .getSource ();
5784 }
5885
5986 /**
60- * Gets the UI the source component is attached to .
87+ * Gets the UI associated with this event .
6188 * <p>
62- * This is a convenience for {@code getSource().getUI().get()} when the
63- * event is fired while the source is attached to a UI, which is the common
64- * case.
89+ * The UI is resolved in the following order:
90+ * <ol>
91+ * <li>If a {@link UI} was explicitly provided at
92+ * {@linkplain #ComponentEvent(Component, boolean, UI) construction time},
93+ * that instance is returned.</li>
94+ * <li>If the source component is itself a {@link UI}, it is returned
95+ * directly.</li>
96+ * <li>Otherwise, the UI is obtained from
97+ * {@code getSource().getUI().get()}.</li>
98+ * </ol>
6599 * <p>
66- * If the source component is not currently attached to a UI, this method
67- * throws an {@link IllegalStateException}. This can happen, for example,
68- * when an initial value is set on a field before it is added to the UI and
69- * a value-change listener is invoked. If your listener can run while the
70- * source is detached, use {@code getSource().getUI()} instead, which
71- * returns an {@link java.util.Optional} and lets you handle the detached
72- * case explicitly.
100+ * In the common case the source is attached to a UI when the event fires,
101+ * so this method is a convenient shorthand for
102+ * {@code getSource().getUI().get()}.
103+ * <p>
104+ * If none of the above applies and the source component is not currently
105+ * attached to a UI, this method throws an {@link IllegalStateException}.
106+ * This can happen, for example, when an initial value is set on a field
107+ * before it is added to the UI and a value-change listener is invoked. If
108+ * your listener can run while the source is detached, use
109+ * {@code getSource().getUI()} instead, which returns an
110+ * {@link java.util.Optional} and lets you handle the detached case
111+ * explicitly.
73112 *
74- * @return the UI the source component is attached to , never {@code null}
113+ * @return the UI associated with this event , never {@code null}
75114 * @throws IllegalStateException
76- * if the source component is not currently attached to a UI
115+ * if the source component is not currently attached to a UI and
116+ * no UI was provided at construction time
77117 */
78118 public UI getUI () {
79- T source = getSource ();
80- if (source instanceof UI ui ) {
119+ if (ui != null ) {
81120 return ui ;
82121 }
122+ T source = getSource ();
123+ if (source instanceof UI sourceUI ) {
124+ return sourceUI ;
125+ }
83126 return source .getUI ()
84127 .orElseThrow (() -> new IllegalStateException (
85128 "Cannot resolve UI for event source " + source
0 commit comments