在qtextedit中覆盖paintEvent,以绘制矩形周围的矩形
我从 pyqt5
中使用 qtextedit
,我想在选定的单词上放置一个框架。正如MusicAmante所建议的那样,我试图覆盖 PaintEvent
。我想从光标位置提取矩形的坐标。因此,我将我的 textededitor
的光标放在文本的开头和结尾处,然后尝试从每个开始和结束时获取全局坐标。使用这些坐标,应绘制矩形。但是,当我运行代码时,输出坐标是错误的,只绘制了一个破折号或一个很小的矩形。
import sys
from PyQt5.QtGui import QPainter, QPen
from PyQt5.QtWidgets import QTextEdit, QWidget, QApplication, QVBoxLayout
from PyQt5.QtCore import Qt
class TextEditor(QTextEdit):
def __init__(self, parent=None):
super().__init__(parent)
self.coordinates = []
def paintEvent(self, event):
painter = QPainter(self.viewport())
painter.setPen(QPen(Qt.black, 4, Qt.SolidLine))
if self.coordinates:
for coordinate in self.coordinates:
painter.drawRect(coordinate[0].x(), coordinate[0].y(), coordinate[1].x() - coordinate[0].x(), 10)
super(TextEditor, self).paintEvent(event)
class Window(QWidget):
def __init__(self):
super(Window, self).__init__()
edit = TextEditor(self)
layout = QVBoxLayout(self)
layout.addWidget(edit)
self.boxes = []
text = "Hello World"
edit.setText(text)
word = "World"
start = text.find(word)
end = start + len(word)
edit.coordinates.append(self.emit_coorindate(start, end, edit))
edit.viewport().update()
def emit_coorindate(self, start, end, edit):
cursor = edit.textCursor()
cursor.setPosition(start)
x = edit.cursorRect().topLeft()
cursor.setPosition(end)
y = edit.cursorRect().bottomRight()
return (x, y)
if __name__ == '__main__':
app = QApplication(sys.argv)
window = Window()
window.setGeometry(800, 100, 1000, 1000)
window.show()
sys.exit(app.exec_())
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
注意:我将此答案基于该问题的早期版本,该问题使用QTEXTCHURFORMAT设置文本片段的背景。 但直到现在。
解决方案时,我增加了支持,
当我发现自己寻找有效的 em>丰富的文本,包括多行之类的简单方面。
虽然QT丰富的文本引擎允许设置文本的背景,但在文本周围绘制A border 没有支持。
对于非常基本情况,提供了获得QTEXTEDIT选择的边界盒 Will Affeice,但这有一些缺陷。
首先,如果文本包裹在新行上(即很长的选择),则将显示完整界限rect,其中将包括不属于选择的文本。如上所述,您可以看到结果:
然后,提出的解决方案仅对 static 文本:每当更新文本时,选择都不会随之更新。虽然可以在编程更改文本时更新内部选择,但用户编辑将使其更加复杂,容易出现错误或意外行为。
解决方案:使用QTEXTCHARFORMAT,
而以下方法显然更为复杂,更有效,并且允许进一步自定义(例如设置边框颜色和宽度)。它通过使用QT丰富的文本引擎的现有功能来工作,设置自定义格式属性,该属性将始终保留,无论文本是否更改。一旦为选定的文本片段设置了格式,剩下的就是实现将动态计算边界矩形及其绘画的部分。
为了实现这一目标,有必要循环浏览整个文档布局,并获得每个文本片段的确切坐标,这些片段需要“突出显示”。这是通过:
为了提供此类功能,我使用了一个简单的QPEN实例的自定义QTEXTFORMAT属性,该实例将用于绘制边界,并为特定的QTEXTCHARFORMAT设置了该属性,以用于所需的文本片段。
然后,连接到相关信号的QTIMER将计算边界的几何形状(如果有),并最终请求重新绘制:这是必要的,因为文档布局中的任何更改(文本内容,以及编辑器/文档大小)都可以可能更改边界的几何形状。
然后,
paintEvent()
将在事件矩形中包含在这些边界时(出于优化原因,qtextedit仅重新绘制了实际需要重新粉刷的文本部分)。这是以下代码的结果:
,这是打破“选择”中的行时发生的事情:
[*]位于文本的边界中,否则会重叠,因此矩形始终由1像素左/上方调整为右/底部边框;为了允许矩形连接,我们必须先保留原始矩形,因此我们通过调整这些矩形的“剩余线”来修复所得路径:由于矩形始终是顺时针绘制的,我们调整了从上到下的“右行” (通过将其x点左移动一个像素移动),然后将“底线”从右至左移动(y点以一个像素移动)。
剪贴板问题
现在存在一个问题:由于QT使用系统剪贴板也用于内部切割/复制/粘贴操作,因此在尝试使用该基本功能时,所有格式数据都将丢失。
为了解决此问题,解决的工作是将自定义数据添加到剪贴板中,该剪贴板将格式的内容存储为HTML。请注意,我们不能更改HTML的内容,因为没有可靠的方法可以在生成的代码中找到“边框文本”的特定位置。自定义数据必须以其他方式存储。
qtextedit调用
insertfromimimedata()
用于粘贴操作调用函数。使用上面的类似概念(通过选择的一部分的块骑自行车)并通过
JSON
模块序列化读取边框数据。然后,通过在粘贴之前跟踪先前的光标位置,通过不重新化数据(如果存在)来恢复。注意:在以下解决方案中,我只是 append 将序列化数据添加到html(使用
<! - ... ---->
注释),但是另一个选项是将使用自定义格式的更多数据添加到Mimedata对象。出于明显的原因,这将为
bordertextedit
或其子类别提供边界 的剪贴板支撑HTML数据。Note: I'm basing this answer on an earlier version of the question which used QTextCharFormat to set the background of a text fragment. I added further support as I found myself looking for a valid solution for similar issue, but didn't have the opportunity to do it properly until now.
Premise
The laying out of text is quite complex, especially when dealing with rich text, including simple aspects like multiple lines.
While the Qt rich text engine allows setting the background of text, there is no support to draw a border around text.
For very basic cases, the answer provided for Getting the bounding box of QTextEdit selection will suffice, but it has some flaws.
First of all, if the text wraps on a new line (i.e. a very long selection), the complete bounding rect will be shown, which will include text that is not part of the selection. As shown in the above answer, you can see the result:
Then, the proposed solution is only valid for static text: whenever the text is updated, the selection is not updated along with it. While it's possible to update the internal selection when the text is changed programmatically, user editing would make it much more complex and prone to errors or unexpected behavior.
Solution: using QTextCharFormat
While the following approach is clearly much more complex, it's more effective, and allows further customization (like setting the border color and width). It works by using existing features of the Qt rich text engine, setting a custom format property that will always be preserved, no matter if the text is changed. Once the format is set for the selected text fragment, what's left is implementing the part that will dynamically compute the rectangles of the borders and, obviously, their painting.
In order to achieve this, it is necessary to cycle through the whole document layout and get the exact coordinates of each text fragment that needs "highlighting". This is done by:
To provide such feature, I used a custom QTextFormat property with a simple QPen instance that will be used to draw the borders, and that property is set for a specific QTextCharFormat set for the wanted text fragment.
Then, a QTimer connected to the relevant signals will compute the geometry of the borders (if any) and eventually request a repaint: this is necessary because any change in the document layout (text contents, but also editor/document size) can potentially change the geometry of the borders.
The
paintEvent()
will then paint those borders whenever they are included in the event rectangle (for optimization reasons, QTextEdit only redraws portion of the text that actually needs repainting).Here is the result of the following code:
And here is what happens when breaking the line in the "selection":
[*] - the border should always be within the bounding rect of the text, otherwise there would be overlapping, so the rectangles are always adjusted by 1 pixel left/above for the right/bottom borders; to allow rectangle joining, we must preserve the original rectangles first, so we fix the resulting paths by adjusting the "remaining lines" of those rectangles: since rectangles are always drawn clockwise, we adjust the "right lines" that go from top to bottom (by moving their x points left by one pixel) and the "bottom lines" from right to left (y points moved above by one pixel).
The clipboard issue
Now, there is a problem: since Qt uses the system clipboard also for internal cut/copy/paste operations, all that format data will be lost when trying to use that basic feature.
In order to solve this, a work around is to add the custom data to the clipboard, which stores the formatted contents as HTML. Note that we cannot change the contents of the HTML, becase there is no reliable way to find the specific position of the "border text" in the generated code. The custom data must be stored in other ways.
QTextEdit calls
createMimeDataFromSelection()
whenever it has to cut/copy a selection, so we can override that function by adding custom data to the returned mimedata object, and eventually read it back when the relatedinsertFromMimeData()
function is called for the paste operation.The border data is read using a similar concept above (cycling through the blocks that are part of the selection) and serialized through the
json
module. Then, it gets restored by unserializing the data (if it exists) while keeping track of the previous cursor position before pasting.Note: in the following solution, I just append the serialized data to the HTML (using the
<!-- ... --->
comments), but another option is to add further data with a custom format to the mimeData object.For obvious reasons, this will provide clipboard support for the borders only for instances of
BorderTextEdit
or its subclasses, and will not be available when pasting into other programs, even if they accept HTML data.我找到了一个解决方案带有
qrubberband
,这与我想要的非常接近:I found a solution with
QRubberband
, which is pretty close to what I wanted: