Reputation: 107
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.
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
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:
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)
# ...
painter = QtGui.QPainter(self.label.pixmap())
, not the stored QPixmap of the QLabel so nothing has been modified.Upvotes: 2