【问题标题】:QTooltip with absolute position that still moves relative to parent具有绝对位置的 QTooltip 仍然相对于父级移动
【发布时间】:2020-08-10 15:43:22
【问题描述】:

我正在从下拉菜单的(子类)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_())```

【问题讨论】:

  • 明确一点,你为什么要使用这个类?除了关于焦点的问题之外,您正在寻找什么特殊行为并且 QComboBox 没有?
  • 我开始自定义 QComboBox 如此之多,因为这样做更容易。我正在构建一个模拟这种设计的小部件:material.angular.io/components/autocomplete/examples(带有动画)
  • @MichaelB 根据链接和您所描述的内容,您不需要 QComboBox,而是需要 QCompleter。请使用@username
  • @eyllanesc 我已经实现了自己的逻辑,而不是使用 QCompleter,这一切都很好。我唯一的问题是将我的列表小部件固定在父小部件上,而不会从行编辑中窃取焦点。我已经描述了我尝试过的两个标志(Popup 和 Tool)(我实际上已经尝试过所有这些标志)以及它们各自的问题
  • @MichaelB 除了动画和“顶部标签”之外,您需要与 QCompleter 弹出窗口的默认行为有什么区别?当行编辑获得焦点并且还没有文本时,您想显示所有元素吗?

标签: python pyside2


【解决方案1】:

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

如果您查看 QCompleter implements setPopup() 的方式,您会看到以下内容:

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 已经为您提供的功能,而无需重新发明轮子。在您的情况下,使用 QCompleter 并重新实现弹出窗口所需的内容。

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

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 覆盖中执行相同的操作,在调用基类实现并确保弹出窗口尚不可见。

【讨论】:

    猜你喜欢
    • 2021-12-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-07-19
    • 1970-01-01
    相关资源
    最近更新 更多