将事件重新发送到新启用的子窗口小部件

问题描述 投票:1回答:2

注意:下面是编辑中的一个更完整的示例

我想在Qt中实现以下内容(特别是PyQt,但我相信解决方案在python和C ++中都是类似的):

我希望窗口小部件具有默认禁用的内部窗口小部件,单击时,窗口小部件将被启用,鼠标按下将传播到它。例如,在以下窗口/窗口小部件中:

enter image description here

如果我在cd之间点击,我希望QLineEdit启用,获得焦点,光标位于cd之间。我得到了重新启用QLineEdit,但我似乎无法将事件发回给它。

到目前为止这是我的代码:

from PyQt5.QtWidgets import QWidget, QLineEdit, QVBoxLayout, QPushButton, QApplication


class MyWidget(QWidget):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        layout = QVBoxLayout(self)
        self.edit = QLineEdit('abcdef')
        self.edit.setEnabled(False)
        layout.addWidget(self.edit)

        self.disable_btn = QPushButton('disable edit')
        self.disable_btn.clicked.connect(self._disable_edit)
        layout.addWidget(self.disable_btn)

    def _disable_edit(self, *a):
        self.edit.setEnabled(False)

    def mousePressEvent(self, a0):
        if not self.edit.isEnabled() and self.edit.underMouse():
            self.edit.setEnabled(True)
            QApplication.instance().sendEvent(self.edit, a0)  # <-- this doesn't seem to work
        super().mousePressEvent(a0)


if __name__ == '__main__':
    from PyQt5.QtWidgets import QApplication

    app = QApplication([])
    w = MyWidget()
    w.show()
    res = app.exec_()
    exit(res)

这是一个简化的例子,我也想以这种方式包装其他小部件,因此修改内部小部件几乎是不可能的。

问题是,就我所知,禁用的子窗口小部件拒绝鼠标事件(因为它被禁用),并拒绝从父窗口小部件再次接受它(或任何其他事件)。

任何帮助都将非常感激。

编辑:以下是我的意思的一个更清晰的例子:

from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton


class ComplexInnerWidget(QWidget):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        layout = QVBoxLayout(self)
        self.btn1 = QPushButton('button 1')
        self.btn1.clicked.connect(self._btn1_click)
        layout.addWidget(self.btn1)

        self.btn2 = QPushButton('button 2')
        self.btn2.clicked.connect(self._btn2_click)
        layout.addWidget(self.btn2)

    def _btn1_click(self, *a):
        print('button 1')

    def _btn2_click(self, *a):
        print('button 2')


class MyWidget(QWidget):
    def __init__(self, inner_widget: QWidget, *args, **kwargs):
        super().__init__(*args, **kwargs)

        layout = QVBoxLayout(self)
        self.inner = inner_widget
        self.inner.setEnabled(False)
        layout.addWidget(self.inner)


if __name__ == '__main__':
    from PyQt5.QtWidgets import QApplication

    app = QApplication([])

    inner = ComplexInnerWidget()
    w = MyWidget(inner)
    w.show()
    res = app.exec_()
    exit(res)

我想要的是允许用户按下禁用的内部小部件,从而完全启用它(即btn1和btn2都启用),并同时按下相应的按钮。我需要这样做而不改变ComplexInnerWidget(因为用户应该能够输入任何小部件作为MyWidget的参数)

编辑2:eyllanesc的解决方案适用于所提供的示例,但我已调整它以使MyWidget能够支持多个小部件,并嵌套在其他小部件中:

from PyQt5 import QtCore, QtWidgets


class ComplexInnerWidget(QtWidgets.QWidget):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        layout = QtWidgets.QVBoxLayout(self)
        self.btn1 = QtWidgets.QPushButton('button 1')
        self.btn1.clicked.connect(self._btn1_click)
        layout.addWidget(self.btn1)

        self.btn2 = QtWidgets.QPushButton('button 2')
        self.btn2.clicked.connect(self._btn2_click)
        layout.addWidget(self.btn2)

        self.le = QtWidgets.QLineEdit('abcdef')
        layout.addWidget(self.le)

    def _btn1_click(self, *a):
        print('button 1')

    def _btn2_click(self, *a):
        print('button 2')


