How to keep PyQt Grid elements from resizing and maintain even spacing of all widgets?

10,090

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_())
Share:
10,090

Related videos on Youtube

Logic1
Author by

Logic1

Updated on June 04, 2022

Comments

  • Logic1
    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:

    Run #1 enter image description here Run #2 enter image description here

    What I was hoping to achieve was for the size to be kept evenly distributed regardless of the dimensions of the window:

    Desired Output: enter image description here

    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
      ImportanceOfBeingErnest over 7 years
      Would you mind providing a minimal reproducible example? Otherwise it's really hard to say anything here.
    • ImportanceOfBeingErnest
      ImportanceOfBeingErnest over 7 years
      The 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
    Logic1 over 7 years
    That most definitely solved my problem. I will keep column and row stretch methods in mind for the future. Thank you!