I am trying to add a full-width horizontal line in QTextEdit from PyQt5.QtWidgets.
My function looks like this:
def print_results(self, results):
self.result_text_box.clear()
self.cursor = self.result_text_box.textCursor()
self.cursor.insertText(results[0])
for result in results[1:]:
# how do I add a horizontal line here?
self.cursor.insertText(result)
results
is a list of strings (possibly with newline characters) and self.result_text_box
is an instance of QTextEdit.
When I tried self.cursor.insertHtml("<hr/>")
I got weird results. Starting with the second string, a horizontal line was added whenever there was a newline character. For whatever reason, self.cursor.insertHtml("<p/><hr/><p/>")
solved that problem, but only on macOS, while on Windows it didn't. I tried using just insertHtml
, for the text too, but that didn't do the trick.
I would appreciate your help with this. Thanks!
An important thing that should always be kept in mind is that, while Qt "supports" HTML, it is NOT a web browser [see note].
Whenever a QtGui/QtWidget class or function provides HTML functionalities, it always follows a limited HTML subset support (see the foot note).
Whenever a function like setHtml()
(or setText()
for optional rich-text objects like QLabel) is called, the given HTML is parsed and converted into a QTextDocument: if the text engine parser doesn't support a certain HTML tag/attribute or CSS syntax, it will be completely ignored and will revert to the most possible way to show the given content considering the context and the HTML object tree.
The <hr>
(aka, "Horizontal Rule") element is a peculiar one, even in the HTML world.
In Qt, it is an element that is always part of the current QTextBlock (a "paragraph", as in <p>...</p>
) and shown at its bottom.
I'll repeat this, as it is extremely important: the <hr>
line is always part of a previous block, it is not a separate element.
Now, while we could go extremely into deep of the QTextDocument structure, it seems clear that you just want to show plain text entries that are separated by a horizontal line.
So, let's keep this simple.
If you only want lines between elements, it's quite easy:
def print_results(self, results):
self.result_text_box.setHtml(
'<hr>'.join(results))
Note that I removed self.result_text_box.clear()
, which is completely pointless when using setHtml()
, because that function always overwrites the current contents.
In case you want to add a further line at the end of the last entry and also allow insertion of further elements, things get a bit trickier.
As said above, the horizontal line is always part of a QTextBlock. This means that if you try to do insertHtml()
the insertion will always be within the current text block of the text cursor, and since insertion of new block always inherits the block format of the current block, you'll end up with two or more consecutive lines.
The proper way to add a line at the end and allow insertion of new text after that is the following:
<hr>
element;Here is a possible implementation of the above:
def print_results(self, results):
self.result_text_box.setHtml(
'<hr>'.join(results) + '<hr>') # <-- note the added <hr> element
tc = self.result_text_box.textCursor()
# move the cursor to the end of the document
tc.movePosition(tc.End)
# insert an arbitrary QTextBlock that will inherit the previous format
tc.insertBlock()
# get the block format
fmt = tc.blockFormat()
# remove the horizontal ruler property from the block
fmt.clearProperty(fmt.BlockTrailingHorizontalRulerWidth)
# set (not merge!) the block format
tc.setBlockFormat(fmt)
# eventually, apply the cursor so that editing actually starts at the end
self.result_text_box.setTextCursor(tc)
Note that I just used a local variable for the QTextCursor; creating an instance attribute for it is not only pointless (you're probably going to call that function more than once, so you will also be overwriting it every time), but wrong, since cursor()
is an existing property/function of all QWidgets and, as such, should never be overwritten, especially with something that is completely unrelated to the original purpose.
Be aware that the differences you've seen between macOS and Window might be related to two different aspects:
If you want proper HTML compliance, the only way to do so in Qt is through the Qt WebEngine API; in PyQt5 most of the important classes are within the QtWebEngine
, QtWebEngineWidgets
and QtWebEngineCore
modules, while the QtWebEngine
module classes have all been moved to the QtWebEngineCore
module in PyQt6.