创建自定义时间选择器小部件

Creating Custom Time Picker Widget

我需要创建一个用于选择时间的小部件。 QTimeEdit 小部件似乎不直观或设计不佳。所以我决定创建一个类似于智能手机中的时间选择器的时间选择器。

我成功地创建了时钟并单击使指针(类似于图像中的指针)移动到当前单击的位置(注意:它并不完美,看起来仍然很糟糕)。我想帮忙制作内钟

这是我的代码:

from PyQt5 import QtWidgets, QtGui, QtCore
import math, sys


class ClockWidget(QtWidgets.QWidget):   # I want to be able to reuse this class for other programs also, so please don't hard code values of the list, start and end

    def __init__(self, start, end, lst=[], *args, **kwargs):
        super(ClockWidget, self).__init__(*args, **kwargs)

        self.lst = lst

        if not self.lst:
            self.lst = [*range(start, end)]

        self.index_start = 0  # tune this to move the letters in the circle
        self.pointer_angles_multiplier = 9  # just setting the default values

        self.current = None
        self.rects = []

    @property
    def index_start(self):
        return self._index_start

    @index_start.setter
    def index_start(self, index):
        self._index_start = index

    def paintEvent(self, event):

        self.rects = []

        painter = QtGui.QPainter(self)
        pen = QtGui.QPen()
        pen.setColor(QtCore.Qt.red)
        pen.setWidth(2)
        painter.setPen(pen)

        x, y = self.rect().x(), self.rect().y()
        width, height = self.rect().width(), self.rect().height()

        painter.drawEllipse(x, y, x + width, x + height)

        s, t, equal_angles, radius = self.angle_calc()
        radius -= 30

        pen.setColor(QtCore.Qt.green)
        pen.setWidth(2)
        painter.setPen(pen)

        """ pointer angle helps in determining to which position the pointer should be drawn"""
        self.pointer_x, self.pointer_y = s + ((radius-30) * math.cos(self.pointer_angles_multiplier * equal_angles)), t \
                + ((radius-30)  * math.sin(self.pointer_angles_multiplier * equal_angles))

        """ The pendulum like pointer """
        painter.drawLine(QtCore.QPointF(s, t), QtCore.QPointF(self.pointer_x, self.pointer_y))

        painter.drawEllipse(QtCore.QRectF(QtCore.QPointF(self.pointer_x - 20, self.pointer_y - 40),
                                          QtCore.QPointF(self.pointer_x + 30, self.pointer_y + 10)))

        pen.setColor(QtCore.Qt.blue)
        pen.setWidth(3)

        font = self.font()
        font.setPointSize(14)
        painter.setFont(font)

        painter.setPen(pen)

        """ Drawing the number around the circle formula y = t + radius * cos(a)
            y = s + radius * sin(a) where angle is in radians (s, t) are the mid point of the circle """
        for index, char in enumerate(self.lst, start=self.index_start):
            angle = equal_angles * index

            y = t + radius * math.sin(angle)
            x = s + radius * math.cos(angle)

            # print(f"Add: {add_x}, index: {index}; char: {char}")
            rect = QtCore.QRectF(x - 30, y - 40, x + 60, y)  # clickable point

            self.rects.append([index, char, rect])  # appends index, letter, rect

            painter.setPen(QtCore.Qt.blue)
            painter.drawRect(rect)  # helps in visualizing the points where the click can received

            print(f"Rect: {rect}; char: {char}")
            painter.setPen(QtCore.Qt.red)

            points = QtCore.QPointF(x, y)
            painter.drawText(points, str(char))

    def mousePressEvent(self, event):

        for x in self.rects:

            index, char, rect = x

            if event.button() & QtCore.Qt.LeftButton and rect.contains(event.pos()):

                self.pointer_angles_multiplier = index
                self.current = char
                self.update()
                break

    def angle_calc(self):
        """
         This will simply return (midpoints of circle, divides a circle into the len(list) and return the
         angle in radians, radius)
         """
        return ((self.rect().width() - self.rect().x()) / 2, (self.rect().height() - self.rect().y()) / 2,
                (360 / len(self.lst)) * (math.pi / 180), (self.rect().width() / 2))

    def resizeEvent(self, event: QtGui.QResizeEvent):
        """This is supposed to maintain a Square aspect ratio on widget resizing but doesn't work
        correctly as you will see when executing"""

        if event.size().width() > event.size().height():
            self.resize(event.size().height(), event.size().width())

        else:
            self.resize(event.size().width(), event.size().width())


if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)

    message = ClockWidget(1, 13)
    message.index_start = 10
    message.show()

    sys.exit(app.exec())


输出:

蓝色矩形代表可点击区域。如果您也可以,我会很高兴,当在时钟内部单击时,使指针移动到最接近的数字(而不只是在蓝色区域内单击时移动指针)

我的代码还有一个问题,那就是数字与外圈的间距不均匀。 (比如数字12比数字6更靠近外圈)

免责声明:我不会解释错误的原因,但我提供的代码我认为应该对错误给出明确的解释。

逻辑是计算每个小圆心的位置,以外切矩形为底边绘制文字,检查点击的点是否靠近文字。

from functools import cached_property
import math
import sys

from PyQt5 import QtCore, QtGui, QtWidgets


class ClockWidget(QtWidgets.QWidget):
    L = 12
    r = 40.0
    DELTA_ANGLE = 2 * math.pi / L
    current_index = 9

    def paintEvent(self, event):
        painter = QtGui.QPainter(self)
        painter.setRenderHint(QtGui.QPainter.Antialiasing)

        R = min(self.rect().width(), self.rect().height()) / 2
        margin = 4

        Rect = QtCore.QRectF(0, 0, 2 * R - margin, 2 * R - margin)
        Rect.moveCenter(self.rect().center())

        painter.setBrush(QtGui.QColor("gray"))
        painter.drawEllipse(Rect)

        rect = QtCore.QRectF(0, 0, self.r, self.r)

        if 0 <= self.current_index < 12:
            c = self.center_by_index(self.current_index)
            rect.moveCenter(c)
            pen = QtGui.QPen(QtGui.QColor("red"))
            pen.setWidth(5)
            painter.setPen(pen)
            painter.drawLine(c, self.rect().center())

            painter.setBrush(QtGui.QColor("red"))
            painter.drawEllipse(rect)

        for i in range(self.L):
            j = (i + 2) % self.L + 1
            c = self.center_by_index(i)
            rect.moveCenter(c)
            painter.setPen(QtGui.QColor("white"))
            painter.drawText(rect, QtCore.Qt.AlignCenter, str(j))

    def center_by_index(self, index):
        R = min(self.rect().width(), self.rect().height()) / 2
        angle = self.DELTA_ANGLE * index
        center = self.rect().center()

        return center + (R - self.r) * QtCore.QPointF(math.cos(angle), math.sin(angle))

    def index_by_click(self, pos):
        for i in range(self.L):
            c = self.center_by_index(i)
            delta = QtGui.QVector2D(pos).distanceToPoint(QtGui.QVector2D(c))
            if delta < self.r:
                return i
        return -1

    def mousePressEvent(self, event):
        i = self.index_by_click(event.pos())
        if i >= 0:
            self.current_index = i
            self.update()

    @property
    def hour(self):
        return (self.current_index + 2) % self.L + 1

    def minumumSizeHint(self):
        return QtCore.QSize(100, 100)


def main():
    app = QtWidgets.QApplication(sys.argv)
    view = ClockWidget()
    view.resize(400, 400)
    view.show()
    sys.exit(app.exec_())


if __name__ == "__main__":
    main()