使用 PyQt5 创建时间轴
Create a timeline with PyQt5
我正在用 Python 和 PyQt5 开始一个项目。我想要一个这样的时间轴小部件
所以在未来我希望每个矩形都是一个按钮,但我目前的主要问题是让时间线保持在我的宽度 window。
目前我的时间轴继承自QFrame,每个矩形也是一个QFrame。那么这是一个好方法吗?有更好的方法吗?
这是我尝试过的:
Main.py
# -*- coding: utf-8 -*-
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout
from PyQt5.QtGui import QColor
from Timeline import *
from Job import *
class FreelanceAssistant(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.resize(1080, 720)
self.move(300, 300)
self.setWindowTitle("Freelance Assistant")
p = self.palette()
p.setColor(self.backgroundRole(), QColor(32, 47, 60))
self.setPalette(p)
timeline = Timeline(self)
timeline.addJob(Job(QDate(2017, 11, 21), QDate(2018, 1, 12), "Red Knuckles", 200, QColor(140, 67, 67)))
timeline.addJob(Job(QDate(2018, 1, 15), QDate(2018, 1, 26), "ETC", 200, QColor(67, 76, 140)))
window_layout= QVBoxLayout()
print(self.getContentsMargins())
self.setLayout(window_layout)
window_layout.addWidget(timeline)
self.show()
def main():
app = QApplication(sys.argv)
window = FreelanceAssistant()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
Timeline.py
# -*- coding: utf-8 -*-
import sys
from PyQt5.QtCore import QDate
from PyQt5.QtWidgets import QFrame, QLabel
from PyQt5.QtGui import QColor
class Timeline(QFrame):
def __init__(self, parent):
super().__init__(parent)
self.jobs_number = 0
self.setGeometry(0, 0, parent.width(), 200)
self.setStyleSheet("QWidget { background-color: %s }" % QColor(26, 36, 45).name())
self.__invoice_pos = self.height() * 0.5
self.__invoice_height = 13
self.__job_top_pos = self.height() * 0.5 + self.__invoice_height
self.__job_height = (self.height() - self.__job_top_pos)/2
self.__job_bottom_pos = self.__job_top_pos + self.__job_height
line1 = QFrame(parent)
line1.setGeometry(0, self.height() * 0.5, self.width(), 1)
line1.setStyleSheet("QWidget { background-color: %s }" % QColor(66, 76, 85).name())
line2 = QFrame(parent)
line2.setGeometry(0, self.height(), self.width(), 1)
line2.setStyleSheet("QWidget { background-color: %s }" % QColor(66, 76, 85).name())
self.__starts_on = QDate.currentDate().addMonths(-1)
self.__ends_on = QDate.currentDate().addMonths(5)
self.__days_range = self.__starts_on.daysTo(self.__ends_on)
current_year = QLabel(str(self.__starts_on.year()), parent)
current_year.move(5, self.height() + 5)
current_year.setStyleSheet("QWidget { color: %s }" % QColor(245, 245, 245).name())
for year in range(self.__starts_on.year() + 1, self.__ends_on.year() + 1):
days_to_new_year = self.__starts_on.daysTo(QDate(year, 1, 1))
new_year_line_pos = days_to_new_year/self.__days_range * self.width()
new_year_line = QFrame(parent)
new_year_line.setGeometry(int(new_year_line_pos), 0, 1, self.height() + 20)
new_year_line.setStyleSheet("QWidget { background-color: %s }" % QColor(245, 245, 245).name())
current_year = QLabel(str(year), parent)
current_year.move(int(new_year_line_pos) + 5, self.height() + 5)
current_year.setStyleSheet("QWidget { color: %s }" % QColor(245, 245, 245).name())
invoice_start = QDate(2017, 11, 21)
invoice_end = QDate(2017, 12, 13)
invoice_start_pos = self.__starts_on.daysTo(invoice_start)/self.__days_range * self.width()
invoice_width = invoice_start.daysTo(invoice_end)/self.__days_range * self.width()
invoice_frame = QFrame(self)
invoice_frame.setGeometry(invoice_start_pos, self.__invoice_pos, invoice_width, self.__invoice_height)
invoice_frame.setStyleSheet("QWidget { background-color: %s }" % QColor(50, 200, 50).name())
invoice_end_date = QLabel(invoice_end.toString("dd MMM"), invoice_frame)
invoice_end_date.move(invoice_frame.width() - 40, 0)
invoice_end_date.setStyleSheet("QWidget { color: %s }" % QColor(0, 0, 0).name())
def addJob(self, job):
start_pos = self.__starts_on.daysTo(job.starts_on)/self.__days_range * self.width()
width = job.days_range/self.__days_range * self.width()
job_frame = QFrame(self)
if self.jobs_number % 2 == 0:
job_frame.setGeometry(start_pos, self.__job_bottom_pos, width, self.__job_height)
else:
job_frame.setGeometry(start_pos, self.__job_top_pos, width, self.__job_height)
job_frame.setStyleSheet("QWidget { background-color:" + job.color.name() + " } QWidget:hover{background-color: " + job.color.darker(125).name() + "}")
company_name = QLabel(job.company, job_frame)
company_name.move(5, 5)
company_name.setStyleSheet("QWidget { color: %s }" % QColor(245, 245, 245).name())
start_date = QLabel(job.starts_on.toString("dd MMM"), job_frame)
start_date.move(5, job_frame.height() - 20)
start_date.setStyleSheet("QWidget { color: %s }" % QColor(245, 245, 245).name())
end_date = QLabel(job.ends_on.toString("dd MMM"), job_frame)
end_date.move(job_frame.width() - 40, job_frame.height() - 20)
end_date.setStyleSheet("QWidget { color: %s }" % QColor(245, 245, 245).name())
self.jobs_number += 1
Job.py
# -*- coding: utf-8 -*-
import sys
from PyQt5.QtCore import QDate
from PyQt5.QtGui import QColor
class Job():
def __init__(self, start, end, company, rate, color):
self.starts_on = start
self.ends_on = end
self.company = company
self.rate = rate
self.color = color
self.days_range = self.starts_on.daysTo(self.ends_on)
这种情况的一个简单解决方案是覆盖 resizeEvent()
方法,每次调整小部件大小时都会调用该方法,因此我们利用该方法来更改 line1
的宽度和line2
,但是要从该方法访问这些 QLabel
必须是 class 的成员,为此您必须将 line1
和 line2
更改为 self.line1
和 self.line2
:
class Timeline(QFrame):
def __init__(self, parent):
super().__init__(parent)
[...]
self.line1 = QFrame(self)
self.line1.setGeometry(0, self.height() * 0.5, self.width(), 1)
self.line1.setStyleSheet("QWidget { background-color: %s }" % QColor(66, 76, 85).name())
self.line2 = QFrame(self)
self.line2.setGeometry(0, self.height(), self.width(), 1)
self.line2.setStyleSheet("QWidget { background-color: %s }" % QColor(66, 76, 85).name())
[...]
def resizeEvent(self, event):
for line in [self.line1, self.line2]:
line.resize(self.width(), line.size().height())
QFrame.resizeEvent(self, event)
我正在用 Python 和 PyQt5 开始一个项目。我想要一个这样的时间轴小部件
所以在未来我希望每个矩形都是一个按钮,但我目前的主要问题是让时间线保持在我的宽度 window。
目前我的时间轴继承自QFrame,每个矩形也是一个QFrame。那么这是一个好方法吗?有更好的方法吗?
这是我尝试过的:
Main.py
# -*- coding: utf-8 -*-
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout
from PyQt5.QtGui import QColor
from Timeline import *
from Job import *
class FreelanceAssistant(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.resize(1080, 720)
self.move(300, 300)
self.setWindowTitle("Freelance Assistant")
p = self.palette()
p.setColor(self.backgroundRole(), QColor(32, 47, 60))
self.setPalette(p)
timeline = Timeline(self)
timeline.addJob(Job(QDate(2017, 11, 21), QDate(2018, 1, 12), "Red Knuckles", 200, QColor(140, 67, 67)))
timeline.addJob(Job(QDate(2018, 1, 15), QDate(2018, 1, 26), "ETC", 200, QColor(67, 76, 140)))
window_layout= QVBoxLayout()
print(self.getContentsMargins())
self.setLayout(window_layout)
window_layout.addWidget(timeline)
self.show()
def main():
app = QApplication(sys.argv)
window = FreelanceAssistant()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
Timeline.py
# -*- coding: utf-8 -*-
import sys
from PyQt5.QtCore import QDate
from PyQt5.QtWidgets import QFrame, QLabel
from PyQt5.QtGui import QColor
class Timeline(QFrame):
def __init__(self, parent):
super().__init__(parent)
self.jobs_number = 0
self.setGeometry(0, 0, parent.width(), 200)
self.setStyleSheet("QWidget { background-color: %s }" % QColor(26, 36, 45).name())
self.__invoice_pos = self.height() * 0.5
self.__invoice_height = 13
self.__job_top_pos = self.height() * 0.5 + self.__invoice_height
self.__job_height = (self.height() - self.__job_top_pos)/2
self.__job_bottom_pos = self.__job_top_pos + self.__job_height
line1 = QFrame(parent)
line1.setGeometry(0, self.height() * 0.5, self.width(), 1)
line1.setStyleSheet("QWidget { background-color: %s }" % QColor(66, 76, 85).name())
line2 = QFrame(parent)
line2.setGeometry(0, self.height(), self.width(), 1)
line2.setStyleSheet("QWidget { background-color: %s }" % QColor(66, 76, 85).name())
self.__starts_on = QDate.currentDate().addMonths(-1)
self.__ends_on = QDate.currentDate().addMonths(5)
self.__days_range = self.__starts_on.daysTo(self.__ends_on)
current_year = QLabel(str(self.__starts_on.year()), parent)
current_year.move(5, self.height() + 5)
current_year.setStyleSheet("QWidget { color: %s }" % QColor(245, 245, 245).name())
for year in range(self.__starts_on.year() + 1, self.__ends_on.year() + 1):
days_to_new_year = self.__starts_on.daysTo(QDate(year, 1, 1))
new_year_line_pos = days_to_new_year/self.__days_range * self.width()
new_year_line = QFrame(parent)
new_year_line.setGeometry(int(new_year_line_pos), 0, 1, self.height() + 20)
new_year_line.setStyleSheet("QWidget { background-color: %s }" % QColor(245, 245, 245).name())
current_year = QLabel(str(year), parent)
current_year.move(int(new_year_line_pos) + 5, self.height() + 5)
current_year.setStyleSheet("QWidget { color: %s }" % QColor(245, 245, 245).name())
invoice_start = QDate(2017, 11, 21)
invoice_end = QDate(2017, 12, 13)
invoice_start_pos = self.__starts_on.daysTo(invoice_start)/self.__days_range * self.width()
invoice_width = invoice_start.daysTo(invoice_end)/self.__days_range * self.width()
invoice_frame = QFrame(self)
invoice_frame.setGeometry(invoice_start_pos, self.__invoice_pos, invoice_width, self.__invoice_height)
invoice_frame.setStyleSheet("QWidget { background-color: %s }" % QColor(50, 200, 50).name())
invoice_end_date = QLabel(invoice_end.toString("dd MMM"), invoice_frame)
invoice_end_date.move(invoice_frame.width() - 40, 0)
invoice_end_date.setStyleSheet("QWidget { color: %s }" % QColor(0, 0, 0).name())
def addJob(self, job):
start_pos = self.__starts_on.daysTo(job.starts_on)/self.__days_range * self.width()
width = job.days_range/self.__days_range * self.width()
job_frame = QFrame(self)
if self.jobs_number % 2 == 0:
job_frame.setGeometry(start_pos, self.__job_bottom_pos, width, self.__job_height)
else:
job_frame.setGeometry(start_pos, self.__job_top_pos, width, self.__job_height)
job_frame.setStyleSheet("QWidget { background-color:" + job.color.name() + " } QWidget:hover{background-color: " + job.color.darker(125).name() + "}")
company_name = QLabel(job.company, job_frame)
company_name.move(5, 5)
company_name.setStyleSheet("QWidget { color: %s }" % QColor(245, 245, 245).name())
start_date = QLabel(job.starts_on.toString("dd MMM"), job_frame)
start_date.move(5, job_frame.height() - 20)
start_date.setStyleSheet("QWidget { color: %s }" % QColor(245, 245, 245).name())
end_date = QLabel(job.ends_on.toString("dd MMM"), job_frame)
end_date.move(job_frame.width() - 40, job_frame.height() - 20)
end_date.setStyleSheet("QWidget { color: %s }" % QColor(245, 245, 245).name())
self.jobs_number += 1
Job.py
# -*- coding: utf-8 -*-
import sys
from PyQt5.QtCore import QDate
from PyQt5.QtGui import QColor
class Job():
def __init__(self, start, end, company, rate, color):
self.starts_on = start
self.ends_on = end
self.company = company
self.rate = rate
self.color = color
self.days_range = self.starts_on.daysTo(self.ends_on)
这种情况的一个简单解决方案是覆盖 resizeEvent()
方法,每次调整小部件大小时都会调用该方法,因此我们利用该方法来更改 line1
的宽度和line2
,但是要从该方法访问这些 QLabel
必须是 class 的成员,为此您必须将 line1
和 line2
更改为 self.line1
和 self.line2
:
class Timeline(QFrame):
def __init__(self, parent):
super().__init__(parent)
[...]
self.line1 = QFrame(self)
self.line1.setGeometry(0, self.height() * 0.5, self.width(), 1)
self.line1.setStyleSheet("QWidget { background-color: %s }" % QColor(66, 76, 85).name())
self.line2 = QFrame(self)
self.line2.setGeometry(0, self.height(), self.width(), 1)
self.line2.setStyleSheet("QWidget { background-color: %s }" % QColor(66, 76, 85).name())
[...]
def resizeEvent(self, event):
for line in [self.line1, self.line2]:
line.resize(self.width(), line.size().height())
QFrame.resizeEvent(self, event)