WxWidgets wxRichTextCtrl
Function
Clear()
Clears the buffer content, leaving a single empty paragraph. Cannot be undone.
WriteText()
Writes text at the current position.
介绍
wxRichTextCtrl提供了一个通用的富文本编辑器实现,可以处理不同的字符样式、段落格式化和图像。
它旨在编辑“自然”语言文本 - 如果您需要一个支持代码编辑的编辑器,那么wxStyledTextCtrl是一个更好的选择。
尽管它的名字是“文本编辑器”,但它目前还不能读取或编写RTF(富文本格式)文件。相反,它使用自己的XML格式,也可以读取和编写纯文本文件。在未来,我们计划提供RTF或OpenDocument文件功能。可以通过创建额外的文件处理程序并向控制器注册它们来支持自定义文件格式。
wxRichTextCtrl在很大程度上与wxTextCtrl API兼容,但在必要时会进行扩展。该控件可用于在wxTextCtrl的原生富文本功能不足(特别是在Windows上)的情况下,以及需要更直接访问内容表示的情况。从wxTextCtrl中读取样式信息非常困难且效率低下,而在wxRichTextCtrl中,此类信息则很容易获取。由于它是纯wxWidgets编写的,因此对wxRichTextCtrl所做的任何自定义设置将在所有平台上得到反映。
wxRichTextCtrl 可以通过易于使用的 wxRichTextPrinting 类实现基本的打印功能。通过包含 wxRichTextFormattingDialog,可以简化具有简单文字处理功能的应用程序的开发。这是一个带有选项卡的对话框,允许用户交互式地自定义段落和字符样式。还提供了多用途对话框 wxRichTextStyleOrganiserDialog,可用于管理样式定义、浏览样式并应用它们,或选择带有重排序选项的列表样式。
使用wxRichTextCtrl有一些缺点。它不是原生的,因此行为与原生的wxTextCtrl略有不同,尽管遵循了常见的编辑约定。用户可能会错过macOS内置的拼写检查功能,或者任何由原生控件提供的特殊字符输入功能。如果预期用户依赖于屏幕阅读器,而屏幕阅读器在非原生文本输入实现上可能无法正常工作,那么使用wxRichTextCtrl也不是一个好的选择。您可以通过提供在wxTextCtrl和wxRichTextCtrl之间进行选择的选项来缓解这一问题,在前一种情况下减少一些功能。
了解 wxRichTextCtrl 功能的一个好方法是编译并运行示例程序(samples/richtext),并查看代码。
wxRichTextCtrl* richTextCtrl = new wxRichTextCtrl( splitter, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(200, 200), wxVSCROLL | wxHSCROLL | wxBORDER_NONE | wxWANTS_CHARS); wxFont textFont = wxFont(12, wxROMAN, wxNORMAL, wxNORMAL); wxFont boldFont = wxFont(12, wxROMAN, wxNORMAL, wxBOLD); wxFont italicFont = wxFont(12, wxROMAN, wxITALIC, wxNORMAL); wxFont font(12, wxROMAN, wxNORMAL, wxNORMAL); m_richTextCtrl->SetFont(font); wxRichTextCtrl& r = richTextCtrl; r.BeginSuppressUndo(); r.BeginParagraphSpacing(0, 20); r.BeginAlignment(wxTEXT_ALIGNMENT_CENTRE); r.BeginBold(); r.BeginFontSize(14); r.WriteText(wxT("Welcome to wxRichTextCtrl, a wxWidgets control for editing and presenting styled text and images")); r.EndFontSize(); r.Newline(); r.BeginItalic(); r.WriteText(wxT("by Julian Smart")); r.EndItalic(); r.EndBold(); r.Newline(); r.WriteImage(wxBitmap(zebra_xpm)); r.EndAlignment(); r.Newline(); r.Newline(); r.WriteText(wxT("What can you do with this thing? ")); r.WriteImage(wxBitmap(smiley_xpm)); r.WriteText(wxT(" Well, you can change text ")); r.BeginTextColour(wxColour(255, 0, 0)); r.WriteText(wxT("colour, like this red bit.")); r.EndTextColour(); r.BeginTextColour(wxColour(0, 0, 255)); r.WriteText(wxT(" And this blue bit.")); r.EndTextColour(); r.WriteText(wxT(" Naturally you can make things ")); r.BeginBold(); r.WriteText(wxT("bold ")); r.EndBold(); r.BeginItalic(); r.WriteText(wxT("or italic ")); r.EndItalic(); r.BeginUnderline(); r.WriteText(wxT("or underlined.")); r.EndUnderline(); r.BeginFontSize(14); r.WriteText(wxT(" Different font sizes on the same line is allowed, too.")); r.EndFontSize(); r.WriteText(wxT(" Next we'll show an indented paragraph.")); r.BeginLeftIndent(60); r.Newline(); r.WriteText(wxT("Indented paragraph.")); r.EndLeftIndent(); r.Newline(); r.WriteText(wxT("Next, we'll show a first-line indent, achieved using BeginLeftIndent(100, -40).")); r.BeginLeftIndent(100, -40); r.Newline(); r.WriteText(wxT("It was in January, the most down-trodden month of an Edinburgh winter.")); r.EndLeftIndent(); r.Newline(); r.WriteText(wxT("Numbered bullets are possible, again using subindents:")); r.BeginNumberedBullet(1, 100, 60); r.Newline(); r.WriteText(wxT("This is my first item. Note that wxRichTextCtrl doesn't automatically do numbering, but this will be added later.")); r.EndNumberedBullet(); r.BeginNumberedBullet(2, 100, 60); r.Newline(); r.WriteText(wxT("This is my second item.")); r.EndNumberedBullet(); r.Newline(); r.WriteText(wxT("The following paragraph is right-indented:")); r.BeginRightIndent(200); r.Newline(); r.WriteText(wxT("It was in January, the most down-trodden month of an Edinburgh winter. An attractive woman came into the cafe, which is nothing remarkable.")); r.EndRightIndent(); r.Newline(); wxArrayInt tabs; tabs.Add(400); tabs.Add(600); tabs.Add(800); tabs.Add(1000); wxTextAttr attr; attr.SetFlags(wxTEXT_ATTR_TABS); attr.SetTabs(tabs); r.SetDefaultStyle(attr); r.WriteText(wxT("This line contains tabs:\tFirst tab\tSecond tab\tThird tab")); r.Newline(); r.WriteText(wxT("Other notable features of wxRichTextCtrl include:")); r.BeginSymbolBullet(wxT('*'), 100, 60); r.Newline(); r.WriteText(wxT("Compatibility with wxTextCtrl API")); r.EndSymbolBullet(); r.WriteText(wxT("Note: this sample content was generated programmatically from within the MyFrame constructor in the demo. The images were loaded from inline XPMs. Enjoy wxRichTextCtrl!")); r.EndSuppressUndo();
开始使用wxRichTextCtrl
您需要在源代码中包含<wx/richtext/richtextctrl.h>,并使用带有“richtext”后缀的适当的wxWidgets库进行链接。在链接行中将富文本库放在第一位,以避免未解析的符号。
然后你可以创建一个wxRichTextCtrl控件,如果想要让Tab键被控件处理而不是用于在控件之间导航,可以使用wxWANT_CHARS样式。
文本样式
样式属性由wxTextAttr表示,或者对于需要更精确控制的属性,如边距和大小,可以使用派生类wxRichTextAttr。
在设置样式时,属性对象的标志决定了哪些属性会被应用。在查询样式时,传递的标志会被忽略(可选地),仅用于确定是否应从字符内容或段落对象中检索属性。
wxRichTextCtrl采用分层的样式处理方式,这样不同的内容部分可以负责为最终在屏幕上呈现的样式贡献不同的属性。
控制中有四种主要的风格概念:
- 基本样式:控件的基本样式,在其上可以叠加其他任何样式。它提供默认属性,更改基本样式可能会立即根据其他样式对内容的使用情况改变内容的外观。通过调用wxRichTextCtrl::SetFont可以更改基本样式的字体。基本样式通过调用wxRichTextCtrl::SetBasicStyle进行设置。
- 段落样式:每个段落都有独立的属性,这些属性与其他段落的属性以及段落内的内容无关。通常,这些属性与段落相关,例如对齐方式和缩进,但也可以设置字符属性。可以通过将wxRICHTEXT_SETSTYLE_PARAGRAPHS_ONLY传递给wxRichTextCtrl::SetStyleEx来独立设置段落样式及其内容。
- 字符样式:每个段落中的字符可以具有属性。单个字符或字符串可以具有特定的属性集。可以通过调用wxRichTextCtrl::SetStyle或wxRichTextCtrl::SetStyleEx来设置字符样式。
- 默认样式:这是当前样式,决定了随后输入、粘贴或通过程序插入的内容的样式。默认样式是通过调用wxRichTextCtrl::SetDefaultStyle设置的。您在屏幕上看到的样式是通过将上述前三种样式类型(第四种仅作为将来插入内容的指南,因此不会影响当前显示的内容)合并动态生成的。
基本的wxTextCtrl与wxRichTextCtrl在属性存储方面有不同的区分。因此,在设置和检索属性时,我们需要更精细的控制。wxRichTextCtrl::SetStyleEx有一个flags参数:
- wxRICHTEXT_SETSTYLE_OPTIMIZE 表示应仅在组合属性与当前对象的属性不同的情况下更改样式。当应用用户编辑过的样式时,这一点很重要,因为用户刚刚编辑了组合(可见)样式,而 wxRichTextCtrl 希望保留与其原始对象关联的属性,而不是将它们应用于段落和内容对象。
- wxRICHTEXT_SETSTYLE_PARAGRAPHS_ONLY 表示仅应在给定范围内的段落对象应用属性。
- wxRICHTEXT_SETSTYLE_CHARACTERS_ONLY 表示仅应在给定范围内的内容对象(文本或图像)应用属性。
- wxRICHTEXT_SETSTYLE_WITH_UNDO 表示该操作应可撤销。
能够在 wxRichTextCtrl 中更改任意属性固然不错,但对于用户或程序员来说,分别设置属性可能会变得难以处理。文字处理器具有一系列可自定义或直接使用的样式,这意味着您只需单击一下即可设置标题,而无需将文本设置为粗体、指定较大的字体大小并应用特定的段落间距和对齐方式。同样地,WxWidgets提供了一个名为wxRichTextStyleSheet的类,用于管理样式定义(wxRichTextParagraphStyleDefinition、wxRichTextListStyleDefinition和wxRichTextCharacterStyleDefinition)。一旦将定义添加到样式表并与wxRichTextCtrl关联,就可以将一个命名定义应用于文本的特定范围。WxWidgets还提供了wxRichTextStyleComboCtrl和wxRichTextStyleListCtrl类,用于在风格组合框和风格列表框中显示和选择样式。
你可以通过调用wxRichTextCtrl::ApplyStyleSheet方法重新应用样式表到控制中的内容。如果样式定义已经更改,并且你想让内容反映这些更改,那么这很有用。这依赖于这样一个事实:当应用一个命名样式时,该样式的定义名称会被记录在内容中。因此,ApplyStyleSheet通过查找具有样式名称的段落属性并重新将定义的属性应用于段落来工作。目前,它仅与段落和列表样式定义一起使用。
包含对话框
wxRichTextCtrl 提供了标准对话框,以便更轻松地实现文本编辑功能。
wxRichTextFormattingDialog可用于字符或段落格式化,也可以同时进行两者的格式化。它是一个wxPropertySheetDialog,具有以下可用的选项卡:字体、缩进与间距、制表符、项目符号、样式、边框、边距、背景、大小和列表样式。可以通过向对话框构造函数传递标志来选择要显示的页面。在字符格式化对话中,通常只会显示Font页面。在段落格式化对话中,将显示Indents & Spacing、Tabs和Bullets页面。在编辑样式定义时,Style选项卡非常有用。
你可以通过提供自己的 wxRichTextFormattingDialogFactory 对象来自定义这个对话框。该对象告诉格式化对话框支持多少页、它们的标识符是什么以及如何创建这些页。
wxRichTextStyleOrganiserDialog是一个多用途对话框,可用于管理样式定义、浏览样式并应用它们,或选择带有重排序选项的列表样式。请查看示例以了解使用方法——它用于“管理样式”和“项目符号和编号”菜单命令。
wxSymbolPickerDialog 可以让用户从指定字体中插入符号。除了包含在富文本库中之外,它与 wxRichTextCtrl 没有依赖关系。
wxRichTextCtrl是如何实现的?
数据表示由wxRichTextBuffer处理,每个wxRichTextCtrl对象都始终有一个这样的缓冲区。
内容由一系列对象表示,这些对象都继承自wxRichTextObject。一个对象可能是一张图片、一段文本、一个段落,或者一个更复杂的组合对象。对象存储一个wxRichTextAttr对象,其中包含样式信息;段落对象可以包含段落和字符信息,但内容对象(如文本)只能存储字符信息。在控制器或打印输出中最终显示的样式是基础样式、段落样式和内容(字符)样式的组合。
层次结构的顶端是缓冲区,它是一种wxRichTextParagraphLayoutBox类型的对象,其中包含进一步的wxRichTextParagraph对象,每个wxRichTextParagraph对象都可以包含文本、图像和其他潜在类型的对象。
每个对象都保持一个相对于主父对象起始位置的范围(起始和结束位置)。
当调用 Layout 方法时,会给对象一个大小,该对象必须限制在其中,或者一个或多个灵活的方向(垂直或水平)。因此,例如,居中的段落会被赋予页面宽度(减去任何边距),但在垂直方向上可以无限扩展。Layout 的实现会缓存计算出的大小和位置。
当缓存被修改时,一个范围将被无效化(标记为需要布局),以便仅执行最小量的布局操作。
一段纯文本中只有一个额外的对象,即wxRichTextPlainText对象。当对该对象的部分进行格式化时,对象会被分解为单独的对象,每个不同的字符样式对应一个对象。因此,段落中的每个对象总是只有一个wxTextAttr对象来表示其字符样式。当然,在进行大量编辑操作后可能会导致碎片化,从而可能导致多个具有相同样式的对象,而只需要一个即可。因此,在更新控制的显示时会调用Defragment函数,以确保使用尽可能少的对象。
嵌套对象
wxRichTextCtrl支持嵌套对象,如文本框和表格。为了与现有的API兼容,引入了“对象焦点”的概念。当用户点击嵌套的文本框时,将焦点设置为该容器对象,因此所有键盘输入和API函数都应用于该容器。应用程序可以使用wxRichTextCtrl::SetObjectFocus函数更改焦点。使用空参数调用该函数可将焦点设置为顶层对象。
当焦点发生变化时,将会发送一个事件给控制器。
当用户点击控件时,wxRichTextCtrl 通过调用找到的容器的重写的wxRichTextObject::AcceptsFocus函数来确定要设置为当前对象焦点的容器。例如,虽然表格也是一个容器,但它本身不应该成为焦点对象,因为在表格级别没有文本编辑功能。相反,表格中的一个单元格必须接受焦点。
由于嵌套对象无法仅通过起始位置和结束位置来表示一个段落,因此提供了wxRichTextSelection类,用于存储多个范围(用于非连续选择,如表格单元格),并指向所涉及的容器对象的指针。您可以将wxRichTextSelection传递给wxRichTextCtrl::SetSelection或从wxRichTextCtrl::GetSelection获取一个实例。
当选择多个对象,例如单元格表格时,wxRichTextCtrl拖放处理程序代码会调用函数wxRichTextObject::HandlesChildSelections来确定子对象是否可以单独选择。目前,只有表格单元格可以通过这种方式进行多选。
上下文菜单和属性对话框
您可以使用三种方法来使用上下文菜单:您可以让wxRichTextCtrl处理所有事情并提供基本菜单;您可以使用wxRichTextCtrl::SetContextMenu设置自己的上下文菜单,让wxRichTextCtrl负责显示它并添加属性项;或者您可以像通常一样为类添加上下文菜单事件处理程序来覆盖默认的上下文菜单行为。
如果你在表格中的单元格内的文本框上右键单击,你可能想要编辑这些对象中的一个的属性 - 但你想要编辑哪个属性呢?
默认情况下,允许同时显示最多三个可编辑属性的菜单项——针对被单击的对象、该对象的容器以及该容器的父容器(取决于这些对象是否从wxRichTextObject::CanEditProperties函数中返回true)。如果您提供了一个上下文菜单,请使用wxID_RICHTEXT_PROPERTIES1标识符添加一个属性命令项,以便wxRichTextCtrl可以找到添加命令项的位置。对象应通过从wxRichTextObject::GetPropertiesMenuLabel返回一个字符串来告诉控制使用哪个标签。
因为可能有多个属性编辑命令显示,因此建议不要包含“Properties”一词,只需使用对象的名称,例如“Text Box”或“Table”。