Как автоматически настроить размер Qwidget с помощью основного окна? - PullRequest
0 голосов
/ 28 октября 2019

Я кодирую приложение списка дел для отображения ежедневных (в формате 24H) расписаний на неделю. Вот мой дизайн:

  1. в нижнем слое - временной интервал, использующий QtableWidget вvboxlayout
  2. перекрытие Qtablewidget, я использую 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_())

1 Ответ

0 голосов
/ 28 октября 2019

Это не работает, потому что вы пытаетесь установить геометрию 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)

Тем не менее, хотя ваш подход к перекрытию табличных представлений на самом деле является разумной идеей, естьнесколько проблем с вашей реализацией.

  • нет необходимости постоянно изменять размер разделов заголовка, просто используйте setSectionResizeMode(QtWidgets.QHeaderView.Stretch) для обоих заголовков
  • не надоустановите ширину таблицы для вычисления положения (и ширины) столбцов, так как это даст ненадежные значения: вы используете деление, которое возвращает значение с плавающей запятой, в то время как установка каждого размера раздела заголовка приводит к целочисленному размеру;если вы используете режим Stretch, как написано выше, вы можете получить точное начало раздела от self.table.horizontalHeader().sectionPosition(int(Start_ColCount)) и размер, добавив все self.table.horizontalHeader().sectionSize(column) для индексов столбцов, которые занимает метка;то же самое относится к вертикальному размеру / положению
  • при использовании виджетов фиксированного размера будет проблемой, как только у вас будет высота строки <(высота todo * число одновременных задач), так как вы получите перекрывающиеся или неправильно выровненные виджеты;Допустим, вы изменили размер таблицы до точки, где высота ячейки равна 40, и у вас есть 3 одновременных задания: последний объект будет выровнен на следующий день, и, если там уже есть другое задание, он, вероятно, будет скрыт </li>

Учитывая то, что написано выше, я бы предложил вам лучшую реализацию 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, представляющийкаждое отдельное событие, которое будет сохранять (обновлять и, в конечном итоге, очищать / добавлять) свои виджеты меток. В этом случае вы не будете использовать основной self.label_widgets для изменения размера, но, вероятно, будете циклически перебирать все события, а затем каждый виджет метки задания, содержащийся в событии.

...