PyQt - open only one child window and minimize it with parent window - python

The idea is to open a child window from parent window menu and when I minimize the parent window, the child window must be minimized also and only one child window can be opened.
I have the solution for minimizing the child when parent is minimized, but I can open child window multiple-times (although the child is already opened) and I would like to disable opening of multiple child windows.
The parent window is MainWindow.py:
class MainWindow(QtGui.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setWindowTitle('Parent window')
self.flags = QtCore.Qt.Window
self.ControlPanel = Control_panel_window()
self.createActions()
self.createMenus()
def createActions(self):
# window - menu
self.windowShowControlPanelAction = QtGui.QAction(self.tr("&Control panel"), self, statusTip='Control panel')
self.connect(self.windowShowControlPanelAction, QtCore.SIGNAL("triggered()"), self.ShowControlPanel)
def createMenus(self):
# window
self.windowMenu = QtGui.QMenu(self.tr("&Window"), self)
self.windowMenu.addAction(self.windowShowControlPanelAction)
self.menuBar().addMenu(self.windowMenu)
def ShowControlPanel(self):
self.ControlPanel = Control_panel_window(self)
self.ControlPanel.setWindowFlags(QtCore.Qt.Window)
self.ControlPanel.show()
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
win = MainWindow()
win.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
win.show()
sys.exit(app.exec_())
The child window is ChildWindow.py:
class Control_panel_window(QWidget):
def __init__(self, parent = None):
super(Control_panel_window, self).__init__(parent)
self.setFixedSize(200, 300)
def setWindowFlags(self, flags):
print "flags value in setWindowFlags"
print flags
super(Control_panel_window, self).setWindowFlags(flags)
The problem is: how can I set that only one child window is opened?

In your ShowControlPanel function you are creating a new control panel each time the signal is triggered. Since you already have an instance available why don't you use that instead?
class MainWindow(QtGui.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setWindowTitle('Parent window')
self.flags = QtCore.Qt.Window
self.control_panel = ControlPanelWindow(self)
self.control_panel.setWindowFlags(self.flags)
#...
def create_actions(self):
self.show_control_panel_action = QtGui.QAction(
self.tr("&Control panel"),
self,
statusTip='Control panel'
)
self.show_control_panel_action.triggered.connect(self.show_control_panel)
#...
def show_control_panel(self):
self.control_panel.show()
Some stylistic notes:
Try to follow PEP8 official python coding-style guide. This include using CamelCase for classes, lowercase_with_underscore for almost everything else. In this case, since Qt uses halfCamelCase for methods etc you may use it too for consistency.
Use the new-style signal syntax:
the_object.signal_name.connect(function)
instead of:
self.connect(the_object, QtCore.SIGNAL('signal_name'), function)
not only it reads nicer, but it also provides better debugging information. Using QtCore.SIGNAL you will not receive an error if the signal doesn't exist (e.g. you wrote a typo like trigered() instead of triggered()). The new-style syntax does raise an exception in that case you will be able to correct the mistake earlier, without having to guess why something is not working right and searching the whole codebase for the typo.

Related

Access items and attributes across multiple windows

i have a main GUI-Window from which i open a new Window (FCT-popup) with a buttonclick:
class MainWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.ui = Ui_MainWindow() # sets ui = to the main window from the ui-file
self.ui.setupUi(self)
[...]
def enter_fct_results(self):
self.FCTpopup = FCT_Window()
self.FCTpopup.show()
In the Window i have a QTable to fill and a button to submit the data and close the window:
class FCT_Window(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.ui = Ui_FCT_Window()
self.ui.setupUi(self)
[...]
self.ui.pushButton_submitFCT.clicked.connect(lambda: MainWindow.store_fct_data(MainWindow, self.on_submit()[0]))
def on_submit(self): # event when user clicks
fct_nparray = np.zeros((self.ui.tableFCTinputs.rowCount(), self.ui.tableFCTinputs.columnCount()))
for j in range(self.ui.tableFCTinputs.columnCount()):
for i in range(self.ui.tableFCTinputs.rowCount()):
fct_nparray[i, j] = float(self.ui.tableFCTinputs.item(i, j).text())
return fct_nparray, lambda: self.close()
self.ui.pushButton_submitFCT.clicked.connect(lambda: MainWindow.store_fct_data(MainWindow, self.on_submit()[0]))
The receiving function iin the main window looks like ths:
def store_fct_data(self, data):
self.fct_data = data
Now i just want to understand how i can make either the mainwindow or the pushbutton which opens the 2nd window disabled. Disabling inside enter_fct_results() works, but if i want to enable it again with either store_fct_data or on_submit provides errors like this:
self.ui.pushButton_FCTresults.setEnabled(1)
self.ui.pushButton_submitFCT.clicked.connect(lambda: MainWindow.store_fct_data(MainWindow, self.on_submit()[0]))
AttributeError: type object 'MainWindow' has no attribute 'ui'
I dont think i have understood it here how to deal with multiple windows and stuff. For example how would i change a the color of a button in the main window by using a button in window2. How do i access the widgets? if i am inside the same Window i do that easily by
self.ui.pushbutton.setText("New Text")
I dont get how to access items and attributes across Windows :( Can you help me?
Access to attributes of another instance
There is a fundamental difference between disabling the button of the second window in enter_fct_results and what you tried in the lambda: in the first case, you're accessing an instance attribute (for instance, self.FCTpopup.ui.pushButton), while in the second you're trying to access a class attribute.
The self.ui object is created in the __init__ (when the class instance is created), so the instance will have an ui attribute, not the class:
class Test:
def __init__(self):
self.value = True
test = Test()
print(test.value)
>>> True
print(Test.value)
>>> AttributeError: type object 'Test' has no attribute 'value'
Provide a reference
The simple solution is to create a reference of the instance of the first window for the second:
def enter_fct_results(self):
self.FCTpopup = FCT_Window(self)
self.FCTpopup.show()
class FCT_Window(QMainWindow):
def __init__(self, mainWindow):
QMainWindow.__init__(self)
self.mainWindow = mainWindow
self.ui.pushButton_submitFCT.clicked.connect(self.doSomething)
def doSomething(self):
# ...
self.mainWindow.ui.pushButton.setEnabled(True)
Using modal windows (aka, dialogs)
Whenever a window is required for some temporary interaction (data input, document preview, etc), a dialog is preferred: the main benefit of using dialogs is that they are modal to the parent, preventing interaction on that parent until the dialog is closed; another benefit is that (at least on Qt) they also have a blocking event loop within their exec() function, which will only return as soon as the dialog is closed. Both of these aspects also make unnecessary disabling any button in the parent window. Another important reason is that QMainWindow is not intended for this kind of operation, also because it has features that generally unnecessary for that (toolbars, statusbars, menus, etc).
def enter_fct_results(self):
self.FCTpopup = FCT_Window(self)
self.FCTpopup.exec_()
class FCT_Window(QDialog):
def __init__(self, parent):
QMainWindow.__init__(self, parent)
self.ui.pushButton_submitFCT.clicked.connect(self.doSomething)
def doSomething(self):
# ...
self.accept()
The above makes mandatory to recreate the ui in designer using a QDialog (and not a QMainWindow) instead. You can just create a new one and drag&drop widgets from the original one.
i finally found my mistake: It was the place of the signal connection. It has to be right before the 2nd window is opened:
def enter_fct_results(self):
self.FCTpopup = Dialog(self.fct_data)
self.FCTpopup.submitted.connect(self.store_fct_data)
self.FCTpopup.exec_()
With this now i can send the stored data from the mainwindow to the opened window, import into the table, edit it and send it back to the main window on submit.

What kind of difference does 'self' makes as parameter in PyQt5

In PyQt5, what does self keyword do as a parameter regarding creating Widgets? I don't see any difference between those two and both work totally fine.
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
###############This Part#############
#QLCDNumber() and QSLider() also works fine below
lcd = QLCDNumber(self)
sld = QSlider(Qt.Horizontal, self)
#####################################
vbox = QVBoxLayout()
vbox.addWidget(lcd)
vbox.addWidget(sld)
sld.valueChanged.connect(lcd.display)
self.setLayout(vbox)
self.setGeometry(300, 300, 250, 150)
self.setWindowTitle('Signal and slot')
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
There's no difference.
TL;DR;
QWidget inherit from QObject, and QObjects have a tree of hierarchy between parents and children, in C ++ it serves so that when the parent is deleted it eliminates its children so that memory can be handled easily, in the case of PyQt it happens same since the memory handle is not handled directly by python but by C++.
The previous reason is that it serves to pass the parent parameter to the QObjects, on the other hand in the QWidgets the position of the children is always relative to the parent, so if you pass to self as parent the widget will be drawn on the parent.
Going to your specific code there is no difference because the layouts establish the parent of the widgets that handle the widget where it is established so you can eliminate the initial relationship of kinship since the layout will establish it.
We can see the difference if we do not use the layout since when removing the self nobody will establish where the widget will be drawn and therefore the widgets will not be shown.
Without self:
def initUI(self):
lcd = QLCDNumber()
sld = QSlider(Qt.Horizontal)
With self:
def initUI(self):
lcd = QLCDNumber(self)
sld = QSlider(Qt.Horizontal, self)

How to connect to different classes in pyqt5?

Id like to ask how to connect 2 different classes. I have the following codes in 2 classes (2 classes because i have created 2 different interface. 1 is the QMainwindow and the other 1 is QWidget).
Here's the code:
class MainWindow(QMainWindow, Ui_MainWindow):
def open_inv_search_form(self):
self.window = QtWidgets.QWidget()
self.ui = Ui_Inv_Search()
self.ui.setupUi(self.window)
self.window.show()
MainWindow.setEnabled(False)
class Inv_Search(QWidget, Ui_Inv_Search):
def __init__(self,):
super(Inv_Search, self).__init__()
self.btn_close_search.clicked.connect(self.close_inv_search_form)
def close_inv_search_form(self):
Inv_Search.hide()
MainWindow.setEnabled(True)
The idea is when SEARCH button is clicked in MainWindow, Inv_Search will pop up while MainWindow will be disabled. I have done this part correctly. When CLOSE button is clicked, Inv_Search will be hide and MainWindow will be enabled. However, when CLOSE button is clicked, nothing happened. And there is no error at all.
UPDATE
I was able to successfully do what I wanted. Here's the changes I did. Let me know if this is fine or can be better. Nevertheless, this code works.
class MainWindow(QMainWindow, Ui_MainWindow):
Inv_Search.show() #called to connect to the new window. This is the key since what i previously did only call the ui, but not connect it to the class itself.
MainWindow.setEnabled(False)
class Inv_Search(QWidget, Ui_Inv_Search):
def __init__(self,):
super(Inv_Search, self).__init__()
self.setupUi(self)
self.btn_close_search.clicked.connect(self.close_inv_search_form)
def close_inv_search_form(self):
Inv_Search.close() # I cant figure out how can I use the self but this works fine
MainWindow.setEnabled(True)
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
MainWindow = MainWindow()
Inv_Search = Inv_Search() #added this one
#ui = Ui_MainWindow()
#ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
When you call Inv_Search.hide() and MainWindow.setEnabled(True) in close_inv_search_form, you call the methods on the class itself not on the instance, which is what you actually have to.
from PyQt5.QtCore import qApp
class MainWindow(QMainWindow, Ui_MainWindow):
def open_inv_search_form(self):
self.window = QtWidgets.QWidget()
self.window.ui = Ui_Inv_Search() # You have to set the attributes of the
self.window.ui.setupUi(self.window) # "window" not the instance of MainWindow
self.window.show()
self.setEnabled(False)
class Inv_Search(QWidget, Ui_Inv_Search):
def __init__(self,):
super(Inv_Search, self).__init__()
self.btn_close_search.clicked.connect(self.close_inv_search_form)
def close_inv_search_form(self):
self.hide() # Use the method "hide" on the instance
qApp.setEnabled(True) # Use the method "setEnabled" on the instance
if __name__ == "__main__" :
app = QApplication()
main = MainWindow() # This is the instance that can be referred to by qApp
main.exec_()
I am assuming that there is more code and a .ui file somewhere. It looks like this line
Inv_Search.hide()
should be changed to
self.hide()
Also, I think that because you need to call the method on the instance, not the class.
self.ui = Ui_Inv_Search()
should probably be
self.ui = Inv_Search()
You are doing a similar thing with MainWindow. It's a little more difficult here, because you will need to have an instance of MainWindow stored somewhere accessible. Although you may be able to access the MainWindow instance through QtWidget.parentWidget, in Python I prefer to just pass the instance to the constructor. So
def __init__(self, mainWindow):
self.mainWindow = mainWindow
# ... all the other stuff too
as your Inv_Search constructor, and
self.ui = Inv_Search(self)
# notice the new ^ argument
in your MainWindow constructor. And then
self.mainWindow.setEnabled(True)
in your class method. Also, your argument signature is wrong for the clicked signal. Use
def close_inv_search_form(self, checked=None):
# Need this ^ argument, even if you don't use it.
In reality, it seems like the functionality you are trying to accomplish is best suited for a modal dialog, such as that provided by QDialog, which will natively handle many of the effect I think you are looking for.

pyqt dialog not showing a second time

I have a pyqt app where I want a dialog to display when I click a menu item. If the dialog loses focus and the menu item is clicked again, it brings the dialog to the front. This is working fine so far.
The problem is that when the dialog is opened and then closed, clicking the menu item doesnt create/display a new dialog. I think I know why, but can't figure out a solution
Heres the code:
from ui import mainWindow, aboutDialog
class ReadingList(QtGui.QMainWindow, mainWindow.Ui_MainWindow):
def __init__(self):
super(self.__class__, self).__init__()
self.setupUi(self)
self.about = None
self.actionAbout.triggered.connect(self.showAbout)
def showAbout(self):
# If the about dialog does not exist, create one
if self.about is None:
self.about = AboutDialog(self)
self.about.show()
# If about dialog exists, bring it to the front
else:
self.about.activateWindow()
self.about.raise_()
class AboutDialog(QtGui.QDialog, aboutDialog.Ui_Dialog):
def __init__(self, parent=None):
super(self.__class__, self).__init__()
self.setupUi(self)
def main():
app = QtGui.QApplication(sys.argv)
readingList = ReadingList()
readingList.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
The problem lies in the fact that when the dialog is created the first time, self.about is no longer None. This is good because the conditional in showAbout() allows me to bring the dialog to the front instead of creating a new one (the else condition)
However, when the dialog is closed, self.about is no longer None due to the previous dialog creation, which means it doesn't create a new one and just jumps to the else condition
How can I make it so that dialogs can be created after the first?
I thought about overriding the closeEvent method in the AboutDialog class but I'm not sure how to get a reference to readingList to send a message back saying the dialog has been closed. Or maybe I'm overthinking it, maybe the return from self.about.show() can be used somehow?
(I know I can probably avoid all of this using modal dialogs but want to try to figure this out)
There are probably several ways to do this, but here's one possibility:
class ReadingList(QtGui.QMainWindow, mainWindow.Ui_MainWindow):
def __init__(self):
super(ReadingList, self).__init__()
self.setupUi(self)
self.actionAbout.triggered.connect(self.showAbout)
self.about = None
def showAbout(self):
if self.about is None:
self.about = AboutDialog(self)
self.about.show()
self.about.finished.connect(
lambda: setattr(self, 'about', None))
else:
self.about.activateWindow()
self.about.raise_()
class AboutDialog(QtGui.QDialog):
def __init__(self, parent=None):
super(AboutDialog, self).__init__(parent)
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
(NB: you should never use self.__class__ with super as it can lead to an infinite recursion under certain circumstances. Always pass in the subclass as the first argument - unless you're using Python 3, in which case you can omit all the arguments).

Proper parenting of QWidgets generated from a QThread to be inserted into a QTreeWidget

I have a QTreeWidget which needs to be populated with a large sum of information. So that I can style it and set it up the way I really wanted, I decided I'd create a QWidget which was styled and dressed all pretty-like. I would then populate the TreeWidget with generic TreeWidgetItems and then use setItemWidget to stick the custom QWidgets in the tree. This works when the QWidgets are called inside the main PyQt thread, but since there is a vast sum of information, I'd like to create and populate the QWidgets in the thread, and then emit them later on to be added in the main thread once they're all filled out. However, when I do this, the QWidgets appear not to be getting their parents set properly as they all open in their own little window. Below is some sample code recreating this issue:
from PyQt4.QtGui import *
from PyQt4.QtCore import *
class ItemWidget(QWidget):
def __init__(self, parent=None):
QWidget.__init__(self, parent)
btn = QPushButton(self)
class populateWidgets(QThread):
def __init__(self):
QThread.__init__(self)
def run(self):
widget = ItemWidget()
for x in range(5):
self.emit(SIGNAL("widget"), widget)
class MyMainWindow(QMainWindow):
def __init__(self, parent=None):
QMainWindow.__init__(self, parent)
self.tree = QTreeWidget(self)
self.tree.setColumnCount(2)
self.setCentralWidget(self.tree)
self.pop = populateWidgets()
self.connect(self.pop, SIGNAL("widget"), self.addItems)
self.pop.start()
itemWidget = QTreeWidgetItem()
itemWidget.setText(0, "This Works")
self.tree.addTopLevelItem(itemWidget)
self.tree.setItemWidget(itemWidget, 1, ItemWidget(self))
def addItems(self, widget):
itemWidget = QTreeWidgetItem()
itemWidget.setText(0, "These Do Not")
self.tree.addTopLevelItem(itemWidget)
self.tree.setItemWidget(itemWidget, 1, widget)
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
ui = MyMainWindow()
ui.show()
sys.exit(app.exec_())
As you can see, doing it inside MyMainWindow is fine, but somehow things go awry once it gets processed in the thread and returns. Is this possible to do? If so, how do I properly parent the ItemWidget class inside the QTreeWidgetItem? Thanks in advance.
AFAICT Qt doesn't support the creation of QWidgets in a thread other than the thread where the QApplication object was instantiated (i.e. usually the main() thread). Here are some posts on the subject with responses from Qt developers:
http://www.qtcentre.org/archive/index.php/t-27012.html
http://www.archivum.info/qt-interest#trolltech.com/2009-07/00506/Re-(Qt-interest)-QObject-moveToThread-Widgets-cannot-be-moved-to-a-new-thread.html
http://www.archivum.info/qt-interest#trolltech.com/2009-07/00055/Re-(Qt-interest)-QObject-moveToThread-Widgets-cannot-be-moved-to-a-new-thread.html
http://www.archivum.info/qt-interest#trolltech.com/2009-07/00712/Re-(Qt-interest)-QObject-moveToThread-Widgets-cannot-be-moved-toa-new-thread.html
(if it were possible, the way to do it would be to call moveToThread() on the QWidgets from within the main thread to move them to the main thread -- but apparently that technique doesn't work reliably, to the extent that QtCore has a check for people trying to do that prints a warning to stdout to tell them not to do it)

Categories

Resources