PyQt中如何在Label同时显示图片与文字?解决子控件覆盖问题
Hey there, let's figure out why your drawn text keeps getting covered by L2 and fix this properly. The root cause is Qt's widget drawing hierarchy: anything you render in a parent widget's paintEvent will always be drawn beneath all its child widgets. So your custom text in L1's paintEvent is inherently under L2, which is a child control of L1.
Here are three practical solutions, ordered by simplicity and reliability:
Solution 1: Turn the text into a movable child label too
Since you need L2 to be freely movable, why not make your text a separate QLabel as well? This way both elements are child controls, and you can easily manage their layering and positions without any paintEvent conflicts.
import sys from PyQt5.QtCore import * from PyQt5.QtGui import * from PyQt5.QtWidgets import * class MyLabel(QLabel): def __init__(self): super(MyLabel, self).__init__() self.setFixedSize(300, 200) self.setStyleSheet("background-color: #f0f0f0;") # Create text label self.text_label = QLabel("Custom Text", self) self.text_label.move(20, 20) # Make text stay on top (optional) self.text_label.raise_() # Create movable image label self.image_label = QLabel(self) self.image_label.setPixmap(QPixmap("your_image.png").scaled(50, 50)) self.image_label.move(100, 20) # Setup drag functionality for image label self.drag_start_pos = None def mousePressEvent(self, event): if event.button() == Qt.LeftButton and self.image_label.underMouse(): self.drag_start_pos = event.globalPos() - self.image_label.pos() event.accept() def mouseMoveEvent(self, event): if self.drag_start_pos is not None: self.image_label.move(event.globalPos() - self.drag_start_pos) event.accept() def mouseReleaseEvent(self, event): self.drag_start_pos = None if __name__ == '__main__': app = QApplication(sys.argv) window = QWidget() layout = QVBoxLayout(window) layout.addWidget(MyLabel()) window.show() sys.exit(app.exec_())
With this setup, you can drag L2 anywhere, and use self.text_label.raise_() or self.image_label.lower() to control which element sits on top. No more unexpected covering!
Solution 2: Draw both text and image in the same paintEvent
If you prefer not to use multiple child controls, you can render both the text and image directly in L1's paintEvent. This gives you full control over the drawing order (text first, then image, or vice versa) and lets you handle image movement by updating its position and triggering a redraw.
import sys from PyQt5.QtCore import * from PyQt5.QtGui import * from PyQt5.QtWidgets import * class MyLabel(QLabel): def __init__(self): super(MyLabel, self).__init__() self.setFixedSize(300, 200) self.setStyleSheet("background-color: #f0f0f0;") # Track image position and load pixmap self.image_pos = QPoint(100, 20) self.image_pixmap = QPixmap("your_image.png").scaled(50, 50) self.drag_start_pos = None def paintEvent(self, event): super().paintEvent(event) painter = QPainter(self) # Draw text first (so it's under the image) # Swap order here if you want text on top painter.drawText(20, 40, "Custom Text") painter.drawPixmap(self.image_pos, self.image_pixmap) def mousePressEvent(self, event): # Check if click is within image bounds image_rect = QRect(self.image_pos, self.image_pixmap.size()) if event.button() == Qt.LeftButton and image_rect.contains(event.pos()): self.drag_start_pos = event.pos() - self.image_pos event.accept() def mouseMoveEvent(self, event): if self.drag_start_pos is not None: self.image_pos = event.pos() - self.drag_start_pos self.update() # Trigger redraw with new image position event.accept() def mouseReleaseEvent(self, event): self.drag_start_pos = None if __name__ == '__main__': app = QApplication(sys.argv) window = QWidget() layout = QVBoxLayout(window) layout.addWidget(MyLabel()) window.show() sys.exit(app.exec_())
This approach eliminates child widget layering issues entirely because everything is drawn in a single pass. You just adjust the order of drawText and drawPixmap to control which element is visible on top.
Solution 3: Event filtering (advanced, not recommended)
If you absolutely need to keep L2 as a child widget and draw text over it, you can use an event filter to trigger text drawing after L2 finishes rendering. This is more complex and can cause flickering, so it's only for edge cases.
import sys from PyQt5.QtCore import * from PyQt5.QtGui import * from PyQt5.QtWidgets import * class MyLabel(QLabel): def __init__(self): super(MyLabel, self).__init__() self.setFixedSize(300, 200) self.setStyleSheet("background-color: #f0f0f0;") self.image_label = QLabel(self) self.image_label.setPixmap(QPixmap("your_image.png").scaled(50, 50)) self.image_label.move(100, 20) # Install event filter to track L2's paint events self.image_label.installEventFilter(self) self._redraw_text = False def eventFilter(self, obj, event): if obj == self.image_label and event.type() == QEvent.Paint: # Mark that we need to redraw text after L2 renders self._redraw_text = True self.update() return super().eventFilter(obj, event) def paintEvent(self, event): super().paintEvent(event) if self._redraw_text: painter = QPainter(self) painter.drawText(20, 40, "Custom Text") self._redraw_text = False # Drag code same as Solution 1, omitted for brevity if __name__ == '__main__': app = QApplication(sys.argv) window = QWidget() layout = QVBoxLayout(window) layout.addWidget(MyLabel()) window.show() sys.exit(app.exec_())
Again, this is not ideal due to potential rendering glitches, so stick with Solutions 1 or 2 if possible.
内容的提问来源于stack exchange,提问作者Qiang Zhang

