• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# SelectionMenu
2<!--Kit: ArkUI-->
3<!--Subsystem: ArkUI-->
4<!--Owner: @hddgzw-->
5<!--Designer: @pssea-->
6<!--Tester: @jiaoaozihao-->
7<!--Adviser: @HelloCrease-->
8
9
10文本选择菜单,适用于[RichEditor](ts-basic-components-richeditor.md)组件通过[bindSelectionMenu](ts-basic-components-richeditor.md#bindselectionmenu)或[Text](ts-basic-components-text.md)组件通过[bindSelectionMenu](ts-basic-components-text.md#bindselectionmenu11)绑定自定义文本选择菜单,建议绑定鼠标右键或者鼠标选中方式弹出,不支持作为普通组件单独使用。
11
12
13> **说明:**
14>
15> 该组件从API Version 11开始支持。后续版本如有新增内容,则采用上角标单独标记该内容的起始版本。
16
17
18## 导入模块
19
20```ts
21import { SelectionMenu, EditorMenuOptions, ExpandedMenuOptions, EditorEventInfo, SelectionMenuOptions } from '@kit.ArkUI';
22```
23
24## 子组件
25
26无。
27
28## SelectionMenu
29
30SelectionMenu(options: SelectionMenuOptions): void
31
32入参为空时,文本选择菜单组件SelectionMenu内容区大小及组件大小为零。表现例如,富文本组件[RichEditor](ts-basic-components-richeditor.md)使用[bindSelectionMenu](ts-basic-components-richeditor.md#bindselectionmenu)接口绑定一个SelectionMenu的右键菜单,则右键富文本组件区域时无任何菜单弹出。
33
34**装饰器类型:**\@Builder
35
36**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。
37
38**系统能力:** SystemCapability.ArkUI.ArkUI.Full
39
40**设备行为差异:** 该接口在Wearable设备上使用时,应用程序运行异常, 异常信息中提示接口未定义,在其他设备中可正常调用。
41
42**参数:**
43
44| 参数名 | 类型 | 必填 | 说明 |
45| -------- | -------- | -------- | -------- |
46| options | [SelectionMenuOptions](#selectionmenuoptions) | 是 | 文本选择菜单可选项。 |
47
48## SelectionMenuOptions
49
50SelectionMenuOptions定义SelectionMenu的可选菜单类型项及其具体配置参数。
51
52**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。
53
54**系统能力:** SystemCapability.ArkUI.ArkUI.Full
55
56**设备行为差异:** 该接口在Wearable设备上使用时,应用程序运行异常,异常信息中提示接口未定义,在其他设备中可正常调用。
57
58| 名称 | 类型 | 只读 | 可选 | 说明 |
59| -------- | -------- | -------- | -------- | -------- |
60| editorMenuOptions | Array&lt;[EditorMenuOptions](#editormenuoptions)&gt; | 否 | 是 | 编辑菜单。<br/>editorMenuOptions未配置时,不显示编辑菜单。<br/>同时配置EditorMenuOptions中action和builder时,点击图标会同时响应。<br/>点击编辑菜单图标默认不关闭整个菜单,应用可以通过action接口配置RichEditorController的closeSelectionMenu主动关闭菜单。 |
61| expandedMenuOptions | Array&lt;[ExpandedMenuOptions](#expandedmenuoptions)&gt; | 否 | 是 | 扩展下拉菜单。<br/>expandedMenuOptions参数为空时无更多按钮,不显示扩展下拉菜单。<br/>expandedMenuOptions参数不为空时显示更多按钮,配置菜单项收起在更多按钮中,点击更多按钮展示。 |
62| 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`时,内置的复制剪切功能不会被限制。|
63| onCopy | (event?: [EditorEventInfo](#editoreventinfo))&nbsp;=&gt;&nbsp;void | 否 | 是 | 替代内置系统菜单复制项的事件回调。<br/>生效前提是一定要有controller参数,有系统默认菜单才能替换内置复制功能。<br/>**说明:**<br/> event为返回信息。|
64| onPaste | (event?: [EditorEventInfo](#editoreventinfo))&nbsp;=&gt;&nbsp;void | 否 | 是 | 替代内置系统菜单粘贴项的事件回调。<br/>生效前提是一定要有controller参数,有系统默认菜单才能替换内置粘贴功能。<br/>**说明:**<br/> event为返回信息。 |
65| onCut | (event?: [EditorEventInfo](#editoreventinfo))&nbsp;=&gt;&nbsp;void | 否 | 是 | 替代内置系统菜单剪切项的事件回调。<br/>生效前提是一定要有controller参数,有系统默认菜单才能替换内置剪切功能。<br/>**说明:**<br/>event为返回信息。|
66| onSelectAll | (event?: [EditorEventInfo](#editoreventinfo))&nbsp;=&gt;&nbsp;void | 否 | 是 | 替代内置系统菜单全选项的事件回调。<br/>生效前提是一定要有controller参数,有系统默认菜单才能替换内置全选功能。<br/>**说明:**<br/>event为返回信息。|
67
68
69## EditorMenuOptions
70
71编辑菜单选项。
72
73**系统能力:** SystemCapability.ArkUI.ArkUI.Full
74
75**设备行为差异:** 该接口在Wearable设备上使用时,应用程序运行异常,异常信息中提示接口未定义,在其他设备中可正常调用。
76
77| 名称 | 类型 | 只读 | 可选 | 说明 |
78| -------- | -------- | -------- | -------- | -------- |
79| icon | [ResourceStr](ts-types.md#resourcestr) | 否 | 否 | 图标资源。<br/>**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。 |
80| builder | ()&nbsp;=&gt;&nbsp;void | 否 | 是 | 点击时显示用户自定义组件,自定义组件在构造时结合@Builder使用。<br/>**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。 |
81| action | ()&nbsp;=&gt;&nbsp;void | 否 | 是 | 点击菜单项的事件回调。<br/>**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。 |
82| symbolStyle<sup>18+</sup> | [SymbolGlyphModifier](ts-universal-attributes-text-style.md#symbolglyphmodifier12) | 否 | 是 | Symbol图标资源,优先级大于icon。<br/>**原子化服务API:** 从API version 18开始,该接口支持在原子化服务中使用。 |
83
84
85## ExpandedMenuOptions
86
87扩展下拉菜单。
88
89继承于[MenuItemOptions](ts-basic-components-menuitem.md#menuitemoptions对象说明)。
90
91**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。
92
93**系统能力:** SystemCapability.ArkUI.ArkUI.Full
94
95**设备行为差异:** 该接口在Wearable设备上使用时,应用程序运行异常,异常信息中提示接口未定义,在其他设备中可正常调用。
96
97| 名称 | 类型 | 只读 | 可选 | 说明 |
98| -------- | -------- | -------- | -------- | -------- |
99| action | ()&nbsp;=&gt;&nbsp;void | 否 | 是 | 点击菜单项的事件回调。 |
100
101## EditorEventInfo
102
103选中内容信息。
104
105**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。
106
107**系统能力:** SystemCapability.ArkUI.ArkUI.Full
108
109**设备行为差异:** 该接口在Wearable设备上使用时,应用程序运行异常,异常信息中提示接口未定义,在其他设备中可正常调用。
110
111| 名称 | 类型 | 只读 | 可选 | 说明 |
112| -------- | -------- | -------- | -------- | -------- |
113| content | [RichEditorSelection](ts-basic-components-richeditor.md#richeditorselection) | 否 | 是 | 选中内容信息。|
114
115## 属性
116
117不支持[通用属性](ts-component-general-attributes.md),宽度默认224vp, 高度自适应内容。
118
119## 事件
120不支持[通用事件](ts-component-general-events.md)。
121
122## 示例
123### 示例1(绑定不同触发方式的自定义文本选择菜单)
124
125该示例展示了文本绑定不同触发方式的自定义文本选择菜单的效果。
126
127```ts
128import {
129  SelectionMenu,
130  EditorMenuOptions,
131  ExpandedMenuOptions,
132  EditorEventInfo,
133  SelectionMenuOptions
134} from '@kit.ArkUI';
135
136@Entry
137@Component
138struct Index {
139  @State select: boolean = true;
140  controller: RichEditorController = new RichEditorController();
141  options: RichEditorOptions = { controller: this.controller };
142  @State message: string = 'Hello world';
143  @State textSize: number = 30;
144  @State fontWeight: FontWeight = FontWeight.Normal;
145  @State start: number = -1;
146  @State end: number = -1;
147  @State visibleValue: Visibility = Visibility.Visible;
148  @State colorTransparent: Color = Color.Transparent;
149  @State textStyle: RichEditorTextStyle = {};
150  private editorMenuOptions: Array<EditorMenuOptions> =
151    [
152      {
153        // $r('app.media.ic_notepad_textbold')需要替换为开发者所需的图像资源文件。
154        icon: $r("app.media.ic_notepad_textbold"), action: () => {
155        if (this.controller) {
156          let selection = this.controller.getSelection();
157          let spans = selection.spans;
158          spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
159            if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
160              let span = item as RichEditorTextSpanResult;
161              this.textStyle = span.textStyle;
162              let start = span.offsetInSpan[0];
163              let end = span.offsetInSpan[1];
164              let offset = span.spanPosition.spanRange[0];
165              if (this.textStyle.fontWeight != 11) {
166                this.textStyle.fontWeight = FontWeight.Bolder;
167              } else {
168                this.textStyle.fontWeight = FontWeight.Normal;
169              }
170              this.controller.updateSpanStyle({
171                start: offset + start,
172                end: offset + end,
173                textStyle: this.textStyle
174              })
175            }
176          })
177        }
178      }
179      },
180      {
181        // $r('app.media.ic_notepad_texttilt')需要替换为开发者所需的图像资源文件。
182        icon: $r("app.media.ic_notepad_texttilt"), action: () => {
183        if (this.controller) {
184          let selection = this.controller.getSelection();
185          let spans = selection.spans;
186          spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
187            if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
188              let span = item as RichEditorTextSpanResult;
189              this.textStyle = span.textStyle;
190              let start = span.offsetInSpan[0];
191              let end = span.offsetInSpan[1];
192              let offset = span.spanPosition.spanRange[0];
193              if (this.textStyle.fontStyle == FontStyle.Italic) {
194                this.textStyle.fontStyle = FontStyle.Normal;
195              } else {
196                this.textStyle.fontStyle = FontStyle.Italic;
197              }
198              this.controller.updateSpanStyle({
199                start: offset + start,
200                end: offset + end,
201                textStyle: this.textStyle
202              })
203            }
204          })
205        }
206      }
207      },
208      {
209        // $r('app.media.ic_notepad_underline')需要替换为开发者所需的图像资源文件。
210        icon: $r("app.media.ic_notepad_underline"),
211        action: () => {
212          if (this.controller) {
213            let selection = this.controller.getSelection();
214            let spans = selection.spans;
215            spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
216              if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
217                let span = item as RichEditorTextSpanResult;
218                this.textStyle = span.textStyle;
219                let start = span.offsetInSpan[0];
220                let end = span.offsetInSpan[1];
221                let offset = span.spanPosition.spanRange[0];
222                if (this.textStyle.decoration) {
223                  if (this.textStyle.decoration.type == TextDecorationType.Underline) {
224                    this.textStyle.decoration.type = TextDecorationType.None;
225                  } else {
226                    this.textStyle.decoration.type = TextDecorationType.Underline;
227                  }
228                } else {
229                  this.textStyle.decoration = { type: TextDecorationType.Underline, color: Color.Black }
230                }
231                this.controller.updateSpanStyle({
232                  start: offset + start,
233                  end: offset + end,
234                  textStyle: this.textStyle
235                })
236              }
237            })
238          }
239        }
240      },
241      {
242        // $r('app.media.ic_notepad_fontsize')需要替换为开发者所需的图像资源文件。
243        icon: $r("app.media.ic_notepad_fontsize"), action: () => {
244      }, builder: (): void => this.sliderPanel()
245      },
246      {
247        // $r('app.media.ic_notepad_textcolor')需要替换为开发者所需的图像资源文件。
248        icon: $r("app.media.ic_notepad_textcolor"), action: () => {
249        if (this.controller) {
250          let selection = this.controller.getSelection();
251          let spans = selection.spans;
252          spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
253            if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
254              let span = item as RichEditorTextSpanResult;
255              this.textStyle = span.textStyle;
256              let start = span.offsetInSpan[0];
257              let end = span.offsetInSpan[1];
258              let offset = span.spanPosition.spanRange[0];
259              if (this.textStyle.fontColor == Color.Orange || this.textStyle.fontColor == '#FFFFA500') {
260                this.textStyle.fontColor = Color.Black;
261              } else {
262                this.textStyle.fontColor = Color.Orange;
263              }
264              this.controller.updateSpanStyle({
265                start: offset + start,
266                end: offset + end,
267                textStyle: this.textStyle
268              })
269            }
270          })
271        }
272      }
273      }]
274  private expandedMenuOptions: Array<ExpandedMenuOptions> =
275    [{
276      // $r('app.media.startIcon')需要替换为开发者所需的图像资源文件。
277      startIcon: $r("app.media.startIcon"), content: '词典', action: () => {
278      }
279    }, {
280      // $r('app.media.startIcon')需要替换为开发者所需的图像资源文件。
281      startIcon: $r("app.media.startIcon"), content: '翻译', action: () => {
282      }
283    }, {
284      // $r('app.media.startIcon')需要替换为开发者所需的图像资源文件。
285      startIcon: $r("app.media.startIcon"), content: '搜索', action: () => {
286      }
287    }]
288  private expandedMenuOptions1: Array<ExpandedMenuOptions> = [];
289  private selectionMenuOptions: SelectionMenuOptions = {
290    editorMenuOptions: this.editorMenuOptions,
291    expandedMenuOptions: this.expandedMenuOptions,
292    controller: this.controller,
293    onCut: (event?: EditorEventInfo) => {
294      if (event && event.content) {
295        event.content.spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
296          if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
297            let span = item as RichEditorTextSpanResult;
298            console.info('test cut' + span.value);
299            console.info('test start ' + span.offsetInSpan[0] + ' end: ' + span.offsetInSpan[1]);
300          }
301        })
302      }
303    },
304    onPaste: (event?: EditorEventInfo) => {
305      if (event && event.content) {
306        event.content.spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
307          if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
308            let span = item as RichEditorTextSpanResult;
309            console.info('test onPaste' + span.value);
310            console.info('test start ' + span.offsetInSpan[0] + ' end: ' + span.offsetInSpan[1]);
311          }
312        })
313      }
314    },
315    onCopy: (event?: EditorEventInfo) => {
316      if (event && event.content) {
317        event.content.spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
318          if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
319            let span = item as RichEditorTextSpanResult;
320            console.info('test cut' + span.value);
321            console.info('test start ' + span.offsetInSpan[0] + ' end: ' + span.offsetInSpan[1]);
322          }
323        })
324      }
325    },
326    onSelectAll: (event?: EditorEventInfo) => {
327      if (event && event.content) {
328        event.content.spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
329          if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
330            let span = item as RichEditorTextSpanResult;
331            console.info('test onPaste' + span.value);
332            console.info('test start ' + span.offsetInSpan[0] + ' end: ' + span.offsetInSpan[1]);
333          }
334        })
335      }
336    }
337  };
338
339  @Builder
340  sliderPanel() {
341    Column() {
342      Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) {
343        Text('A').fontSize(15)
344        Slider({ value: this.textSize, step: 10, style: SliderStyle.InSet })
345          .width(210)
346          .onChange((value: number, mode: SliderChangeMode) => {
347            if (this.controller) {
348              let selection = this.controller.getSelection();
349              if (mode == SliderChangeMode.End) {
350                if (this.textSize == undefined) {
351                  this.textSize = 0;
352                }
353                let spans = selection.spans;
354                spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
355                  if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
356                    this.textSize = Math.max(this.textSize, (item as RichEditorTextSpanResult).textStyle.fontSize);
357                  }
358                })
359              }
360              if (mode == SliderChangeMode.Moving || mode == SliderChangeMode.Click) {
361                this.start = selection.selection[0];
362                this.end = selection.selection[1];
363                this.textSize = value;
364                this.controller.updateSpanStyle({
365                  start: this.start,
366                  end: this.end,
367                  textStyle: { fontSize: this.textSize }
368                })
369              }
370            }
371          })
372        Text('A').fontSize(20).fontWeight(FontWeight.Medium)
373      }.borderRadius($r('sys.float.ohos_id_corner_radius_card'))
374    }
375    .shadow(ShadowStyle.OUTER_DEFAULT_MD)
376    .backgroundColor(Color.White)
377    .borderRadius($r('sys.float.ohos_id_corner_radius_card'))
378    .padding(15)
379    .height(48)
380  }
381
382  @Builder
383  MyMenu() {
384    Column() {
385      SelectionMenu(this.selectionMenuOptions)
386    }
387    .width(256)
388    .backgroundColor(Color.Transparent)
389  }
390
391  @Builder
392  MyMenu2() {
393    Column() {
394      SelectionMenu({
395        editorMenuOptions: this.editorMenuOptions,
396        expandedMenuOptions: this.expandedMenuOptions1,
397        controller: this.controller,
398      })
399    }
400    .width(256)
401    .backgroundColor(Color.Transparent)
402  }
403
404  @Builder
405  MyMenu3() {
406    Column() {
407      SelectionMenu({
408        editorMenuOptions: this.editorMenuOptions,
409        expandedMenuOptions: this.expandedMenuOptions,
410        controller: this.controller,
411      })
412    }
413    .width(256)
414    .backgroundColor(Color.Transparent)
415  }
416
417  build() {
418    Column() {
419      Button("SetSelection")
420        .onClick((event: ClickEvent) => {
421          if (this.controller) {
422            this.controller.setSelection(0, 2);
423          }
424        })
425
426      RichEditor(this.options)
427        .onReady(() => {
428          this.controller.addTextSpan(this.message, { style: { fontColor: Color.Orange, fontSize: 30 } });
429          this.controller.addTextSpan(this.message, { style: { fontColor: Color.Black, fontSize: 25 } });
430        })
431        .onSelect((value: RichEditorSelection) => {
432          if (value.selection[0] == -1 && value.selection[1] == -1) {
433            return;
434          }
435          this.start = value.selection[0];
436          this.end = value.selection[1];
437        })
438        .bindSelectionMenu(RichEditorSpanType.TEXT, this.MyMenu3(), RichEditorResponseType.RIGHT_CLICK)
439        .bindSelectionMenu(RichEditorSpanType.TEXT, this.MyMenu2(), RichEditorResponseType.SELECT)
440        .borderWidth(1)
441        .borderColor(Color.Red)
442        .width(200)
443        .height(200)
444        .margin(10)
445    }
446  }
447}
448```
449> **说明:**
450>
451> 系统暂未预置加粗、斜体等图标,示例代码使用本地资源图标,开发者使用时需自行替换editorMenuOptions中icon项的资源。
452
453![selectionmenu](figures/selectionmenu.gif)
454
455### 示例2(设置Symbol类型图标)
456
457从API version 11开始,该示例通过设置[EditorMenuOptions](#editormenuoptions)的属性symbolStyle,展示了自定义Symbol类型图标。
458
459```ts
460import {
461  SelectionMenu,
462  EditorMenuOptions,
463  ExpandedMenuOptions,
464  EditorEventInfo,
465  SelectionMenuOptions,
466  SymbolGlyphModifier
467} from '@kit.ArkUI'
468
469@Entry
470@Component
471struct Index {
472  @State select: boolean = true;
473  controller: RichEditorController = new RichEditorController();
474  options: RichEditorOptions = { controller: this.controller };
475  @State message: string = 'Hello world';
476  @State textSize: number = 30;
477  @State fontWeight: FontWeight = FontWeight.Normal;
478  @State start: number = -1;
479  @State end: number = -1;
480  @State visibleValue: Visibility = Visibility.Visible;
481  @State colorTransparent: Color = Color.Transparent;
482  @State textStyle: RichEditorTextStyle = {};
483  private editorMenuOptions: Array<EditorMenuOptions> =
484    [
485      {
486        icon: $r("sys.media.wifi_router_fill"),
487        symbolStyle: new SymbolGlyphModifier($r('sys.symbol.save')),
488        action: () => {
489          if (this.controller) {
490            let selection = this.controller.getSelection();
491            let spans = selection.spans;
492            spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
493              if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
494                let span = item as RichEditorTextSpanResult;
495                this.textStyle = span.textStyle;
496                let start = span.offsetInSpan[0];
497                let end = span.offsetInSpan[1];
498                let offset = span.spanPosition.spanRange[0];
499                if (this.textStyle.fontWeight != 11) {
500                  this.textStyle.fontWeight = FontWeight.Bolder;
501                } else {
502                  this.textStyle.fontWeight = FontWeight.Normal;
503                }
504                this.controller.updateSpanStyle({
505                  start: offset + start,
506                  end: offset + end,
507                  textStyle: this.textStyle
508                })
509              }
510            })
511          }
512        }
513      },
514      {
515        icon: $r("sys.media.save_button_picture"),
516        symbolStyle: new SymbolGlyphModifier($r('sys.symbol.camera')),
517        action: () => {
518          if (this.controller) {
519            let selection = this.controller.getSelection();
520            let spans = selection.spans;
521            spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
522              if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
523                let span = item as RichEditorTextSpanResult;
524                this.textStyle = span.textStyle;
525                let start = span.offsetInSpan[0];
526                let end = span.offsetInSpan[1];
527                let offset = span.spanPosition.spanRange[0];
528                if (this.textStyle.fontStyle == FontStyle.Italic) {
529                  this.textStyle.fontStyle = FontStyle.Normal;
530                } else {
531                  this.textStyle.fontStyle = FontStyle.Italic;
532                }
533                this.controller.updateSpanStyle({
534                  start: offset + start,
535                  end: offset + end,
536                  textStyle: this.textStyle
537                })
538              }
539            })
540          }
541        }
542      },
543      {
544        icon: $r("sys.media.waveform_folder_fill"),
545        symbolStyle: new SymbolGlyphModifier($r('sys.symbol.car')),
546        action: () => {
547          if (this.controller) {
548            let selection = this.controller.getSelection();
549            let spans = selection.spans;
550            spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
551              if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
552                let span = item as RichEditorTextSpanResult;
553                this.textStyle = span.textStyle;
554                let start = span.offsetInSpan[0];
555                let end = span.offsetInSpan[1];
556                let offset = span.spanPosition.spanRange[0];
557                if (this.textStyle.decoration) {
558                  if (this.textStyle.decoration.type == TextDecorationType.Underline) {
559                    this.textStyle.decoration.type = TextDecorationType.None;
560                  } else {
561                    this.textStyle.decoration.type = TextDecorationType.Underline;
562                  }
563                } else {
564                  this.textStyle.decoration = { type: TextDecorationType.Underline, color: Color.Black }
565                }
566                this.controller.updateSpanStyle({
567                  start: offset + start,
568                  end: offset + end,
569                  textStyle: this.textStyle
570                })
571              }
572            })
573          }
574        }
575      },
576      {
577        // $r('app.media.app_icon')需要替换为开发者所需的图像资源文件。
578        icon: $r("app.media.app_icon"), action: () => {
579      }, builder: (): void => this.sliderPanel()
580      },
581      {
582        icon: $r("sys.media.thermometer_fill"), action: () => {
583        if (this.controller) {
584          let selection = this.controller.getSelection();
585          let spans = selection.spans;
586          spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
587            if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
588              let span = item as RichEditorTextSpanResult;
589              this.textStyle = span.textStyle;
590              let start = span.offsetInSpan[0];
591              let end = span.offsetInSpan[1];
592              let offset = span.spanPosition.spanRange[0];
593              if (this.textStyle.fontColor == Color.Orange || this.textStyle.fontColor == '#FFFFA500') {
594                this.textStyle.fontColor = Color.Black;
595              } else {
596                this.textStyle.fontColor = Color.Orange;
597              }
598              this.controller.updateSpanStyle({
599                start: offset + start,
600                end: offset + end,
601                textStyle: this.textStyle
602              })
603            }
604          })
605        }
606      }
607      }]
608  private expandedMenuOptions: Array<ExpandedMenuOptions> =
609    [{
610      // $r('app.media.startIcon')需要替换为开发者所需的图像资源文件。
611      startIcon: $r("app.media.startIcon"), content: '词典', action: () => {
612      }
613    }, {
614      // $r('app.media.startIcon')需要替换为开发者所需的图像资源文件。
615      startIcon: $r("app.media.startIcon"), content: '翻译', action: () => {
616      }
617    }, {
618      // $r('app.media.startIcon')需要替换为开发者所需的图像资源文件。
619      startIcon: $r("app.media.startIcon"), content: '搜索', action: () => {
620      }
621    }]
622  private expandedMenuOptions1: Array<ExpandedMenuOptions> = []
623  private editorMenuOptions1: Array<EditorMenuOptions> = []
624  private selectionMenuOptions: SelectionMenuOptions = {
625    editorMenuOptions: this.editorMenuOptions,
626    expandedMenuOptions: this.expandedMenuOptions,
627    controller: this.controller,
628    onCut: (event?: EditorEventInfo) => {
629      if (event && event.content) {
630        event.content.spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
631          if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
632            let span = item as RichEditorTextSpanResult;
633            console.info('test cut' + span.value);
634            console.info('test start ' + span.offsetInSpan[0] + ' end: ' + span.offsetInSpan[1]);
635          }
636        })
637      }
638    },
639    onPaste: (event?: EditorEventInfo) => {
640      if (event && event.content) {
641        event.content.spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
642          if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
643            let span = item as RichEditorTextSpanResult;
644            console.info('test onPaste' + span.value);
645            console.info('test start ' + span.offsetInSpan[0] + ' end: ' + span.offsetInSpan[1]);
646          }
647        })
648      }
649    },
650    onCopy: (event?: EditorEventInfo) => {
651      if (event && event.content) {
652        event.content.spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
653          if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
654            let span = item as RichEditorTextSpanResult;
655            console.info('test cut' + span.value);
656            console.info('test start ' + span.offsetInSpan[0] + ' end: ' + span.offsetInSpan[1]);
657          }
658        })
659      }
660    },
661    onSelectAll: (event?: EditorEventInfo) => {
662      if (event && event.content) {
663        event.content.spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
664          if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
665            let span = item as RichEditorTextSpanResult;
666            console.info('test onPaste' + span.value);
667            console.info('test start ' + span.offsetInSpan[0] + ' end: ' + span.offsetInSpan[1]);
668          }
669        })
670      }
671    }
672  }
673
674  @Builder
675  sliderPanel() {
676    Column() {
677      Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) {
678        Text('A').fontSize(15)
679        Slider({ value: this.textSize, step: 10, style: SliderStyle.InSet })
680          .width(210)
681          .onChange((value: number, mode: SliderChangeMode) => {
682            if (this.controller) {
683              let selection = this.controller.getSelection();
684              if (mode == SliderChangeMode.End) {
685                if (this.textSize == undefined) {
686                  this.textSize = 0;
687                }
688                let spans = selection.spans;
689                spans.forEach((item: RichEditorTextSpanResult | RichEditorImageSpanResult, index) => {
690                  if (typeof (item as RichEditorTextSpanResult)['textStyle'] != 'undefined') {
691                    this.textSize = Math.max(this.textSize, (item as RichEditorTextSpanResult).textStyle.fontSize);
692                  }
693                })
694              }
695              if (mode == SliderChangeMode.Moving || mode == SliderChangeMode.Click) {
696                this.start = selection.selection[0];
697                this.end = selection.selection[1];
698                this.textSize = value;
699                this.controller.updateSpanStyle({
700                  start: this.start,
701                  end: this.end,
702                  textStyle: { fontSize: this.textSize }
703                })
704              }
705            }
706          })
707        Text('A').fontSize(20).fontWeight(FontWeight.Medium)
708      }.borderRadius($r('sys.float.ohos_id_corner_radius_card'))
709    }
710    .shadow(ShadowStyle.OUTER_DEFAULT_MD)
711    .backgroundColor(Color.White)
712    .borderRadius($r('sys.float.ohos_id_corner_radius_card'))
713    .padding(15)
714    .height(48)
715  }
716
717  @Builder
718  MyMenu() {
719    Column() {
720      SelectionMenu(this.selectionMenuOptions)
721    }
722    .width(256)
723    .backgroundColor(Color.Transparent)
724  }
725
726  @Builder
727  MyMenu2() {
728    Column() {
729      SelectionMenu({
730        editorMenuOptions: this.editorMenuOptions,
731        expandedMenuOptions: this.expandedMenuOptions1,
732        controller: this.controller,
733      })
734    }
735    .width(256)
736    .backgroundColor(Color.Transparent)
737  }
738
739  @Builder
740  MyMenu3() {
741    Column() {
742      SelectionMenu({
743        editorMenuOptions: this.editorMenuOptions1,
744        expandedMenuOptions: this.expandedMenuOptions,
745        controller: this.controller,
746      })
747    }
748    .width(256)
749    .backgroundColor(Color.Transparent)
750  }
751
752  build() {
753    Column() {
754      Button("SetSelection")
755        .onClick((event: ClickEvent) => {
756          if (this.controller) {
757            this.controller.setSelection(0, 2);
758          }
759        })
760
761      RichEditor(this.options)
762        .onReady(() => {
763          this.controller.addTextSpan(this.message, { style: { fontColor: Color.Orange, fontSize: 30 } });
764          this.controller.addTextSpan(this.message, { style: { fontColor: Color.Black, fontSize: 25 } });
765        })
766        .onSelect((value: RichEditorSelection) => {
767          if (value.selection[0] == -1 && value.selection[1] == -1) {
768            return;
769          }
770          this.start = value.selection[0];
771          this.end = value.selection[1];
772        })
773        .bindSelectionMenu(RichEditorSpanType.TEXT, this.MyMenu3(), RichEditorResponseType.RIGHT_CLICK)
774        .bindSelectionMenu(RichEditorSpanType.TEXT, this.MyMenu2(), RichEditorResponseType.SELECT)
775        .borderWidth(1)
776        .borderColor(Color.Red)
777        .width(200)
778        .height(200)
779    }
780  }
781}
782```
783
784![selectionmenu02](figures/selectionmenu02.jpg)
785