Skip to content

키보드 이벤트

데스크톱용 Compose Multiplatform에서는 두 가지 서로 다른 스코프에서 이벤트 핸들러를 설정하여 키보드 이벤트를 관리할 수 있습니다:

  • 포커스된 요소 기반의 이벤트 핸들러.
  • 윈도우 스코프의 이벤트 핸들러.

undefined

포커스된 컴포넌트에서의 이벤트

이 방식은 키보드의 키를 누를 때 현재 포커스된 컴포넌트의 이벤트 핸들러가 트리거됨을 의미합니다.

일반적인 시나리오는 TextField와 같은 활성 컨트롤에 대해 키보드 핸들러를 정의하는 것입니다. 기본 동작이 트리거되기 전에 키 이벤트를 가로채려면 onKeyEventonPreviewKeyEvent Modifier를 모두 사용할 수 있습니다. onKeyEvent Modifier를 사용하면 개별 키 누름을 처리할 수 있으며, 단축키를 정의할 때는 onPreviewKeyEvent를 사용하는 것이 좋습니다.

다음 예제는 키를 누른 상태에서 누르는 키에 따라 서로 다른 동작을 수행하는 TextField 상호작용을 보여줍니다. 이 코드를 sharedUI/src/jvmMain/kotlin 경로의 main.kt 파일에 추가하세요:

kotlin
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

윈도우 스코프에서의 이벤트

현재 윈도우 내에서 항상 활성화되는 키보드 이벤트 핸들러를 정의하려면 Window, singleWindowApplicationDialog 함수에서 사용할 수 있는 onPreviewKeyEventonKeyEvent 파라미터를 사용하세요. 이들은 이벤트가 소비되지 않았을 때 이벤트가 전달(dispatch)되는 방식에서 차이가 있습니다: onPreviewKeyEvent는 이벤트를 첫 번째 자식에게 전달하고, onKeyEvent는 이벤트를 컴포저블의 부모에게 전달합니다. 일반적으로 onPreviewKeyEvent는 화면 전체의 키보드 단축키까지 구현할 수 있어 이벤트를 가로채는 데 선호됩니다.

다음 샘플은 Escape를 눌러 팝업 다이얼로그를 닫거나 단축키를 눌러 윈도우 콘텐츠를 변경하는 등의 윈도우 상호작용을 보여줍니다. 이 코드를 sharedUI/src/jvmMain/kotlin 경로의 main.kt 파일에 추가하세요:

kotlin
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

다음 단계는 무엇인가요?