wxPython官方文档中文翻译 - RichTextCtrl概述

158 阅读19分钟

官方文档:docs.wxpython.org/richtextctr…

介绍

RichTextCtrl提供了一个富文本编辑器的通用实现,它能够处理不同的字符样式、段落格式以及图像。

它旨在编辑“自然”语言文本——如果你需要一个支持代码编辑的编辑器,wx.stc.StyledTextCtrl是一个更好的选择。

尽管它叫这个名字,但目前它还不能读取或写入RTF(富文本格式)文件。相反,它使用自己的XML格式,并且也能读取和写入纯文本。未来,我们期望能够提供读取和写入RTF或OpenDocument文件的功能。通过创建额外的文件处理程序并将其注册到该控件,就可以支持自定义文件格式。

RichTextCtrl在很大程度上与wx.TextCtrl的API兼容,但在必要时对其进行了扩展。当wx.TextCtrl的原生富文本功能不够用(在Windows系统上尤其如此),并且需要更直接地访问内容表示形式时,就可以使用该控件。读取wx.TextCtrl中的样式信息既困难又低效,而在RichTextCtrl中,这些信息很容易获取。由于RichTextCtrl是用纯wxWidgets编写的,所以你对RichTextCtrl所做的任何自定义设置都会在所有平台上体现出来。

RichTextCtrl通过易于使用的RichTextPrinting类支持基本的打印功能。RichTextFormattingDialog(一种选项卡式对话框,允许以交互方式定制段落和字符样式)的加入,简化了具有简单文字处理功能的应用程序的创建过程。此外还提供了多功能对话框RichTextStyleOrganiserDialog,它可用于管理样式定义、浏览样式并应用样式,或者选择带有重新编号选项的列表样式。

使用RichTextCtrl存在一些缺点。它并非原生控件,因此其表现不会与原生的wx.TextCtrl完全一样,尽管它遵循了常见的编辑规范。用户可能会怀念Mac OS X系统上内置的拼写检查功能,或者原生控件可能提供的任何特殊字符输入功能。如果目标用户依赖于屏幕阅读器,而屏幕阅读器与非原生的文本输入实现不能很好地配合使用,那么RichTextCtrl也不是一个好的选择。你可以通过提供wx.TextCtrlRichTextCtrl两种选择来缓解这个问题,前者的功能相对较少。

了解RichTextCtrl功能的一个好方法是运行wxPython演示程序中的示例,然后浏览相关代码。

相关类

主要类RichTextCtrlRichTextBufferRichTextEvent

辅助类: wx.TextAttrRichTextRange

文件处理类: RichTextFileHandlerRichTextHTMLHandlerRichTextXMLHandler

样式类: RichTextCharacterStyleDefinitionRichTextParagraphStyleDefinitionRichTextListStyleDefinitionRichTextStyleSheet

附加控件RichTextStyleComboCtrlRichTextStyleListBoxRichTextStyleListCtrl

打印类RichTextPrintingRichTextPrintoutRichTextHeaderFooterData

对话框类RichTextStyleOrganiserDialogRichTextFormattingDialogSymbolPickerDialog

代码示例

取自wxPython演示程序:

import wx
import wx.richtext as rt
import images

#----------------------------------------------------------------------

