Gene
Gene

Reputation: 107

paint method doesn't paint whole widget when resized

I have a PyQt window built in Qt Designer and I am writing a custom paint method. The main window creates a label and sets it as the central widget. Then I override the paint method to draw a simple column chart.

The widget works well until it is resized. The widget calls the resize method and repaints as expected, but it uses the same size rectangle as before it was resized. There's a big black area -- the resized part -- that's not being painted on.

enter image description here

To test this out I grab the rectangle of the widget and draw a big rectangle with a light blue fill and red line outside. When the window is resized part of the outer rectangle is missing too.

Debugging statements show that the new rectangle is the correct size and the width and height values are fed properly into the paint event.

But when I resize, this is what I see. Why is paint not painting in the black area? I've checked my code and there are no hard coded limits on the paint. Is there some hidden clipping that occurs?

I couldn't find any questions with exactly this problem, so it would seem I'm missing something. This similar question says to invalidate the window before repaint, but that's for C++: Graphics.DrawImage Doesn't Always Paint The Whole Bitmap?

Do I need to invalidate the widget somehow? I couldn't find the PyQt method to do that.

import sys
from PyQt5 import QtCore, QtGui, QtWidgets, uic
import PyQt5 as qt

import numpy as np

class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()

        self.label = QtWidgets.QLabel()
        self.max_x = 600
        self.max_y = 400
        canvas = QtGui.QPixmap(self.max_x, self.max_y)
        self.label.setPixmap(canvas)
        self.setCentralWidget(self.label)

        np.random.seed(777)
        self.x_time = np.linspace(0, 12.56, 3000)
        rand_data = np.random.uniform(0.0, 1.0, 3000)
        self.data = np.sin(self.x_time) + rand_data

        pal = self.palette()
        pal.setColor(self.backgroundRole(), QtGui.QColor('black'))
        self.setPalette(pal)
        self.setAutoFillBackground(True)

    def resizeEvent(self, a0: QtGui.QResizeEvent):
        print("resizeEvent")
        max_x = self.size().width()
        max_y = self.size().height()
        self.draw(max_x, max_y)

    def mousePressEvent(self, a0: QtGui.QMouseEvent):
        print("mousePressEvent")

    def paintEvent(self, a0: QtGui.QPaintEvent):
        print("New window size = ", self.size())
        print("canvas size = ", self.label.size())
        max_x = self.label.size().width()
        max_y = self.label.size().height()
        self.draw(max_x, max_y)

    def draw(self, max_x, max_y):
        x_final = self.x_time[-1]

        data = self.data/np.max(np.abs(self.data))
        data = [abs(int(k*max_y)) for k in self.data]
        x_pos_all = [int(self.x_time[i]*max_x / x_final) for i in range(len(data))]

        # Find and use only the max y value for each x pixel location
        y_pos = []
        x_pos = list(range(max_x))
        cnt = 0
        for x_pixel in range(max_x):
            mx = 0.0
            v = x_pos_all[cnt]
            while cnt < len(x_pos_all) and x_pos_all[cnt] == x_pixel:
                if data[cnt] > mx:
                    mx = data[cnt]
                cnt += 1
            y_pos.append(mx)

        print("data = ")
        dat = np.array(data)
        print(dat[dat > 0].shape[0])

        painter = QtGui.QPainter(self.label.pixmap()) # takes care of painter.begin(self)

        pen = QtGui.QPen()

        rect = self.label.rect()
        print("rect = {}".format(rect))
        painter.fillRect(rect, QtGui.QColor('lightblue'))
        pen.setWidth(2)
        pen.setColor(QtGui.QColor('green'))

        for i in range(len(x_pos)):
            painter.setPen(pen)
            painter.drawLine(x_pos[i], max_y, x_pos[i], max_y - y_pos[i])

        pen.setWidth(5)
        pen.setColor(QtGui.QColor('red'))
        painter.setPen(pen)
        painter.drawRect(rect)

        painter.end()

app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()

I expect that as the widget is resized, the paint event will repaint over the entire new size of the widget, not just the original size. Curiously, the graph part (green lines) looks like it is scaling as I resize, but everything's just cut off at the original widget size.

How do I fix this?

Upvotes: 1

Views: 1140

Answers (1)

eyllanesc
eyllanesc

Reputation: 244003

If you are using a QLabel then it is not necessary to override paintEvent since it is enough to create a new QPixmap and set it in the QLabel.

import sys
import numpy as np

from PyQt5 import QtCore, QtGui, QtWidgets


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()

        self.label = QtWidgets.QLabel()
        self.setCentralWidget(self.label)

        np.random.seed(777)
        self.x_time = np.linspace(0, 12.56, 3000)
        rand_data = np.random.uniform(0.0, 1.0, 3000)
        self.data = np.sin(self.x_time) + rand_data

        pal = self.palette()
        pal.setColor(self.backgroundRole(), QtGui.QColor("black"))
        self.setPalette(pal)
        self.setAutoFillBackground(True)

    def resizeEvent(self, a0: QtGui.QResizeEvent):
        self.draw()
        super().resizeEvent(a0)

    def draw(self):
        max_x, max_y = self.label.width(), self.label.height()
        x_final = self.x_time[-1]
        data = self.data / np.max(np.abs(self.data))
        data = [abs(int(k * max_y)) for k in self.data]
        x_pos_all = [int(self.x_time[i] * max_x / x_final) for i in range(len(data))]

        y_pos = []
        x_pos = list(range(max_x))
        cnt = 0
        for x_pixel in range(max_x):
            mx = 0.0
            v = x_pos_all[cnt]
            while cnt < len(x_pos_all) and x_pos_all[cnt] == x_pixel:
                if data[cnt] > mx:
                    mx = data[cnt]
                cnt += 1
            y_pos.append(mx)

        print("data = ")
        dat = np.array(data)
        print(dat[dat > 0].shape[0])

        pixmap = QtGui.QPixmap(self.size())
        painter = QtGui.QPainter(pixmap)

        pen = QtGui.QPen()
        rect = self.label.rect()
        print("rect = {}".format(rect))
        painter.fillRect(rect, QtGui.QColor("lightblue"))
        pen.setWidth(2)
        pen.setColor(QtGui.QColor("green"))

        painter.setPen(pen)
        for x, y in zip(x_pos, y_pos):
            painter.drawLine(x, max_y, x, max_y - y)

        pen.setWidth(5)
        pen.setColor(QtGui.QColor("red"))
        painter.setPen(pen)
        painter.drawRect(rect)
        painter.end()
        self.label.setPixmap(pixmap)


app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()

Update:

  1. Why can I not shrink the window after enlarging it? The layout of the QMainWindow takes as a reference the minimum size of the QMainWindow to the minimumSizeHint of the centralWidget, and in your case it is the QLabel that takes as minimumSizeHint the size of the QPixmap. If you want to be able to reduce the size you must override that method:
class Label(QtWidgets.QLabel):
    def minimumSizeHint(self):
        return QtCore.QSize(1, 1)

class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()

        self.label = Label()
        self.setCentralWidget(self.label)
        # ...
  1. Why was the whole area not being painted before? Because you were painting a copy of the QPixmap: painter = QtGui.QPainter(self.label.pixmap()), not the stored QPixmap of the QLabel so nothing has been modified.

Upvotes: 2

Related Questions