将事件重新发送到新启用的子部件
re-send an event to a newly enabled child widget
注意:下面是一个更完整的例子的编辑
我想在 Qt 中实现以下内容(特别是 PyQt,但我相信解决方案在 python 和 C++ 中都是相似的):
我想要一个小部件有一个默认禁用的内部小部件,当点击时,小部件将被启用,鼠标按下将传播到它。例如,在下面 window/widget:
如果我在 c
和 d
之间单击,我希望 QLineEdit
启用,获得焦点,并且光标位于 [=13= 之间] 和 d
。我已经重新启用 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)
试一试:
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)
您不能发送事件对象,因为 Qt 会在小部件使用它时将其删除,您必须做的是使用相同的数据创建另一个事件。我创建了一个 class,它允许您注册小部件来为您提供此 属性,而无需覆盖 class。
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_())
更新:活动不需要转发,启用widget即可。
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)
注意:下面是一个更完整的例子的编辑
我想在 Qt 中实现以下内容(特别是 PyQt,但我相信解决方案在 python 和 C++ 中都是相似的):
我想要一个小部件有一个默认禁用的内部小部件,当点击时,小部件将被启用,鼠标按下将传播到它。例如,在下面 window/widget:
如果我在 c
和 d
之间单击,我希望 QLineEdit
启用,获得焦点,并且光标位于 [=13= 之间] 和 d
。我已经重新启用 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)
试一试:
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)
您不能发送事件对象,因为 Qt 会在小部件使用它时将其删除,您必须做的是使用相同的数据创建另一个事件。我创建了一个 class,它允许您注册小部件来为您提供此 属性,而无需覆盖 class。
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_())
更新:活动不需要转发,启用widget即可。
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)