Determine QWidget that had last focus before button press

时间:2017-10-12 09:57:08

标签: qt focus qdialog

I want to implement the following functionality into my Qt application:

  • User opens one or more 'input' widgets (instances of an InputWidget class), each containing a QLineEdit widget
  • User opens a 'helper' dialog
  • User selects a value in the 'helper' dialog
  • User presses the 'Insert' QPushButton in the 'helper' dialog
  • The selected value from the 'helper' dialog' is inserted into the QLineEdit of that 'input' dialog that had the last focus before the 'Insert' button was pressed

So, basically, what I want is that if the user clicks on 'Insert' in the following screenshot, the string 'Apple' should appear in the focused input dialog. The code example below does kind of work, only that the string is (usually, see below) inserted into the second one.

enter image description here

Here's is the code example that creates this setup:

from PyQt5.QtWidgets import (QApplication, QWidget, QHBoxLayout,
                             QLineEdit, QLabel, QPushButton, QComboBox)
import sys


# this is the missing bit
def determineWhichWidgetHadLastFocus():
    for widget in QApplication.instance().topLevelWidgets():
        if isinstance(widget, InputWidget):
            # do something wonderful to determine whether this widget 
            # is the one that had last focus 
            wonderful = True

        if wonderful: 
            return widget
    return None


class BaseWidget(QWidget):
    """ Base widget type """

    def __init__(self, name):
        super(BaseWidget, self).__init__()
        self.setWindowTitle(name)
        self.setupUi()
        self.show()

    def setupUi(self):
        pass


class InputWidget(BaseWidget):
    """ InputWidget contains a QLabel and a QLineEdit widget """

    def setupUi(self):
        self.label = QLabel("Input string:")
        self.edit = QLineEdit()

        layout = QHBoxLayout(self)
        layout.addWidget(self.label)
        layout.addWidget(self.edit)


class HelperWidget(BaseWidget):
    """ HelperWidget contains a QLineEdit and a QPushButton widget. Pressing 
    the button inserts the content of the edit widget into the edit widget of 
    the last activated InputWidget """ 

    def setupUi(self):
        self.combo = QComboBox()
        self.combo.addItems(["Apple", "Pear", "Banana"])
        self.button = QPushButton("Insert")
        self.button.clicked.connect(self.insertString)

        layout = QHBoxLayout(self)
        layout.addWidget(self.combo)
        layout.addWidget(self.button)

    def insertString(self):
        widget = determineWhichWidgetHadLastFocus()
        if widget:
            widget.edit.insert(self.combo.currentText())

def main():
    app = QApplication(sys.argv)

    diag1 = InputWidget("Input dialog")
    diag2 = InputWidget("Another input")

    helper = HelperWidget("Helper")

    app.exec_()


if __name__ == "__main__":
    main()

The missing part is the determineWhichWidgetHadLastFocus() function.

This function is supposed to do something wonderful that allows it to determine which 'input' was the last to hold focus. Currently, it traverses the list of top level widgets from QApplication, but the order of top level widgets doesn't reflect the activation order (it usually, but not always appears to be the order of creation).

One idea that came to my mind was to install an event filter that keeps track of the FocusIn events. That would be easy for the InputWidget class in my example, but might not work so well for my real application that has many QLineEdits, QTextEdits and descended classes all over the place. I'd rather not go that way.

Any other ideas?

1 个答案:

答案 0 :(得分:0)

事实证明,事件过滤器的想法是最重要的。我所做的是首先创建一个事件过滤器类,如果发送对象是QLineEdit对象,则发出一个信号:

from PyQt5.QtCore import QObject, QEvent, pyqtSignal


class inputFocusFilter(QObject):
    focusIn = pyqtSignal(object)

    def eventFilter(self, widget, event):
        if event.type() == QEvent.FocusIn and isinstance(widget, QLineEdit):
            # emit a `focusIn` signal, with the widget as its argument:
            self.focusIn.emit(widget)
        return super(inputFocusFilter, self).eventFilter(widget, event)

为自定义QApplication类安装此过滤器,以便创建的任何事件都通过过滤器。信号focusIn连接到一个setter函数,该函数会记住收到焦点的最后一个输入窗口小部件:

class MyApplication(QApplication):
    def __init__(self, *arg, **kwarg):
        super(MyApplication, self).__init__(*arg, **kwarg)

        self._input_focus_widget = None

        self.event_filter = inputFocusFilter()
        self.event_filter.focusIn.connect(self.setInputFocusWidget)
        self.installEventFilter(self.event_filter)

    def setInputFocusWidget(self, widget):
        self._input_focus_widget = widget

    def inputFocusWidget(self):
        return self._input_focus_widget
MyApplication的第一行中使用

QApplication代替main()。现在,determineWhichWidgetHadLastFocus()中对HelperWidget.insertString()的调用可以替换为QApplication.instance().inputFocusWidget(),一切都按预期运行。