Q绝对位置仍然相对于父对象移动的工具提示



我正在从下拉菜单的(子类(QLineEdit和QListWidget构建一个自定义组合框

我将窗口标志设置为QTool,这样它就是一个浮动窗口,但不会从lineedit中窃取焦点(因为用户需要能够输入文本来过滤列表(。这很好,但列表现在已经完全脱离了父窗口小部件,所以我可以拖动顶部菜单栏并将其从我不想要的列表中移开。

有没有一种方法可以使用QTool或QTooltip,但将其作为小部件的父级

另一种方法是将窗口标志设置为QPopup,在这种情况下,单击顶部菜单栏时弹出窗口将关闭,因此无法拖动。然而,使用QPopup,它会从行编辑中窃取焦点

下面是一个简单的例子来说明这个问题:

from PySide2 import QtCore, QtWidgets, QtGui
import sys
class LineEditClickable(QtWidgets.QLineEdit):
"""Custom QLineEdit to detect clicked, focus and key events
Signals: clicked, focusOut, arrowUp, arrowDown
"""
clicked = QtCore.Signal(QtGui.QMouseEvent)
def __init__(self, value=''):
super(LineEditClickable, self).__init__(value)
# remove border on Mac
self.setAttribute(QtCore.Qt.WA_MacShowFocusRect, 0)
self.setFocusPolicy(QtCore.Qt.ClickFocus)
def mousePressEvent(self, event):
"""Emit clicked signal"""
self.clicked.emit(event)
super(LineEditClickable, self).mousePressEvent(event)
class popup(QtWidgets.QWidget):
def __init__(self, parent = None, widget=None):    
QtWidgets.QWidget.__init__(self, parent)
layout = QtWidgets.QVBoxLayout(self)
self.list = QtWidgets.QListWidget()
layout.addWidget(self.list)
# adjust the margins or you will get an invisible, unintended border
layout.setContentsMargins(0, 0, 0, 0)
self.adjustSize()
# tag this widget as a popup
self.setWindowFlags(QtCore.Qt.FramelessWindowHint | QtCore.Qt.Tool)
# self.setWindowFlags(QtCore.Qt.Popup)
def update(self, widget):
# calculate the botoom right point from the parents rectangle
point        = widget.rect().bottomRight()
# map that point as a global position
global_point = widget.mapToGlobal(point)
# by default, a widget will be placed from its top-left corner, so
# we need to move it to the left based on the widgets width
self.move(global_point - QtCore.QPoint(self.width(), 0))
def show_popup(self, widget):
self.update(widget)
self.show()
class Window(QtWidgets.QWidget):
def __init__(self):
QtWidgets.QWidget.__init__(self)
self.le = LineEditClickable(self)
self.le.clicked.connect(self.handleOpenDialog)
self.le.move(250, 50)
self.resize(600, 200)
self.popup = popup(self, self.le)
self.popup.list.addItems(['one','two','three'])
def handleOpenDialog(self):
self.popup.show_popup(self.le)
self.popup.show()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
win = Window()
win.show()
sys.exit(app.exec_())```

问题的基本答案是使用正确的标志和焦点选项。

如果你看看QCompleterimplementssetPopup()是如何完成的,你会看到以下内容:

popup->setWindowFlag(Qt::Popup);
popup->setFocusPolicy(Qt::NoFocus);
[...]
popup->setFocusProxy(d->widget);

正如您已经经历过的那样,Tool不是一个好的选择:虽然可以避免从行编辑中窃取焦点,但它也会遇到在UI之外单击鼠标的问题。

如果你仍然想使用Tool可以通过在行编辑的顶层窗口上安装事件过滤器来更新小部件的位置,并拦截其移动事件,但不能保证它能工作,完全取决于你使用的平台。例如,在某些Linux窗口管理器上,只有当鼠标在拖动窗口后释放时,才会收到它。

class popup(QtWidgets.QWidget):
_widget = None
_window = None
# ...
def show_popup(self, widget):
if self._window:
self._window.removeEventFilter(self)
self.update(widget)
self.show()
self._widget = widget
self._window = widget.window()
self._window.installEventFilter(self)
def hideEvent(self, event):
if self._window:
self._window.removeEventFilter(self)
def closeEvent(self, event):
if self._window:
self._window.removeEventFilter(self)
def eventFilter(self, source, event):
if event.type() == QtCore.QEvent.Move:
self.update(self._widget)
return super().eventFilter(source, event)

坦率地说,我建议你使用Qt已经为你提供的东西,而不是试图重新发明轮子。在您的情况下,使用QComplete并重新实现弹出窗口所需的内容。

请注意,如果您想在行编辑获得焦点并且还没有文本时显示所有项目,您可以更改完成模式。

class LineEdit(QtWidgets.QLineEdit):
def __init__(self, *args, **kwargs):
# ...
self.textChanged.connect(self.showCompleter)
def showCompleter(self):
completer = self.completer()
if not completer:
return
if not self.text():
completer.setCompletionMode(completer.UnfilteredPopupCompletion)
else:
completer.setCompletionMode(completer.PopupCompletion)
completer.complete()

您可能还想在keyPressEvent覆盖中执行同样的操作,调用基类实现并确保弹出窗口还不可见之后。

最新更新