Search code examples
pythonpyqtpyqt5qscrollarea

QPainter draw lines inside a QScrollArea


I raised a problem earlier on how to draw lines between widgets. and @eyllanesc solves it very well. But I faced another problem. I try to make the widget with QScrollarea. But the lines are not shown.

When using Widget it works fine with drawing lines, but seems QScrollArea need another paint_event. Don't know how to do that.

Can anyone help me to show lines with QScrollarea.

import functools
import sys

from PyQt5.QtCore import QEvent, QLine
from PyQt5.QtGui import QPainter
from PyQt5.QtWidgets import QApplication, QPushButton, QVBoxLayout, QWidget, QScrollArea


class Drawer:
    def __init__(self):
        self._lines = []

    @property
    def lines(self):
        return self._lines

    @lines.setter
    def lines(self, l):
        self._lines = l[:]
        self.update()

    def paintEvent(self, event):
        painter = QPainter(self)
        for line in self.lines:
            painter.drawLine(line)


class Example(QScrollArea, Drawer):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.initUI()

    def initUI(self):
        self.AddButton = QPushButton("Add")
        self.AddButton.clicked.connect(self.addbuttonwithline)

        self.w = QWidget()
        self.setWidget(self.w)
        self.setWidgetResizable(True)

        self.vbox = QVBoxLayout(self.w)
        self.vbox.addWidget(self.AddButton)

        self.setGeometry(300, 300, 300, 150)
        self.setWindowTitle("Buttons")

        self.buttons = []

    def addbuttonwithline(self):
        button = QPushButton("delete")
        button.clicked.connect(functools.partial(self.remove_button, button))
        button.installEventFilter(self)
        self.vbox.addWidget(button)
        self.buttons.append(button)
        self.recalculate_position()

    def remove_button(self, button):
        self.buttons.remove(button)
        button.deleteLater()
        self.recalculate_position()

    def recalculate_position(self):
        lines = []
        for last_button, next_button in zip(self.buttons, self.buttons[1:]):
            l = QLine(last_button.pos().x()+50, last_button.pos().y(), next_button.pos().x()+50, next_button.pos().y())
            lines.append(l)
        self.lines = lines

    def eventFilter(self, o, e):
        if e.type() == QEvent.Move and o in self.buttons:
            self.recalculate_position()
        return super().eventFilter(o, e)


if __name__ == "__main__":

    app = QApplication(sys.argv)
    ex = Example()
    ex.show()
    sys.exit(app.exec_())

Solution

  • QScrollArea is designed to be a container so you should not paint over that item since the content covers it, instead it sets the painting in the content widget.

    Based on my code from my previous answer then just change to:

    if __name__ == "__main__":
    
        app = QApplication(sys.argv)
        ex = Example()
        w = QScrollArea(widgetResizable=True)
        w.setWidget(ex)
        w.show()
        sys.exit(app.exec_())
    

    enter image description here