英文:
How to enable mouse tracking on a QLabel that has RichText
问题
I really want a single QLabel to have different colours for different parts of the string. Past questions have led me to the solution of using HTML4 rich text options within it, for example:
'<font color="red">I\'m red! </font><font color="blue">I\'m blue!</font>'
This solution works great visually when passed into QLabel.setText()
, but for some reason I'm finding that mouse tracking completely breaks within the widget once it uses rich text.
Here's the MRE, with a normal QLabel and empty background space as a control:
from PyQt5.QtWidgets import QApplication, QMainWindow, QLabel
from PyQt5.QtGui import QMouseEvent
from PyQt5.QtCore import Qt, QEvent
import sys
class testWindow(QMainWindow):
def __init__(self):
super(testWindow, self).__init__()
self.setWindowTitle('QLabel Test')
self.setMouseTracking(True)
self.resize(600, 200)
# label that displays coordinates picked up from mouseMoveEvent
self.coordLabel = QLabel(self)
self.coordLabel.setText('Mouse at:')
self.coordLabel.setStyleSheet('''QLabel {background-color:#088;}''')
self.coordLabel.setGeometry(0, 0, 200, 200)
self.coordLabel.setMouseTracking(True)
# label with multiple colours for different sections of the string
self.richTextLabel = QLabel(self)
self.richTextLabel.setText('<font color="red">I\'m red! </font><font color="blue">I\'m blue!</font>')
self.richTextLabel.setStyleSheet('''QLabel {background-color:#880;}''')
self.richTextLabel.setTextFormat(Qt.RichText) # text format is explicitly set to RichText
self.richTextLabel.setGeometry(400, 0, 200, 200)
self.richTextLabel.setMouseTracking(True)
# everything has mouse tracking set to True
# 3 blocks: coordinate label, empty space, rich text label
self.show()
def mouseMoveEvent(self, event: QMouseEvent) -> None:
if event.type() == QEvent.MouseMove:
x, y = event.x(), event.y() # coordinates of mouse
self.coordLabel.setText('Mouse at: {}, {}'.format(x, y)) # set to coordLabel text
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = testWindow()
sys.exit(app.exec_())
What I'm looking for is either a way to fix the mouse tracking on these QLabels. Although if there's another way to make a QLabel with multiple colours in one string of text that would be helpful too.
Thanks
英文:
I really want a single QLabel to have different colours for different parts of the string. Past questions have led me to the solution of using HTML4 rich text options within it, for example:
'<font color="red">I\'m red! </font><font color="blue">I\'m blue!</font>'
This solution works great visually when passed into QLabel.setText()
, but for some reason I'm finding that mouse tracking completely breaks within the widget once it uses rich text.
Here's the MRE, with a normal QLabel and empty background space as a control:
from PyQt5.QtWidgets import QApplication, QMainWindow, QLabel
from PyQt5.QtGui import QMouseEvent
from PyQt5.QtCore import Qt, QEvent
import sys
class testWindow(QMainWindow):
def __init__(self):
super(testWindow, self).__init__()
self.setWindowTitle('QLabel Test')
self.setMouseTracking(True)
self.resize(600, 200)
# label that displays coordinates picked up from mouseMoveEvent
self.coordLabel = QLabel(self)
self.coordLabel.setText('Mouse at:')
self.coordLabel.setStyleSheet('''QLabel {background-color:#088;}''')
self.coordLabel.setGeometry(0, 0, 200, 200)
self.coordLabel.setMouseTracking(True)
# label with multiple colours for different sections of the string
self.richTextLabel = QLabel(self)
self.richTextLabel.setText('<font color="red">I\'m red! </font><font color="blue">I\'m blue!</font>')
self.richTextLabel.setStyleSheet('''QLabel {background-color:#880;}''')
self.richTextLabel.setTextFormat(Qt.RichText) # text format is explicitly set to RichText
self.richTextLabel.setGeometry(400, 0, 200, 200)
self.richTextLabel.setMouseTracking(True)
# everything has mouse tracking set to True
# 3 blocks: coordinate label, empty space, rich text label
self.show()
def mouseMoveEvent(self, event: QMouseEvent) -> None:
if event.type() == QEvent.MouseMove:
x, y = event.x(), event.y() # coordinates of mouse
self.coordLabel.setText('Mouse at: {}, {}'.format(x, y)) # set to coordLabel text
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = testWindow()
sys.exit(app.exec_())
What I'm looking for is either a way to fix the mouse tracking on these QLabels. Although if there's another way to make a QLabel with multiple colours in one string of text that would be helpful too.
Thanks
答案1
得分: 1
以下是您要翻译的内容:
默认情况下,Qt小部件(直接或间接继承自QWidget的任何内容)不会接受鼠标事件。
每当Qt对象(也称为QObject)不接受事件时,事件会自动发送到其父级,沿着对象树层次结构向上。只要该层次结构中的对象接受了事件,它就不再传播。
请考虑以下事项:
- 默认情况下,事件通常会被接受(请参阅文档);
- 如果事件已被接受且未明确忽略(或之前未被忽略),则父对象将不会收到它;
- 事件可能由对象处理(因为它对其做出反应),但仍然可以设置为被忽略,因为重要的是父对象接收它;
每当QLabel具有富文本内容(自动检测或明确使用setTextFormat(Qt.RichText)
设置),鼠标事件总是会自动接受,这是因为它们通常会被处理;最重要的是,因为富文本通常可以包括锚点(超链接)。
如果您需要获取子小部件接收的任何事件,唯一安全的方法是在其上安装事件过滤器。
现在,问题在于发送到小部件的鼠标事件使用本地坐标,因此如果您希望获取相对于父对象的绝对坐标,您必须映射它们(请参阅QWidget文档中的map*()
函数)。
以下是考虑到上述情况的可能实现:
class testWindow(QMainWindow):
def __init__(self):
...
self.richTextLabel.installEventFilter(self)
def updateCoordLabel(self, pos, obj=None):
if isinstance(obj, QWidget) and obj != self:
if not self.isAncestorOf(obj):
return
pos = obj.mapTo(self, pos)
self.coordLabel.setText('Mouse at: {}, {}'.format(pos.x(), pos.y())) # 设置coordLabel文本
def eventFilter(self, obj, event):
if obj == self.richTextLabel and event.type() == event.MouseMove:
self.updateCoordLabel(event.pos(), obj)
return super().eventFilter(obj, event)
def mouseMoveEvent(self, event: QMouseEvent) -> None:
if event.type() == QEvent.MouseMove:
self.updateCoordLabel(event.pos())
请注意,除非您真的知道自己在做什么,否则几乎总是不推荐避免布局管理器。布局管理考虑了许多重要因素;最重要的是系统字体、操作系统字体缩放以及屏幕DPI(包括高DPI)设置;然后,这些因素会导致更改每个小部件的要求、外观,最重要的是可用性。忽略所有这些因素几乎总是会导致界面因某种原因而变得完全无法使用:
- 小部件可能会重叠,导致:
- 使它们部分不可见(因为它们已被其他小部件隐藏);
- 无法使用,因为鼠标事件被位于其上方的小部件捕获(即使看起来不像)会获取为底层小部件而设计的鼠标事件;
- 由于强制几何形状,内容可能会被剪切;这特别适用于QLabel:如果用户使用大字体大小或设置了巨大的字体缩放(例如,因为他们有一些视觉障碍),则文本将仅部分可见,如果不是完全隐藏;
- 屏幕可能不足够大,无法显示所有内容,从而隐藏某些小部件;
您可能不关心上述因素,但您必须记住,您在屏幕上看到的几乎永远不会是其他人看到的,有时甚至会完全不同于您预期的外观。忽略这些因素将使您的程序对某些人而言无法使用,原因可能是错误的。
如果您这样做是因为您想更多地控制布局的方式,那么这是错误的原因。只需学会处理布局管理器:这意味着理解sizeHint()
的含义以及它的用法,大小策略的目的,通用布局管理(包括间距和拉伸因子)以及布局项的目的。
英文:
By default, Qt widgets (anything that, directly or not, inherits from QWidget) do not accept mouse events.
Whenever an event is not accepted by a Qt object (aka, a QObject), it is automatically sent to its parent, up in the object tree hierarchy. As soon as an object in that hierarchy accepts the event, it's not propagated anymore.
Consider that:
- by default, events are normally accepted (see the documentation);
- if the event was already accepted and isn't explicitly ignored (or wasn't previously ignored), the parent will not receive it;
- an event could be handled by an object (because it reacts to it) but it could still be set as ignored, because it's important that the parent receives it;
Whenever a QLabel has rich text content (either automatically detected or explicitly set using setTextFormat(Qt.RichText)
), mouse events are automatically accepted anyway, and that's because they're normally handled; most importantly, because rich text can often include anchors (hyperlinks).
If you need to get any event received by a child widget, the only safe way to do so is by installing an event filter on it.
Now, the problem is that mouse events sent to a widget use local coordinates, so if you want to get absolute coordinates relative to the parent, you have to map them (see the map*()
functions in the QWidget documentation).
This is a possible implementation that considers the above:
class testWindow(QMainWindow):
def __init__(self):
...
self.richTextLabel.installEventFilter(self)
def updateCoordLabel(self, pos, obj=None):
if isinstance(obj, QWidget) and obj != self:
if not self.isAncestorOf(obj):
return
pos = obj.mapTo(self, pos)
self.coordLabel.setText('Mouse at: {}, {}'.format(pos.x(), pos.y())) # set to coordLabel text
def eventFilter(self, obj, event):
if obj == self.richTextLabel and event.type() == event.MouseMove:
self.updateCoordLabel(event.pos(), obj)
return super().eventFilter(obj, event)
def mouseMoveEvent(self, event: QMouseEvent) -> None:
if event.type() == QEvent.MouseMove:
self.updateCoordLabel(event.pos())
Note that avoiding layout managers is almost always a bad idea, unless you really know what you're doing. Layout management considers a lot of important aspects; the most important are the system font, OS font scaling, and screen DPI (including High DPI) settings; those aspects then result in changing the requirements, aspect, and, most importantly, usability of each widget. Ignoring all these aspects will almost always result in having a UI that for some reason becomes completely unusable:
- widgets could overlap, resulting in:
- making them partially invisible (because they've been hidden by others);
- not usable, because mouse events are captured by widgets stacked above them (even if it doesn't look like they are) which will get mouse events intended for the underlying widget;
- contents could be clipped due to the forced geometry; this is specifically the case of QLabel: if the user uses a big font size or has huge font scaling set (for instance, because they have some visual impairment), the text will be only partially visible, if not completely hidden;
- the screen might not be big enough to show all contents, hiding some widgets;
You may not care for the above aspects, but you must remember that what you see on your screen is almost NEVER what anybody else will see, which in some cases is a completely different look than the one you expected.
Ignoring those aspects will make your program unusable for some people, and for the wrong reasons.
If you do that because you want more control on the way the layout is managed, then it's the wrong reason. Just learn how to deal with layout managers: which means understanding what sizeHint()
is and how it's used, the purpose of size policies, generic layout management (including spacing and stretch factors) and the purpose of layout items.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论