QTimeEdit没有弹出窗口,它的整体设计使它"不好用".

因此,我编写了两个自定义的QWidget类:

  • class Clock_hourWidget(QWidget): class/object返回/发出第一个有效小时输入
  • class Clock_minuteWidget(QWidget): class/object返回/发出第一个有效分钟输入

我把它们嵌入到一个自定义的QTimeEdit中:

import math
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QTimeEdit, QApplication
from PyQt5.QtCore import QTime, Qt, QTimer
from PyQt5.QtGui import QIcon


class Clock_hourWidget(QtWidgets.QWidget):
    return_hour = 15
    first = True

    def __init__(self, hours, callback, parent=None):
        super(Clock_hourWidget, self).__init__(parent)
        self.setWindowFlags(Qt.Popup)
        self.callback = callback
        self.setFixedSize(150,150)
        self.hours = hours

    def mousePressEvent(self, event):
        if self.rect().contains(event.localPos().toPoint()):
            self.first = False
            self.return_hour = self.hours
        self.close()

    @property
    def hour(self):
        if self.first:
            result = -1
        else:
            result = self.return_hour
        return result
    
    def closeEvent(self, event):
        self.callback(self.hour)
        event.accept()


class Clock_minuteWidget(QtWidgets.QWidget):
    return_minutes = 30

    def __init__(self, minutes, callback, parent=None):
        super(Clock_minuteWidget, self).__init__(parent)
        self.setWindowFlags(Qt.Popup)
        self.setFixedSize(150,150)
        self.callback = callback
        self.minutes = minutes

    def mousePressEvent(self, event):
        if self.rect().contains(event.localPos().toPoint()):
            self.return_minutes = self.minutes
        self.close()

    @property
    def minute(self):
        return self.return_minutes

    def closeEvent(self, event):
        self.callback(self.minute)
        event.accept()


class CustomTimeEdit(QTimeEdit):
    def __init__(self, parent=None):
        super(CustomTimeEdit, self).__init__(parent)
        self.setDisplayFormat("HH:mm")
        self.setCalendarPopup(False)
        # calendar_icon = QIcon.fromTheme("calendar")
        # self.setButtonSymbols(calendar_icon)

    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton:
            self.open_clock_hourwidget()

    def return_hour(self, hour):
        self.clock_hourwidget = None
        if hour != -1:
            self.hours = hour
            self.minutes = self.time().minute()
            self.setTime(QTime(self.hours, self.minutes))
            self.clock_minutewidget = Clock_minuteWidget(self.minutes, self.clock_finalizer)
            pos = self.mapToGlobal(self.rect().bottomLeft())
            pos = self.adjust_Popup_positioning(pos, self.clock_minutewidget)
            self.clock_minutewidget.move(pos)
            self.clock_minutewidget.show()

    def open_clock_hourwidget(self):
        self.hours = self.time().hour()
        self.clock_hourwidget = Clock_hourWidget(self.hours, self.return_hour)
        pos = self.mapToGlobal(self.rect().bottomLeft())
        pos = self.adjust_Popup_positioning(pos, self.clock_hourwidget)
        self.clock_hourwidget.move(pos)
        self.clock_hourwidget.show()

    def adjust_Popup_positioning(self, pos, widget):
        screen = QApplication.desktop().screenNumber(QApplication.desktop().cursor().pos())
        screen_geometry = QApplication.desktop().screenGeometry(screen)
        if pos.y() + widget.height() > screen_geometry.height():
            pos = self.mapToGlobal(self.rect().topLeft())
            pos.setY(pos.y() - widget.height())
        if pos.x() < screen_geometry.left():
            pos.setX(screen_geometry.left())
        elif pos.x() + widget.width() > screen_geometry.right():
            pos.setX(screen_geometry.right() - widget.width())
        return pos

    def clock_finalizer(self, minute):
        self.clock_minutewidget = None
        self.setTime(QTime(self.hours, minute))


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    time_edit = CustomTimeEdit()
    time_edit.show()
    sys.exit(app.exec_())

