Kotlin Multiplatform Development Help

Keyboard events

In Compose Multiplatform for desktop, you can manage keyboard events by setting up event handlers in two different scopes:

  • Event handlers based on the focused element.

  • Event handlers in the window scope.

Events in a focused component

This approach implies that pressing a key on a keyboard triggers event handlers for the currently focused component.

A typical scenario is to define keyboard handlers for active controls like TextField. To intercept a key event before it triggers the default action, you can use both the onKeyEvent and onPreviewKeyEvent modifiers. With the onKeyEvent modifier, you can handle an individual keystroke, while onPreviewKeyEvent is preferable for defining shortcuts.

The following sample demonstrates TextField interactions with different actions depending on which key is pressed while you hold Ctrl:

import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.material.TextField import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.input.key.Key import androidx.compose.ui.input.key.isCtrlPressed import androidx.compose.ui.input.key.key import androidx.compose.ui.input.key.onPreviewKeyEvent import androidx.compose.ui.input.key.type import androidx.compose.ui.input.key.KeyEventType import androidx.compose.ui.window.singleWindowApplication fun main() = singleWindowApplication (title = "Key events") { MaterialTheme { var consumedText by remember { mutableStateOf(0) } var text by remember { mutableStateOf("") } Column(Modifier.fillMaxSize(), Arrangement.spacedBy(5.dp)) { Text("Consumed text: $consumedText") TextField( value = text, onValueChange = { text = it }, modifier = Modifier.onPreviewKeyEvent { when { (it.isCtrlPressed && it.key == Key.Minus && it.type == KeyEventType.KeyUp) -> { consumedText -= text.length text = "" true } (it.isCtrlPressed && it.key == Key.Equals && it.type == KeyEventType.KeyUp) -> { consumedText += text.length text = "" true } else -> false } } ) } } }
Keyboard events in a focused component

Events in a window scope

To define keyboard event handlers that are always active within the current window, use the onPreviewKeyEvent and onKeyEvent parameters, available for the Window, singleWindowApplication, and Dialog functions. They differ in how the event is dispatched when it is not consumed: onPreviewKeyEvent dispatches the event to its first child, and onKeyEvent dispatches the event to the composable's parent. Typically, onPreviewKeyEvent is preferred for intercepting events, as it allows implementing even screen-wide keyboard shortcuts.

The following sample demonstrates window interactions such as closing a pop-up dialog by pressing Escape and changing the window content by pressing the Ctrl+Shift+C shortcut:

import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.material.Button import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.input.key.Key import androidx.compose.ui.input.key.KeyEventType import androidx.compose.ui.input.key.isCtrlPressed import androidx.compose.ui.input.key.isShiftPressed import androidx.compose.ui.input.key.key import androidx.compose.ui.input.key.type import androidx.compose.ui.unit.dp import androidx.compose.ui.window.DialogWindow import androidx.compose.ui.window.singleWindowApplication private var cleared by mutableStateOf(false) fun main() = singleWindowApplication( title = "Keyboard events", onKeyEvent = { if ( it.isCtrlPressed && it.isShiftPressed && it.key == Key.C && it.type == KeyEventType.KeyDown ) { cleared = true true } else { false } } ) { MaterialTheme { if (cleared) { Text("The App was cleared!") } else { App() } } } @Composable fun App() { var isDialogOpen by remember { mutableStateOf(false) } if (isDialogOpen) { DialogWindow(onCloseRequest = { isDialogOpen = false }, title = "Dialog", onPreviewKeyEvent = { if (it.key == Key.Escape && it.type == KeyEventType.KeyDown) { isDialogOpen = false true } else { false } }) { Text("Hello!") } } Column(Modifier.fillMaxSize(), Arrangement.spacedBy(5.dp)) { Button( modifier = Modifier.padding(4.dp), onClick = { isDialogOpen = true } ) { Text("Open dialog") } } }
Keyboard events in a window scope

What's next?

Last modified: 29 November 2024