Compose Multiplatform是 JetBrains 开发的声明式 UI 框架,可让您为 Android、iOS、桌面和 Web 开发共享 UI。将 Compose Multiplatform 集成到您的 Kotlin Multiplatform 项目中,即可更快地交付您的应用和功能,而无需维护多个 UI 实现。

在(2025.06.05) Compose Multiplatform 中对于 Desktop 的开发,如果使用了托盘,会发现托盘中的中文竟然是乱码。为了解决这个问题,只能重新实现一个系统托盘,因此该托盘具备了以下特性。

  • 解决中文乱码
  • 更多的Swing 组件可以被放到托盘
  • 允许你监听单击事件,并获取单击位置。方便你绘制类似于 Toolbox 的窗体
  • 乱序的菜单项,除非你手动指定菜单顺序
package io.github.zimoyin.xianyukefuimport androidx.compose.runtime.*
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.graphics.toAwtImage
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.window.Notification
import androidx.compose.ui.window.TrayState
import androidx.compose.ui.window.rememberTrayState
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.awt.*
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
import java.awt.event.MouseListener
import java.util.*
import javax.swing.*
import javax.swing.border.Border/*** 托盘窗口* 使用 JDialog 作为 JPopupMenu 载体。实现托盘菜单。* 允许在里面设置复杂菜单项,并解决了中文乱码问题。* 使用方式与 Tray() 接近** @param icon 图标* @param tooltip 提示* @param state 控制托盘和显示通知的状态* @param onClick 菜单被鼠标单击时触发,无论是左键还是右键* @param onAction 菜单被双击时触发* @param onVisible 菜单显示时触发* @param onInvisible 菜单隐藏时触发* @param isSort 是否对菜单进行排序,默认为 false* @param setLookAndFeel 设置Swing 的皮肤。如果使用系统的皮肤请使用 UIManager.getSystemLookAndFeelClassName() 获取值* @param content 菜单内容*/
@Composable
fun TrayWindow(icon: Painter,tooltip: String? = null,state: TrayState = rememberTrayState(),onClick: (TrayClickEvent) -> Unit = {},isSort: Boolean = false,onAction: () -> Unit = {},onVisible: () -> Unit = {},onInvisible: () -> Unit = {},style: ComponentStyle = ComponentStyle(),setLookAndFeel: String? = null,content: @Composable MenuScope.() -> Unit = {},
) {setLookAndFeel?.let { UIManager.setLookAndFeel(it) }val awtIcon = remember(icon) {icon.toAwtImage(GlobalDensity, GlobalLayoutDirection, iconSize)}val menuWindow = remember { JDialog() }.apply {isUndecorated = true//作为菜单载体不需要存在可以视的窗体setSize(0, 0)}val coroutineScopeR = rememberCoroutineScope()val onClickR by rememberUpdatedState(onClick)val onActionR by rememberUpdatedState(onAction)val contentR by rememberUpdatedState(content)val onVisibleR by rememberUpdatedState(onVisible)val onInvisibleR by rememberUpdatedState(onInvisible)//创建JPopupMenuval menu: JPopupMenu = remember {TrayMenu(onVisible = {menuWindow.isVisible = trueonVisibleR()},onInvisible = {menuWindow.isVisible = falseonInvisibleR()})}.apply {style.setStyle2(this)}val menuScopeR by rememberUpdatedState(MenuScope(menu, isSort = isSort))//重绘菜单menu.removeAll()contentR(menuScopeR)val menuSizeR = calculationMenuSize(menu)val trayIcon = remember {TrayIcon(awtIcon).apply {isImageAutoSize = true//给托盘图标添加鼠标监听addMouseListener(object : MouseAdapter() {override fun mouseReleased(e: MouseEvent) {val pointer = MouseInfo.getPointerInfo().locationonClickR(TrayClickEvent(e.x,e.y,pointer.x,pointer.y,ButtonType.createButtonType(e.button),e.isPopupTrigger,e))if (e.button == 3 && e.isPopupTrigger) {openMenu(pointer, menuWindow, menu, menuSizeR)}}})addActionListener {onActionR()}}}.apply {if (toolTip != tooltip) toolTip = tooltip}DisposableEffect(Unit) {// 将托盘图标添加到系统的托盘实例中SystemTray.getSystemTray().add(trayIcon)state.notificationFlow.onEach(trayIcon::displayMessage).launchIn(coroutineScopeR)onDispose {menuWindow.dispose()SystemTray.getSystemTray().remove(trayIcon)}}
}private fun TrayIcon.displayMessage(notification: Notification) {val messageType = when (notification.type) {Notification.Type.None -> TrayIcon.MessageType.NONENotification.Type.Info -> TrayIcon.MessageType.INFONotification.Type.Warning -> TrayIcon.MessageType.WARNINGNotification.Type.Error -> TrayIcon.MessageType.ERROR}displayMessage(notification.title, notification.message, messageType)
}/*** 弹出菜单* @param menuWindow 菜单绑定的容器* @param menu 菜单*/
private fun openMenu(pointer: Point, menuWindow: JDialog, menu: JPopupMenu, menuSize: Dimension) {val x = pointer.xval y = pointer.y//右键点击弹出JPopupMenu绑定的载体以及JPopupMenumenuWindow.setLocation(x, y)menuWindow.isVisible = truemenu.show(menuWindow, 3, 0 - (menuSize.height + 3))
}/*** 点击事件*/
data class TrayClickEvent(val x: Int,val y: Int,val mouseX: Int,val mouseY: Int,val buttonType: ButtonType,val isPopupTrigger: Boolean,val awtEvent: MouseEvent,
)/*** 按钮类型*/
enum class ButtonType {LEFT,RIGHT,UNDEFINED;companion object {fun createButtonType(button: Int): ButtonType = when (button) {1 -> LEFT3 -> RIGHTelse -> UNDEFINED}}
}/*** 计算菜单的尺寸*/
fun calculationMenuSize(menu: JPopupMenu): Dimension {var menuHeight = 0var menuWidth = 0for (component in menu.components) {if (component is JMenuItem && component.isVisible) {val size = component.getPreferredSize()menuHeight += size.heightmenuWidth += size.width}}return Dimension(menuWidth, menuHeight)
}/*** 菜单域,用于添加控件*/
class MenuScope(val menu: JPopupMenu, val menuItem: JMenu? = null, var isSort: Boolean = false) {private fun Painter.toAwtImageIcon(): ImageIcon {return ImageIcon(toAwtImage(GlobalDensity, GlobalLayoutDirection))}companion object {private val orderMap = HashMap<Int, Int>()private val COM = HashMap<Int, HashSet<Order>>()}data class Order(val key: UUID,var order: Int,) {override fun equals(other: Any?): Boolean {if (this === other) return trueif (other !is Order) return falseif (key != other.key) return falsereturn true}override fun hashCode(): Int {return key.hashCode()}}fun getItemCount(): Int {return menuItem?.itemCount ?: menu.componentCount}private fun getOrderKey(): Int {return menuItem?.hashCode() ?: menu.hashCode()}@Composableprivate fun rememberOrder(): Int {if (!isSort) return -1val orderKey = getOrderKey()val key by remember { mutableStateOf(UUID.randomUUID()) }val list = COM.getOrPut(orderKey) {hashSetOf()}var order = list.lastOrNull { it.key == key }if (order == null) {order = Order(key, list.size)if (order.order <= getItemCount()) list.add(order)else order.order -= 1}//        println("${if (menuItem != null) "menuItem" else "menu"} : $order itemCount: ${getItemCount()}   key: $key")return order.order}private fun removeOrder(order: Int) {if (order == -1) returnval orderKey = getOrderKey()val list = COM[orderKey] ?: returnif (list.isEmpty()) returnlist.removeIf {it.order == order}val result = list.filter {it.order >= order}.map {Order(it.key, it.order - 1)}result.forEach { rus ->list.removeIf {it.key == rus.key}}list.addAll(result)}/*** 通用菜单项** @param text 菜单项文本内容,默认为 null* @param icon 菜单项图标,默认为 null* @param enabled 是否启用,默认为 true* @param mnemonic 快捷键字符,默认为 null* @param style 组件样式,默认为 [ComponentStyle]* @param orderIndex 菜单项排序索引,默认为 -1* @param onClick 点击菜单项时的回调函数*/@Composablefun Item(text: String? = null,icon: Painter? = null,enabled: Boolean = true,mnemonic: Char? = null,style: ComponentStyle = ComponentStyle(),orderIndex: Int = -1,onClick: () -> Unit = {},) {val scope = rememberCoroutineScope()val order = if (orderIndex >= 0) {scope.launch { withContext(Dispatchers.IO) { initCustomSorting() } }if (isSort) orderIndex else -1} else {rememberOrder()}fun createItem() = JMenuItem(text, icon?.toAwtImageIcon()).apply {addActionListener {if (isEnabled) onClick()}if (mnemonic != null) this.accelerator = KeyStroke.getKeyStroke(mnemonic.uppercaseChar())isEnabled = enabledstyle.setStyle(this)
//            println("text: $text  order: $order sort:$isSort")menuItem?.add(this, order) ?: menu.add(this, order)}var item by remember { mutableStateOf(createItem()) }LaunchedEffect(icon, text, enabled, onClick, style.id(), mnemonic, order) {menuItem?.remove(item) ?: menu.remove(item)item = createItem()}if (menuItem != null) {menuItem.remove(item)menuItem.add(item, order)}DisposableEffect(Unit) {onDispose {menuItem?.remove(item) ?: menu.remove(item)removeOrder(order)}}}/*** 文字标签** @param text 标签文本内容* @param enabled 是否启用,默认为 true* @param mnemonic 快捷键字符,默认为 null* @param style 组件样式,默认为 [ComponentStyle]* @param orderIndex 标签排序索引,默认为 -1* @param onClick 点击标签时的回调函数*/@Composablefun Label(text: String,enabled: Boolean = true,mnemonic: Char? = null,style: ComponentStyle = ComponentStyle(),orderIndex: Int = -1,onClick: () -> Unit = {},) {Item(text, enabled = enabled, mnemonic = mnemonic, style = style, onClick = onClick, orderIndex = orderIndex)}/*** 分割线* @param orderIndex 排序序号,-1表示默认排序*/@Composablefun Separator(orderIndex: Int = -1) {check(menuItem == null) { "Separator only support menu" }val scope = rememberCoroutineScope()val order = if (orderIndex >= 0) {scope.launch { withContext(Dispatchers.IO) { initCustomSorting() } }if (isSort) orderIndex else -1} else {rememberOrder()}val jSeparator = remember {JSeparator(SwingConstants.HORIZONTAL).apply {menu.add(this, order)}}DisposableEffect(Unit) {onDispose {menu.remove(jSeparator)removeOrder(order)}}}/*** 垂直分割线* @param orderIndex 排序序号,-1表示默认排序*/@Composablefun VerticalSeparator(orderIndex: Int = -1) {check(menuItem == null) { "VerticalSeparator only support menu" }val scope = rememberCoroutineScope()val order = if (orderIndex >= 0) {scope.launch { withContext(Dispatchers.IO) { initCustomSorting() } }if (isSort) orderIndex else -1} else {rememberOrder()}val jSeparator = remember {JSeparator(SwingConstants.VERTICAL).apply {menu.add(this, order)removeOrder(order)}}DisposableEffect(Unit) {onDispose {menu.remove(jSeparator)removeOrder(order)}}}/*** 复选框菜单项** @param text 菜单项文本内容,默认为 null* @param icon 菜单项图标,默认为 null* @param selected 是否选中,默认为 false* @param enabled 是否启用,默认为 true* @param mnemonic 快捷键字符,默认为 null* @param style 组件样式,默认为 [ComponentStyle]* @param orderIndex 菜单项排序索引,默认为 -1* @param onCheckedChange 复选框状态变化时的回调函数*/@Composablefun CheckboxItem(text: String? = null,icon: Painter? = null,selected: Boolean = false,enabled: Boolean = true,mnemonic: Char? = null,style: ComponentStyle = ComponentStyle(),orderIndex: Int = -1,onCheckedChange: (Boolean) -> Unit = {},) {val scope = rememberCoroutineScope()val order = if (orderIndex >= 0) {scope.launch { withContext(Dispatchers.IO) { initCustomSorting() } }if (isSort) orderIndex else -1} else {rememberOrder()}fun createItem() = JCheckBoxMenuItem(text, icon?.toAwtImageIcon(), selected).apply {addActionListener {onCheckedChange(isSelected)}if (mnemonic != null) this.accelerator = KeyStroke.getKeyStroke(mnemonic.uppercaseChar())isEnabled = enabledstyle.setStyle(this)menuItem?.add(this, order) ?: menu.add(this, order)}var item by remember { mutableStateOf(createItem()) }LaunchedEffect(icon, text, enabled, selected, style.id(), mnemonic, onCheckedChange, orderIndex) {menuItem?.remove(item) ?: menu.remove(item)item = createItem()}DisposableEffect(Unit) {onDispose {menuItem?.remove(item) ?: menu.remove(item)removeOrder(order)}}}/*** 单选按钮菜单项** @param text 菜单项文本内容,默认为 null* @param icon 菜单项图标,默认为 null* @param selected 是否选中,默认为 false* @param enabled 是否启用,默认为 true* @param style 组件样式,默认为 [ComponentStyle]* @param orderIndex 菜单项排序索引,默认为 -1* @param onCheckedChange 单选按钮状态变化时的回调函数**/@Composablefun RadioButtonItem(text: String? = null,icon: Painter? = null,selected: Boolean = false,enabled: Boolean = true,style: ComponentStyle = ComponentStyle(),orderIndex: Int = -1,onCheckedChange: (Boolean) -> Unit = {},) {val scope = rememberCoroutineScope()val order = if (orderIndex >= 0) {scope.launch { withContext(Dispatchers.IO) { initCustomSorting() } }if (isSort) orderIndex else -1} else {rememberOrder()}fun createItem() = JRadioButton(text, icon?.toAwtImageIcon(), selected).apply {addActionListener {onCheckedChange(isSelected)}isEnabled = enabledstyle.setStyle(this)menuItem?.add(this, order) ?: menu.add(this, order)}var item by remember {mutableStateOf(createItem())}LaunchedEffect(icon, text, enabled, selected, style.id(), onCheckedChange, orderIndex) {menuItem?.remove(item) ?: menu.remove(item)item = createItem()}DisposableEffect(Unit) {onDispose {menuItem?.remove(item) ?: menu.remove(item)removeOrder(order)}}}/*** 子菜单** @param text 子菜单名称* @param visible 是否可见,默认为 true* @param enabled 是否启用,默认为 true* @param mnemonic 快捷键字符,默认为 null* @param style 组件样式,默认为 [ComponentStyle]* @param orderIndex 菜单项排序索引,默认为 -1* @param content 菜单内容的组合构建器**/@Composablefun Menu(text: String = "子菜单",visible: Boolean = true,enabled: Boolean = true,mnemonic: Char? = null,style: ComponentStyle = ComponentStyle(),orderIndex: Int = -1,content: @Composable MenuScope.() -> Unit,) {val scope = rememberCoroutineScope()val order = if (orderIndex >= 0) {scope.launch { withContext(Dispatchers.IO) { initCustomSorting() } }if (isSort) orderIndex else -1} else {rememberOrder()}fun createItem() = JMenu(text).apply {isVisible = visibleisEnabled = enabledif (mnemonic != null) this.accelerator = KeyStroke.getKeyStroke(mnemonic.uppercaseChar())style.setStyle(this)menuItem?.add(this, order) ?: menu.add(this, order)}var item by remember {mutableStateOf(createItem())}MenuScope(menu, item, isSort = isSort).apply {content(this)}LaunchedEffect(text, enabled, visible, style.id(), content, mnemonic, orderIndex) {menuItem?.remove(item) ?: menu.remove(item)item = createItem()}DisposableEffect(Unit) {onDispose {menuItem?.remove(item) ?: menu.remove(item)removeOrder(order)}}}@Deprecated("可能存在bug")@Composablefun Component(orderIndex: Int = -1,content: @Composable MenuScope.() -> Component,) {val scope = rememberCoroutineScope()val order = if (orderIndex >= 0) {scope.launch { withContext(Dispatchers.IO) { initCustomSorting() } }if (isSort) orderIndex else -1} else {rememberOrder()}val item by rememberUpdatedState(content())DisposableEffect(order, content) {menuItem?.add(item, order) ?: menu.add(item, order)onDispose {menuItem?.remove(item) ?: menu.remove(item)removeOrder(order)}}}@Deprecated("可能存在bug")@Composablefun Component(orderIndex: Int = -1,component: Component,) {val scope = rememberCoroutineScope()val order = if (orderIndex >= 0) {scope.launch { withContext(Dispatchers.IO) { initCustomSorting() } }if (isSort) orderIndex else -1} else {rememberOrder()}val item = remember { component }menuItem?.add(item, order) ?: menu.add(item, order)DisposableEffect(orderIndex, component) {onDispose {menuItem?.remove(item) ?: menu.remove(item)removeOrder(order)}}}/*** 初始化菜单排序*/private fun initCustomSorting() {if (!isSort) returnif (menu.components.count { !it.isVisible } <= 9) {for (i in 0..10) {menu.add(JMenuItem("Null").apply {isVisible = false})}}if (menuItem != null) {var count = 0var composeCount = 0for (i in 0 until menuItem.itemCount) {if (!menuItem.getItem(i).isVisible) {count++} else {composeCount++}}if (count <= 9) {for (i in 0..10) {menuItem.add(JMenuItem("Null").apply {isVisible = false})}}}}}/*** 菜单主体*/
internal class TrayMenu(val onInvisible: () -> Unit = {},val onVisible: () -> Unit = {},
) : JPopupMenu() {init {setSize(100, 30)}override fun firePopupMenuWillBecomeInvisible() {onInvisible()}override fun firePopupMenuWillBecomeVisible() {super.firePopupMenuWillBecomeVisible()onVisible()}
}/*** 组件样式*/
data class ComponentStyle(/*** 组件字体*/val font: Font? = null,/*** 组件背景色*/val background: androidx.compose.ui.graphics.Color? = null,/*** 组件文字颜色*/val foreground: androidx.compose.ui.graphics.Color? = null,/*** 组件边框*/val border: Border? = null,/*** 组件边距*/val margin: Insets? = null,/*** 组件位置*/val bounds: Rectangle? = null,/*** 组件位置*/val location: Point? = null,/*** 组件大小*/val size: Dimension? = null,
) {private var color: Color? = background?.toAwtColor()/*** 鼠标进入事件*/val onMouseEnter: (MouseEvent) -> Unit = {color = it.component.backgroundit.component.background = color}/*** 鼠标离开事件*/val onMouseExit: (MouseEvent) -> Unit = {it.component.background = color ?: Color.white}/*** 鼠标点击事件*/val onMouseClick: (MouseEvent) -> Unit = {}/*** 鼠标按下事件*/val onMousePressed: (MouseEvent) -> Unit = {}/*** 鼠标释放事件*/val onMouseReleased: (MouseEvent) -> Unit = {}/*** 计算组件样式的唯一标识,注意部分样式未能计算到*/fun id(): Int {val s = font?.hashCode().toString() +background?.toArgb().toString() +foreground?.toArgb().toString() +margin?.top.toString() + margin?.left?.toString() + margin?.bottom?.toString() + margin?.right?.toString() +bounds?.x?.toString() + bounds?.y.toString() + bounds?.height.toString() + bounds?.width.toString() +location?.x.toString() + location?.y.toString() +size?.height.toString() + size?.width.toString()return s.hashCode()}fun setStyle(component: AbstractButton) {val style = thisif (font != null) component.font = fontif (foreground != null) component.foreground = foreground.toAwtColor()if (background != null) component.background = background.toAwtColor()if (border != null) component.border = borderif (size != null) component.size = this.sizeif (location != null) component.location = this.locationif (margin != null) component.margin = marginif (bounds != null) component.bounds = boundscomponent.addMouseListener(object : MouseListener {override fun mouseClicked(e: MouseEvent) {style.onMouseClick(e)}override fun mousePressed(e: MouseEvent) {style.onMousePressed(e)}override fun mouseReleased(e: MouseEvent) {style.onMouseReleased(e)}override fun mouseEntered(e: MouseEvent) {style.onMouseEnter(e)}override fun mouseExited(e: MouseEvent) {style.onMouseExit(e)}})}fun setStyle2(component: JComponent) {val style = thisif (font != null) component.font = fontif (foreground != null) component.foreground = foreground.toAwtColor()if (background != null) component.background = background.toAwtColor()if (border != null) component.border = borderif (size != null) component.size = this.sizeif (location != null) component.location = this.locationif (bounds != null) component.bounds = boundscomponent.addMouseListener(object : MouseListener {override fun mouseClicked(e: MouseEvent) {style.onMouseClick(e)}override fun mousePressed(e: MouseEvent) {style.onMousePressed(e)}override fun mouseReleased(e: MouseEvent) {style.onMouseReleased(e)}override fun mouseEntered(e: MouseEvent) {style.onMouseEnter(e)}override fun mouseExited(e: MouseEvent) {style.onMouseExit(e)}})}
}// 辅助函数
// 来自于 Compose 内部的函数,不确定是否会引发问题
internal val GlobalDensityget() = GraphicsEnvironment.getLocalGraphicsEnvironment().defaultScreenDevice.defaultConfiguration.density
private val GraphicsConfiguration.density: Densityget() = Density(defaultTransform.scaleX.toFloat(),fontScale = 1f)internal val GlobalLayoutDirection get() = Locale.getDefault().layoutDirection
internal val Locale.layoutDirection: LayoutDirectionget() = ComponentOrientation.getOrientation(this).layoutDirection
internal val ComponentOrientation.layoutDirection: LayoutDirectionget() = when {isLeftToRight -> LayoutDirection.LtrisHorizontal -> LayoutDirection.Rtlelse -> LayoutDirection.Ltr}internal val iconSize = when (DesktopPlatform.Current) {// https://doc.qt.io/qt-5/qtwidgets-desktop-systray-example.html (search 22x22)DesktopPlatform.Linux -> Size(22f, 22f)// https://doc.qt.io/qt-5/qtwidgets-desktop-systray-example.html (search 16x16)DesktopPlatform.Windows -> Size(16f, 16f)// https://medium.com/@acwrightdesign/creating-a-macos-menu-bar-application-using-swiftui-54572a5d5f87DesktopPlatform.MacOS -> Size(22f, 22f)DesktopPlatform.Unknown -> Size(32f, 32f)
}enum class DesktopPlatform {Linux,Windows,MacOS,Unknown;companion object {/*** Identify OS on which the application is currently running.*/val Current: DesktopPlatform by lazy {val name = System.getProperty("os.name")when {name?.startsWith("Linux") == true -> Linuxname?.startsWith("Win") == true -> Windowsname == "Mac OS X" -> MacOSelse -> Unknown}}}
}private fun androidx.compose.ui.graphics.Color.toAwtColor(): Color = Color(this.red, this.green, this.blue, this.alpha)

使用示例

import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.window.Tray
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.WindowState
import androidx.compose.ui.window.application
import androidx.compose.ui.window.rememberNotification
import androidx.compose.ui.window.rememberTrayState
import androidx.compose.ui.window.rememberWindowState
import io.github.zimoyin.xianyukefu.ButtonType
import io.github.zimoyin.xianyukefu.TrayWindow
import javax.swing.JButtonfun main() = application {var count by remember { mutableStateOf(0) }val WindowState = rememberWindowState()val isWindowShow = remember { mutableStateOf(true) }val trayState = rememberTrayState()val notification = rememberNotification("Notification", "Message from MyApp!")TrayWindow(state = trayState,icon = TrayIcon,onAction = {if (!isWindowShow.value) isWindowShow.value = trueWindowState.isMinimized = false},onClick = {if (!isWindowShow.value) isWindowShow.value = trueWindowState.isMinimized = false}) {Box {Text("23123")}Item("增加值") {count++}Item("发送通知") {trayState.sendNotification(notification)}Item("退出") {exitApplication()}// Item// Label// Separator// VerticalSeparator// CheckboxItem// RadioButtonItem// Menu// Component // 用于添加 JWT 的组件}Window(onCloseRequest = {isWindowShow.value},icon = MyAppIcon,state = WindowState) {// Content:Box(modifier = Modifier.fillMaxSize(),contentAlignment = Alignment.Center) {Text(text = "Value: $count")}}}object MyAppIcon : Painter() {override val intrinsicSize = Size(256f, 256f)override fun DrawScope.onDraw() {drawOval(Color.Green, Offset(size.width / 4, 0f), Size(size.width / 2f, size.height))drawOval(Color.Blue, Offset(0f, size.height / 4), Size(size.width, size.height / 2f))drawOval(Color.Red, Offset(size.width / 4, size.height / 4), Size(size.width / 2f, size.height / 2f))}
}object TrayIcon : Painter() {override val intrinsicSize = Size(256f, 256f)override fun DrawScope.onDraw() {drawOval(Color(0xFFFFA500))}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/908360.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/908360.shtml
英文地址,请注明出处:http://en.pswp.cn/news/908360.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++11 Move Constructors and Move Assignment Operators 从入门到精通

文章目录 一、引言二、基本概念2.1 右值引用&#xff08;Rvalue References&#xff09;2.2 移动语义&#xff08;Move Semantics&#xff09; 三、移动构造函数&#xff08;Move Constructors&#xff09;3.1 定义和语法3.2 示例代码3.3 使用场景 四、移动赋值运算符&#xff…

Linux配置yum 时间同步服务 关闭防火墙 关闭ESlinux

1、配置yum 1.1、Could not resolve host: mirrorlist.centos.org; 未知的错误 https://blog.csdn.net/fansfi/article/details/146369946?fromshareblogdetail&sharetypeblogdetail&sharerId146369946&sharereferPC&sharesourceRockandrollman&sharefr…

使用 uv 工具快速部署并管理 vLLM 推理环境

uv&#xff1a;现代 Python 项目管理的高效助手 uv&#xff1a;Rust 驱动的 Python 包管理新时代 在部署大语言模型&#xff08;LLM&#xff09;推理服务时&#xff0c;vLLM 是一个备受关注的方案&#xff0c;具备高吞吐、低延迟和对 OpenAI API 的良好兼容性。为了提高部署效…

基于sqlite的任务锁(支持多进程/多线程)

前言 介绍 任务锁,在多进程服务间控制耗时任务的锁,确保相同id的耗时任务同时只有一个在执行 依赖 SqliteOp,参考这篇文章 https://blog.csdn.net/weixin_43721000/article/details/137019125 实现方式 utils/taskLock.py import timefrom utils.SqliteOp import Sqli…

html表格转换为markdown

文章目录 工具功能亮点1.核心实现解析1. 剪贴板交互2. HTML检测与提取3. 转换规则设计 2. 完整代码 在日常工作中&#xff0c;我们经常遇到需要将网页表格快速转换为Markdown格式的场景。无论是文档编写、知识整理还是数据迁移&#xff0c;手动转换既耗时又容易出错。本文将介绍…

IDEA 中 Undo Commit,Revert Commit,Drop Commit区别

一、Undo Commit 适用情况&#xff1a;代码修改完了&#xff0c;已经Commit了&#xff0c;但是还未push&#xff0c;然后发现还有地方需要修改&#xff0c;但是又不想增加一个新的Commit记录。这时可以进行Undo Commit&#xff0c;修改后再重新Commit。如果已经进行了Push&…

【Linux】Linux 进程间通讯-管道

参考博客&#xff1a;https://blog.csdn.net/sjsjnsjnn/article/details/125864580 一、进程间通讯介绍 1.1 进程间通讯的概念 进程通信&#xff08;Interprocess communication&#xff09;&#xff0c;简称&#xff1a;IPC 本来进程之间是相互独立的。但是由于不同的进程…

深度剖析 DeepSeek 开源模型部署与应用:策略、权衡与未来走向

在人工智能技术呈指数级发展的当下&#xff0c;大模型已然成为推动各行业变革的核心驱动力。DeepSeek 开源模型以其卓越的性能和灵活的开源特性&#xff0c;吸引了众多企业与开发者的目光。如何高效且合理地部署与运用 DeepSeek 模型&#xff0c;成为释放其巨大潜力的关键所在&…

第34次CCF-CSP认证真题解析(目标300分做法)

第34次CCF-CSP认证 矩阵重塑&#xff08;其一&#xff09;AC代码及解析矩阵重塑&#xff08;其二&#xff09;AC代码及解析货物调度AC代码及解析 矩阵重塑&#xff08;其一&#xff09; 输入输出及样例&#xff1a; AC代码及解析 1.线性化原矩阵 &#xff1a;由于cin的特性我们…

智能制造数字孪生全要素交付一张网:智造中枢,孪生领航,共建智造生态共同体

在制造业转型升级的浪潮中&#xff0c;数字孪生技术正成为推动行业变革的核心引擎。从特斯拉通过数字孪生体实现车辆全生命周期优化&#xff0c;到海尔卡奥斯工业互联网平台赋能千行百业&#xff0c;数字孪生技术已从概念验证走向规模化落地。通过构建覆盖全国的交付网络&#…

【技术】跨设备链路聚合的技术——M-LAG

原创&#xff1a;厦门微思网络 M-LAG&#xff08;Multichassis Link Aggregation Group&#xff09;提供一种跨设备链路聚合的技术。M-LAG通过将两台接入交换机以同一个状态和用户侧设备或服务器进行跨设备的链路聚合&#xff0c;把链路的可靠性从单板级提升到设备级。同时&…

AI健康小屋+微高压氧舱:科技如何重构我们的健康防线?

目前&#xff0c;随着科技和社会的不断发展&#xff0c;人们的生活水平和方式有了翻天覆地的变化。 从吃饱穿暖到吃好喝好再到健康生活&#xff0c;观念也在逐渐发生改变。 尤其是在21世纪&#xff0c;大家对健康越来越重视&#xff0c;这就不得不提AI健康小屋和氧舱。 一、A…

Python训练营---Day44

DAY 44 预训练模型 知识点回顾&#xff1a; 预训练的概念常见的分类预训练模型图像预训练模型的发展史预训练的策略预训练代码实战&#xff1a;resnet18 作业&#xff1a; 尝试在cifar10对比如下其他的预训练模型&#xff0c;观察差异&#xff0c;尽可能和他人选择的不同尝试通…

1.文件操作相关的库

一、filesystem(C17) 和 fstream 1.std::filesystem::path - cppreference.cn - C参考手册 std::filesystem::path 表示路径 构造函数&#xff1a; path( string_type&& source, format fmt auto_format ); 可以用string进行构造&#xff0c;也可以用string进行隐式类…

【 java 集合知识 第二篇 】

目录 1.Map集合 1.1.快速遍历Map 1.2.HashMap实现原理 1.3.HashMap的扩容机制 1.4.HashMap在多线程下的问题 1.5.解决哈希冲突的方法 1.6.HashMap的put过程 1.7.HashMap的key使用什么类型 1.8.HashMapkey可以为null的原因 1.9.HashMap为什么不采用平衡二叉树 1.10.Hash…

【Dify 知识库 API】“根据文本更新文档” 真的是差异更新吗?一文讲透真实机制!

在使用 Dify 知识库 API 过程中,很多开发者在调用 /datasets/{dataset_id}/document/update-by-text 接口时,常常会产生一个疑问: 👉 这个接口到底是 “智能差异更新” 还是 “纯覆盖更新”? 网上的资料并不多,很多人根据接口名误以为是增量更新。今天我结合官方源码 …

大模型如何革新用户价值、内容匹配与ROI预估

写在前面 在数字营销的战场上,理解用户、精准触达、高效转化是永恒的追求。传统方法依赖结构化数据和机器学习模型,在用户价值评估、人群素材匹配以及策略ROI预估等核心问题上取得了显著成就。然而,随着数据维度日益复杂,用户行为愈发多变,传统方法也面临着特征工程繁琐、…

基于端到端深度学习模型的语音控制人机交互系统

基于端到端深度学习模型的语音控制人机交互系统 摘要 本文设计并实现了一个基于端到端深度学习模型的人机交互系统,通过语音指令控制其他设备的程序运行,并将程序运行结果通过语音合成方式反馈给用户。系统采用Python语言开发,使用PyTorch框架实现端到端的语音识别(ASR)…

【2025年】解决Burpsuite抓不到https包的问题

环境&#xff1a;windows11 burpsuite:2025.5 在抓取https网站时&#xff0c;burpsuite抓取不到https数据包&#xff0c;只显示&#xff1a; 解决该问题只需如下三个步骤&#xff1a; 1、浏览器中访问 http://burp 2、下载 CA certificate 证书 3、在设置--隐私与安全--…

Jenkins 工作流程

1. 触发构建 Jenkins 的工作流程从触发构建开始。构建可以由以下几种方式触发&#xff1a; 代码提交触发&#xff1a;通过与版本控制系统&#xff08;如 Git、SVN&#xff09;集成&#xff0c;当代码仓库有新的提交时&#xff0c;Jenkins 会自动触发构建。 定时触发&#xff…