"I want to delete and reinstantiate a QWidget." I am writing a GUI in PyQt4 and for that I write classes which inherit from QGridLayout. These would look like this:
from PyQt4 import QtGui
class StartTab(QtGui.QGridLayout):
def __init__(self, mainWindow, tab):
super(StartTab, self).__init__(tab)
< body (whatever this tab needs) >
Here mainWindow is somewhat the framing window of the GUI to which the menu bar belongs and is instatiated when the program starts. So things like "File -> New Project" will be handled in this class. When the program starts, the tabs will be created in the __init__ of mainWindow, so like:
from PyQt4 import QtGui, QtCore
from start_tab import StartTab
class MainWindow(QtGui.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
< some variables >
self.tabWidget = QtGui.QTabWidget(self)
self.tab1 = QtGui.QWidget()
self.startTab = StartTab(self, self.tab1)
self.tabWidget.addTab(self.tab1, 'Start Tab')
< some other tabs >
In mainWindow I also have a function newProject() which is called when the user hits the above mentioned "File -> New Project" button. There is my problem. I tried reinitializing the tabs in the newProject() function so the old tabs will be deleted and new ones will be created when the user wants to start a new project from a running program. I don't know how though. What I tried is somewhat to just copy the line from the __init__ of mainWindow:
def newProject(self) :
self.startTab = StartTab(self, self.tab1)
but this leaves me with an error message:
QLayout: Attempting to add QLayout "" to QWidget "", which already has
a layout
I know I probably haven't understood the concept of this whole Qt thing yet, but I would much appreciate some help. Thanks!
Related
When I set focus on a widget and then check whether it has focus, the result is False instead of True. When the application loads, however, the desired widget has focus.
from PySide2 import QtCore, QtWidgets, QtGui
app = QtWidgets.QApplication([])
widget = QtWidgets.QWidget()
button = QtWidgets.QPushButton()
line_edit = QtWidgets.QLineEdit()
layout = QtWidgets.QVBoxLayout()
layout.addWidget(button)
layout.addWidget(line_edit)
widget.setLayout(layout)
widget.show()
print(f"{line_edit.hasFocus()}") # False
line_edit.setFocus(QtCore.Qt.OtherFocusReason) # without this, nothing has focus
print(f"{line_edit.hasFocus()}") # Expected True, actually False!
app.exec_()
How can I set focus in such a way that I could test it?
EDIT: I'm running these versions:
>>> import PySide2
>>> from PySide2 import QtCore, QtWidgets, QtGui
>>> print("Qt: ", PySide2.QtCore.__version__, " PySide2: ", PySide2.__version__)
Qt: 5.15.2 PySide2: 5.15.2
EDIT2: If I create a shortcut which prints the focus status, the result is True. I suspect that is because the app is executing. It still stands, how could I test this?
QtWidgets.QShortcut(QtGui.QKeySequence("F6"), widget, lambda: print(f"{line_edit.hasFocus()}"))
In order to test whether a widget has focus, the QCoreApplication event loop must be running and the widget must be visible. Focus is the result of an event and it only makes sense to talk about focus if the widget is showing (Qt seems to require the widget be visible for a focus event to occur). This presents a problem for running tests without user interaction: how do we automatically exit the main event loop? The answer is in the question; by using events.
QCoreApplication.exec_() starts the main event loop. Events are put on a queue and processed in turn (according to a priority). The loop goes on forever until it receives an event telling it to stop. Use postEvent to add an event to the queue. There are different handlers for different events. The QWidget.focusInEvent method is called when the widget takes focus. Other events pass through the general QObject.eventFilter method.
We can install a custom event filter on a QObject using its installEventFilter method. This method takes a QObject with a custom eventFilter:
class CustomEventFilterObject(QObject):
def eventFilter(self, obj, event):
if event.type() == QEvent.User:
print("Handle QEvent types")
return True
else:
# standard event processing
return QObject.eventFilter(self, obj, event)
# ...
custom_event_filter = CustomEventFilterObject()
widget.installEventFilter(custom_event_filter)
Armed with this knowledge, we can contrive a solution (and is it ever contrived!).
Here is the original question, modified so that the show method is no longer called from this module:
# focus_problem.py
from PySide2 import QtCore, QtWidgets, QtGui
widget = QtWidgets.QWidget()
button = QtWidgets.QPushButton()
line_edit = QtWidgets.QLineEdit()
layout = QtWidgets.QVBoxLayout()
layout.addWidget(button)
layout.addWidget(line_edit)
widget.setLayout(layout)
# line_edit.setFocus(QtCore.Qt.OtherFocusReason) # without this, nothing has focus
We know that the line_edit focus is only testable after app.exec_() has started the event loop. Create a custom focusInEvent function and attach it to the line_edit. This will get called if the line_edit takes focus. If that happens, exit the application with a return value indicating success. In case focus never lands on the line_edit, create a custom eventFilter on the widget. Post an event that closes the application with a return value indicating failure. Make sure this event is posted after the focus is set. Since focus is set on the line_edit when the module is imported, this won't be an issue for us.
Using unittest, the test looks like:
# test_focus_problem.py
#
# Run with:
#
# python3 -m unittest discover focus_problem/ --failfast --quiet
import unittest
from PySide2 import QtCore, QtWidgets, QtGui, QtTest
# need a QApplication in order to define the widgets in focus_problem
# module
if not QtWidgets.QApplication.instance():
QtWidgets.QApplication([])
import focus_problem
class TestFocusEvent(unittest.TestCase):
# called before the body of test_application_starts_with_line_edit_in_focus is executed
def setUp(self):
def focusInEvent(event):
QtWidgets.QApplication.instance().exit(0) # Success
# override the focus event handler
focus_problem.line_edit.focusInEvent = focusInEvent
class KillApplicationIfLineEditNeverTakesFocus(QtCore.QObject):
def eventFilter(self, obj, event):
if event.type() == QtCore.QEvent.User:
QtWidgets.QApplication.instance().exit(1) # Fail
return True
else:
return QtCore.QObject.eventFilter(self, obj, event)
custom_filter = KillApplicationIfLineEditNeverTakesFocus()
focus_problem.widget.installEventFilter(custom_filter)
focus_problem.widget.show()
QtWidgets.QApplication.instance().postEvent(focus_problem.widget, QtCore.QEvent(QtCore.QEvent.User))
self.rv = QtWidgets.QApplication.instance().exec_()
def test_application_starts_with_line_edit_in_focus(self):
self.assertEqual(self.rv, 0)
The application is started using setUp on a dedicated test class. This allows test_application_starts_with_line_edit_in_focus to collect the return value and report it along with other tests. It checks to see if the application was killed by the focusInEvent (a success) or the eventFilter (a failure).
I'd be surprised if there wasn't an easier way to do all this. :)
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.
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?
It turns out that the event filter idea is the way to go after all. What I did was first create an event filter class that emits a signal if the sending object is a QLineEdit object:
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)
This filter is installed for a custom QApplication class so that any event that is created passes the filter. The signal focusIn is connected to a setter function that remembers the last input widget that received focus:
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 is used instead of QApplication in the first line of main(). Now, the call to determineWhichWidgetHadLastFocus() in HelperWidget.insertString() can be replaced by QApplication.instance().inputFocusWidget(), and everything works as intended.
This is a class which form i made in qt5 designer. The slot is called twice when I click the button.
class CustomerList(QWidget, Ui_CustomerList):
def __init__(self, parent=None):
QWidget.__init__(self, parent)
self.setupUi(self)
self.buttX.clicked.connect(self.on_buttX_clicked)
#pyqtSlot()
def on_buttX_clicked(self):
print("on_buttX_clicked")
if __name__ == '__main__':
app = QApplication(sys.argv)
w = CustomerList()
w.show()
sys.exit(app.exec_())
What am I missig here?
Your button is called buttX in designer, so the "Auto connect by name" feature in setupUi() finds a matching slot.
You can either
remove the explicit connect
rename the button
rename the slot
I would personally go for the latter, i.e. use a slot name that does not have the pattern the "auto name connect" is looking for.
E.g. onButtXClicked
How to have all attributes and Qt views of an object updated when reloading the python module that defines this object, without breaking or even touching the links between this object and other objects?
Here is an exemple:
The QMainWindow of a Qt application includes both local widgets and a reloadable widget:
from PyQt4 import QtCore, QtGui
import reloadable
name='Test reloadable'
class MyWindow(QtGui.QMainWindow):
def __init__(self):
# Initialize the main window
super(MyWindow, self).__init__()
self.setWindowTitle(name)
# Create an instance of the reloadable widget
self.reloadableWidget= reloadable.ReloadabelWidget()
# Create a local widget that can ask for and display an attribute of the reloadable widget
button1 = QtGui.QPushButton('Identify')
button1.clicked.connect(self.identifyReloadable)
self.label1=QtGui.QLabel('')
layout1 = QtGui.QHBoxLayout()
layout1.addWidget(button1)
layout1.addWidget(self.label1)
localWidget=QtGui.QWidget()
localWidget.setLayout(layout1)
# Put the local widget and the reloadable widget in the window
centralWidget=QtGui.QWidget()
layout = QtGui.QVBoxLayout()
layout.addWidget(localWidget)
layout.addWidget(self.reloadableWidget)
centralWidget.setLayout(layout)
self.setCentralWidget(centralWidget)
def identifyReloadable(self):
identity=self.reloadableWidget.identify()
self.label1.setText(identity)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
app.setApplicationName(name)
main = MyWindow()
main.show()
sys.exit(app.exec_())
This reloadable widget (QFrame) has a method 'identify()' called by main and a method 'reload()' called by itself. This reload method essentially reloads the class module, replace the class attribute by the possibly updated module.class, and tries to update the Qt views of the object.
# This is module reloadble.py
from PyQt4 import QtCore, QtGui
import inspect
identity='1'
color='blue'
class ReloadabelWidget(QtGui.QFrame,object):
def __init__(self):
QtGui.QFrame.__init__(self)
self.setFrameStyle(1)
self.reinit()
def reinit(self):
button = QtGui.QPushButton('Reload',self)
button.clicked.connect(self.reload)
label=QtGui.QLabel(identity)
label.setStyleSheet("QLabel { background-color : white; color : %s; }" % color);
layout = QtGui.QHBoxLayout()
layout.addWidget(button)
layout.addWidget(label)
self.setLayout(layout)
self.setUpdatesEnabled(True)
def identify(self):
return identity # will change each time the file is edited with a new identity in line 3
def reload(self):
module=inspect.getmodule(self)
reload(module)
self.reinit() # object is reinitialized
self.__class__= module.ReloadabelWidget # callable attributes are now up to date
self.update() # naive attempt to update the Qt view => does not work
# add code here to have the GUI part of self, i.e. the QFrame updated ...
# ... whereever it is displayed.
The main application is running and the programmer redefines the method identify() and the color of a label by editing and saving the code of the reloadable module.
identity='2'
color='red'
The reload button is then clicked to run the reload() method of the reloadable widget. Its methods are properly updated and clicking the identify button displays the updated identity 2. But the widget's view is not updated because I don't know how to do it. What are the lines to be added at the end of the reload method to obtain the redisplay of the new interface, created by self.reinit()
?
PS1: For those who like to know what is the goal behind a question, I want to accelerate the development of a complicated multi-module application by having both the attributes and the gui of a module updated at code edition, without restarting the application.
PS2: This question replaces a bad one that I had asked without being sufficiently explicit. Since there was already several answers, I make this new post. I now provide a complete code entirely tested and make my point clearly.
How can I make a popup window that appears before the mainwindow begins? I want the popup to have several QLineEdit widgets to receive input that I will need for the mainwindow. I searched for solutions, but I could not understand most of the examples I found. Can some one help me?
Just make a subclass of QDialog, execute it modally before running your normal startup logic.
Thats how I did it for an app that required a login, worked just fine. This would be the general idea in Python (it takes me less time to think about it in PyQt):
import sys
from PyQt4 import QtGui, QtCore
from mymodule import MyDialog, MyWindow
def main(argv):
app = QtGui.QApplication(argv)
# make a dialog that runs in its own event loop
dlg = MyDialog()
if ( not dlg.exec_() ): # in C++, this would be dlg->exec()
sys.exit(0)
var1, var2, var3 = dlg.values()
window = MyWindow()
window.setPropertyOne(var1)
window.setPropertyTwo(var2)
window.setPropertyThree(var3)
window.show()
sys.exit(app.exec_())
if ( __name__ == '__main__' ):
main(sys.argv)