1# SelectionMenu 2 3 4文本选择菜单,适用于富文本组件通过[bindSelectionMenu](./ts-basic-components-richeditor.md#bindselectionmenu)绑定自定义文本选择菜单,建议绑定鼠标右键或者鼠标选中方式弹出,不支持作为普通组件单独使用。 5 6 7> **说明:** 8> 9> 该组件从API Version 11开始支持。后续版本如有新增内容,则采用上角标单独标记该内容的起始版本。 10 11 12## 导入模块 13 14``` 15import { SelectionMenu, EditorMenuOptions, ExpandedMenuOptions, EditorEventInfo, SelectionMenuOptions } from '@kit.ArkUI' 16``` 17 18## 子组件 19 20无。 21 22## SelectionMenu 23 24SelectionMenu(options: SelectionMenuOptions): void 25 26入参为空时,文本选择菜单组件SelectionMenu内容区大小及组件大小为零。表现例如,富文本组件[RichEditor](ts-basic-components-richeditor.md)使用[bindSelectionMenu](ts-basic-components-richeditor.md#bindselectionmenu)接口绑定一个SelectionMenu的右键菜单,则右键富文本组件区域时无任何菜单弹出。 27 28**装饰器类型:**\@Builder 29 30**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。 31 32**系统能力:** SystemCapability.ArkUI.ArkUI.Full 33 34**参数:** 35 36| 参数名 | 类型 | 必填 | 说明 | 37| -------- | -------- | -------- | -------- | 38| options | [SelectionMenuOptions](#selectionmenuoptions) | 是 | 文本选择菜单可选项。 | 39 40## SelectionMenuOptions 41 42SelectionMenuOptions定义SelectionMenu的可选菜单类型项及其具体配置参数。 43 44**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。 45 46**系统能力:** SystemCapability.ArkUI.ArkUI.Full 47 48| 名称 | 类型 | 必填 | 说明 | 49| -------- | -------- | -------- | -------- | 50| editorMenuOptions | Array<[EditorMenuOptions](#editormenuoptions)> | 否 | 编辑菜单。<br/>editorMenuOptions未配置时,不显示编辑菜单。<br/>同时配置EditorMenuOptions中action和builder时,点击图标会同时响应。<br/>点击编辑菜单图标默认不关闭整个菜单,应用可以通过action接口配置RichEditorController的closeSelectionMenu主动关闭菜单。 | 51| expandedMenuOptions | Array<[ExpandedMenuOptions](#expandedmenuoptions)> | 否 | 扩展下拉菜单。<br/>expandedMenuOptions参数为空时无更多按钮,不显示扩展下拉菜单。<br/>expandedMenuOptions参数不为空时显示更多按钮,配置菜单项收起在更多按钮中,点击更多按钮展示。 | 52| controller | [RichEditorController](ts-basic-components-richeditor.md#richeditorcontroller) | 否 | 富文本控制器不为空时显示默认系统菜单(包含剪切复制粘贴等部分)且默认菜单功能内置。<br/>controller为空时不显示更多按钮,expandedMenuOptions参数不为空则显示下拉菜单中。<br/>系统默认只支持复制粘贴富文本文本内容,图文混排需要应用自定义onCopy、onPaste接口。应用自行配置onCopy \| onPaste接口时,系统菜单默认复制粘贴失效,调用应用自定义函数。 <br/>**说明:**<br/> 点击自定义文本选择菜单内置复制功能选项后,自定义菜单消失选中文本高亮保留。<br/> 点击自定义文本选择菜单内置全选功能选项后,自定义菜单消失文本全选高亮。<br/> 点击自定义文本选择菜单内置粘贴功能选项后,空白处粘贴或者选中文本替换粘贴均是保留被复制文本的样式。<br/> 当富文本组件[RichEditor](ts-basic-components-richeditor.md)的copyOptions属性设置为`CopyOptions.None`时,内置的复制剪切功能不会被限制。| 53| onCopy | (event?: [EditorEventInfo](#editoreventinfo)) => void | 否 | 替代内置系统菜单复制项的事件回调。<br/>生效前提是一定要有controller参数,有系统默认菜单才能替换内置复制功能。<br/>**说明:**<br/> event为返回信息。| 54| onPaste | (event?: [EditorEventInfo](#editoreventinfo)) => void | 否 | 替代内置系统菜单粘贴项的事件回调。<br/>生效前提是一定要有controller参数,有系统默认菜单才能替换内置粘贴功能。<br/>**说明:**<br/> event为返回信息。 | 55| onCut | (event?: [EditorEventInfo](#editoreventinfo)) => void | 否 | 替代内置系统菜单剪切项的事件回调。<br/>生效前提是一定要有controller参数,有系统默认菜单才能替换内置剪切功能。<br/>**说明:**<br/>event为返回信息。| 56| onSelectAll | (event?: [EditorEventInfo](#editoreventinfo)) => void | 否 | 替代内置系统菜单全选项的事件回调。<br/>生效前提是一定要有controller参数,有系统默认菜单才能替换内置全选功能。<br/>**说明:**<br/>event为返回信息。| 57 58 59## EditorMenuOptions 60 61编辑菜单选项。 62 63**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。 64 65**系统能力:** SystemCapability.ArkUI.ArkUI.Full 66 67| 名称 | 类型 | 必填 | 说明 | 68| -------- | -------- | -------- | -------- | 69| icon | [ResourceStr](ts-types.md#resourcestr) | 是 | 图标资源。 | 70| builder | () => void | 否 | 点击时显示用户自定义组件,自定义组件在构造时结合@Builder使用。 | 71| action | () => void | 否 | 点击菜单项的事件回调。 | 72 73 74## ExpandedMenuOptions 75 76扩展下拉菜单。 77 78继承于[MenuItemOptions](ts-basic-components-menuitem.md#menuitemoptions对象说明)。 79 80**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。 81 82**系统能力:** SystemCapability.ArkUI.ArkUI.Full 83 84| 名称 | 类型 | 必填 | 说明 | 85| -------- | -------- | -------- | -------- | 86| action | () => void | 否 | 点击菜单项的事件回调。 | 87 88## EditorEventInfo 89 90选中内容信息。 91 92**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。 93 94**系统能力:** SystemCapability.ArkUI.ArkUI.Full 95 96| 名称 | 类型 | 必填 | 说明 | 97| -------- | -------- | -------- | -------- | 98| content | [RichEditorSelection](ts-basic-components-richeditor.md#richeditorselection) | 否 | 选中内容信息。| 99 100## 属性 101 102不支持[通用属性](ts-component-general-attributes.md),宽度默认224vp, 高度自适应内容。 103 104## 事件 105不支持[通用事件](ts-component-general-events.md)。 106 107## 示例 108 109该示例展示了文本绑定不同触发方式的自定义文本选择菜单的效果。 110 111```ts 112import { 113 SelectionMenu, 114 EditorMenuOptions, 115 ExpandedMenuOptions, 116 EditorEventInfo, 117 SelectionMenuOptions 118} from '@kit.ArkUI' 119 120@Entry 121@Component 122struct Index { 123 @State select: boolean = true; 124 controller: RichEditorController = new RichEditorController(); 125 options: RichEditorOptions = { controller: this.controller }; 126 @State message: string = 'Hello world'; 127 @State textSize: number = 30; 128 @State fontWeight: FontWeight = FontWeight.Normal; 129 @State start: number = -1; 130 @State end: number = -1; 131 @State visibleValue: Visibility = Visibility.Visible; 132 @State colorTransparent: Color = Color.Transparent; 133 @State textStyle: RichEditorTextStyle = {}; 134 private editorMenuOptions: Array<EditorMenuOptions> = 135 [ 136 { 137 icon: $r("app.media.ic_notepad_textbold"), action: () => { 138 if (this.controller) { 139 let selection = this.controller.getSelection(); 140 let spans = selection.spans; 141 spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => { 142 if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') { 143 let span = item as RichEditorTextSpanResult; 144 this.textStyle = span.textStyle; 145 let start = span.offsetInSpan[0]; 146 let end = span.offsetInSpan[1]; 147 let offset = span.spanPosition.spanRange[0]; 148 if (this.textStyle.fontWeight != 11) { 149 this.textStyle.fontWeight = FontWeight.Bolder; 150 } else { 151 this.textStyle.fontWeight = FontWeight.Normal; 152 } 153 this.controller.updateSpanStyle({ 154 start: offset + start, 155 end: offset + end, 156 textStyle: this.textStyle 157 }) 158 } 159 }) 160 } 161 } 162 }, 163 { 164 icon: $r("app.media.ic_notepad_texttilt"), action: () => { 165 if (this.controller) { 166 let selection = this.controller.getSelection(); 167 let spans = selection.spans; 168 spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => { 169 if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') { 170 let span = item as RichEditorTextSpanResult; 171 this.textStyle = span.textStyle; 172 let start = span.offsetInSpan[0]; 173 let end = span.offsetInSpan[1]; 174 let offset = span.spanPosition.spanRange[0]; 175 if (this.textStyle.fontStyle == FontStyle.Italic) { 176 this.textStyle.fontStyle = FontStyle.Normal; 177 } else { 178 this.textStyle.fontStyle = FontStyle.Italic; 179 } 180 this.controller.updateSpanStyle({ 181 start: offset + start, 182 end: offset + end, 183 textStyle: this.textStyle 184 }) 185 } 186 }) 187 } 188 } 189 }, 190 { 191 icon: $r("app.media.ic_notepad_underline"), 192 action: () => { 193 if (this.controller) { 194 let selection = this.controller.getSelection(); 195 let spans = selection.spans; 196 spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => { 197 if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') { 198 let span = item as RichEditorTextSpanResult; 199 this.textStyle = span.textStyle; 200 let start = span.offsetInSpan[0]; 201 let end = span.offsetInSpan[1]; 202 let offset = span.spanPosition.spanRange[0]; 203 if (this.textStyle.decoration) { 204 if (this.textStyle.decoration.type == TextDecorationType.Underline) { 205 this.textStyle.decoration.type = TextDecorationType.None; 206 } else { 207 this.textStyle.decoration.type = TextDecorationType.Underline; 208 } 209 } else { 210 this.textStyle.decoration = { type: TextDecorationType.Underline, color: Color.Black } 211 } 212 this.controller.updateSpanStyle({ 213 start: offset + start, 214 end: offset + end, 215 textStyle: this.textStyle 216 }) 217 } 218 }) 219 } 220 } 221 }, 222 { 223 icon: $r("app.media.ic_notepad_fontsize"), action: () => { 224 }, builder: (): void => this.sliderPanel() 225 }, 226 { 227 icon: $r("app.media.ic_notepad_textcolor"), action: () => { 228 if (this.controller) { 229 let selection = this.controller.getSelection(); 230 let spans = selection.spans; 231 spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => { 232 if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') { 233 let span = item as RichEditorTextSpanResult; 234 this.textStyle = span.textStyle; 235 let start = span.offsetInSpan[0]; 236 let end = span.offsetInSpan[1]; 237 let offset = span.spanPosition.spanRange[0]; 238 if (this.textStyle.fontColor == Color.Orange || this.textStyle.fontColor == '#FFFFA500') { 239 this.textStyle.fontColor = Color.Black; 240 } else { 241 this.textStyle.fontColor = Color.Orange; 242 } 243 this.controller.updateSpanStyle({ 244 start: offset + start, 245 end: offset + end, 246 textStyle: this.textStyle 247 }) 248 } 249 }) 250 } 251 } 252 }] 253 private expandedMenuOptions: Array<ExpandedMenuOptions> = 254 [{ 255 startIcon: $r("app.media.startIcon"), content: '词典', action: () => { 256 } 257 }, { 258 startIcon: $r("app.media.startIcon"), content: '翻译', action: () => { 259 } 260 }, { 261 startIcon: $r("app.media.startIcon"), content: '搜索', action: () => { 262 } 263 }] 264 private expandedMenuOptions1: Array<ExpandedMenuOptions> = [] 265 private editorMenuOptions1: Array<EditorMenuOptions> = [] 266 private selectionMenuOptions: SelectionMenuOptions = { 267 editorMenuOptions: this.editorMenuOptions, 268 expandedMenuOptions: this.expandedMenuOptions, 269 controller: this.controller, 270 onCut: (event?: EditorEventInfo) => { 271 if (event && event.content) { 272 event.content.spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => { 273 if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') { 274 let span = item as RichEditorTextSpanResult; 275 console.info('test cut' + span.value); 276 console.info('test start ' + span.offsetInSpan[0] + ' end: ' + span.offsetInSpan[1]); 277 } 278 }) 279 } 280 }, 281 onPaste: (event?: EditorEventInfo) => { 282 if (event && event.content) { 283 event.content.spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => { 284 if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') { 285 let span = item as RichEditorTextSpanResult; 286 console.info('test onPaste' + span.value); 287 console.info('test start ' + span.offsetInSpan[0] + ' end: ' + span.offsetInSpan[1]); 288 } 289 }) 290 } 291 }, 292 onCopy: (event?: EditorEventInfo) => { 293 if (event && event.content) { 294 event.content.spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => { 295 if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') { 296 let span = item as RichEditorTextSpanResult; 297 console.info('test cut' + span.value); 298 console.info('test start ' + span.offsetInSpan[0] + ' end: ' + span.offsetInSpan[1]); 299 } 300 }) 301 } 302 }, 303 onSelectAll: (event?: EditorEventInfo) => { 304 if (event && event.content) { 305 event.content.spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => { 306 if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') { 307 let span = item as RichEditorTextSpanResult; 308 console.info('test onPaste' + span.value); 309 console.info('test start ' + span.offsetInSpan[0] + ' end: ' + span.offsetInSpan[1]); 310 } 311 }) 312 } 313 } 314 } 315 316 @Builder 317 sliderPanel() { 318 Column() { 319 Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) { 320 Text('A').fontSize(15) 321 Slider({ value: this.textSize, step: 10, style: SliderStyle.InSet }) 322 .width(210) 323 .onChange((value: number, mode: SliderChangeMode) => { 324 if (this.controller) { 325 let selection = this.controller.getSelection(); 326 if (mode == SliderChangeMode.End) { 327 if (this.textSize == undefined) { 328 this.textSize = 0; 329 } 330 let spans = selection.spans; 331 spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => { 332 if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') { 333 this.textSize = Math.max(this.textSize, (item as RichEditorTextSpanResult).textStyle.fontSize); 334 } 335 }) 336 } 337 if (mode == SliderChangeMode.Moving || mode == SliderChangeMode.Click) { 338 this.start = selection.selection[0]; 339 this.end = selection.selection[1]; 340 this.textSize = value; 341 this.controller.updateSpanStyle({ 342 start: this.start, 343 end: this.end, 344 textStyle: { fontSize: this.textSize } 345 }) 346 } 347 } 348 }) 349 Text('A').fontSize(20).fontWeight(FontWeight.Medium) 350 }.borderRadius($r('sys.float.ohos_id_corner_radius_card')) 351 } 352 .shadow(ShadowStyle.OUTER_DEFAULT_MD) 353 .backgroundColor(Color.White) 354 .borderRadius($r('sys.float.ohos_id_corner_radius_card')) 355 .padding(15) 356 .height(48) 357 } 358 359 @Builder 360 MyMenu() { 361 Column() { 362 SelectionMenu(this.selectionMenuOptions) 363 } 364 .width(256) 365 .backgroundColor(Color.Transparent) 366 } 367 368 @Builder 369 MyMenu2() { 370 Column() { 371 SelectionMenu({ 372 editorMenuOptions: this.editorMenuOptions, 373 expandedMenuOptions: this.expandedMenuOptions1, 374 controller: this.controller, 375 }) 376 } 377 .width(256) 378 .backgroundColor(Color.Transparent) 379 } 380 381 @Builder 382 MyMenu3() { 383 Column() { 384 SelectionMenu({ 385 editorMenuOptions: this.editorMenuOptions, 386 expandedMenuOptions: this.expandedMenuOptions, 387 controller: this.controller, 388 }) 389 } 390 .width(256) 391 .backgroundColor(Color.Transparent) 392 } 393 394 build() { 395 Column() { 396 Button("SetSelection") 397 .onClick((event: ClickEvent) => { 398 if (this.controller) { 399 this.controller.setSelection(0, 2); 400 } 401 }) 402 403 RichEditor(this.options) 404 .onReady(() => { 405 this.controller.addTextSpan(this.message, { style: { fontColor: Color.Orange, fontSize: 30 } }); 406 this.controller.addTextSpan(this.message, { style: { fontColor: Color.Black, fontSize: 25 } }); 407 }) 408 .onSelect((value: RichEditorSelection) => { 409 if (value.selection[0] == -1 && value.selection[1] == -1) { 410 return; 411 } 412 this.start = value.selection[0]; 413 this.end = value.selection[1]; 414 }) 415 .bindSelectionMenu(RichEditorSpanType.TEXT, this.MyMenu3(), RichEditorResponseType.RIGHT_CLICK) 416 .bindSelectionMenu(RichEditorSpanType.TEXT, this.MyMenu2(), RichEditorResponseType.SELECT) 417 .borderWidth(1) 418 .borderColor(Color.Red) 419 .width(200) 420 .height(200) 421 .margin(10) 422 } 423 } 424} 425``` 426> **说明:** 427> 428> 系统暂未预置加粗、斜体等图标,示例代码使用本地资源图标,开发者使用时需自行替换editorMenuOptions中icon项的资源。 429 430 431