How to keep PyQt Grid elements from resizing and maintain even spacing of all widgets?
Considering an even more simplified example made me find the solution.
It seems that setting the styleSheet to the PlotWidget
lets the widget resize itself and thereby negotiate for more or less space with the QGridLayout
. It does not matter that the style wouldn't change the size of the Widget, even setting something completely unrelated like font or even invalid styles reproduces the problem. Using a normal QWidget
instead of a PlotWidget
does not produce this problem.
In any case the solution therefore needs to be to tell QGridLayout
not to change the space for its columns and rows.
This can be done using
QGridLayout.setColumnStretch (self, int column, int stretch)
and
QGridLayout.setRowStretch (self, int row, int stretch)
where stretch
needs to be the same for all rows/columns and larger than zero. See below for the minimal example. It should be rather straight forward to adapt the real code accordingly.
import sys, time
import numpy as np
import pyqtgraph as pg
from PyQt4 import QtCore, QtGui
class MainApp(QtGui.QMainWindow):
def __init__(self):
super(MainApp, self).__init__()
self.win = QtGui.QWidget()
self.setCentralWidget(self.win)
self.resize(800, 250)
self.grid = QtGui.QGridLayout()
self.win.setLayout(self.grid)
self.colors = ["yellow", "green", "red", "blue"]
self.n = 8
self.create_boxes()
self.thread = MyThread()
self.thread.update.connect(self.setBoxColor)
self.thread.start()
self.show()
def create_boxes(self):
self.boxes = []
for i in range(self.n):
r = (4 % (i + 1)) / 4
box = pg.PlotWidget()
#box = QtGui.QWidget() # problem does not appear when using QWidget
self.boxes.append(box)
self.setBoxColor(i,0)
#########
# The following two lines solve the problem!!!
# comment them out to see old unwanted behaviour
self.grid.setColumnStretch(i % 4, 1)
self.grid.setRowStretch(r, 1)
#########
self.grid.addWidget(box, r, i % 4)
def setBoxColor(self, boxnumber, color):
stylesheet = """
border-top: 5px solid %s;
border-radius: 12px;
""" % self.colors[color]
self.boxes[boxnumber].setStyleSheet(stylesheet)
class MyThread(QtCore.QThread):
update = QtCore.pyqtSignal(int, int)
def __init__(self, parent=None):
super(MyThread, self).__init__(parent)
def run(self):
time.sleep(1)
while True:
boxnumber = np.random.randint(0,8)
color = np.random.randint(0,4)
self.update.emit(boxnumber, color)
time.sleep(0.34)
if __name__ == "__main__":
app = QtGui.QApplication([])
widget = MainApp()
widget.move(300, 300)
widget.show()
sys.exit(app.exec_())
Related videos on Youtube
Logic1
Updated on June 04, 2022Comments
-
Logic1 about 2 years
I am having some trouble keeping the size even across all elements in my gridLayout.
Ocasionally the inner widgets just completely change size after I update the plot data (on both width and height), and especially after maximizing/minimizing or changing the window size:
What I was hoping to achieve was for the size to be kept evenly distributed regardless of the dimensions of the window:
This is a summary of what I have written to populate the grid, and how I update the CSS to make the borders change color. I notice that if I do not change the CSS then this issue is not apparent.
class ResultsViewer(QtGui.QWidget): plots = {} #the currently displayed plot widgets curves = {} #the currently displayed data that store the points def __init__(self): super(ResultsViewer, self).__init__() self.win = QtGui.QMainWindow() self.win.setCentralWidget(self) self.win.resize(800, 250) self.win.setWindowTitle("Cavity Results Viewer") self.grid = QtGui.QGridLayout(self) self.win.setWindowFlags(self.win.windowFlags() | QtCore.Qt.WindowStaysOnTopHint) def reset_indicators(self): for plot in self.plots.keys(): self.plots[plot].setStyleSheet(""" border-top: 5px solid rgba(0,0,0,0); border-radius: 12px; """) def set_indicator_border_color(self, cavnum, result): color = "lime" if result['decision'] else "red" self.plots[cavnum].setStyleSheet(""" border-top: 5px solid %s; border-radius: 12px; """ % color) def create_indicators(self, params): for c, cavnum in enumerate(params.keys()): r = (4 % (c + 1)) / 4 #every fourth column jump to next row box = QtGui.QHBoxLayout() plt = pg.PlotWidget() plt.setStyleSheet(""" border-top: 5px solid yellow; border-radius: 12px; """) curve_blue = plt.plotItem.plot(pen=None, symbol='o', symbolPen=None, symbolSize=8, symbolBrush=(100, 100, 255, 80)) #points for showing history data curve_green = plt.plotItem.plot(pen=None, symbol='o', symbolPen=None, symbolSize=8,symbolBrush=(100, 255, 100, 80)) curve_blue_last = plt.plotItem.plot(pen=None, symbol='x', symbolPen=None, symbolSize=18, symbolBrush=(50, 50, 255, 255)) #points for showing the newest data curve_green_last = plt.plotItem.plot(pen=None, symbol='x', symbolPen=None, symbolSize=18,symbolBrush=(50, 255, 50, 255)) plt.plotItem.setLabel('left', "Amplitude", units='A') plt.plotItem.setLabel('bottom', "Frequency", units='Hz') self.plots[cavnum] = plt self.curves[cavnum] = {"blue": curve_blue, "blue_last": curve_blue_last, "green": curve_green, "green_last": curve_green_last} box.addWidget(plt) self.grid.addLayout(box, r, c % 4) def update(self, cavnum, results): #update the plots and render all points ... ... self.set_indicator_border_color(cavnum, result)
[UPDATE]
And this is a fully functional example:
import sys, time import random import numpy as np import pyqtgraph as pg from PyQt4 import QtCore, QtGui class ResultsViewer(QtGui.QWidget): plots = {} #the currently displayed plot widgets curves = {} #the currently displayed data that store the points def __init__(self): super(ResultsViewer, self).__init__() self.win = QtGui.QMainWindow() self.win.setCentralWidget(self) self.win.resize(800, 250) self.win.setWindowTitle("Cavity Results Viewer") self.grid = QtGui.QGridLayout(self) self.win.setWindowFlags(self.win.windowFlags() | QtCore.Qt.WindowStaysOnTopHint) self.win.show() def reset_indicators(self): #return #Uncomment this to skip modifying the CSS (resizing problem seems to go away!!!) for plot in self.plots.keys(): self.plots[plot].setStyleSheet(""" border-top: 5px solid rgba(0,0,0,0); border-radius: 12px; """) def set_indicator_border_color(self, cavnum, result): #return #Uncomment this to skip modifying the CSS (resizing problem seems to go away!!!) color = "lime" if result['decision'] else "red" self.plots[cavnum].setStyleSheet(""" border-top: 5px solid %s; border-radius: 12px; """ % color) def create_indicators(self, params): for c, cavnum in enumerate(params.keys()): r = (4 % (c + 1)) / 4 #every fourth column jump to next row box = QtGui.QHBoxLayout() plt = pg.PlotWidget() plt.setStyleSheet(""" border-top: 5px solid yellow; border-radius: 12px; """) curve_blue = plt.plotItem.plot(pen=None, symbol='o', symbolPen=None, symbolSize=8, symbolBrush=(100, 100, 255, 80)) #points for showing history data curve_green = plt.plotItem.plot(pen=None, symbol='o', symbolPen=None, symbolSize=8,symbolBrush=(100, 255, 100, 80)) plt.plotItem.setLabel('left', "Amplitude", units='A') plt.plotItem.setLabel('bottom', "Frequency", units='Hz') self.plots[cavnum] = plt self.curves[cavnum] = {"blue": curve_blue, "green": curve_green} box.addWidget(plt) self.grid.addLayout(box, r, c % 4) def update(self, cavnum, results): #update the plots and render all points max_history = 1000 #max points per plot to store result = results[cavnum] if result.has_key('peaks'): peaks = result['peaks'] if peaks.has_key('amps'): amps = np.round(peaks['amps'], 2) freqs = np.round(peaks['freqs'], 2) x_blue, y_blue = self.curves[cavnum]['blue'].getData() x_blue = np.append(freqs, x_blue)[:max_history] y_blue = np.append(amps, y_blue)[:max_history] x_blue = x_blue[x_blue != np.array(None)] #remove any none from the initial getData y_blue = y_blue[y_blue != np.array(None)] #remove any none from the initial getData self.curves[cavnum]['blue'].setData(x_blue, y_blue) self.set_indicator_border_color(cavnum, result) class MyThread(QtCore.QThread): update = QtCore.pyqtSignal(int, object) def __init__(self, resutls, parent=None): super(MyThread, self).__init__(parent) self.results = resutls #number of plots def run(self): while True: for cavnum in range(len(self.results.keys())): time.sleep(1) peaks = {'amps': np.random.rand(3,1), 'freqs': np.random.rand(3,1)} self.results[cavnum]['decision'] = bool(random.getrandbits(1)) self.results[cavnum]['peaks'] = peaks self.update.emit(cavnum, self.results) # create the dialog for zoom to point class MainApp(QtGui.QMainWindow): def __init__(self, parent=None): super(MainApp, self).__init__(parent) # Set up the user interface from Designer. n = 8 results = {} for x in range(n): results[x] = {} self.thread = MyThread(results) self.thread.update.connect(self.update) self.viewer = ResultsViewer() self.viewer.create_indicators(results) self.thread.start() def update(self, cavnum, data): self.viewer.update(cavnum, data) if __name__ == "__main__": app = QtGui.QApplication([]) widget = MainApp() widget.move(300, 300) widget.show() sys.exit(app.exec_())
-
ImportanceOfBeingErnest over 7 yearsWould you mind providing a minimal reproducible example? Otherwise it's really hard to say anything here.
-
ImportanceOfBeingErnest over 7 yearsThe newly added example does not run for me (there are multiple issues with
results
in various places and once that's fixed.setData
throws errors). I modified the code such that it actually runs and reproduces the issue, find it here. I have not found the problem but at least with this code it's much easier for everyone to have a look at it.
-
-
Logic1 over 7 yearsThat most definitely solved my problem. I will keep column and row stretch methods in mind for the future. Thank you!