我希望弹出窗口的行为像QDateEdit的CalendarPopup(已经在上面的代码片段中删除了功能):

  • 弹出窗口应在自定义QTimeEdit下打开
  • 如果自定义QTimeEdit下的空间不足,则应显示在自定义QTimeEdit上方
  • 如果右边没有足够的空间,它应该稍微向左移动,反之亦然
  • 最重要的是,似乎是不可撤销的:如果用户点击弹出窗口外,或者如果弹出窗口(可能是整个应用程序)失go 焦点(通过按下windows键或alt—tab,例如):弹出窗口关闭.
  • 自定义QTimeEdit的按钮外观和行为应该像QDateEdit/QDateTimeEdit(setCalendarPopup(True))的CalendarPopup按钮:外观应该根据使用的样式和平台而改变

如果需要的话,我可以提供完整的代码,但出于简单的原因,我试图尽可能地削减它.

推荐答案

QTimeEdit和QDateEdit基本上是哑类:它们在行为上没有提供任何与它们继承的QDateTimeEdit不同的特定差异,事实上,在QDateTimeEdit上设置一个适当的displayFormat()就足以获得相同的结果.

现在,QDateTimeEdit所做的是,当calendarPopup属性为True时,它实际上显示date Section(无论是日,月或年),部分地对一个可编辑的QComboBox进行similarly操作.

实现这一点(鼠标处理程序除外)在paintEvent():如果弹出窗口不应该显示(calendarPopupFalse ordisplayFormat()中没有日期部分),它只是调用QAbstractSpinBox的basic paintEvent(),它在内部创建一个QStyleOptionSpinBox实例,调用虚拟initStyleOption()(这是QDateTimeEdit的,但在这种情况下是相同的,因为没有显示日期部分),并最终使用小部件的风格drawComplexControl()函数.

否则,如果弹出窗口may被显示,它使用QStyle特征通过添加一些特定于它的subControls个来显示自己像一个可编辑的组合框:组合框,其编辑字段,以及最重要的是箭头.

因此,为了force画箭头,我们需要模仿同样的行为.请注意,由于箭头可能会根据鼠标悬停状态而显示不同,因此我们需要确保选项activeSubControls也是一致的,这可以通过强制鼠标跟踪和判断mouseMoveEvent()中的位置以及进入/离开事件来实现.

class CustomTimeEdit(QTimeEdit):
    clock_hourwidget = clock_minutewidget = None
    _onArrow = False
    def __init__(self, parent=None):
        super(CustomTimeEdit, self).__init__(parent)
        self.setDisplayFormat("HH:mm")
        self.setCalendarPopup(False)
        self.setMouseTracking(True)

    ...

    def _checkOnArrow(self, pos):
        style = self.style()
        opt = QStyleOptionComboBox()
        opt.initFrom(self)
        arrowRect = style.subControlRect(style.CC_ComboBox, opt, 
            style.SC_ComboBoxArrow, self)
        onArrow = arrowRect.contains(pos)
        if self._onArrow != onArrow:
            self._onArrow = onArrow
            self.update()

    def mouseMoveEvent(self, event):
        if not event.buttons():
            self._checkOnArrow(event.pos())
        else:
            super().mouseMoveEvent(event)

    def enterEvent(self, event):
        super().enterEvent(event)
        self._checkOnArrow(QCursor.pos())

    def leaveEvent(self, event):
        super().leaveEvent(event)
        if self._onArrow:
            self._onArrow = False
            self.update()

    def paintEvent(self, event):
        opt = QStyleOptionSpinBox()
        QAbstractSpinBox.initStyleOption(self, opt)
        style = self.style()

        opt.subControls = (
            style.SC_ComboBoxFrame | style.SC_ComboBoxEditField
            | style.SC_ComboBoxArrow
        )
        if self.clock_hourwidget or self.clock_minutewidget:
            opt.state |= style.State_Sunken
        else:
            opt.state &= ~style.State_Sunken
        optCombo = QStyleOptionComboBox()
        optCombo.initFrom(self)
        optCombo.editable = True
        optCombo.frame = opt.frame
        optCombo.subControls = opt.subControls
        optCombo.state = opt.state

        if self._onArrow:
            optCombo.activeSubControls = style.SC_ComboBoxArrow

        qp = QPainter(self)
        self.style().drawComplexControl(
            style.CC_ComboBox, optCombo, qp, self)

请注意,QDesktopWidget已经被宣布过时一段时间了,应该始终首选QScreen.此外,调用桌面小部件cursor()来获取其位置是毫无意义的(因为它与静态小部件QCursor.pos()相同),几何计算应该更加小心(而不是更简单).例如:

...
    def open_clock_hourwidget(self):
        self.hours = self.time().hour()
        self.clock_hourwidget = Clock_hourWidget(self.hours, self.return_hour)
        self.adjustPopup(self.clock_hourwidget)

    def adjustPopup(self, widget):
        pos = self.mapToGlobal(self.rect().bottomLeft())
        geo = widget.geometry()
        geo.moveTopLeft(pos)
        screen = (QApplication.screenAt(pos) or self.screen()).geometry()
        if geo.right() > screen.right():
            geo.moveRight(screen.right())
        if geo.x() < screen.x():
            geo.moveLeft(screen.left())
        if geo.bottom() > screen.bottom():
            geo.moveBottom(screen.bottom())
        if geo.y() < screen.y():
            geo.moveTop(screen.y())
        widget.setGeometry(geo)
        widget.show()

注意,我特别避免了上面的elif个.虽然我们可以有把握地假设几乎没有人会使用一个太小的屏幕,不适合弹出窗口,但最好还是正式考虑这种可能性.

最后,提出了进一步的建议:

  • 对于小时/分钟小部件有两个实例属性没有什么意义,因为它们总是被单独显示,最终被销毁(然后再次重新创建);更好的方法是只在必要时创建它们,并最终创建它们,可能在QTimeEdit的timeChanged信号发出时更新它们的值;
  • 虽然使用"回调"在技术上没有什么问题,但Qt的信号/槽机制提供了更好的模块化和关注点分离;有趣的是,你在问题中使用了术语"emits ",但你没有emits 任何东西;而不是使用回调,使用自定义信号,实际上在需要时使用emit个信号;
  • 基于QAbstractSpinBox的类处理的鼠标事件不是由其内部lineEdit()处理的,这意味着简单地基于按钮覆盖mousePressEvent()可能是不一致的,因为用户可能正在点击frame;考虑根据上面的_checkOnArrow()函数是如何实现的,这是可编辑组合框实际上做的事情;

Python相关问答推荐

如何根据另一列值用字典中的值替换列值

Pystata:从Python并行运行stata实例

根据另一列中的nan重置值后重新加权Pandas列

如何获取TFIDF Transformer中的值?

PyQt5,如何使每个对象的 colored颜色 不同?'

如何从pandas的rame类继承并使用filepath实例化

当点击tkinter菜单而不是菜单选项时,如何执行命令?

多处理队列在与Forking http.server一起使用时随机跳过项目

如何在Python中获取`Genericums`超级类型?

如何在TensorFlow中分类多个类

如何在Great Table中处理inf和nans

并行编程:同步进程

Js的查询结果可以在PC Chrome上显示,但不能在Android Chrome、OPERA和EDGE上显示,而两者都可以在Firefox上运行

如何将泛型类类型与函数返回类型结合使用?

合并相似列表

python3中np. divide(x,y)和x/y有什么区别?'

以极轴表示的行数表达式?

EST格式的Azure数据库笔记本中的当前时间戳

Match-Case构造中的对象可调用性测试

Python:在cmd中添加参数时的语法