Kotlin Multiplatform Development Help

上下文菜单

Compose Multiplatform for desktop 为文本上下文菜单提供了开箱即用的支持,并允许您通过添加更多菜单项、设置主题和自定义文本来方便地定制任何上下文菜单。

自定义区域的上下文菜单

您可以为应用程序的任意区域创建上下文菜单。使用 ContextMenuArea 定义一个容器,在该容器中右键单击将触发上下文菜单的出现:

import androidx.compose.foundation.ContextMenuArea import androidx.compose.foundation.ContextMenuItem import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.width import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import androidx.compose.ui.window.singleWindowApplication fun main() = singleWindowApplication(title = "Context menu") { ContextMenuArea(items = { listOf( ContextMenuItem("User-defined action") { // Custom action }, ContextMenuItem("Another user-defined action") { // Another custom action } ) }) { // Blue box where context menu will be available Box(modifier = Modifier.background(Color.Blue).height(100.dp).width(100.dp)) } }
上下文菜单:ContextMenuArea

设置主题

您可以自定义上下文菜单的颜色,以创建与系统设置匹配的响应式 UI,并避免在应用程序之间切换时出现强烈的对比变化。对于默认的浅色和深色主题,有两个内置实现: LightDefaultContextMenuRepresentationDarkDefaultContextMenuRepresentation 。它们不会自动应用于上下文菜单颜色,因此您需要通过 LocalContextMenuRepresentation 设置合适的主题:

import androidx.compose.foundation.DarkDefaultContextMenuRepresentation import androidx.compose.foundation.LightDefaultContextMenuRepresentation import androidx.compose.foundation.LocalContextMenuRepresentation import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material.MaterialTheme import androidx.compose.material.Surface import androidx.compose.material.TextField import androidx.compose.material.darkColors import androidx.compose.material.lightColors import androidx.compose.runtime.CompositionLocalProvider 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.window.singleWindowApplication fun main() = singleWindowApplication(title = "Dark theme") { MaterialTheme( colors = if (isSystemInDarkTheme()) darkColors() else lightColors() ) { val contextMenuRepresentation = if (isSystemInDarkTheme()) { DarkDefaultContextMenuRepresentation } else { LightDefaultContextMenuRepresentation } CompositionLocalProvider(LocalContextMenuRepresentation provides contextMenuRepresentation) { Surface(Modifier.fillMaxSize()) { Box { var value by remember { mutableStateOf("") } TextField(value, { value = it }) } } } } }
上下文菜单:深色主题

本地化菜单项

默认情况下,上下文菜单将显示为系统设置的首选语言:

上下文菜单:本地化

如果您想使用特定语言,请在运行应用程序之前明确将其设置为默认语言:

java.util.Locale.setDefault(java.util.Locale("en"))

文本上下文菜单

默认文本上下文菜单

Compose Multiplatform for desktop 为 TextField 和可选的 Text 提供了内置的上下文菜单。

文本字段的默认上下文菜单包括以下操作,具体取决于光标的位置和选择范围:复制、剪切、粘贴和全选。这个标准的上下文菜单默认在 material TextFieldandroidx.compose.material.TextFieldandroidx.compose.material3.TextField )和 foundation BasicTextFieldandroidx.compose.foundation.text.BasicTextField )中可用。

import androidx.compose.material.Text import androidx.compose.material.TextField import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.window.singleWindowApplication fun main() = singleWindowApplication(title = "Context menu") { val text = remember { mutableStateOf("Hello!") } TextField( value = text.value, onValueChange = { text.value = it }, label = { Text(text = "Input") } ) }
TextField 的默认上下文菜单

简单文本元素的默认上下文菜单仅包含复制操作。要为 Text 组件启用上下文菜单,请将其包装在 SelectionContainer 中使其可选中:

import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.material.Text import androidx.compose.ui.window.singleWindowApplication fun main() = singleWindowApplication(title = "Context menu") { SelectionContainer { Text("Hello World!") } }
Text 的默认上下文菜单

添加自定义项

要为 TextFieldText 组件添加自定义上下文菜单操作,请通过 ContextMenuItem 指定新项,并通过 ContextMenuDataProvider 将它们添加到上下文菜单项的层次结构中。例如,以下代码示例展示了如何向文本字段和简单可选文本元素的默认上下文菜单添加两个新的自定义操作:

import androidx.compose.foundation.ContextMenuDataProvider import androidx.compose.foundation.ContextMenuItem import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.material.Text import androidx.compose.material.TextField import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.compose.ui.window.singleWindowApplication fun main() = singleWindowApplication(title = "Context menu") { val text = remember { mutableStateOf("Hello!") } Column { ContextMenuDataProvider( items = { listOf( ContextMenuItem("User-defined action") { // Custom action }, ContextMenuItem("Another user-defined action") { // Another custom action } ) } ) { TextField( value = text.value, onValueChange = { text.value = it }, label = { Text(text = "Input") } ) Spacer(Modifier.height(16.dp)) SelectionContainer { Text("Hello World!") } } } }
带有自定义操作的上下文菜单

覆盖默认文本上下文菜单

要覆盖文本字段和可选文本元素的默认上下文菜单,请覆盖 TextContextMenu 接口。在以下代码示例中,我们重用了原始的 TextContextMenu ,但在列表底部添加了一个额外的项。新项会根据文本选择进行调整:

import androidx.compose.foundation.ContextMenuDataProvider import androidx.compose.foundation.ContextMenuItem import androidx.compose.foundation.ContextMenuState import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Column import androidx.compose.foundation.text.LocalTextContextMenu import androidx.compose.foundation.text.TextContextMenu import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.material.Text import androidx.compose.material.TextField import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.window.singleWindowApplication import java.net.URLEncoder import java.nio.charset.Charset fun main() = singleWindowApplication(title = "Context menu") { CustomTextMenuProvider { Column { SelectionContainer { Text("Hello, Compose!") } var text by remember { mutableStateOf("") } TextField(text, { text = it }) } } } @OptIn(ExperimentalFoundationApi::class) @Composable fun CustomTextMenuProvider(content: @Composable () -> Unit) { val textMenu = LocalTextContextMenu.current val uriHandler = LocalUriHandler.current CompositionLocalProvider( LocalTextContextMenu provides object : TextContextMenu { @Composable override fun Area( textManager: TextContextMenu.TextManager, state: ContextMenuState, content: @Composable () -> Unit ) { // Reuses original TextContextMenu and adds a new item ContextMenuDataProvider({ val shortText = textManager.selectedText.crop() if (shortText.isNotEmpty()) { val encoded = URLEncoder.encode(shortText, Charset.defaultCharset()) listOf(ContextMenuItem("Search $shortText") { uriHandler.openUri("https://google.com/search?q=$encoded") }) } else { emptyList() } }) { textMenu.Area(textManager, state, content = content) } } }, content = content ) } private fun AnnotatedString.crop() = if (length <= 5) toString() else "${take(5)}..."
上下文菜单:LocalTextContextMenu

Swing 互操作性

如果您将 Compose 代码嵌入到现有的 Swing 应用程序中,并需要上下文菜单与其他部分的外观和行为匹配,可以使用 JPopupTextMenu 类。在该类中, LocalTextContextMenu 使用 Swing 的 JPopupMenu 来处理 Compose 组件中的上下文菜单。

import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Column import androidx.compose.foundation.text.JPopupTextMenu import androidx.compose.foundation.text.LocalTextContextMenu import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.material.Text import androidx.compose.material.TextField import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.awt.ComposePanel import androidx.compose.ui.platform.LocalLocalization import java.awt.Color import java.awt.Component import java.awt.Dimension import java.awt.Graphics import java.awt.event.KeyEvent import java.awt.event.KeyEvent.CTRL_DOWN_MASK import java.awt.event.KeyEvent.META_DOWN_MASK import javax.swing.Icon import javax.swing.JFrame import javax.swing.JMenuItem import javax.swing.JPopupMenu import javax.swing.KeyStroke.getKeyStroke import javax.swing.SwingUtilities import org.jetbrains.skiko.hostOs fun main() = SwingUtilities.invokeLater { val panel = ComposePanel() panel.setContent { JPopupTextMenuProvider(panel) { Column { SelectionContainer { Text("Hello, World!") } var text by remember { mutableStateOf("") } TextField(text, { text = it }) } } } val window = JFrame() window.contentPane.add(panel) window.size = Dimension(800, 600) window.isVisible = true window.title = "Swing interop" } @OptIn(ExperimentalFoundationApi::class) @Composable fun JPopupTextMenuProvider(owner: Component, content: @Composable () -> Unit) { val localization = LocalLocalization.current CompositionLocalProvider( LocalTextContextMenu provides JPopupTextMenu(owner) { textManager, items -> JPopupMenu().apply { textManager.cut?.also { add( swingItem(localization.cut, Color.RED, KeyEvent.VK_X, it) ) } textManager.copy?.also { add( swingItem(localization.copy, Color.GREEN, KeyEvent.VK_C, it) ) } textManager.paste?.also { add( swingItem(localization.paste, Color.BLUE, KeyEvent.VK_V, it) ) } textManager.selectAll?.also { add(JPopupMenu.Separator()) add( swingItem(localization.selectAll, Color.BLACK, KeyEvent.VK_A, it) ) } // Adds items that can be defined via ContextMenuDataProvider in other parts of the application for (item in items) { add( JMenuItem(item.label).apply { addActionListener { item.onClick() } } ) } } }, content = content ) } private fun swingItem( label: String, color: Color, key: Int, onClick: () -> Unit ) = JMenuItem(label).apply { icon = circleIcon(color) accelerator = getKeyStroke(key, if (hostOs.isMacOS) META_DOWN_MASK else CTRL_DOWN_MASK) addActionListener { onClick() } } private fun circleIcon(color: Color) = object : Icon { override fun paintIcon(c: Component?, g: Graphics, x: Int, y: Int) { g.create().apply { this.color = color translate(8, 2) fillOval(0, 0, 16, 16) } } override fun getIconWidth() = 16 override fun getIconHeight() = 16 }
上下文菜单:Swing 互操作性

下一步

探索关于其他桌面组件的教程。

22 四月 2025