python - 如何使用主窗口自动调整 Qwidget 的大小?
问题描述
我正在编写一个待办事项列表应用程序来显示一周的每日(24 小时格式)日程安排,这是我的设计:
- 在底部层是在 vboxlayout 中使用 QtableWidget 的时间框架
- Qtablewidget 的重叠,我使用 QLabels 来显示 schedule.Qlabels 在任何布局中都没有组织。
当主窗口调整大小时,按钮层可以调整其大小,因为 vboxlayout。我想知道的是如何在调整底部层大小时调整顶层的大小。或任何其他方法来实现我的想法?我覆盖了 resizeEvent 但失败了。
import sys
from PyQt5 import QtWidgets,QtGui,QtCore
from PyQt5.QtWidgets import QApplication,QWidget,QFrame,QMainWindow,QLabel,QTableWidget,QVBoxLayout
from PyQt5.QtGui import QPainter,QFont,QBrush,QPen
from PyQt5.QtCore import Qt,QEvent
StyleSheet = '''
#central {
border: 0px;
background: gray;
}
QLabel {
border: 1px solid #C0C0C0;
background: #CCFF99;
font: 8pt Comic Sans MS;
border-radius: 4px;
}
#mainFrame {
border: 1px solid gray;
background: white;
}
QTableWidget {
background-color: white;
border: 0.5px solid #C0C0C0;
color: #F0F0F0;
gridline-color: #C0C0C0;
}
QHeaderView::section {
background-color: #FFD800;
padding: 0px;
font-size: 8pt;
border-style: none;
border-bottom: 1px solid #fffff8;
border-right: 1px solid #fffff8;
}
QHeaderView::section:horizontal
{
border-top: 1px solid #fffff8;
}
QHeaderView::section:vertical
{
border-left: 1px solid #fffff8;
font: 6pt Comic Sans MS;
}
QTableWidget::item::hover {
background-color: gray;
border: 0.5px solid #148CD2;
}
'''
class JobLabel(QLabel):
def __init__(self,parent):
QLabel.__init__(self,parent)
def enterEvent(self, event):
self.setStyleSheet("background-color: #99CCFF;")
def leaveEvent(self, event):
self.setStyleSheet("background-color: #CCFF99;")
class window(QMainWindow):
def __init__(self):
super(window, self).__init__()
self.left=100
self.top=100
self.width=1368
self.height=900
self.initUI()
def initUI(self):
self.setWindowTitle("Painting")
self.resize(1368,900)
self.setObjectName("mainWindow")
self.setStyleSheet(StyleSheet)
self.centralWidget=QWidget()
self.centralWidget.resize(self.width,self.height)
self.centralWidget.setObjectName("central")
mainlayout=QVBoxLayout()
ColumnCount=24
RowCount=7
JobBarHeight=20
self.table = QTableWidget(self.centralWidget)
self.table.move(0,0)
self.table.resize(self.centralWidget.width(),self.centralWidget.height())
self.table.setColumnCount(ColumnCount)
self.table.setRowCount(RowCount)
self.table.horizontalHeader().setVisible(True)
self.table.verticalHeader().setVisible(True)
self.table.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.table.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
RowHeight=(self.table.size().height()-self.table.horizontalHeader().height())/RowCount
ColumnWidth=(self.table.size().width() - self.table.verticalHeader().width())/ColumnCount
hheaders = []
for i in range(1,ColumnCount+1):
if i<10:
hheaders.append("0{}:00".format(i))
else:
hheaders.append("{}:00".format(i))
self.table.setHorizontalHeaderLabels(hheaders)
for i in range(ColumnCount):
self.table.setColumnWidth(i, ColumnWidth)
vheaders = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
self.table.setVerticalHeaderLabels(vheaders)
for i in range(RowCount):
self.table.setRowHeight(i, RowHeight)
mainlayout.addWidget(self.table)
self.centralWidget.setLayout(mainlayout)
self.setCentralWidget(self.centralWidget)
self.job_run_times=[["Sun 12:00","Sun 14:35"],["Fri 22:00","Sat 4:35"],["Tue 1:00","Tue 6:00"],["Wed 17:00","Thu 19:00"],["Wed 18:00","Wed 21:30"],
["Mon 22:00","Mon 23:30"],["Sat 22:00","Mon 3:30"],["Sun 12:00","Sun 14:35"],["Fri 22:00","Sat 4:35"],["Mon 3:00","Mon 6:00"],
["Tue 1:00","Tue 6:00"],["Wed 17:00","Wed 19:30"],["Mon 17:00","Thu 19:00"],["Wed 18:00","Wed 21:30"],["Mon 4:00","Mon 7:00"],
["Mon 22:00","Mon 23:30"],["Sat 22:00","Mon 3:30"]]
#job count
n=0
#label count
k=0
self.label_list=[]
for job_run_time in self.job_run_times:
start=job_run_time[0]
end=job_run_time[1]
weekday_start=start.split()[0]
time_start=start.split()[1]
weekday_end=end.split()[0]
time_end=end.split()[1]
for i in range(RowCount):
if weekday_start==vheaders[i]:
Start_RowCount=i
if weekday_end==vheaders[i]:
End_RowCount=i
Start_ColCount=int(time_start.split(":")[0])+round(int(time_start.split(":")[1])/60,2)
End_ColCount=int(time_end.split(":")[0])+round(int(time_end.split(":")[1])/60,2)
#job time span multiple days
DaySpan=End_RowCount-Start_RowCount
if DaySpan==0:
label_name="label_{}".format(k)
label_pos=[]
overlap_jobbar_count=1
for j in range(len(self.label_list)):
if Start_RowCount==self.label_list[j][3]:
if (Start_ColCount>=self.label_list[j][5] and Start_ColCount<=self.label_list[j][6]) or (self.label_list[j][5]<=End_ColCount and End_ColCount<=self.label_list[j][6]):
overlap_jobbar_count=overlap_jobbar_count+1
label_pos = n, k, label_name, Start_RowCount,overlap_jobbar_count,Start_ColCount, End_ColCount
self.label_list.append(label_pos)
k=k+1
n = n + 1
elif DaySpan>0:
label_name = "label_{}".format(k)
label_pos=[]
overlap_jobbar_count=1
for j in range(len(self.label_list)):
if Start_RowCount==self.label_list[j][3]:
if (Start_ColCount>=self.label_list[j][5] and Start_ColCount<=self.label_list[j][6]) :
overlap_jobbar_count=overlap_jobbar_count+1
label_pos = n, k, label_name, Start_RowCount, overlap_jobbar_count, Start_ColCount, ColumnCount
self.label_list.append(label_pos)
k = k + 1
for i in range(DaySpan-1):
label_name="label_{}".format(k)
label_pos = []
overlap_jobbar_count = 1
for j in range(len(self.label_list)):
if Start_RowCount+i+1 == self.label_list[j][3]:
overlap_jobbar_count = overlap_jobbar_count + 1
label_pos = n, k, label_name, Start_RowCount + i + 1, overlap_jobbar_count, 0, ColumnCount
self.label_list.append(label_pos)
k=k+1
label_name="label_{}".format(k)
label_pos=[]
overlap_jobbar_count=1
for j in range(len(self.label_list)):
if End_RowCount==self.label_list[j][3] and End_ColCount>=self.label_list[j][5]:
overlap_jobbar_count=overlap_jobbar_count+1
label_pos = n, k, label_name, End_RowCount,overlap_jobbar_count, 0, End_ColCount
self.label_list.append(label_pos)
k=k+1
n = n + 1
else:
label_name = "label_{}".format(k)
label_pos=[]
overlap_jobbar_count=1
for j in range(len(self.label_list)):
if Start_RowCount==self.label_list[j][3]:
if Start_ColCount>=self.label_list[j][5] and Start_ColCount<=self.label_list[j][6]:
overlap_jobbar_count=overlap_jobbar_count+1
label_pos = n, k, label_name, Start_RowCount, overlap_jobbar_count, Start_ColCount, ColumnCount
self.label_list.append(label_pos)
k = k + 1
for i in range(6-Start_RowCount):
label_name="label_{}".format(k)
label_pos = []
overlap_jobbar_count = 1
for j in range(len(self.label_list)):
if Start_RowCount+i+1 == self.label_list[j][3]:
overlap_jobbar_count = overlap_jobbar_count + 1
label_pos = n, k, label_name, Start_RowCount + i + 1, overlap_jobbar_count, 0, ColumnCount
self.label_list.append(label_pos)
k=k+1
for i in range(End_RowCount):
label_name="label_{}".format(k)
label_pos = []
overlap_jobbar_count = 1
for j in range(len(self.label_list)):
if Start_RowCount+i + 1 == self.label_list[j][3]:
overlap_jobbar_count = overlap_jobbar_count + 1
label_pos = n, k, label_name, Start_RowCount + i + 1, overlap_jobbar_count, 0, ColumnCount
self.label_list.append(label_pos)
k=k+1
label_name="label_{}".format(k)
label_pos=[]
overlap_jobbar_count=1
for j in range(len(self.label_list)):
if End_RowCount==self.label_list[j][3]:
if End_RowCount==self.label_list[j][3] and End_ColCount>=self.label_list[j][5]:
overlap_jobbar_count=overlap_jobbar_count+1
label_pos = n, k, label_name, End_RowCount, overlap_jobbar_count, 0, End_ColCount
self.label_list.append(label_pos)
k=k+1
n = n + 1
for label in self.label_list:
jobcount=label[0]
label_count=label[1]
label_name=label[2]
Start_Row=label[3]
overlap_jobbar_count=label[4]
Start_ColCount=label[5]
End_ColCount=label[6]
JobBarLeft=self.table.verticalHeader().width()+Start_ColCount*ColumnWidth
JobBarTop=self.table.horizontalHeader().height()+JobBarHeight*(overlap_jobbar_count-1)+Start_Row*RowHeight
JobBarWidth=(End_ColCount-Start_ColCount)*ColumnWidth
self.label_name=JobLabel(self.table)
self.label_name.setGeometry(JobBarLeft, JobBarTop,JobBarWidth, JobBarHeight)
self.label_name.setText(self.job_run_times[jobcount][0]+"-"+self.job_run_times[jobcount][1])
self.show()
def resizeEvent(self, event):
self.centralWidget.resize(event.size())
self.table.resize(self.centralWidget.size())
event.accept()
RowHeight=(self.table.size().height()-self.table.horizontalHeader().height())/7
ColumnWidth=(self.table.size().width() - self.table.verticalHeader().width())/24
JobBarHeight=20
for i in range(7):
self.table.setRowHeight(i,RowHeight)
for i in range(24):
self.table.setColumnWidth(i,ColumnWidth)
for label in self.label_list:
jobcount = label[0]
label_count = label[1]
label_name = label[2]
Start_Row = label[3]
overlap_jobbar_count = label[4]
Start_ColCount = label[5]
End_ColCount = label[6]
self.label_name.repaint()
self.label_name.parentWidget().repaint()
if __name__=="__main__":
app=QApplication(sys.argv)
win=window()
sys.exit(app.exec_())
解决方案
它不起作用,因为您正在尝试设置 的几何形状self.label_name
,但只有一个对 JobLabel 的引用,它始终是for
您在创建所有 JobLabel 实例的循环中创建的最后一个引用(接近结束initUI
)。
每次你这样做:
self.label_name=JobLabel(self.table)
JobLabel 已正确创建,但您丢失了对前一个的引用(如果有),因此总会有一个可访问self.label_name
的,尽管它们仍然存在于程序中(因为它已经取得了他们的所有权,所以他们赢了'不要被垃圾收集)。
相反,您应该做的是保留所有 JobLabels 的引用,相应地设置它们的数据并在调整大小时循环它们。
class JobLabel(QLabel):
def __init__(self,parent, label_data):
QLabel.__init__(self,parent)
self.label_data = label_data
# ...
class window(QMainWindow):
# ...
def initUI(self):
# ...
self.label_list = []
self.label_widgets = []
# be careful, because you made an indentation error:
for job_run_time in self.job_run_times:
# ...
# self.label_list.append(label_pos)
# this for cycle should be aligned at the same line of the previous one,
# while you have put it inside it
for label in self.label_list:
label_widget = JobLabel(self.table, label)
# the following is unnecessary, as a resizeEvent will be sent before
# the window is shown the first time anyway
label_widget.setGeometry(JobBarLeft, JobBarTop,JobBarWidth, JobBarHeight)
label_widget.setText(self.job_run_times[jobcount][0]+"-"+self.job_run_times[jobcount][1])
self.label_widgets.append(label_widget)
def resizeEvent(self, event):
# ...
for label_widget in self.label_widgets:
label = label_widget.label_data
Start_Row = label[3]
overlap_jobbar_count = label[4]
Start_ColCount = label[5]
End_ColCount = label[6]
JobBarLeft=self.table.verticalHeader().width()+Start_ColCount*ColumnWidth
JobBarTop=self.table.horizontalHeader().height()+JobBarHeight*(overlap_jobbar_count-1)+Start_Row*RowHeight
JobBarWidth=(End_ColCount-Start_ColCount)*ColumnWidth
label_widget.setGeometry(JobBarLeft, JobBarTop,JobBarWidth, JobBarHeight)
也就是说,虽然您的 tableview 重叠方法实际上是一个聪明的想法,但您的实现存在几个问题。
- 无需不断调整标题部分的大小,只需
setSectionResizeMode(QtWidgets.QHeaderView.Stretch)
用于两个标题 - 不要使用表格宽度来计算列的位置(和宽度),因为它会给出不可靠的值:您正在使用返回浮点值的除法,而设置每个标题部分的大小会导致整数大小;如果你使用上面写的拉伸模式,你可以通过为标签占用的列索引
self.table.horizontalHeader().sectionPosition(int(Start_ColCount))
添加所有来获得准确的部分开始和大小;self.table.horizontalHeader().sectionSize(column)
垂直尺寸/位置也是如此 - 一旦你有一个行高 < (todo height * concurrent todo count) ,使用固定大小的小部件将是一个问题,因为你会得到重叠或错误对齐的小部件;假设您将表格的大小调整为单元格高度为 40 并且您有 3 个并发待办事项:最后一个对象将与第二天对齐,如果那里已经有另一个工作,它可能会被隐藏
考虑到上面写的,我建议你一个更好的resizeEvent
实现:
def initUI(self):
# ...
# remove both for cycles of self.table.setColumnWidth and
# self.table.setRowHeight and replace them with this
self.table.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.Stretch)
self.table.verticalHeader().setSectionResizeMode(QtWidgets.QHeaderView.Stretch)
# ...
def resizeEvent(self, event):
# item views need some time to adjust their size (usually one "cycle" of
# the event loop), resulting in incoherent positioning. If the "oldSize"
# is invalid (a QSize with width or height < 0) we can assume that the
# window has not been shown yet, so we ignore the event and create a new
# one that will be "posted" afterwards, giving time to the table view to
# adjust its internal geometry and eventually get the correct sizes.
if not event.oldSize().isValid():
QApplication.postEvent(self, QtGui.QResizeEvent(event.size(), QtCore.QSize(1, 1)))
return
super(QMainWindow, self).resizeEvent(event)
hHeader = self.table.horizontalHeader()
vHeader = self.table.verticalHeader()
left = vHeader.width()
top = hHeader.height()
# adjust the job bar size if too small, otherwise keep the default 20px
JobBarHeight = min(20, vHeader.sectionSize(1) / 6)
for label_widget in self.label_widgets:
label = label_widget.label_data
Start_Row = label[3]
overlap_jobbar_count = label[4]
# I'm converting counts to integers as it's a requirement for range
# functions, and these values are actually float according to your
# implementation. You should make them as integers in the first place.
Start_ColCount = int(label[5])
End_ColCount = int(label[6])
vPos = vHeader.sectionPosition(Start_Row)
JobBarLeft = left + hHeader.sectionPosition(Start_ColCount)
JobBarTop = top + JobBarHeight*(overlap_jobbar_count-1) + vPos
JobBarWidth = sum(hHeader.sectionSize(s) for s in range(Start_ColCount, End_ColCount))
label_widget.setGeometry(JobBarLeft, JobBarTop,JobBarWidth, JobBarHeight)
我还建议您将 QDateTime 用于“工作”时间,原因有两个:
- 使用字符串来匹配活动日期并不总是一个好主意;虽然您可以对它们进行硬编码(就像您实际上所做的那样),但如果有一天您决定切换到另一种格式(或者甚至使用本地化的日期名称作为垂直标题,因为您正在使用它们来匹配日期),这可能是一个问题;
- 显然,您可以安排一周内开始并在下一周结束的活动;如果你不是很小心,你可能会在本周开始时以可见的事件结束,而实际上它们在下一周结束;
我假设您将使用某种基于字符串的序列化来保存和恢复事件数据,但这不是问题,因为您可以将 QDateTimes 转换为字符串并QDateTime.toString
使用QDateTime.fromString
参数QtCore.Qt.ISODate
来确保应用正确的时区。
最后,虽然您的方法很有趣,但您需要小心,因为如果您需要更改每个“事件”的开始/结束数据,它可能会更改其结果标签(可能通过删除一些最后的标签如果事件的持续时间不会持续到接下来的几天)。
我可能会创建一个 python 类来表示每个将保留(更新并最终清除/添加)其 joblabel 小部件的单个事件。在这种情况下,您不会使用 mainself.label_widgets
来调整大小,但可能会循环遍历所有事件,然后是事件包含的每个 joblabel 小部件。
推荐阅读
- python - 从 Matplotlib 中的一组重叠艺术家中挑选一个艺术家
- javascript - 替换给定范围的字符串中间
- cloud - 我们可以从 s3 存储桶中逐条读取数据记录而不将文件存储在本地路径中吗?
- javascript - 如何设置summernote设置图像始终采用全宽
- java - 使用 Apache poi
- angular - 如何在 Angular 5 应用程序中正确设置验证?
- python - 找不到满足要求 lxkeymap==0.1 的版本
- ios - 无法将 barbuttonitem 添加到导航控制器
- php - 在 PHP 中删除图像的背景颜色
- ios - 如何从具有多个值的 Codable 结构中获取一个值?