PyQt5桌面应用系列
- PyQt5桌面应用开发(0/22):总结再出发
- PyQt5桌面应用开发(1):需求分析
- PyQt5桌面应用开发(2):事件循环
- PyQt5桌面应用开发(3):并行设计
- PyQt5桌面应用开发(4):界面设计
- PyQt5桌面应用开发(5):对话框
- PyQt5桌面应用开发(6):文件对话框
- PyQt5桌面应用开发(7):文本编辑+语法高亮与行号
- PyQt5桌面应用开发(8):从QInputDialog转进到函数参数传递
- PyQt5桌面应用开发(9):经典布局QMainWindow
- PyQt5桌面应用开发(10):界面布局基本支持
- PyQt5桌面应用开发(11):摸鱼也要讲基本法,两个字,16
- PyQt5桌面应用开发(12):QFile与线程安全
- PyQt5桌面应用开发(13):QGraphicsView框架
- PyQt5桌面应用开发(14):数据库+ModelView+QCharts
- PyQt5桌面应用开发(15):界面动画
- PyQt5桌面应用开发(16):定制化控件-QPainter绘图
- PyQt5桌面应用开发(17):类结构+QWebEngineView
- PyQt5桌面应用开发(18):自定义控件界面设计与实现
- PyQt5桌面应用开发(19):事件过滤器
- PyQt5桌面应用开发(20):界面设计结果自动测试(一)
- PyQt5桌面应用开发(21):界面设计结果自动测试(二)
代码编辑和语法高亮的亿点点细节
接着上回的文件对话框,我们来看看代码编辑器和语法高亮的实现。文件打开和显示、文件编辑这是跟文本相关的用户界面的两个核心的功能,在严肃的桌面应用开发中,这是必不可少的。
- 用户报表,例如:显示Log文件;
- 用户交互,例如:编辑配置文件。
PyQt5提供了三个控件,继承关系如下图。
QTextEdit
:支持HTML语法的控件;QTextBrowser
:只读,支持链接和跳转。
QPlainTextEdit
:基于纯文本的控件。
这两个控件的主要不同在与文本布局计算的方式,实际上QPlainTextEdit
实现了基于行的文本布局,其文本滚动则是基于段落的。
我们想要实现代码编辑器,那就必须不考虑采用HTML语法来格式化显示的文本,就算是HTML代码编辑器,也要用纯文本!所以我们选择QPlainTextEdit
。
QPlainTextEdit
是一个高级的查看器/编辑器,支持纯文本。它被优化用于处理大型文档,并快速响应用户输入。 这个控件使用了和QTextEdit
相同的技术和概念,但是它是为了纯文本处理而优化的。控件的文档是由段落组成的,段落是格式化的字符串,它会自动换行以适应控件的宽度。默认情况下,一个换行符表示一个段落。一个文档由零个或多个段落组成。段落由硬换行符分隔。段落中的每个字符都有自己的属性,例如字体和颜色。
鼠标光标的形状默认是Qt::IBeamCursor
。可以通过viewport()
的cursor
属性来改变。
作为用户报表的文本控件
文本采用setPlainText()
设置或替换,该函数删除现有文本并用传递给setPlainText()
的文本替换它。
文本可以使用QTextCursor
类或使用insertPlainText()
,appendPlainText()
或paste()
的便利函数插入。
默认情况下,文本编辑器在空格处换行以适应文本编辑器窗体。 setLineWrapMode()
函数用于指定所需的换行方式,如果不需要任何换行,则为WidgetWidth
或NoWrap
。如果使用单词换行到窗体宽度WidgetWidth
,则可以使用setWordWrapMode()
指定是否在空格处或任何位置中断。
find
函数可以用于查找和选择文本中的字符串。
如果要限制QPlainTextEdit
中段落的总数,例如在日志查看器中非常有用,则可以使用maximumBlockCount
属性。 setMaximumBlockCount()
和appendPlainText()
的组合将QPlainTextEdit
变为日志文本的高效查看器。 可以使用centerOnScroll()
属性减少滚动,从而使日志查看器更快。 可以以有限的方式格式化文本,要么使用语法突出显示器,要么使用appendHtml()
附加html
格式的文本。 虽然QPlainTextEdit
不支持具有表格和浮动的复杂富文本呈现,但它支持您可能需要的日志查看器中的有限基于段落的格式。
作为编辑器的文本控件
编辑器首先也是一个展示文本的控件,所以上述的内容也适用于编辑器。
选择文本由QTextCursor
类处理,该类提供了创建选择,检索文本内容或删除选择的功能。 您可以使用textCursor()
方法检索与用户可见光标对应的对象。 如果要在QPlainTextEdit
中设置选择,只需在QTextCursor
对象上创建一个选择,然后使用setCursor()
将该光标设置为可见光标。 可以使用copy()
将选择复制到剪贴板,或使用cut()
将其剪切到剪贴板。 可以使用selectAll()
选择整个文本。
QPlainTextEdit
组合了一个QTextDocument
对象,可以使用document()
方法检索该对象。 您还可以使用setDocument()
设置自己的文档对象。 如果文本更改,则QTextDocument
发出textChanged()
信号,它还提供了一个isModified()
函数,如果自加载以来文本已被修改或自上次调用setModified()
,则返回true,参数为false。 此外,它提供了撤消和重做的方法。
代码编辑器的需求
QPlainTextEdit
是一个纯文本编辑/查看器;- 它提供了很强大的编辑和查看功能;
- 编辑对象由
QTextDocument
类提供。
实现代码编辑器,则主要有两个方面的内容:
- 显示行号,高亮当前行;
- 语法高亮。
这都有现成的参考例子,例如我们正在编的代码,用的是IDEA,它的代码编辑器就是这样的:
代码编辑1
这个地方,就是简单的实现行号显示和高亮当前行。从图中可以看出,编辑器在编辑区域左侧的区域中显示行号。 编辑器将突出显示包含光标的行。
参考官方代码,我们实现继承自QPlainTextEdit
的CodeEditor
类;增加LineNumberArea
类,用于显示行号。LineNumberArea
类继承自QWidget
,并与CodeEditor
类形成组合关系,也就是作为一个成员变量。
下面就是LineNumberArea
的代码,这个类与一个Editor联系在一起,当Editor的块计数变化、更新的时候,就调用这里的两个方法来计算宽度、行数,更新外观。 这里重载了QWidget.paintEvent
方法,来设置相应的字体、背景,显示行数,可以看到,这里的行数从1开始计数。
painter.drawText(paint_rect, Qt.AlignRight, str(block_number + 1))
from PyQt5.QtCore import Qt
from PyQt5.QtCore import QRect
from PyQt5.QtGui import QFont, QColor, QPainter
from PyQt5.QtWidgets import QWidget
class LineNumberArea(QWidget):
def __init__(self, editor):
QWidget.__init__(self, editor)
self.editor = editor
self.editor.blockCountChanged.connect(self.update_width)
self.editor.updateRequest.connect(self.update_contents)
self.font = QFont()
self.numberBarColor = QColor("#e8e8e8")
def paintEvent(self, event):
# Override paintEvent to draw the line numbers
painter = QPainter(self)
painter.fillRect(event.rect(), self.numberBarColor)
block = self.editor.firstVisibleBlock()
# Iterate over all visible text blocks in the document.
while block.isValid():
block_number = block.blockNumber()
block_top = self.editor.blockBoundingGeometry(block).translated(self.editor.contentOffset()).top()
# Check if the position of the block is outside the visible area.
if not block.isVisible() or block_top >= event.rect().bottom():
break
# We want the line number for the selected line to be bold.
if block_number == self.editor.textCursor().blockNumber():
self.font.setBold(True)
painter.setPen(QColor("#000000"))
else:
self.font.setBold(False)
painter.setPen(QColor(