You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A window will only focus properly if certain conditions are met.
While researching this issue, I came accross the WindowAdapter workaround in #4231 by @m-sasha. This is one of the requirements that must be met, but it alone is not enough.
In the scenario we have two windows. Window 1 has a text field, and the window 2 has a button. The button in window 2 should focus window 1 and its text field so that the user can start typing.
The expectation is that it will just work without complication.
What actually happens is that numerous strange requirements exist. It can work, but only if all of the following are true:
We must use the WindowAdapter workaround, but the simple version in which focus.requestFocus() is called right away only works about 50% of the time. In order for it to work 100% of the time, we need to run it after a short delay.
The compose content of the window that is being focused must be clicked at least once. The button will not work at all until it is clicked. Clicking the empty space in the window is enough, but clicking the decoration bar is not.
Both the ComposeWindow and the FocusRequester must request focus (if there are multiple components in the window with the text field)
Requirement 3 is not a bug, but 1 and 2 are definitely bugs. The reason I include 3 here is to emphasize that even without 1 and 2, this operation is already quite complex and easy to mess up, which increases the cognitive burden caused by 1 and 2
Affected platforms
Desktop (M1 Mac OS). No other machine tested.
Versions
Libraries:
Compose Multiplatform version: 1.6.2
Kotlin version: 2.0.0-RC1
OS version(s) (required for Desktop and iOS issues): Mac OS 14.4.1
OS architecture (x86 or arm64): arm64
JDK (for desktop issues): 20.0.2+9
To Reproduce
Part 1
Run the code, do not touch the window titled "Window 1"
Click the "Focus TextField" button
Observe that Window 1 lights up as if it is focused, but typing does not work
Mash the "Focus TextField" out of frustration (still without clicking "Window 1")
Mash some keys
Observe still no text in text field
Carefully click the title bar of "Window 1". Drag it around a bit.
Click "Focus TextField"
Try to type
Observe still no typed text on screen
Finally, click the empty space below the text field in "Window 1" once
Click "Focus TextField"
Type something, and observe it works
At this point, it will fully work for the remainder of the process. You can select any window from any application, do whatever, and observe the "Focus TextField" button now works 100% of the time. It is unbreakable. All it needed was for some empty space in "Window 1" to be clicked once.
Observe that the button works 50% of the time. It seems rhythmic, too. Like once it works, then once it doesn't work, then once it works, and so on.
Observe that even if you click random windows in between button presses, that rhythm still exists.
Context
In the end, it seems that the second issue does have an ok workaround. It adds complexity to the code and the variable delay also adds room for error, but its ok.
But the first issue is worse, in my opinion, because the only workaround I have found so far is that the user has to literally click the window, if that could even be considered a workaround. Maybe java.awt.Robot could click the window, but I don't think I would want to go there so I didn't try it.
importandroidx.compose.material3.Buttonimportandroidx.compose.material3.Textimportandroidx.compose.material3.TextFieldimportandroidx.compose.runtime.DisposableEffectimportandroidx.compose.runtime.mutableStateOfimportandroidx.compose.runtime.rememberimportandroidx.compose.runtime.rememberCoroutineScopeimportandroidx.compose.ui.Modifierimportandroidx.compose.ui.awt.ComposeWindowimportandroidx.compose.ui.focus.FocusRequesterimportandroidx.compose.ui.focus.focusRequesterimportandroidx.compose.ui.unit.DpSizeimportandroidx.compose.ui.unit.dpimportandroidx.compose.ui.window.Windowimportandroidx.compose.ui.window.WindowPositionimportandroidx.compose.ui.window.WindowStateimportandroidx.compose.ui.window.applicationimportkotlinx.coroutines.delayimportkotlinx.coroutines.launchimportjava.awt.event.WindowAdapterimportjava.awt.event.WindowEventfunmain() {
val state1 =WindowState()
val state2 =WindowState()
// position the two windows next to eachother. // They are negative for my external monitor, but this can be changed
state1.position =WindowPosition(-1300.dp, -600.dp)
state1.size =DpSize(300.dp, 300.dp)
state2.position =WindowPosition(-900.dp, -600.dp)
state2.size =DpSize(300.dp, 300.dp)
var window1:ComposeWindow?=nullval focus =FocusRequester()
application {
Window(
state = state1,
onCloseRequest = ::exitApplication,
title ="Window 1"
) {
window1 = window
val s = remember { mutableStateOf("") }
TextField(
s.value,
onValueChange = {
s.value = it
},
modifier =Modifier.focusRequester(focus)
)
val scope = rememberCoroutineScope()
DisposableEffect(window) {
val listener =object:WindowAdapter() {
overridefunwindowActivated(e:WindowEvent) {
javax.swing.SwingUtilities.invokeLater {
focus.requestFocus()
}
scope.launch {
delay(100)
javax.swing.SwingUtilities.invokeLater {
focus.requestFocus()
}
}
}
}
window.addWindowListener(listener)
onDispose {
window.removeWindowListener(listener)
}
}
}
Window(
state = state2,
onCloseRequest = ::exitApplication,
title ="Window 2"
) {
Button(
onClick = {
window1!!.requestFocus()
focus.requestFocus()
}
) {
Text("Focus TextField")
}
}
}
}
The text was updated successfully, but these errors were encountered:
The problem here is that you're calling Window.requestFocus, which transfers focus to the window component itself. Compose, then, doesn't receive the key events, because they go to the window, rather than to the component on which Compose listens to key events.
You simply need to do window.toFront() instead.
P.S. All the workarounds in #4231 are needed because in that ticket the window receiving focus does not exist yet when the request is being made (and the app is not even in the foreground), which is not the case here.
Describe the bug
A window will only focus properly if certain conditions are met.
While researching this issue, I came accross the
WindowAdapter
workaround in #4231 by @m-sasha. This is one of the requirements that must be met, but it alone is not enough.In the scenario we have two windows. Window 1 has a text field, and the window 2 has a button. The button in window 2 should focus window 1 and its text field so that the user can start typing.
The expectation is that it will just work without complication.
What actually happens is that numerous strange requirements exist. It can work, but only if all of the following are true:
WindowAdapter
workaround, but the simple version in whichfocus.requestFocus()
is called right away only works about 50% of the time. In order for it to work 100% of the time, we need to run it after a short delay.FocusRequester
must request focus (if there are multiple components in the window with the text field)Requirement 3 is not a bug, but 1 and 2 are definitely bugs. The reason I include 3 here is to emphasize that even without 1 and 2, this operation is already quite complex and easy to mess up, which increases the cognitive burden caused by 1 and 2
Affected platforms
Versions
To Reproduce
Part 1
Part 2
Context
In the end, it seems that the second issue does have an ok workaround. It adds complexity to the code and the variable delay also adds room for error, but its ok.
But the first issue is worse, in my opinion, because the only workaround I have found so far is that the user has to literally click the window, if that could even be considered a workaround. Maybe
java.awt.Robot
could click the window, but I don't think I would want to go there so I didn't try it.The text was updated successfully, but these errors were encountered: