Qt/PyQt4/PySide QDateEdit 日历弹窗掉屏

Posted

技术标签:

【中文标题】Qt/PyQt4/PySide QDateEdit 日历弹窗掉屏【英文标题】:Qt/PyQt4/PySide QDateEdit calendar popup falls off screen 【发布时间】:2014-02-10 10:02:34 【问题描述】:

Qt.QDateEdit 日历弹出小部件似乎从屏幕边缘脱落,我遇到了一个小问题。该小部件似乎通过移动日历弹出窗口来说明靠近屏幕边缘的问题,但屏幕边缘悬挂着大约 20 个像素:

屏幕中心的整个小部件:

整个小部件移动到屏幕的右边缘:

关于如何解决这个问题的任何想法?我试图手动移动日历小部件 (cal.move(-20, 0) 但这没有任何作用

from PyQt4 import QtGui
from PyQt4 import QtCore
import datetime
from os import path
import decimal

headers = [('BA Number', int), ('Applicant Name', str), ('Site Address', str), ('Application Type', str), ('Applicant Address', str), ('Applicant Tel.', str), ('Applicant Fax', str), ('Applicant Email', str), ('Applicant Status', str), ('Agent Name', str), ('Agent Address', str), ('Agent Tel.', str), ('Agent Fax', str), ('Agent Email', str), ('Property No.', str), ('Proposed Works', str), ('Landuse', str), ('Application Date', datetime.datetime), ('Application Received', datetime.datetime), ('Estimated Costs', float), ('EIA', str), ('Inspectors Initials', str), ('Landlord Name', str), ('Landlord Address', str), ('Landlord Tel.', str), ('Landlord Fax', str), ('Landlord Email', str), ('Decision', str), ('Plan Permit No.', str), ('Plan Permit Date', datetime.datetime), ('Build Notice No.', str), ('Build Notice Date', datetime.datetime), ('Build Notice Comments', str), ('Plan Permit Expiry Date', datetime.datetime), ('Build Notice Expiry Date', datetime.datetime), ('Fee Building', float), ('Fee Planning', float), ('Fee Inspection', float), ('Works Completed', datetime.datetime), ('Works Started', datetime.datetime), ('Appeal', str), ('Notes', str), ('Completion Certificate', str), ('Fitness Certificate Issued', str), ('DPC Decision', str), ('DPC Date', datetime.datetime), ('DPC Approved by', str), ('GOG', str)]

numbercomparisonoperators = [('=', 'Equal to'), ('>', 'Greater than'), ('<', 'Less than'), ('>=', 'Greater than or equal to'), ('<=', 'Less than or equal to')]
datecomparisonoperators = [('=', 'On'), ('>', 'More recent than'), ('<', 'Earlier than'), ('>=', 'More recent than or on'), ('<=', 'Earlier than or on')]
stringcomparisonoperators = [('=', 'Equal to'), ('in', 'Contains'), ('any', 'Match any sequence of character in')]
logicaloperators = [('and', 'Match both criteria'), ('or', 'Match either or both criteria'), ('not', 'Don\'t match the following criteria')]

iconpath = ':/plugins/gogtownplanning/icons'


class QueryBuilderWidget(QtGui.QWidget):
    """
    Display a widget to enable filtering multiple columns in a QTreeWidget
    """
    def __init__(self, parent=None):
        super(QueryBuilderWidget, self).__init__()
        self.parent = parent
        self.setMinimumSize(300, 100)

        self.centralLayout = QtGui.QGridLayout()
        self.centralLayout.setContentsMargins(0, 0, 0, 0)
        self.centralLayout.setSpacing(2)
        self.centralLayout.setColumnStretch(1, 1)
        self.centralLayout.setRowStretch(3, 1)
        self.setLayout(self.centralLayout)

        AddFieldLabel = QtGui.QLabel()
        AddFieldLabel.setText('Add field:')
        self.centralLayout.addWidget(AddFieldLabel, 0, 0)

        self.FieldSelectorCombo = FieldSelectorCombo(self)
        self.centralLayout.addWidget(self.FieldSelectorCombo, 0, 1)
        self.FieldSelectorCombo.activated.connect(self.enable_add_item)

        self.QueryTreeWidget = QueryTreeWidget()
        self.centralLayout.addWidget(self.QueryTreeWidget, 1, 0, 3, 2)
        self.QueryTreeWidget.itemSelectionChanged.connect(self.enable_buttons)

        self.AddItemButton = AddItemButton(self)
        self.AddItemButton.setEnabled(False)
        self.centralLayout.addWidget(self.AddItemButton, 0, 2)

        self.RemoveItemButton = RemoveItemButton(self.QueryTreeWidget)
        self.RemoveItemButton.setEnabled(False)
        self.centralLayout.addWidget(self.RemoveItemButton, 1, 2)

        self.AndButton = AndButton(self)
        self.AndButton.setEnabled(False)
        self.centralLayout.addWidget(self.AndButton, 2, 2)

    def enable_buttons(self):
        """
        Enable certain UI buttons as required when items are selected in a
        QTreeWidget
        """
        if self.QueryTreeWidget.currentItem():
            self.RemoveItemButton.setEnabled(True)
            self.AndButton.setEnabled(True)
        else:
            self.RemoveItemButton.setEnabled(False)
            self.AndButton.setEnabled(False)

    def enable_add_item(self, index):
        """
        Enable certain UI buttons as required when items are selected in a
        QComboBox
        """
        if index != -1:
            self.AddItemButton.setEnabled(True)
        else:
            self.AddItemButton.setEnabled(False)


class RemoveItemButton(QtGui.QPushButton):
    """docstring for RemoveItemButton"""
    def __init__(self, parent=None):
        super(RemoveItemButton, self).__init__()
        self.parent = parent

        self.setMaximumSize(24, 24)
        minusicon = QtGui.QIcon()
        minusicon.addPixmap(QtGui.QPixmap(path.join(iconpath, 'minus.png')),
                            QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.setIconSize(QtCore.QSize(16, 16))
        self.setIcon(minusicon)
        self.setToolTip('Remove selected item')

        self.clicked.connect(self.remove_selected_item)

    def remove_selected_item(self):
        currentitem = self.parent.currentItem()
        currentindex = self.parent.indexOfTopLevelItem(currentitem)
        self.parent.takeTopLevelItem(currentindex)


class AndButton(QtGui.QPushButton):
    """docstring for AndButton"""
    def __init__(self, parent=None):
        super(AndButton, self).__init__()
        self.parent = parent

        self.setMaximumSize(24, 24)
        self.setText('and')
        self.setToolTip('Match both criteria')

        self.clicked.connect(self.add_and_item)

    def add_and_item(self):
        treewidget = self.parent.QueryTreeWidget
        treewidget.add_logic_item('and')


class AddItemButton(QtGui.QPushButton):
    """docstring for AddItemButton"""
    def __init__(self, parent=None):
        super(AddItemButton, self).__init__()
        self.parent = parent

        self.setMaximumSize(24, 24)
        addicon = QtGui.QIcon()
        addicon.addPixmap(QtGui.QPixmap(path.join(iconpath, 'plus.png')),
                          QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.setIconSize(QtCore.QSize(16, 16))
        self.setIcon(addicon)
        self.setToolTip('Add selected item')

        self.clicked.connect(self.add_selected_item)

    def add_selected_item(self):
        FieldCombo = self.parent.FieldSelectorCombo
        index = FieldCombo.currentIndex()
        fieldname = FieldCombo.currentText()
        fieldtype = FieldCombo.itemData(index, QtCore.Qt.UserRole).toPyObject()
        if fieldname:
            self.parent.QueryTreeWidget.addItem(fieldname, fieldtype)


class FieldSelectorCombo(QtGui.QComboBox):
    """docstring for FieldSelectorCombo"""
    def __init__(self, parent=None):
        super(FieldSelectorCombo, self).__init__()
        self.parent = parent

        count = 0
        for field, fieldtype in headers:
            self.addItem(field)
            self.setItemData(count, fieldtype, QtCore.Qt.UserRole)
            count += 1
        self.setCurrentIndex(-1)

    def keyPressEvent(self, event):
        """ """
        if event.key() == QtCore.Qt.Key_Return:
            self.parent.AddItemButton.add_selected_item()
        return QtGui.QComboBox.keyPressEvent(self, event)

    def wheelEvent(self, event):
        event.ignore()


class OperatorComboBox(QtGui.QComboBox):
    """docstring for OperatorComboBox"""
    def __init__(self, parent=None, operatorhint=int):
        super(OperatorComboBox, self).__init__()
        self.parent = parent

        if operatorhint in (int, float):
            operatorlist = numbercomparisonoperators
        elif operatorhint == str:
            operatorlist = stringcomparisonoperators
        elif operatorhint == datetime.datetime:
            operatorlist = datecomparisonoperators

        count = 0
        for comparisonoperator, tooltip in operatorlist:
            self.addItem(comparisonoperator)
            self.setItemData(count, comparisonoperator)
            self.setItemData(count, tooltip, QtCore.Qt.ToolTipRole)
            count += 1

    def wheelEvent(self, event):
        event.ignore()


class OperatorLineEdit(QtGui.QLineEdit):
    """docstring for OperatorLineEdit"""
    def __init__(self, parent=None):
        super(OperatorLineEdit, self).__init__()
        self.parent = parent


class DateWidget(QtGui.QDateEdit):
    """docstring for DateWidget"""
    def __init__(self, parent=None):
        super(DateWidget, self).__init__()
        self.parent = parent

        self.setDate(QtCore.QDate.currentDate())
        self.setCalendarPopup(True)
        self.setDisplayFormat('dd/MM/yyyy')
        self.cal = self.calendarWidget()
        self.cal.setFirstDayOfWeek(QtCore.Qt.Monday)
        self.cal.setHorizontalHeaderFormat(QtGui.QCalendarWidget.SingleLetterDayNames)
        self.cal.setVerticalHeaderFormat(QtGui.QCalendarWidget.NoVerticalHeader)
        self.cal.setGridVisible(True)


class QueryTreeWidget(QtGui.QTreeWidget):
    """docstring for QueryTreeWidget"""
    def __init__(self, parent=None):
        super(QueryTreeWidget, self).__init__()
        self.parent = parent
        self.setAlternatingRowColors(True)
        headerlabels = ('Field', 'Operator', 'Value')
        self.setHeaderLabels(headerlabels)
        self.setColumnCount(len(headerlabels))
        self.header().setResizeMode(QtGui.QHeaderView.ResizeToContents)
        self.header().setMovable(False)
        self.setSelectionMode(QtGui.QAbstractItemView.ContiguousSelection)

    def addItem(self, itemtext, itemtype):
        treewidgetitem = QtGui.QTreeWidgetItem()
        treewidgetitem.setText(0, itemtext)

        OperatorSelectorBox = OperatorComboBox(operatorhint=itemtype)

        if itemtype in (str, int, float):
            valueWidget = OperatorLineEdit(self)
        elif itemtype == datetime.datetime:
            valueWidget = DateWidget(self)

        self.insertTopLevelItem(self.insert_index(), treewidgetitem)
        self.setItemWidget(treewidgetitem, 1, OperatorSelectorBox)
        self.setItemWidget(treewidgetitem, 2, valueWidget)
        self.setCurrentItem(treewidgetitem)

    def add_logic_item(self, logic):
        treewidgetitem = QtGui.QTreeWidgetItem()
        treewidgetitem.setText(0, 'and')
        treewidgetitem.setFont(0, QtGui.QFont('', -1, QtGui.QFont.Bold))
        treewidgetitem.setData(0, QtCore.Qt.UserRole, 'and')
        self.insertTopLevelItem(self.insert_index(), treewidgetitem)

    def insert_index(self):
        if self.currentItem():
            currentindex = self.indexOfTopLevelItem(self.currentItem()) + 1
        else:
            currentindex = 0
        return currentindex


if __name__ == '__main__':
    import sys
    app = QtGui.QApplication(sys.argv)
    w = QueryBuilderWidget(app)
    w.show()
    app.exec_()

【问题讨论】:

【参考方案1】:

我无法重现您的问题。日历弹出窗口在 Windows 和 Linux 中的位置很好。您可以通过在其上安装事件过滤器并监视显示事件来手动更改日历弹出窗口的位置。在以下示例中,弹出窗口始终位于编辑的右下角的右侧(见图)。

class Widget(QWidget):
    def __init__(self):
        super().__init__()
        self.setupUi()

    def setupUi(self):
        layout = QGridLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        self.dateEdit = QDateEdit()
        self.dateEdit.setCalendarPopup(True)
        self.dateEdit.calendarWidget().installEventFilter(self)
        layout.addWidget(self.dateEdit, 0, 0)
        layout.addItem(QSpacerItem(20, 20, QSizePolicy.Minimum, QSizePolicy.Expanding))
        self.setLayout(layout)

    def eventFilter(self, obj, event):
        if obj == self.dateEdit.calendarWidget() and event.type() == QEvent.Show:
            pos = self.dateEdit.mapToGlobal(self.dateEdit.geometry().bottomRight())
            width = self.dateEdit.calendarWidget().window().width()
            self.dateEdit.calendarWidget().window().move(pos.x() - width, pos.y())
        return False

【讨论】:

谢谢,我在我重新实现的 QDateEdit 小部件(实际上是一个 QTreeWidgetItem 小部件)上监视了 show 事件,并且必须对偏移量进行硬编码,但似乎可以工作。我的原始代码在上面,所以你可以看到我的问题。从组合框中添加任何日期字段都将显示一个 dateedit 小部件。

以上是关于Qt/PyQt4/PySide QDateEdit 日历弹窗掉屏的主要内容,如果未能解决你的问题,请参考以下文章

Qt QDateEdit QDateTimeEdit

QT5-控件-QDateEdit 和 日期类QDate

2.6.2 QDate类与QDateEdit控件介绍(日期处理)

QT控件的使用

从表单的小部件中收集值

PyQt DateEdit 小部件不默认为系统时间