class MyWidget(QtWidgets.QWidget):
    class EnableMouseHelper(QtCore.QObject):
        def __init__(self, *args, warden):
            super().__init__(*args)
            self.warden = warden

        def eventFilter(self, obj, event):
            if obj.isWidgetType() and event.type() == QtCore.QEvent.MouseButtonPress:
                if self.warden in obj.window().findChildren(QtWidgets.QWidget) \
                        and self.warden.underMouse() and not self.warden.isEnabled():
                    self.warden.setEnabled(True)
                obj.setFocus()
            return super().eventFilter(obj, event)

    def __init__(self, inner_widget: QtWidgets.QWidget, *args, **kwargs):
        super().__init__(*args, **kwargs)
        layout = QtWidgets.QVBoxLayout(self)
        self.inner = inner_widget
        self.inner.setEnabled(False)
        layout.addWidget(self.inner)
        self.helper = self.EnableMouseHelper(warden=self.inner)
        QtWidgets.QApplication.instance().installEventFilter(self.helper)


class OuterWidget(QtWidgets.QWidget):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        layout = QtWidgets.QVBoxLayout(self)
        layout.addWidget(MyWidget(ComplexInnerWidget()))

        layout.addWidget(MyWidget(ComplexInnerWidget()))

        le = QtWidgets.QLineEdit('hi there')
        le.setEnabled(False)
        layout.addWidget(le)

        le = QtWidgets.QLineEdit('hi there')
        layout.addWidget(le)


if __name__ == '__main__':
    from PyQt5.QtWidgets import QApplication

    app = QApplication([])
    w = OuterWidget()
    w.show()
    res = app.exec_()
    exit(res)

python pyqt pyqt5 qevent qmouseevent
2个回答
1
投票

您无法发送事件对象,因为Qt会在窗口小部件使用它时删除它,您必须做的是创建具有相同数据的另一个事件。我创建了一个类,允许您注册小部件,为您提供此属性,而无需覆盖该类。

from functools import partial
from PyQt5 import QtCore, QtGui, QtWidgets

class Singleton(type(QtCore.QObject), type):
    def __init__(cls, name, bases, dict):
        super().__init__(name, bases, dict)
        cls.instance=None

    def __call__(cls,*args,**kw):
        if cls.instance is None:
            cls.instance=super().__call__(*args, **kw)
        return cls.instance

class EnableMouseHelper(QtCore.QObject, metaclass=Singleton):
    def __init__(self, parent=None):
        super(EnableMouseHelper, self).__init__(parent)
        self._widgets = []

    @staticmethod
    def addWidget(widget):
        if isinstance(widget, QtWidgets.QWidget):
            helper = EnableMouseHelper()
            helper._widgets.append(widget)
            widget.installEventFilter(helper)
            return True
        return False

    @staticmethod
    def removeWidget(widget):
        helper = EnableMouseHelper()
        if widget is helper._widgets:
            widget.removeEventFilter(helper)
            helper._widgets.remove(widget)

    def eventFilter(self, obj, event):
        if obj in self._widgets and event.type() == QtCore.QEvent.MouseButtonPress:
            if not obj.isEnabled():
                new_event = QtGui.QMouseEvent(
                    event.type(),
                    event.localPos(),
                    event.windowPos(),
                    event.screenPos(),
                    event.button(),
                    event.buttons(),
                    event.modifiers(),
                    event.source()
                )
                obj.setEnabled(True)
                obj.setFocus()
                QtCore.QCoreApplication.postEvent(obj, new_event)
        return super(EnableMouseHelper, self).eventFilter(obj, event)


