# SelectionMenu 文本选择菜单,适用于富文本组件通过[bindSelectionMenu](./ts-basic-components-richeditor.md#bindselectionmenu)绑定自定义文本选择菜单,建议绑定鼠标右键或者鼠标选中方式弹出,不支持作为普通组件单独使用。 > **说明:** > > 该组件从API Version 11开始支持。后续版本如有新增内容,则采用上角标单独标记该内容的起始版本。 ## 导入模块 ``` import { SelectionMenu, EditorMenuOptions, ExpandedMenuOptions, EditorEventInfo, SelectionMenuOptions } from '@kit.ArkUI' ``` ## 子组件 无。 ## SelectionMenu SelectionMenu(options: SelectionMenuOptions): void 入参为空时,文本选择菜单组件SelectionMenu内容区大小及组件大小为零。表现例如,富文本组件[RichEditor](ts-basic-components-richeditor.md)使用[bindSelectionMenu](ts-basic-components-richeditor.md#bindselectionmenu)接口绑定一个SelectionMenu的右键菜单,则右键富文本组件区域时无任何菜单弹出。 **装饰器类型:**\@Builder **原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。 **系统能力:** SystemCapability.ArkUI.ArkUI.Full **参数:** | 参数名 | 类型 | 必填 | 说明 | | -------- | -------- | -------- | -------- | | options | [SelectionMenuOptions](#selectionmenuoptions) | 是 | 文本选择菜单可选项。 | ## SelectionMenuOptions SelectionMenuOptions定义SelectionMenu的可选菜单类型项及其具体配置参数。 **原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。 **系统能力:** SystemCapability.ArkUI.ArkUI.Full | 名称 | 类型 | 必填 | 说明 | | -------- | -------- | -------- | -------- | | editorMenuOptions | Array<[EditorMenuOptions](#editormenuoptions)> | 否 | 编辑菜单。
editorMenuOptions未配置时,不显示编辑菜单。
同时配置EditorMenuOptions中action和builder时,点击图标会同时响应。
点击编辑菜单图标默认不关闭整个菜单,应用可以通过action接口配置RichEditorController的closeSelectionMenu主动关闭菜单。 | | expandedMenuOptions | Array<[ExpandedMenuOptions](#expandedmenuoptions)> | 否 | 扩展下拉菜单。
expandedMenuOptions参数为空时无更多按钮,不显示扩展下拉菜单。
expandedMenuOptions参数不为空时显示更多按钮,配置菜单项收起在更多按钮中,点击更多按钮展示。 | | controller | [RichEditorController](ts-basic-components-richeditor.md#richeditorcontroller) | 否 | 富文本控制器不为空时显示默认系统菜单(包含剪切复制粘贴等部分)且默认菜单功能内置。
controller为空时不显示更多按钮,expandedMenuOptions参数不为空则显示下拉菜单中。
系统默认只支持复制粘贴富文本文本内容,图文混排需要应用自定义onCopy、onPaste接口。应用自行配置onCopy \| onPaste接口时,系统菜单默认复制粘贴失效,调用应用自定义函数。
**说明:**
点击自定义文本选择菜单内置复制功能选项后,自定义菜单消失选中文本高亮保留。
点击自定义文本选择菜单内置全选功能选项后,自定义菜单消失文本全选高亮。
点击自定义文本选择菜单内置粘贴功能选项后,空白处粘贴或者选中文本替换粘贴均是保留被复制文本的样式。
当富文本组件[RichEditor](ts-basic-components-richeditor.md)的copyOptions属性设置为`CopyOptions.None`时,内置的复制剪切功能不会被限制。| | onCopy | (event?: [EditorEventInfo](#editoreventinfo)) => void | 否 | 替代内置系统菜单复制项的事件回调。
生效前提是一定要有controller参数,有系统默认菜单才能替换内置复制功能。
**说明:**
event为返回信息。| | onPaste | (event?: [EditorEventInfo](#editoreventinfo)) => void | 否 | 替代内置系统菜单粘贴项的事件回调。
生效前提是一定要有controller参数,有系统默认菜单才能替换内置粘贴功能。
**说明:**
event为返回信息。 | | onCut | (event?: [EditorEventInfo](#editoreventinfo)) => void | 否 | 替代内置系统菜单剪切项的事件回调。
生效前提是一定要有controller参数,有系统默认菜单才能替换内置剪切功能。
**说明:**
event为返回信息。| | onSelectAll | (event?: [EditorEventInfo](#editoreventinfo)) => void | 否 | 替代内置系统菜单全选项的事件回调。
生效前提是一定要有controller参数,有系统默认菜单才能替换内置全选功能。
**说明:**
event为返回信息。| ## EditorMenuOptions 编辑菜单选项。 **原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。 **系统能力:** SystemCapability.ArkUI.ArkUI.Full | 名称 | 类型 | 必填 | 说明 | | -------- | -------- | -------- | -------- | | icon | [ResourceStr](ts-types.md#resourcestr) | 是 | 图标资源。 | | builder | () => void | 否 | 点击时显示用户自定义组件,自定义组件在构造时结合@Builder使用。 | | action | () => void | 否 | 点击菜单项的事件回调。 | ## ExpandedMenuOptions 扩展下拉菜单。 继承于[MenuItemOptions](ts-basic-components-menuitem.md#menuitemoptions对象说明)。 **原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。 **系统能力:** SystemCapability.ArkUI.ArkUI.Full | 名称 | 类型 | 必填 | 说明 | | -------- | -------- | -------- | -------- | | action | () => void | 否 | 点击菜单项的事件回调。 | ## EditorEventInfo 选中内容信息。 **原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。 **系统能力:** SystemCapability.ArkUI.ArkUI.Full | 名称 | 类型 | 必填 | 说明 | | -------- | -------- | -------- | -------- | | content | [RichEditorSelection](ts-basic-components-richeditor.md#richeditorselection) | 否 | 选中内容信息。| ## 属性 不支持[通用属性](ts-universal-attributes-size.md),宽度默认224vp, 高度自适应内容。 ## 事件 不支持[通用事件](ts-universal-events-click.md)。 ## 示例 该示例展示了文本绑定不同触发方式的自定义文本选择菜单的效果。 ```ts import { SelectionMenu, EditorMenuOptions, ExpandedMenuOptions, EditorEventInfo, SelectionMenuOptions } from '@kit.ArkUI' @Entry @Component struct Index { @State select: boolean = true controller: RichEditorController = new RichEditorController(); options: RichEditorOptions = { controller: this.controller } @State message: string = 'Hello world' @State textSize: number = 30 @State fontWeight: FontWeight = FontWeight.Normal @State start: number = -1 @State end: number = -1 @State visibleValue: Visibility = Visibility.Visible @State colorTransparent: Color = Color.Transparent @State textStyle: RichEditorTextStyle = {} private editorMenuOptions: Array = [ { icon: $r("app.media.ic_notepad_textbold"), action: () => { if (this.controller) { let selection = this.controller.getSelection(); let spans = selection.spans spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => { if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') { let span = item as RichEditorTextSpanResult this.textStyle = span.textStyle let start = span.offsetInSpan[0] let end = span.offsetInSpan[1] let offset = span.spanPosition.spanRange[0] if (this.textStyle.fontWeight != 11) { this.textStyle.fontWeight = FontWeight.Bolder } else { this.textStyle.fontWeight = FontWeight.Normal } this.controller.updateSpanStyle({ start: offset + start, end: offset + end, textStyle: this.textStyle }) } }) } } }, { icon: $r("app.media.ic_notepad_texttilt"), action: () => { if (this.controller) { let selection = this.controller.getSelection(); let spans = selection.spans spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => { if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') { let span = item as RichEditorTextSpanResult this.textStyle = span.textStyle let start = span.offsetInSpan[0] let end = span.offsetInSpan[1] let offset = span.spanPosition.spanRange[0] if (this.textStyle.fontStyle == FontStyle.Italic) { this.textStyle.fontStyle = FontStyle.Normal } else { this.textStyle.fontStyle = FontStyle.Italic } this.controller.updateSpanStyle({ start: offset + start, end: offset + end, textStyle: this.textStyle }) } }) } } }, { icon: $r("app.media.ic_notepad_underline"), action: () => { if (this.controller) { let selection = this.controller.getSelection(); let spans = selection.spans spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => { if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') { let span = item as RichEditorTextSpanResult this.textStyle = span.textStyle let start = span.offsetInSpan[0] let end = span.offsetInSpan[1] let offset = span.spanPosition.spanRange[0] if (this.textStyle.decoration) { if (this.textStyle.decoration.type == TextDecorationType.Underline) { this.textStyle.decoration.type = TextDecorationType.None } else { this.textStyle.decoration.type = TextDecorationType.Underline } } else { this.textStyle.decoration = { type: TextDecorationType.Underline, color: Color.Black } } this.controller.updateSpanStyle({ start: offset + start, end: offset + end, textStyle: this.textStyle }) } }) } } }, { icon: $r("app.media.app_icon"), action: () => { }, builder: (): void => this.sliderPanel() }, { icon: $r("app.media.ic_notepad_textcolor"), action: () => { if (this.controller) { let selection = this.controller.getSelection(); let spans = selection.spans spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => { if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') { let span = item as RichEditorTextSpanResult this.textStyle = span.textStyle let start = span.offsetInSpan[0] let end = span.offsetInSpan[1] let offset = span.spanPosition.spanRange[0] if (this.textStyle.fontColor == Color.Orange || this.textStyle.fontColor == '#FFFFA500') { this.textStyle.fontColor = Color.Black } else { this.textStyle.fontColor = Color.Orange } this.controller.updateSpanStyle({ start: offset + start, end: offset + end, textStyle: this.textStyle }) } }) } } }] private expandedMenuOptions: Array = [{ startIcon: $r("app.media.icon"), content: '词典', action: () => { } }, { startIcon: $r("app.media.icon"), content: '翻译', action: () => { } }, { startIcon: $r("app.media.icon"), content: '搜索', action: () => { } }] private expandedMenuOptions1: Array = [] private editorMenuOptions1: Array = [] private selectionMenuOptions: SelectionMenuOptions = { editorMenuOptions: this.editorMenuOptions, expandedMenuOptions: this.expandedMenuOptions, controller: this.controller, onCut: (event?: EditorEventInfo) => { if (event && event.content) { event.content.spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => { if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') { let span = item as RichEditorTextSpanResult console.info('test cut' + span.value) console.info('test start ' + span.offsetInSpan[0] + ' end: ' + span.offsetInSpan[1]) } }) } }, onPaste: (event?: EditorEventInfo) => { if (event && event.content) { event.content.spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => { if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') { let span = item as RichEditorTextSpanResult console.info('test onPaste' + span.value) console.info('test start ' + span.offsetInSpan[0] + ' end: ' + span.offsetInSpan[1]) } }) } }, onCopy: (event?: EditorEventInfo) => { if (event && event.content) { event.content.spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => { if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') { let span = item as RichEditorTextSpanResult console.info('test cut' + span.value) console.info('test start ' + span.offsetInSpan[0] + ' end: ' + span.offsetInSpan[1]) } }) } }, onSelectAll: (event?: EditorEventInfo) => { if (event && event.content) { event.content.spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => { if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') { let span = item as RichEditorTextSpanResult console.info('test onPaste' + span.value) console.info('test start ' + span.offsetInSpan[0] + ' end: ' + span.offsetInSpan[1]) } }) } } } @Builder sliderPanel() { Column() { Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) { Text('A').fontSize(15) Slider({ value: this.textSize, step: 10, style: SliderStyle.InSet }) .width(210) .onChange((value: number, mode: SliderChangeMode) => { if (this.controller) { let selection = this.controller.getSelection(); if (mode == SliderChangeMode.End) { if (this.textSize == undefined) { this.textSize = 0 } let spans = selection.spans spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => { if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') { this.textSize = Math.max(this.textSize, (item as RichEditorTextSpanResult).textStyle.fontSize) } }) } if (mode == SliderChangeMode.Moving || mode == SliderChangeMode.Click) { this.start = selection.selection[0] this.end = selection.selection[1] this.textSize = value this.controller.updateSpanStyle({ start: this.start, end: this.end, textStyle: { fontSize: this.textSize } }) } } }) Text('A').fontSize(20).fontWeight(FontWeight.Medium) }.borderRadius($r('sys.float.ohos_id_corner_radius_card')) } .shadow(ShadowStyle.OUTER_DEFAULT_MD) .backgroundColor(Color.White) .borderRadius($r('sys.float.ohos_id_corner_radius_card')) .padding(15) .height(48) } @Builder MyMenu() { Column() { SelectionMenu(this.selectionMenuOptions) } .width(256) .backgroundColor(Color.Transparent) } @Builder MyMenu2() { Column() { SelectionMenu({ editorMenuOptions: this.editorMenuOptions, expandedMenuOptions: this.expandedMenuOptions1, controller: this.controller, }) } .width(256) .backgroundColor(Color.Transparent) } @Builder MyMenu3() { Column() { SelectionMenu({ editorMenuOptions: this.editorMenuOptions1, expandedMenuOptions: this.expandedMenuOptions, controller: this.controller, }) } .width(256) .backgroundColor(Color.Transparent) } build() { Column() { Button("SetSelection") .onClick((event: ClickEvent) => { if (this.controller) { this.controller.setSelection(0, 2) } }) RichEditor(this.options) .onReady(() => { this.controller.addTextSpan(this.message, { style: { fontColor: Color.Orange, fontSize: 30 } }) this.controller.addTextSpan(this.message, { style: { fontColor: Color.Black, fontSize: 25 } }) }) .onSelect((value: RichEditorSelection) => { if (value.selection[0] == -1 && value.selection[1] == -1) { return } this.start = value.selection[0] this.end = value.selection[1] }) .bindSelectionMenu(RichEditorSpanType.TEXT, this.MyMenu3(), RichEditorResponseType.RIGHT_CLICK) .bindSelectionMenu(RichEditorSpanType.TEXT, this.MyMenu2(), RichEditorResponseType.SELECT) .borderWidth(1) .borderColor(Color.Red) .width(200) .height(200) } } } ``` > **说明:** > > 系统暂未预置加粗、斜体等图标,示例代码使用本地资源图标,开发者使用时需自行替换editorMenuOptions中icon项的资源。 ![selectionmenu](figures/selectionmenu.jpeg)