Opening a QDialog and saving last state - python

I am trying to open a QDialog from a QMainWindow, and after closing the `QDialog, if I need to open it again, it has to open and show the same information that had when I close it.
Here is the code of the QMainWindow:
class A (QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
#I create a QPushButton to open the QDialog
self.axes1 = self.figure_canvas.figure.add_axes ([0.8, 0.01, 0.19, 0.05])
self.button = QPushButton(self.axes1,"Open Dialog")
self.button.on_clicked(self.OpenDialog)
#This is the method to open the QDialog which is in another module
def OpenDialog(self, event):
text = configurePort.ConfigurePort.retrieve_data(self)
print text
What this code does is create a button in my QMainWindow and when I click it, it opens a QDialog, which is created in another module. And this is the code of the QDialog:
class ConfigurePort(QDialog):
def __init__(self, parent = None):
QDialog.__init__(self, parent)
uic.loadUi("configurePort.ui", self)
#I create a button to check active ports and show them
self.connect(self.btn_checkconn, SIGNAL("clicked()"), self.check_ports)
#This method calls another class which opens another QDialog
#and I select the port that I want
def check_ports(self):
self.check_serial = CheckPorts(self)
self.check_serial.exec_()
#After selecting the port, when I close the QDialog of the class named above
#the port´s name appears in the first QDialog
#classmethod
def retrieve_data(cls, parent = None):
dlg = cls(parent)
dlg.exec_()
text = dlg.getPortText()
return text
def closeEvent(self, event):
#Here is where I need to write the code to close the QDialog
#and it does not has to be an event
In the method, closeEvent, I need to write the necessary code, so I can close the window, and using the same button that I use to open it, open it again with the last information that it showed when I closed it.
I have tried to use QSettings but it did not worked (maybe I used it wrong). And I tried the show() and hide() classes of PyQt too, but it did not work. Hope you can help me.
----- EDIT -----
I edited the code of above. and I added some methods for a better understanding. So, i open the QDialog called ConfigurePort and it shows this:
The red circle, surrounds the port´s name. It is shown in a QLabel,and I take this text from the QDialog and then print it when I close the QDialog. I acomplish this thanks to a question I asked before, wich is in this link:
Getting data from child using PyQt
The check_port method shown in the code above, opens another QDialog that works great. With this I can select the ports that I need in my pc. So, this does not matter.
So, after closing the QDialog(and selecting for example "COM3", as you can see in the picture), I need to open it again, and see the same information that was shown before I closed it.
I tried to add this lines, using QSettings :
self.settings = QSettings("MyCompany", "MyApp")
if not self.settings.value("windowsState") == None:
self.restoreState(self.settings.value("windowState"))
But as I said before, I think that I did not use it right, but I hope that I solve this using something simpler.
----- EDIT 2 -----
Thank to the help of #Brendan Abel I have this code:
class ConfigurePort(QDialog):
def __init__(self, parent):
super(ConfigurePort, self).__init__(parent)
uic.loadUi("configurePort.ui", self)
self.myValue = 10
self.restoreSettings()
self.connect(self.btn_checkconn, SIGNAL("clicked()"), self.check_ports)
self.buttonBox.button(QDialogButtonBox.Cancel).clicked.connect(self.close)
self.buttonBox.button(QDialogButtonBox.Ok).clicked.connect(self.closeEvent)
self.iniUi()
def check_ports(self):
pass
def iniUi(self):
pass #I just create some QLabels in here
#classmethod
def retrieve_data(cls, parent = None):
dlg = cls(parent)
dlg.exec_()
text = dlg.getPortText()
return text
def closeEvent(self, event):
self.saveSettings()
super(QDialog,self).closeEvent(event)
def saveSettings(self):
settings = QSettings("MyOrg", "MyApp")
settings.setValue("myValue", self.myValue)
def restoreSettings(self):
settings = QSettings("MyOrg", "MyApp")
self.myValue = settings.value("myValue", self.myValue)
This gives me this error: TypeError: QWidget.closeEvent(QCloseEvent): argument 1 has unexpected type 'bool'
I know that I am missing something, but I can not see it.

There are a couple ways you could persist this data Generally, to persist data across sessions, you use QSettings and load the data in the __init__ and save it in the closeEvent method
Generally it looks something like this. This also assumes your using the v2 version of the QVariant api; otherwise, the results returned from QSettings.value is going to be a QVariant and you'll need to cast it to the appropriate python type. If you're using a recent version of PyQt then you should be on v2, but if not you can force it by sticking this at the top of your file
import sip
sip.setapi('QVariant', 2)
sip.setapi('QString', 2)
class MyDialog(QDialog):
def __init__(self, parent):
super(MyDialog, self).__init__(parent)
self.myvalue = 10
self.restoreSettings()
def closeEvent(self, event):
self.saveSettings()
super(MyDialog, self).closeEvent(event)
def saveSettings(self):
settings = QSettings('myorg', 'myapp')
settings.setValue('myvalue', self.myvalue)
def restoreSettings(self):
settings = QSettings('myorg', 'myapp')
self.myvalue = settings.value('myvalue', self.myvalue)
EDIT:
The error in your code is caused by this:
self.buttonBox.button(QDialogButtonBox.Ok).clicked.connect(self.closeEvent)
You shouldn't be calling or connecting to closeEvent directly. Instead, you should connect to .close or .accept
self.buttonBox.button(QDialogButtonBox.Ok).clicked.connect(self.accept)

You need to instantiate the ConfigurePort class then the self.configurePortDialog object should keep consistent. You will need to make sure if you have the user enter data that a cancel does not store the data and that an "ok" stores the data, but I not sure what you are putting in your dialog.
class A (QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
#I create a QPushButton to open the QDialog
self.button = QPushButton("Open Dialog")
self.button.on_clicked(self.OpenDialog)
self.configurePortDialog = configurePort.ConfigurePort(parent=self)
self.configurePortDialog.accepted.connect(self.get_data)
#This is the method to open the QDialog which is in another module
def OpenDialog(self, event):
self.configurePortDialog.show()
#QtCore.Slot()
def get_data(self)
text = self.configurePortDialog.retrieve_data()
print text

Related

Menubar sometimes does not become un-greyed when QFileDialog closes

OS: W10. This may be significant. If you have different results on a different platform, feedback would be helpful.
Here is an MRE. If you run it and go Ctrl+O, the menu labels become greyed. If you select a file in the QFileDialog by clicking the "Open" button or using its mnemonic (Alt+O), the open-file dialog is dismissed and the "Files" and "Help" menus become un-greyed.
However, if you go Ctrl+O again, and this time enter the name of a file in the "File name" box (QLineEdit), and then press Return, the dialog is dismissed (with a successful selection result) but the "Files" and "Help" menus remain greyed-out. It looks like this:
import sys, os
from PyQt5 import QtWidgets, QtCore, QtGui
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle('Greying of menus MRE')
self.setGeometry(QtCore.QRect(100, 100, 400, 200))
menubar = QtWidgets.QMenuBar(self)
self.setMenuBar(menubar)
self.files_menu = QtWidgets.QMenu('&Files', self)
menubar.addMenu(self.files_menu)
self.help_menu = QtWidgets.QMenu('&Help', self)
menubar.addMenu(self.help_menu)
self.new_action = QtWidgets.QAction('&New', self)
self.files_menu.addAction(self.new_action)
self.open_action = QtWidgets.QAction('&Open', self)
self.files_menu.addAction(self.open_action)
self.open_action.setShortcut("Ctrl+O")
self.open_action.triggered.connect(self.open_file)
def focusInEvent(self, event ):
print('main_window focusInEvent')
super().focusInEvent(event)
def focusOutEvent(self, event ):
print('main_window focusOutEvent')
super().focusInEvent(event)
def activateWindow(self):
print('main_window activateWindow')
super().activateWindow()
def open_file(self):
print('open file')
main_window_self = self
# open_doc_dialog = QtWidgets.QFileDialog(self.get_main_window())
class OpenDocFileDialog(QtWidgets.QFileDialog):
def accepted(self):
print('accepted')
super().accepted()
def accept(self):
print('accept')
super().accept()
def close(self):
print('close')
super().close()
def done(self, r):
print(f'done r {r}')
# neither of these solves the problem:
# main_window_self.activateWindow()
# main_window_self.files_menu.activateWindow()
super().done(r)
def hide(self):
print(f'hide')
super().hide()
def focusInEvent(self, event ):
print('focusInEvent')
super().focusInEvent(event)
def focusOutEvent(self, event ):
print('focusOutEvent')
super().focusInEvent(event)
def activateWindow(self):
print('activateWindow')
super().activateWindow()
open_doc_dialog = OpenDocFileDialog(self)
open_doc_dialog.setWindowTitle('Choose file')
open_doc_dialog.setDirectory(os.getcwd())
# we cannot use the native dialog, because we need control over the UI
options = open_doc_dialog.Options(open_doc_dialog.DontUseNativeDialog)
open_doc_dialog.setOptions(options)
open_doc_button = open_doc_dialog.findChild(QtWidgets.QDialogButtonBox).button(QtWidgets.QDialogButtonBox.Open)
lineEdit = open_doc_dialog.findChild(QtWidgets.QLineEdit)
# this does not solve the problem
# lineEdit.returnPressed.disconnect()
# lineEdit.returnPressed.connect(open_doc_button.click)
print(f'open_doc_button {open_doc_button}, lineEdit {lineEdit}')
# show the dialog
dialog_code = open_doc_dialog.exec()
if dialog_code != QtWidgets.QDialog.Accepted: return
sel_files = open_doc_dialog.selectedFiles()
print(f'sel_files: {sel_files}')
app = QtWidgets.QApplication([])
main_window = MainWindow()
main_window.show()
sys.exit(app.exec())
This problem can be understood, if not solved, with reference to this answer.
Note that this greying-out is not disablement. As explained in the above link, this has to do with "active/inactive states" of the menus (or their labels). The menus remain enabled throughout, although in this case it's impossible to know that while the open-file dialog is showing because it is modal. Clicking on one menu after the dialog has gone, or just hovering over it, is enough to un-grey them both...
The explanation, as I understand it, is that the "File name" box QLineEdit has a signal, returnPressed, which appears to activate something subtley different to the slot which is invoked when you use the "Choose" button. You can see I have experimented with trying to re-wire that signal, to no avail.
The method done of the QFileDialog appears to be called however the dialog closes (unlike close!), so I tried "activating" the main window... and then the individual QMenus... Doesn't work.
I am not clear how to get a handle on this "active state" business or why the slot connected to returnPressed is (seemingly) unable to give the "active state" back to the menus when the other slot manages to do so.
Edit
Searching on Musicamante's "unpolishing" suggestion led me to this:
lineEdit.returnPressed.disconnect()
def return_pressed():
style = main_window_self.menubar.style()
style.unpolish(main_window_self.menubar)
open_doc_button.click()
lineEdit.returnPressed.connect(return_pressed)
... unfortunately this doesn't work.
This looks like a possible Windows-related bug, since I can't reproduce it on Linux. As a work-around, you could try forcing a repaint after the dialog closes:
# show the dialog
dialog_code = open_doc_dialog.exec()
self.menubar.repaint()
Finally got it, thanks to Musicamante's suggestion:
lineEdit.returnPressed.disconnect()
def return_pressed():
style = main_window_self.menubar.style()
style.unpolish(main_window_self.menubar)
open_doc_button.click()
main_window_self.menubar.repaint()
lineEdit.returnPressed.connect(return_pressed)
... I actually tried this several times, just to make sure it was doing what was intended. So in fact, fortunately, no single-shot timer was needed in this case.

When changing screens with QStackedWidget, how to call a function in the changed screen?

I'm trying to call the init function of the screen I'm changing my screen index to
For an example, i have this code:
from PyQt5 import QtWidgets as qtw
from PyQt5 import QtGui as qtg
from sys import argv as sysArgv
from sys import exit as sysExit
arialLarge = qtg.QFont("Arial", 18)
class MainWindow(qtw.QWidget):
def __init__(self):
super().__init__()
# Current screen label;
mainWindowLabel = qtw.QLabel("This is the main window", self)
mainWindowLabel.setFont(arialLarge)
mainWindowLabel.move(20, 40)
# Button for going to the HelloWindow screen;
gotoHelloWindowButton = qtw.QPushButton("Go to hello window", self, clicked=lambda: appStack.setCurrentIndex(appStack.currentIndex()+1))
gotoHelloWindowButton.move(100, 100)
class HelloWindow(qtw.QWidget):
def __init__(self):
super().__init__()
# EG: print hello world when I visit this page
print("hello world")
# Current screen label;
helloWindowLabel = qtw.QLabel("This is the hello window", self)
helloWindowLabel.setFont(arialLarge)
helloWindowLabel.move(20, 40)
# Button for going to the MainWindow screen;
gotoMainWindowButton = qtw.QPushButton("Go to main window", self, clicked=lambda: appStack.setCurrentIndex(appStack.currentIndex()-1))
gotoMainWindowButton.move(100, 100)
if __name__ == "__main__":
app = qtw.QApplication(sysArgv)
appStack = qtw.QStackedWidget()
appStack.addWidget(MainWindow())
appStack.setFixedSize(300, 300)
appStack.show()
appStack.addWidget(HelloWindow())
sysExit(app.exec())
If im visiting the HelloWindow from the MainWindow, how can i run the init function of the HelloWindow screen so I can run whatever code I want in there?
I need to be able to do this as on the app im working on as on the mainpage i have dynamically created buttons that all have functions parameters with different indexes to my server, and i need to be able to fetch the data from server based off the clicked button's data index so on the other page I can view the desired data.
The __init__ of a python class is what is called when an instance is created (using SomeClass()), so you should not try (or even think) to call it again, as it could create serious problems and bugs that are hard to track.
I strongly suggest you to read the documentation about classes in Python, as you cannot ignore that aspect in object oriented programming.
If you need to call something everytime the index is changed, then you should better subclass QStackedWidget and control everything from there.
A good solution is to create a standardized function that will be called everytime the page is presented, and ensure that the stack widget correctly calls it.
class FirstPage(QtWidgets.QWidget):
def __init__(self):
super().__init__(self)
# ...
self.nextButton = QtWidgets.QPushButton('Next')
self.doSomething()
def doSomething(self):
...
class SecondPage(QtWidgets.QWidget):
def __init__(self):
super().__init__(self)
# ...
self.prevButton = QtWidgets.QPushButton('Previous')
self.doSomething()
def doSomething(self):
...
class Stack(QtWidgets.QStackedWidget):
def __init__(self):
super().__init__(self)
self.first = FirstPage()
self.first.nextButton.clicked.connect(self.goNext)
self.addWidget(self.first)
self.second = SecondPage()
self.second.prevButton.clicked.connect(self.goPrev)
self.currentChanged.connect(self.initCurrent)
def goNext(self):
self.setCurrentIndex(1)
def goPrev(self):
self.setCurrentIndex(0)
def initCurrent()
if self.currentWidget():
self.currentWidget().doSomething()
if __name__ == "__main__":
app = qtw.QApplication(sysArgv)
appStack = Stack()
appStack.setFixedSize(300, 300)
appStack.show()
sysExit(app.exec())
Note that adding a QMainWindow to a parent is not a good idea, as Qt main windows are intended to be used as top level windows; also note that using fixed geometries (positions and sizes) is often considered bad practice, and you should use layout managers instead.

PyQt - Hide MainWindow and show QDialog without the taskbar icon disappearing

I've been using the code from this example PyQt: How to hide QMainWindow:
class Dialog_02(QtGui.QMainWindow):
def __init__(self, parent):
super(Dialog_02, self).__init__(parent)
# ensure this window gets garbage-collected when closed
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
...
def closeAndReturn(self):
self.close()
self.parent().show()
class Dialog_01(QtGui.QMainWindow):
...
def callAnotherQMainWindow(self):
self.hide()
self.dialog_02 = Dialog_02(self)
self.dialog_02.show()
It works, however when opening a second window, the window's task bar icon doesn't show. I've tried using QtGui.QDialog for the Dialog_02 as well but that gives me the same result.
How do I go about solving this?
Edit: I'm on Windows 10
Just guessing (because I don't know what platform you're on, and I don't use a task-bar myself, so I cant really test it), but try getting rid of the parent:
class Dialog_02(QtGui.QMainWindow):
def __init__(self, other_window):
super(Dialog_02, self).__init__()
# ensure this window gets garbage-collected when closed
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
self._other_window = other_window
...
def closeAndReturn(self):
self.close()
self._other_window.show()

PyQT Window: I want to remember the location it was closed at

I have a QDialog, and when the user closes the QDialog, and reopens it later, I want to remember the location and open the window at the exact same spot. How would I exactly remember that location?
For that, you can use the saveState(), saveGeometry() resize() and move() methods, in conjunction with the closeEvent() and QSettings mentioned by the other answers. Here is some example, to get the idea:
class MyWindow(QMainWindow):
def __init__(self, parent):
QMainWindow.__init__(self, parent)
self.settings = QSettings("MyCompany", "MyApp")
self.restoreGeometry(self.settings.value("geometry", ""))
self.restoreState(self.settings.value("windowState", ""))
def closeEvent(self, event):
self.settings.setValue("geometry", self.saveGeometry())
self.settings.setValue("windowState", self.saveState())
QMainWindow.closeEvent(self, event)
EDIT:
Updated answer to use PyQt API v2. If using API v1, you have to manually cast the result of settings.value() to a ByteArray like
self.restoreState(self.settings.value("windowState").toByteArray())
I also used the window's own size() and pos(), since I'm already loading the windows from a .ui file. You may set it to defaults before those lines if coding the window from scratch. For the state, I'm defaulting to an empty string, which the function happily accepts as an empty ByteArray and does nothing on the first run.
Ronan Paixão's answer is almost correct.
When attempting this a got the error:
AttributeError: 'NoneType' object has no attribute 'toByteArray'
this is because there is, at first, no saved geometry and state. Additionally the return value is already a QByteArray. This code works for me:
class MyWindow(QMainWindow):
def __init__(self, parent):
QMainWindow.__init__(self, parent)
self.settings = QSettings("MyCompany", "MyApp")
if not self.settings.value("geometry") == None:
self.restoreGeometry(self.settings.value("geometry"))
if not self.settings.value("windowState") == None:
self.restoreState(self.settings.value("windowState"))
def closeEvent(self, event):
self.settings.setValue("geometry", self.saveGeometry())
self.settings.setValue("windowState", self.saveState())
QMainWindow.closeEvent(self, event)
You could reimplement the CloseEvent of the dialog (found here in the Qt documentation), and save the appropriate settings using QSettings (docs here).
class MyDialog(QDialog):
def closeEvent(event):
settings = QSettings()
settings.setValue('value1', 1)
event.accept()
It looks like you can use QSettings for this. If you look at the section of the documentation titled Restoring the State of a GUI Application you'll find an example for a main window.
In other words, save the size and location when the user closes the dialog, then next time they open it reload those settings.
_windowStatesEnum = {
0x00000000 : Qt.WindowNoState, # The window has no state set (in normal state).
0x00000001 : Qt.WindowMinimized, # The window is minimized (i.e. iconified).
0x00000002 : Qt.WindowMaximized, # The window is maximized with a frame around it.
0x00000004 : Qt.WindowFullScreen, # The window fills the entire screen without any frame around it.
0x00000008 : Qt.WindowActive, # The window is the active window, i.e. it has keyboard focus.
}
def __setstate__(self, data):
self.__init__()
self.setGeometry(data['geometry'])
self.setWindowState(self._windowStatesEnum[data['window state']])
def __getstate__(self):
return {
'geometry' : self.geometry(),
'window state' : int(self.windowState()),
}

Open a second window in PyQt

I'm trying to use pyqt to show a custom QDialog window when a button on a QMainWindow is clicked. I keep getting the following error:
$ python main.py
DEBUG: Launch edit window
Traceback (most recent call last):
File "/home/james/Dropbox/Database/qt/ui_med.py", line 23, in launchEditWindow
dialog = Ui_Dialog(c)
File "/home/james/Dropbox/Database/qt/ui_edit.py", line 15, in __init__
QtGui.QDialog.__init__(self)
TypeError: descriptor '__init__' requires a 'sip.simplewrapper' object but received a 'Ui_Dialog'
I've gone over several online tutorials, but most of them stop just short of showing how to use a non built-in dialog window. I generated the code for both the main window and the dialog using pyuic4. What I think should be the relevant code is below. What am I missing here?
class Ui_Dialog(object):
def __init__(self, dbConnection):
QtGui.QDialog.__init__(self)
global c
c = dbConnection
class Ui_MainWindow(object):
def __init__(self, dbConnection):
global c
c = dbConnection
def launchEditWindow(self):
print "DEBUG: Launch edit window"
dialog = QtGui.QDialog()
dialogui = Ui_Dialog(c)
dialogui = setupUi(dialog)
dialogui.show()
class StartQT4(QtGui.QMainWindow):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
conn = sqlite3.connect('meds.sqlite')
c = conn.cursor()
self.ui = Ui_MainWindow(c)
self.ui.setupUi(self)
def main():
app = QtGui.QApplication(sys.argv)
program = StartQT4()
program.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Bonus question: since it looks like you can't pass arguments in pyqt function callbacks, is setting something which would otherwise be passed as an argument (the poorly named "c") to be global the best way to get information into those functions?
I've done like this in the past, and i can tell it works.
assuming your button is called "Button"
class Main(QtGui.QMainWindow):
''' some stuff '''
def on_Button_clicked(self, checked=None):
if checked==None: return
dialog = QDialog()
dialog.ui = Ui_MyDialog()
dialog.ui.setupUi(dialog)
dialog.setAttribute(QtCore.Qt.WA_DeleteOnClose)
dialog.exec_()
This works for my application, and I believe it should work with yours as well. hope it'll help, it should be pretty straight forward to do the few changes needed to apply it to your case.
have a good day everybody.
Ui_Dialog should inherent from QtGui.QDialog, not object.
class Ui_Dialog(QtGui.QDialog):
def __init__(self, dbConnection):
QtGui.QDialog.__init__(self)
global c
c = dbConnection
class StartQT4(QtGui.QMainWindow):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
Why QtGui.QWidget.__init___ ???
Use insted:
class StartQT4(QtGui.QMainWindow):
def __init__(self, parent=None):
QtGui.QMainWindow.__init__(self, parent)
You must call __init__ methon from base class (name in parenthesis '()')
QDialog have two useful routins:
exec_()
show()
First wait for closing dialog and then you can access any field form dialog. Second show dialog but don't wait, so to work properly you must set some slot/signals connections to respond for dialog actions.
eg. for exec_():
class Dialog(QDialog):
def __init__(self, parent):
QDialog.__init__(parent)
line_edit = QLineEdit()
...
dialog = Dialog()
if dialog.exec_(): # here dialog will be shown and main script will wait for its closing (with no errors)
data = dialog.line_edit.text()
Small tip: can you change your ui classes into widgets (with layouts). And perhaps problem is that your __init__ should be __init__(self, parent=None, dbConnection)
Because when you create new widget in existing one PyQt may try to set it as children of existing one. (So change all init to have additional parent param (must be on second position)).

Categories