如何防止 PyQt Grid 元素调整大小并保持所有小部件的均匀间距?
How to keep PyQt Grid elements from resizing and maintain even spacing of all widgets?
我在保持 gridLayout 中所有元素的尺寸时遇到一些问题。
有时,在我更新绘图数据(宽度和高度)后,内部小部件的大小会完全改变,尤其是在 maximizing/minimizing 或更改 window 大小之后:
运行#1
运行#2
我希望实现的是无论 window:
的尺寸如何,尺寸都保持均匀分布
期望的输出:
这是我为填充网格而编写的内容的摘要,以及我如何更新 CSS 以使边框改变颜色。我注意到如果我不更改 CSS 那么这个问题就不明显了。
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)
[更新]
这是一个功能齐全的示例:
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_())
考虑一个更简化的例子让我找到了解决方案。
似乎将 styleSheet 设置为 PlotWidget
可以让小部件自行调整大小,从而与 QGridLayout
协商更多或更少的 space。样式不会改变小部件的大小并不重要,即使设置完全不相关的内容(如字体甚至无效样式)也会重现问题。使用普通 QWidget
而不是 PlotWidget
不会产生此问题。
在任何情况下,解决方案因此需要告诉 QGridLayout
不要更改 space 的列和行。
这可以使用
来完成
QGridLayout.setColumnStretch (self, int column, int stretch)
和
QGridLayout.setRowStretch (self, int row, int stretch)
其中 stretch
需要对所有 rows/columns 相同且大于零。请参阅下面的最小示例。相应地调整真实代码应该是相当直接的。
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_())
我在保持 gridLayout 中所有元素的尺寸时遇到一些问题。
有时,在我更新绘图数据(宽度和高度)后,内部小部件的大小会完全改变,尤其是在 maximizing/minimizing 或更改 window 大小之后:
运行#1
我希望实现的是无论 window:
的尺寸如何,尺寸都保持均匀分布期望的输出:
这是我为填充网格而编写的内容的摘要,以及我如何更新 CSS 以使边框改变颜色。我注意到如果我不更改 CSS 那么这个问题就不明显了。
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)
[更新]
这是一个功能齐全的示例:
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_())
考虑一个更简化的例子让我找到了解决方案。
似乎将 styleSheet 设置为 PlotWidget
可以让小部件自行调整大小,从而与 QGridLayout
协商更多或更少的 space。样式不会改变小部件的大小并不重要,即使设置完全不相关的内容(如字体甚至无效样式)也会重现问题。使用普通 QWidget
而不是 PlotWidget
不会产生此问题。
在任何情况下,解决方案因此需要告诉 QGridLayout
不要更改 space 的列和行。
这可以使用
来完成
QGridLayout.setColumnStretch (self, int column, int stretch)
和
QGridLayout.setRowStretch (self, int row, int stretch)
其中 stretch
需要对所有 rows/columns 相同且大于零。请参阅下面的最小示例。相应地调整真实代码应该是相当直接的。
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_())