class RichTextFrame(wx.Frame):

    def __init__(self, *args, **kw):

        wx.Frame.__init__(self, *args, **kw)

        self.MakeMenuBar()
        self.MakeToolBar()
        self.CreateStatusBar()
        self.SetStatusText("Welcome to wx.richtext.RichTextCtrl!")

        self.rtc = rt.RichTextCtrl(self, style=wx.VSCROLL|wx.HSCROLL|wx.NO_BORDER);
        wx.CallAfter(self.rtc.SetFocus)

        self.rtc.Freeze()
        self.rtc.BeginSuppressUndo()

        self.rtc.BeginParagraphSpacing(0, 20)

        self.rtc.BeginAlignment(rt.TEXT_ALIGNMENT_CENTRE)
        self.rtc.BeginBold()

        self.rtc.BeginFontSize(14)
        self.rtc.WriteText("Welcome to wxRichTextCtrl, a wxWidgets control for editing and presenting " \
                           "styled text and images")
        self.rtc.EndFontSize()
        self.rtc.Newline()

        self.rtc.BeginItalic()
        self.rtc.WriteText("by Julian Smart")
        self.rtc.EndItalic()

        self.rtc.EndBold()

        self.rtc.Newline()
        self.rtc.WriteImage(images._rt_zebra.GetImage())

        self.rtc.EndAlignment()

        self.rtc.Newline()
        self.rtc.Newline()

        self.rtc.WriteText("What can you do with this thing? ")
        self.rtc.WriteImage(images._rt_smiley.GetImage())
        self.rtc.WriteText(" Well, you can change text ")

        self.rtc.BeginTextColour((255, 0, 0))
        self.rtc.WriteText("colour, like this red bit.")
        self.rtc.EndTextColour()

        self.rtc.BeginTextColour((0, 0, 255))
        self.rtc.WriteText(" And this blue bit.")
        self.rtc.EndTextColour()

        self.rtc.WriteText(" Naturally you can make things ")
        self.rtc.BeginBold()
        self.rtc.WriteText("bold ")
        self.rtc.EndBold()
        self.rtc.BeginItalic()
        self.rtc.WriteText("or italic ")
        self.rtc.EndItalic()
        self.rtc.BeginUnderline()
        self.rtc.WriteText("or underlined.")
        self.rtc.EndUnderline()

        self.rtc.BeginFontSize(14)
        self.rtc.WriteText(" Different font sizes on the same line is allowed, too.")
        self.rtc.EndFontSize()

        self.rtc.WriteText(" Next we'll show an indented paragraph.")

        self.rtc.BeginLeftIndent(60)
        self.rtc.Newline()

        self.rtc.WriteText("It was in January, the most down-trodden month of an Edinburgh winter. " \
                           "An attractive woman came into the cafe, which is nothing remarkable.")
        self.rtc.EndLeftIndent()

        self.rtc.Newline()

        self.rtc.WriteText("Next, we'll show a first-line indent, achieved using BeginLeftIndent(100, -40).")

        self.rtc.BeginLeftIndent(100, -40)
        self.rtc.Newline()

        self.rtc.WriteText("It was in January, the most down-trodden month of an Edinburgh winter. " \
                           "An attractive woman came into the cafe, which is nothing remarkable.")
        self.rtc.EndLeftIndent()

        self.rtc.Newline()

        self.rtc.WriteText("Numbered bullets are possible, again using sub-indents:")

        self.rtc.BeginNumberedBullet(1, 100, 60)
        self.rtc.Newline()

        self.rtc.WriteText("This is my first item. Note that wxRichTextCtrl doesn't automatically do numbering, " \
                           "but this will be added later.")

        self.rtc.EndNumberedBullet()

        self.rtc.BeginNumberedBullet(2, 100, 60)
        self.rtc.Newline()

        self.rtc.WriteText("This is my second item.")
        self.rtc.EndNumberedBullet()

        self.rtc.Newline()

        self.rtc.WriteText("The following paragraph is right-indented:")

        self.rtc.BeginRightIndent(200)
        self.rtc.Newline()

        self.rtc.WriteText("It was in January, the most down-trodden month of an Edinburgh winter. " \
                           "An attractive woman came into the cafe, which is nothing remarkable.")
        self.rtc.EndRightIndent()

        self.rtc.Newline()

        self.rtc.WriteText("The following paragraph is right-aligned with 1.5 line spacing:")

        self.rtc.BeginAlignment(rt.TEXT_ALIGNMENT_RIGHT)
        self.rtc.BeginLineSpacing(rt.TEXT_ATTR_LINE_SPACING_HALF)
        self.rtc.Newline()

        self.rtc.WriteText("It was in January, the most down-trodden month of an Edinburgh winter. " \
                           "An attractive woman came into the cafe, which is nothing remarkable.")
        self.rtc.EndLineSpacing()
        self.rtc.EndAlignment()

        self.rtc.Newline()
        self.rtc.WriteText("Other notable features of wxRichTextCtrl include:")

        self.rtc.BeginSymbolBullet('*', 100, 60)
        self.rtc.Newline()
        self.rtc.WriteText("Compatibility with wxTextCtrl API")
        self.rtc.EndSymbolBullet()

        self.rtc.BeginSymbolBullet('*', 100, 60)
        self.rtc.Newline()
        self.rtc.WriteText("Easy stack-based BeginXXX()...EndXXX() style setting in addition to SetStyle()")
        self.rtc.EndSymbolBullet()

        self.rtc.BeginSymbolBullet('*', 100, 60)
        self.rtc.Newline()
        self.rtc.WriteText("XML loading and saving")
        self.rtc.EndSymbolBullet()

        self.rtc.BeginSymbolBullet('*', 100, 60)
        self.rtc.Newline()
        self.rtc.WriteText("Undo/Redo, with batching option and Undo suppressing")
        self.rtc.EndSymbolBullet()

        self.rtc.BeginSymbolBullet('*', 100, 60)
        self.rtc.Newline()
        self.rtc.WriteText("Clipboard copy and paste")
        self.rtc.EndSymbolBullet()

        self.rtc.BeginSymbolBullet('*', 100, 60)
        self.rtc.Newline()
        self.rtc.WriteText("wxRichTextStyleSheet with named character and paragraph styles, and control for " \
                           "applying named styles")
        self.rtc.EndSymbolBullet()

        self.rtc.BeginSymbolBullet('*', 100, 60)
        self.rtc.Newline()
        self.rtc.WriteText("A design that can easily be extended to other content types, ultimately with text " \
                           "boxes, tables, controls, and so on")
        self.rtc.EndSymbolBullet()

        self.rtc.BeginSymbolBullet('*', 100, 60)
        self.rtc.Newline()

        # 创建一种适合显示URL的样式
        urlStyle = rt.TextAttrEx()
        urlStyle.SetTextColour(wx.BLUE)
        urlStyle.SetFontUnderlined(True)

        self.rtc.WriteText("RichTextCtrl can also display URLs, such as this one: ")
        self.rtc.BeginStyle(urlStyle)
        self.rtc.BeginURL("http://wxPython.org/")
        self.rtc.WriteText("The wxPython Web Site")
        self.rtc.EndURL();
        self.rtc.EndStyle();
        self.rtc.WriteText(". Click on the URL to generate an event.")

        self.rtc.Bind(wx.EVT_TEXT_URL, self.OnURL)

        self.rtc.Newline()
        self.rtc.WriteText("Note: this sample content was generated programmatically from within the " \
                           "MyFrame constructor " \
                           "in the demo. The images were loaded from inline XPMs. Enjoy wxRichTextCtrl!")

        self.rtc.EndParagraphSpacing()

        self.rtc.EndSuppressUndo()
        self.rtc.Thaw()


    def OnURL(self, evt):

        wx.MessageBox(evt.GetString(), "URL Clicked")


    def OnFileOpen(self, evt):

        # 基于已加载的文件处理程序,为我们提供一个适合文件对话框的字符串
        wildcard, types = rt.RichTextBuffer.GetExtWildcard(save=False)
        dlg = wx.FileDialog(self, "Choose a filename",
                            wildcard=wildcard,
                            style=wx.OPEN)
        if dlg.ShowModal() == wx.ID_OK:
            path = dlg.GetPath()
            if path:
                fileType = types[dlg.GetFilterIndex()]
                self.rtc.LoadFile(path, fileType)
        dlg.Destroy()


    def OnFileSave(self, evt):

        if not self.rtc.GetFilename():
            self.OnFileSaveAs(evt)
            return

        self.rtc.SaveFile()


    def OnFileSaveAs(self, evt):

        wildcard, types = rt.RichTextBuffer.GetExtWildcard(save=True)

        dlg = wx.FileDialog(self, "Choose a filename",
                            wildcard=wildcard,
                            style=wx.SAVE)

        if dlg.ShowModal() == wx.ID_OK:
            path = dlg.GetPath()
            if path:
                fileType = types[dlg.GetFilterIndex()]
                ext = rt.RichTextBuffer.FindHandlerByType(fileType).GetExtension()
                if not path.endswith(ext):
                    path += '.' + ext
                self.rtc.SaveFile(path, fileType)

        dlg.Destroy()


    def OnFileViewHTML(self, evt):

        # 获取一个HTML文件处理程序的实例,使用它将文档保存到一个StringIO流中,
        # 然后在一个包含HtmlWindow的对话框中显示生成的HTML文本 
        handler = rt.RichTextHTMLHandler()
        handler.SetFlags(rt.RICHTEXT_HANDLER_SAVE_IMAGES_TO_MEMORY)
        handler.SetFontSizeMapping([7,9,11,12,14,22,100])

        import cStringIO
        stream = cStringIO.StringIO()
        if not handler.SaveStream(self.rtc.GetBuffer(), stream):
            return

        import wx.html
        dlg = wx.Dialog(self, title="HTML", style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
        html = wx.html.HtmlWindow(dlg, size=(500,400), style=wx.BORDER_SUNKEN)
        html.SetPage(stream.getvalue())
        btn = wx.Button(dlg, wx.ID_CANCEL)
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(html, 1, wx.ALL|wx.EXPAND, 5)
        sizer.Add(btn, 0, wx.ALL|wx.CENTER, 10)
        dlg.SetSizer(sizer)
        sizer.Fit(dlg)

        dlg.ShowModal()

        handler.DeleteTemporaryImages()


    def OnFileExit(self, evt):

        self.Close(True)


    def OnBold(self, evt):

        self.rtc.ApplyBoldToSelection()


    def OnItalic(self, evt):

        self.rtc.ApplyItalicToSelection()


    def OnUnderline(self, evt):

        self.rtc.ApplyUnderlineToSelection()


    def OnAlignLeft(self, evt):

        self.rtc.ApplyAlignmentToSelection(rt.TEXT_ALIGNMENT_LEFT)


    def OnAlignRight(self, evt):

        self.rtc.ApplyAlignmentToSelection(rt.TEXT_ALIGNMENT_RIGHT)


    def OnAlignCenter(self, evt):

        self.rtc.ApplyAlignmentToSelection(rt.TEXT_ALIGNMENT_CENTRE)


    def OnIndentMore(self, evt):

        attr = rt.TextAttrEx()
        attr.SetFlags(rt.TEXT_ATTR_LEFT_INDENT)
        ip = self.rtc.GetInsertionPoint()
        if self.rtc.GetStyle(ip, attr):
            r = rt.RichTextRange(ip, ip)
            if self.rtc.HasSelection():
                r = self.rtc.GetSelectionRange()

            attr.SetLeftIndent(attr.GetLeftIndent() + 100)
            attr.SetFlags(rt.TEXT_ATTR_LEFT_INDENT)
            self.rtc.SetStyle(r, attr)


    def OnIndentLess(self, evt):

        attr = rt.TextAttrEx()
        attr.SetFlags(rt.TEXT_ATTR_LEFT_INDENT)
        ip = self.rtc.GetInsertionPoint()
        if self.rtc.GetStyle(ip, attr):
            r = rt.RichTextRange(ip, ip)
            if self.rtc.HasSelection():
                r = self.rtc.GetSelectionRange()

        if attr.GetLeftIndent() >= 100:
            attr.SetLeftIndent(attr.GetLeftIndent() - 100)
            attr.SetFlags(rt.TEXT_ATTR_LEFT_INDENT)
            self.rtc.SetStyle(r, attr)


    def OnParagraphSpacingMore(self, evt):

        attr = rt.TextAttrEx()
        attr.SetFlags(rt.TEXT_ATTR_PARA_SPACING_AFTER)
        ip = self.rtc.GetInsertionPoint()
        if self.rtc.GetStyle(ip, attr):
            r = rt.RichTextRange(ip, ip)
            if self.rtc.HasSelection():
                r = self.rtc.GetSelectionRange()

            attr.SetParagraphSpacingAfter(attr.GetParagraphSpacingAfter() + 20);
            attr.SetFlags(rt.TEXT_ATTR_PARA_SPACING_AFTER)
            self.rtc.SetStyle(r, attr)


    def OnParagraphSpacingLess(self, evt):

        attr = rt.TextAttrEx()
        attr.SetFlags(rt.TEXT_ATTR_PARA_SPACING_AFTER)
        ip = self.rtc.GetInsertionPoint()
        if self.rtc.GetStyle(ip, attr):
            r = rt.RichTextRange(ip, ip)
            if self.rtc.HasSelection():
                r = self.rtc.GetSelectionRange()

            if attr.GetParagraphSpacingAfter() >= 20:
                attr.SetParagraphSpacingAfter(attr.GetParagraphSpacingAfter() - 20);
                attr.SetFlags(rt.TEXT_ATTR_PARA_SPACING_AFTER)
                self.rtc.SetStyle(r, attr)


    def OnLineSpacingSingle(self, evt):

        attr = rt.TextAttrEx()
        attr.SetFlags(rt.TEXT_ATTR_LINE_SPACING)
        ip = self.rtc.GetInsertionPoint()
        if self.rtc.GetStyle(ip, attr):
            r = rt.RichTextRange(ip, ip)
            if self.rtc.HasSelection():
                r = self.rtc.GetSelectionRange()

            attr.SetFlags(rt.TEXT_ATTR_LINE_SPACING)
            attr.SetLineSpacing(10)
            self.rtc.SetStyle(r, attr)


    def OnLineSpacingHalf(self, evt):

        attr = rt.TextAttrEx()
        attr.SetFlags(rt.TEXT_ATTR_LINE_SPACING)
        ip = self.rtc.GetInsertionPoint()
        if self.rtc.GetStyle(ip, attr):
            r = rt.RichTextRange(ip, ip)
            if self.rtc.HasSelection():
                r = self.rtc.GetSelectionRange()

            attr.SetFlags(rt.TEXT_ATTR_LINE_SPACING)
            attr.SetLineSpacing(15)
            self.rtc.SetStyle(r, attr)


    def OnLineSpacingDouble(self, evt):

        attr = rt.TextAttrEx()
        attr.SetFlags(rt.TEXT_ATTR_LINE_SPACING)
        ip = self.rtc.GetInsertionPoint()
        if self.rtc.GetStyle(ip, attr):
            r = rt.RichTextRange(ip, ip)
            if self.rtc.HasSelection():
                r = self.rtc.GetSelectionRange()

            attr.SetFlags(rt.TEXT_ATTR_LINE_SPACING)
            attr.SetLineSpacing(20)
            self.rtc.SetStyle(r, attr)


    def OnFont(self, evt):

        if not self.rtc.HasSelection():
            return

        r = self.rtc.GetSelectionRange()
        fontData = wx.FontData()
        fontData.EnableEffects(False)
        attr = rt.TextAttrEx()
        attr.SetFlags(rt.TEXT_ATTR_FONT)
        if self.rtc.GetStyle(self.rtc.GetInsertionPoint(), attr):
            fontData.SetInitialFont(attr.GetFont())

        dlg = wx.FontDialog(self, fontData)
        if dlg.ShowModal() == wx.ID_OK:
            fontData = dlg.GetFontData()
            font = fontData.GetChosenFont()
            if font:
                attr.SetFlags(rt.TEXT_ATTR_FONT)
                attr.SetFont(font)
                self.rtc.SetStyle(r, attr)
        dlg.Destroy()


    def OnColour(self, evt):

        colourData = wx.ColourData()
        attr = rt.TextAttrEx()
        attr.SetFlags(rt.TEXT_ATTR_TEXT_COLOUR)
        if self.rtc.GetStyle(self.rtc.GetInsertionPoint(), attr):
            colourData.SetColour(attr.GetTextColour())

        dlg = wx.ColourDialog(self, colourData)
        if dlg.ShowModal() == wx.ID_OK:
            colourData = dlg.GetColourData()
            colour = colourData.GetColour()
            if colour:
                if not self.rtc.HasSelection():
                    self.rtc.BeginTextColour(colour)
                else:
                    r = self.rtc.GetSelectionRange()
                    attr.SetFlags(rt.TEXT_ATTR_TEXT_COLOUR)
                    attr.SetTextColour(colour)
                    self.rtc.SetStyle(r, attr)
        dlg.Destroy()



    def OnUpdateBold(self, evt):

        evt.Check(self.rtc.IsSelectionBold())


    def OnUpdateItalic(self, evt):

        evt.Check(self.rtc.IsSelectionItalics())


    def OnUpdateUnderline(self, evt):

        evt.Check(self.rtc.IsSelectionUnderlined())


    def OnUpdateAlignLeft(self, evt):

        evt.Check(self.rtc.IsSelectionAligned(rt.TEXT_ALIGNMENT_LEFT))


    def OnUpdateAlignCenter(self, evt):

        evt.Check(self.rtc.IsSelectionAligned(rt.TEXT_ALIGNMENT_CENTRE))


    def OnUpdateAlignRight(self, evt):

        evt.Check(self.rtc.IsSelectionAligned(rt.TEXT_ALIGNMENT_RIGHT))


    def ForwardEvent(self, evt):

        # RichTextCtrl能够处理撤销、重做、剪切、复制、粘贴、删除和全选等操作的菜单和更新
        # 事件,所以只需将这些事件转发给它即可。
        self.rtc.ProcessEvent(evt)


    def MakeMenuBar(self):

        def doBind(item, handler, updateUI=None):

            self.Bind(wx.EVT_MENU, handler, item)
            if updateUI is not None:
                self.Bind(wx.EVT_UPDATE_UI, updateUI, item)

        fileMenu = wx.Menu()
        doBind( fileMenu.Append(-1, "&Open\tCtrl+O", "Open a file"),
                self.OnFileOpen )
        doBind( fileMenu.Append(-1, "&Save\tCtrl+S", "Save a file"),
                self.OnFileSave )
        doBind( fileMenu.Append(-1, "&Save As...\tF12", "Save to a new file"),
                self.OnFileSaveAs )
        fileMenu.AppendSeparator()
        doBind( fileMenu.Append(-1, "&View as HTML", "View HTML"),
                self.OnFileViewHTML)
        fileMenu.AppendSeparator()
        doBind( fileMenu.Append(-1, "E&xit\tCtrl+Q", "Quit this program"),
                self.OnFileExit )

        editMenu = wx.Menu()
        doBind( editMenu.Append(wx.ID_UNDO, "&Undo\tCtrl+Z"),
                self.ForwardEvent, self.ForwardEvent)
        doBind( editMenu.Append(wx.ID_REDO, "&Redo\tCtrl+Y"),
                self.ForwardEvent, self.ForwardEvent )
        editMenu.AppendSeparator()
        doBind( editMenu.Append(wx.ID_CUT, "Cu&t\tCtrl+X"),
                self.ForwardEvent, self.ForwardEvent )
        doBind( editMenu.Append(wx.ID_COPY, "&Copy\tCtrl+C"),
                self.ForwardEvent, self.ForwardEvent)
        doBind( editMenu.Append(wx.ID_PASTE, "&Paste\tCtrl+V"),
                self.ForwardEvent, self.ForwardEvent)
        doBind( editMenu.Append(wx.ID_CLEAR, "&Delete\tDel"),
                self.ForwardEvent, self.ForwardEvent)
        editMenu.AppendSeparator()
        doBind( editMenu.Append(wx.ID_SELECTALL, "Select A&ll\tCtrl+A"),
                self.ForwardEvent, self.ForwardEvent )

        formatMenu = wx.Menu()
        doBind( formatMenu.AppendCheckItem(-1, "&Bold\tCtrl+B"),
                self.OnBold, self.OnUpdateBold)
        doBind( formatMenu.AppendCheckItem(-1, "&Italic\tCtrl+I"),
                self.OnItalic, self.OnUpdateItalic)
        doBind( formatMenu.AppendCheckItem(-1, "&Underline\tCtrl+U"),
                self.OnUnderline, self.OnUpdateUnderline)
        formatMenu.AppendSeparator()
        doBind( formatMenu.AppendCheckItem(-1, "L&eft Align"),
                self.OnAlignLeft, self.OnUpdateAlignLeft)
        doBind( formatMenu.AppendCheckItem(-1, "&Centre"),
                self.OnAlignCenter, self.OnUpdateAlignCenter)
        doBind( formatMenu.AppendCheckItem(-1, "&Right Align"),
                self.OnAlignRight, self.OnUpdateAlignRight)
        formatMenu.AppendSeparator()
        doBind( formatMenu.Append(-1, "Indent &More"), self.OnIndentMore)
        doBind( formatMenu.Append(-1, "Indent &Less"), self.OnIndentLess)
        formatMenu.AppendSeparator()
        doBind( formatMenu.Append(-1, "Increase Paragraph &Spacing"), self.OnParagraphSpacingMore)
        doBind( formatMenu.Append(-1, "Decrease &Paragraph Spacing"), self.OnParagraphSpacingLess)
        formatMenu.AppendSeparator()
        doBind( formatMenu.Append(-1, "Normal Line Spacing"), self.OnLineSpacingSingle)
        doBind( formatMenu.Append(-1, "1.5 Line Spacing"), self.OnLineSpacingHalf)
        doBind( formatMenu.Append(-1, "Double Line Spacing"), self.OnLineSpacingDouble)
        formatMenu.AppendSeparator()
        doBind( formatMenu.Append(-1, "&Font..."), self.OnFont)

        mb = wx.MenuBar()
        mb.Append(fileMenu, "&File")
        mb.Append(editMenu, "&Edit")
        mb.Append(formatMenu, "F&ormat")
        self.SetMenuBar(mb)


    def MakeToolBar(self):

        def doBind(item, handler, updateUI=None):

            self.Bind(wx.EVT_TOOL, handler, item)
            if updateUI is not None:
                self.Bind(wx.EVT_UPDATE_UI, updateUI, item)

        tbar = self.CreateToolBar()
        doBind( tbar.AddTool(-1, images._rt_open.GetBitmap(),
                            shortHelpString="Open"), self.OnFileOpen)
        doBind( tbar.AddTool(-1, images._rt_save.GetBitmap(),
                            shortHelpString="Save"), self.OnFileSave)
        tbar.AddSeparator()
        doBind( tbar.AddTool(wx.ID_CUT, images._rt_cut.GetBitmap(),
                            shortHelpString="Cut"), self.ForwardEvent, self.ForwardEvent)
        doBind( tbar.AddTool(wx.ID_COPY, images._rt_copy.GetBitmap(),
                            shortHelpString="Copy"), self.ForwardEvent, self.ForwardEvent)
        doBind( tbar.AddTool(wx.ID_PASTE, images._rt_paste.GetBitmap(),
                            shortHelpString="Paste"), self.ForwardEvent, self.ForwardEvent)
        tbar.AddSeparator()
        doBind( tbar.AddTool(wx.ID_UNDO, images._rt_undo.GetBitmap(),
                            shortHelpString="Undo"), self.ForwardEvent, self.ForwardEvent)
        doBind( tbar.AddTool(wx.ID_REDO, images._rt_redo.GetBitmap(),
                            shortHelpString="Redo"), self.ForwardEvent, self.ForwardEvent)
        tbar.AddSeparator()
        doBind( tbar.AddTool(-1, images._rt_bold.GetBitmap(), isToggle=True,
                            shortHelpString="Bold"), self.OnBold, self.OnUpdateBold)
        doBind( tbar.AddTool(-1, images._rt_italic.GetBitmap(), isToggle=True,
                            shortHelpString="Italic"), self.OnItalic, self.OnUpdateItalic)
        doBind( tbar.AddTool(-1, images._rt_underline.GetBitmap(), isToggle=True,
                            shortHelpString="Underline"), self.OnUnderline, self.OnUpdateUnderline)
        tbar.AddSeparator()
        doBind( tbar.AddTool(-1, images._rt_alignleft.GetBitmap(), isToggle=True,
                            shortHelpString="Align Left"), self.OnAlignLeft, self.OnUpdateAlignLeft)
        doBind( tbar.AddTool(-1, images._rt_centre.GetBitmap(), isToggle=True,
                            shortHelpString="Center"), self.OnAlignCenter, self.OnUpdateAlignCenter)
        doBind( tbar.AddTool(-1, images._rt_alignright.GetBitmap(), isToggle=True,
                            shortHelpString="Align Right"), self.OnAlignRight, self.OnUpdateAlignRight)
        tbar.AddSeparator()
        doBind( tbar.AddTool(-1, images._rt_indentless.GetBitmap(),
                            shortHelpString="Indent Less"), self.OnIndentLess)
        doBind( tbar.AddTool(-1, images._rt_indentmore.GetBitmap(),
                            shortHelpString="Indent More"), self.OnIndentMore)
        tbar.AddSeparator()
        doBind( tbar.AddTool(-1, images._rt_font.GetBitmap(),
                            shortHelpString="Font"), self.OnFont)
        doBind( tbar.AddTool(-1, images._rt_colour.GetBitmap(),
                            shortHelpString="Font Colour"), self.OnColour)

        tbar.Realize()


#----------------------------------------------------------------------

class TestPanel(wx.Panel):

    def __init__(self, parent):

        wx.Panel.__init__(self, parent, -1)

        b = wx.Button(self, -1, "Show the RichTextCtrl sample", (50,50))
        self.Bind(wx.EVT_BUTTON, self.OnButton, b)

        self.AddRTCHandlers()


    def AddRTCHandlers(self):

        # 确保还没有添加过它们
        if rt.RichTextBuffer.FindHandlerByType(rt.RICHTEXT_TYPE_HTML) is not None:
            return

        # 应该放在应用程序的OnInit方法中。我不确定为什么这些文件处理程序在C++富文本代码
        # 中没有默认加载,我猜是为了让你在有需要的时候可以更改文件名或文件扩展名…… 
        rt.RichTextBuffer.AddHandler(rt.RichTextHTMLHandler())
        rt.RichTextBuffer.AddHandler(rt.RichTextXMLHandler())

        # ……就像这样
        rt.RichTextBuffer.AddHandler(rt.RichTextXMLHandler(name="Other XML",
                                                           ext="ox",
                                                           type=99))

        # 对于“以HTML格式查看”选项来说是必需的,因为我们告知它将图像存储在内存文件系统中 
        wx.FileSystem.AddHandler(wx.MemoryFSHandler())


    def OnButton(self, evt):

        win = RichTextFrame(self, -1, "wx.richtext.RichTextCtrl",
                            size=(700, 500),
                            style = wx.DEFAULT_FRAME_STYLE)
        win.Show(True)

        # 如果演示程序的PyShell正在运行,可方便地对其进行访问
        self.rtfrm = win
        self.rtc = win.rtc


app = wx.App(0)

frame = wx.Frame(None)
panel = TestPanel(frame)
frame.Show()

app.MainLoop()

文本样式

样式属性由wx.TextAttr表示,或者对于诸如边距和大小等属性,若要进行更多控制,则由派生类RichTextAttr表示。

在设置样式时,属性对象的标志决定了应用哪些属性。在查询样式时,传递的标志将被忽略,除非(可选择地)用于确定是从字符内容还是从段落对象中检索属性。

RichTextCtrl对样式采用分层方法,这样内容的不同部分可能会为你在屏幕上看到的最终样式贡献不同的属性。

在一个控件中主要有四种样式概念:

  • 基本样式:控件的基础样式,其他任何样式都叠加在它之上。它提供默认属性,并且更改基本样式可能会立即改变内容的外观,这取决于内容所使用的其他样式。调用SetFont会更改基本样式的字体。基本样式通过SetBasicStyle来设置。
  • 段落样式: 每个段落都拥有独立于其他段落且独立于该段落内内容的属性。通常,这些属性是与段落相关的,比如对齐方式和缩进量,但也可以设置字符属性。通过将RICHTEXT_SETSTYLE_PARAGRAPHS_ONLY传递给SetStyleEx方法,可以独立于段落内容来设置段落样式。
  • 字符样式: 每个段落中的字符都可以拥有属性。单个字符或连续的一组字符可以具有特定的一组属性。字符样式可以通过SetStyleSetStyleEx来设置。
  • 默认样式: 这是“当前”样式,它决定了随后输入、粘贴或通过编程方式插入的内容的样式。默认样式通过SetDefaultStyle进行设置。

你在屏幕上看到的是动态组合的样式,它是通过合并上述前三种样式类型(第四种仅作为未来内容插入的指导,因此不会影响当前显示的内容)而得到的。

为了让这一切更加具体明确,以下是一些你可能设置这些不同样式的示例:

  • 你可以将基本样式设置为12磅的Times Roman字体,左对齐,并且每个段落之后有两毫米的间距。
  • 你可以将(某一特定段落的)段落样式设置为居中对齐。
  • 你可以将某一特定单词的字符样式设置为加粗。
  • 你可以将默认样式设置为带有下划线,以便用于随后插入的文本。

你自然既可以通过自己设计的用户界面来完成上述任何操作,也可以通过编程的方式来实现。

基本的wx.TextCtrl在属性存储方面不像RichTextCtrl那样进行区分。所以在设置和检索属性时,我们需要更精细的控制。SetStyleEx方法接受一个标志参数:

  • RICHTEXT_SETSTYLE_OPTIMIZE 指定仅当组合属性与当前对象的属性不同时,才应更改样式。在应用由用户编辑过的样式时,这一点很重要,因为用户刚刚编辑的是组合(可见)样式,而RichTextCtrl希望保持与原始对象相关联的未更改属性,而不是将这些属性同时应用于段落对象和内容对象。
  • RICHTEXT_SETSTYLE_PARAGRAPHS_ONLY 指定给定范围内只有段落对象应采用这些属性。
  • RICHTEXT_SETSTYLE_CHARACTERS_ONLY 指定给定范围内只有内容对象(文本或图像)应采用这些属性。
  • RICHTEXT_SETSTYLE_WITH_UNDO 指定该操作应是可撤销的。

能够在RichTextCtrl中更改任意属性是很棒的,但对于用户或程序员来说,分别设置属性可能会很麻烦。文字处理软件有一系列样式集合,你可以根据需要进行定制,也可以直接使用,这意味着你只需点击一下就能设置一个标题样式,而无需每次都将文本标记为加粗、指定大字体尺寸,还要为每个这样的标题设置特定的段落间距和对齐方式。同样,wxPython提供了一个名为RichTextStyleSheet的类,它用于管理样式定义(RichTextParagraphStyleDefinitionRichTextListStyleDefinitionRichTextCharacterStyleDefinition)。一旦你向样式表中添加了定义,并将其与RichTextCtrl关联起来,你就可以将一个命名的定义应用到一定范围的文本上。RichTextStyleComboCtrlRichTextStyleListBox这两个类可用于向用户展示样式表中的样式列表,并将这些样式应用到所选文本上。

你可以通过调用ApplyStyleSheet方法,将样式表重新应用到控件的内容上。如果样式定义发生了更改,而你又希望内容能体现出这些变化,那么这个操作就很有用。这是基于这样一个事实:当你应用一个命名样式时,样式定义名称会记录在内容中。所以,ApplyStyleSheet方法的工作原理是找到带有样式名称的段落属性,然后将该定义的属性重新应用到段落上。目前,这仅适用于段落样式定义和列表样式定义。

包含的对话框

RichTextCtrl附带了一些标准对话框,以便更轻松地实现文本编辑功能。

RichTextFormattingDialog可用于字符或段落格式设置,或者两者兼具。它是一个PropertySheetDialog,具有以下可用选项卡:字体、缩进和间距、制表位、项目符号、样式、边框、边距、背景、大小以及列表样式。你可以通过向对话框构造函数提供标志来选择要显示哪些页面。在字符格式设置对话框中,通常只会显示“字体”页面。在段落格式设置对话框中,会显示“缩进和间距”“制表位”以及“项目符号”页面。“样式”选项卡在编辑样式定义时很有用。

你可以通过提供自己的RichTextFormattingDialogFactory对象来自定义此对话框,该对象会告知格式设置对话框支持多少个页面、这些页面的标识符是什么,以及如何创建这些页面。

RichTextStyleOrganiserDialog是一个多功能对话框,可用于管理样式定义、浏览样式并应用样式,或者选择带有重新编号选项的列表样式。有关使用方法,请查看示例——它被用于“管理样式”和“项目符号和编号”菜单命令。

SymbolPickerDialog允许用户从指定的字体中插入一个符号。除了被包含在富文本库中之外,它不依赖于RichTextCtrl

RichTextCtrl的实现原理

数据表示由RichTextBuffer来处理,并且一个RichTextCtrl始终会有一个这样的缓冲区。

内容由一个对象层次结构来表示,这些对象均派生自RichTextObject。一个对象可能是一张图像、一段文本、一个段落,或者是一个更复杂的组合对象。对象存储着包含样式信息的RichTextAttr;一个段落对象既可以包含段落信息也可以包含字符信息,但是像文本这样的内容对象只能存储字符信息。在控件中或打印输出中显示的最终样式,是基础样式、段落样式和内容(字符)样式的组合。

层次结构的最顶层是缓冲区,它是一种RichTextParagraphLayoutBox,其中包含更多的RichTextParagraph对象,每个段落对象都可以包含文本、图像,并且有可能包含其他类型的对象。

每个对象都维护一个范围(起始和结束位置),该范围是从主父对象的起始位置开始计算的。

当对一个对象调用Layout方法时,会为该对象指定一个它必须将自身限制在其中的尺寸,或者指定一个或多个可灵活调整的方向(垂直方向或水平方向)。例如,对于一个居中对齐的段落,会为其指定可使用的页面宽度(减去任何边距),但它在垂直方向上可以无限延伸。Layout方法的实现会缓存计算得出的尺寸和位置。

当缓冲区被修改时,某个范围会失效(标记为需要进行布局处理),这样就只会执行最少数量的布局操作。

具有相同样式的纯文本段落仅包含另一个对象,即RichTextPlainText对象。当对该对象的一部分应用样式时,该对象会被分解为多个独立的对象,每种不同的字符样式对应一个对象。因此,一个段落内的每个对象始终只有一个wx.TextAttr对象来表示其字符样式。当然,经过大量的编辑操作后,这可能会导致碎片化,有可能出现本只需一个对象就能表示但却存在多个具有相同样式的对象的情况。所以,在更新控件的显示时会调用“整理碎片(Defragment)”函数,以确保使用最少数量的对象。

嵌套对象

RichTextCtrl支持诸如文本框和表格之类的嵌套对象。为了实现与现有API的兼容性,引入了对象焦点的概念。当用户点击一个嵌套的文本框时,对象焦点会被设置到该容器对象上,这样一来,所有的键盘输入和应用程序编程接口函数都将应用于该容器。应用程序可以使用SetObjectFocu函数来更改焦点。传入参数None调用此函数,可将焦点设置回顶级对象。

当焦点发生变化时,会向该控件发送一个事件。

当用户点击该控件时,RichTextCtrl会通过调用所找到的容器重写后的AcceptsFocus函数,来确定将哪个容器设置为当前的对象焦点。例如,尽管表格是一个容器,但表格本身不能成为对象焦点,因为在表格层面不存在文本编辑操作。相反,表格中的一个单元格必须接受焦点。

由于对于嵌套对象而言,仅用起始位置和结束位置来表示一个部分是不可能的,因此提供了RichTextSelection类,该类存储多个范围(用于非连续的选择,比如表格单元格),以及指向相关容器对象的指针。你可以将RichTextSelection传递给SetSelection方法,或者从GetSelection方法中获取它的一个实例。

在选择多个对象(例如单元格表格)时,RichTextCtrl的拖动手势处理程序代码会调用函数HandlesChildSelections来确定子对象是否可以进行单独选择。目前,只有表格单元格可以通过这种方式进行多重选择。

上下文菜单和属性对话框

你可以通过三种方式来使用上下文菜单:你可以让RichTextCtrl处理所有相关事宜并提供一个基本菜单;你可以使用SetContextMenu方法来设置你自己的上下文菜单,但让RichTextCtrl处理该菜单的显示以及添加属性项的操作;或者你可以通过以常规方式向你的类中添加一个上下文菜单事件处理程序,来覆盖默认的上下文菜单行为。

如果你在表格单元格中的文本框上单击鼠标右键,你可能想要编辑这些对象之一的属性 —— 但你将要编辑的是哪些属性呢?

好的,默认行为允许同时出现最多三个属性编辑菜单项 —— 分别对应于被点击的对象、该对象的容器以及容器的父级(这取决于这些对象中的任何一个是否从其CanEditProperties函数返回true)。如果你提供一个上下文菜单,使用ID_RICHTEXT_PROPERTIES标识符添加一个属性命令项,以便富文本控件RichTextCtrl能够找到添加命令项的位置。该对象应该通过从GetPropertiesMenuLabel函数返回一个字符串,来告知控件使用什么标签。

由于可能会显示多个属性编辑命令,建议你不要包含“属性”这个词,只需显示对象的名称即可,比如“文本框”或“表格”。

开发路线图

原文为空

Bugs

下面是一份不完整的错误列表。

  • 在一行的开头将插入符号向上移动时,有时插入符号的定位会不正确。
  • 当扩展选区时,由于绘制单个文本字符串与分别绘制几个文本片段时在字距调整方面存在差异,文本会轻微跳动。可以通过使用wx.DC.GetPartialTextExtents来精确计算各个文本片段应绘制的位置,从而改善这种情况。请注意,由于文本片段的属性不同而导致的文本片段分离问题,也存在同样的情况。

功能特性

这是一份尚未实现的部分功能特性列表。若能在这些方面提供帮助,将不胜感激。

  • 在一些尚未实现相关功能的函数中增加对组合对象的支持,例如ApplyStyleSheet
  • 表格API的增强以及相关对话框的改进;改善表格布局,尤其是跨行(合并单元格)以及使其更适配的效果
  • 实现来自HTML的转换功能,并对HTML输出处理程序进行重写,使其除了包含用于实现与wxHTML兼容性的简化HTML模式外,还涵盖对CSS、表格、文本框以及浮动图像的支持
  • 开放办公软件(Open Office)的输入与输出
  • 富文本格式(RTF)的输入与输出
  • 一个ruler控件
  • 标准编辑工具栏
  • 位图项目符号
  • 至少在打印/预览时能实现两端对齐的文本排版
  • 缩放:要么对所有内容进行缩放,要么使用自定义的参考点大小以及一个可选的尺寸缩放比例来进行渲染

我们还可以做一些事情来利用平台底层的文本处理能力;某些平台(如Mac OS X)上提供了更高级别的文本格式化API,并且将某些高级功能转换为低级的wx.DC API,但这是没有必要的。不过,这需要对wxPython API进行扩展补充。