class ComplexWidget(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super(ComplexWidget, self).__init__(parent)

        le_1 = QtWidgets.QLineEdit(text='abcdef', enabled=False)
        btn_le_1 = QtWidgets.QPushButton(text='disable edit', clicked=partial(le_1.setEnabled, False))
        EnableMouseHelper.addWidget(le_1) # <---- register widget

        le_2 = QtWidgets.QLineEdit(text='abcdef', enabled=False)
        btn_le_2 = QtWidgets.QPushButton(text='disable edit', clicked=partial(le_2.setEnabled, False))
        EnableMouseHelper.addWidget(le_2) # <---- register widget

        flay = QtWidgets.QFormLayout(self)
        flay.addRow(le_1, btn_le_1)
        flay.addRow(le_2, btn_le_2)

if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    w = ComplexWidget()
    w.show()
    sys.exit(app.exec_())

更新:没有必要转发事件,足以启用窗口小部件。

from PyQt5 import QtCore, QtGui, QtWidgets

class EnableMouseHelper(QtCore.QObject):
    def eventFilter(self, obj, event):
        if obj.isWidgetType() and event.type() == QtCore.QEvent.MouseButtonPress:
            for w in obj.window().findChildren(QtWidgets.QWidget):
                if not w.isEnabled():
                    w.setEnabled(True)
            obj.setFocus()
        return super(EnableMouseHelper, self).eventFilter(obj, event)

class ComplexInnerWidget(QtWidgets.QWidget):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        layout = QtWidgets.QVBoxLayout(self)
        self.btn1 = QtWidgets.QPushButton('button 1')
        self.btn1.clicked.connect(self._btn1_click)
        layout.addWidget(self.btn1)

        self.btn2 = QtWidgets.QPushButton('button 2')
        self.btn2.clicked.connect(self._btn2_click)
        layout.addWidget(self.btn2)

        self.le = QtWidgets.QLineEdit('abcdef')
        layout.addWidget(self.le)

    def _btn1_click(self, *a):
        print('button 1')

    def _btn2_click(self, *a):
        print('button 2')


class MyWidget(QtWidgets.QWidget):
    def __init__(self, inner_widget: QtWidgets.QWidget, *args, **kwargs):
        super().__init__(*args, **kwargs)
        layout = QtWidgets.QVBoxLayout(self)
        self.inner = inner_widget
        self.inner.setEnabled(False)
        layout.addWidget(self.inner)

if __name__ == '__main__':
    from PyQt5.QtWidgets import QApplication
    app = QApplication([])
    helper = EnableMouseHelper()
    app.installEventFilter(helper)
    inner = ComplexInnerWidget()
    w = MyWidget(inner)
    w.show()
    res = app.exec_()
    exit(res)

0
投票

试试吧:

from PyQt5.QtWidgets import QWidget, QLineEdit, QVBoxLayout, QPushButton, QApplication

class LineEdit(QLineEdit):                                    # +++
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.setText('abcdef')
        self.setStyleSheet('color: blue; font-size: 32px')

    def mousePressEvent(self, event):
        super(LineEdit, self).mousePressEvent(event)
        self.cursor = self.cursorPosition() 

    def mouseReleaseEvent(self, event):
        self.setFocus()
        self.setCursorPosition(self.cursor)  


class MyWidget(QWidget):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        layout    = QVBoxLayout(self)

#        self.edit = QLineEdit('abcdef')
        self.edit = LineEdit()                                  # +++

        self.edit.setEnabled(False)
        layout.addWidget(self.edit)

        self.disable_btn = QPushButton('disable edit')
        self.disable_btn.clicked.connect(self._disable_edit)
        layout.addWidget(self.disable_btn)

    def _disable_edit(self, *a):
        self.edit.setEnabled(False)

    def mousePressEvent(self, a0):
        if not self.edit.isEnabled() and self.edit.underMouse():
            self.edit.setEnabled(True)
            QApplication.instance().sendEvent(self.edit, a0)  # <-- this does seem to work
        super().mousePressEvent(a0)


if __name__ == '__main__':
    from PyQt5.QtWidgets import QApplication
    app = QApplication([])
    w = MyWidget()
    w.show()
    res = app.exec_()
    exit(res)

enter image description here

© www.soinside.com 2019 - 2024. All rights reserved.