I wrote a GUI program with Python tkinter. To achieve some function, in a Toplevel window, some event triggered a method of it which would call the Treeview widget's item(ID, tags=(some_tag)) of the Tk window to change the style of Treeview's content. But it doesn't work even if the snippet containing .item() have been run and no error occurs. My corresponding code snippet is as follows(some irrelevant part is omitted).
class Main_window(Tk):
# some_code_omitted...
def create_widgets():
# some_code_omitted...
self.tv1 = ttk.Treeview()
class A_Toplevel(Toplevel):
def __init__(self, parent):
self.parent = parent
# some_code_omitted...
def some_foo(self, event):
self.parent.tv1.item(ID, tags=(some_tag))
After some attempt, I found it seems that only when tv.item() is called in the Main_window, it works. later I wrote a method in Main_window to call tv.item(). But when the instance of A_Toplevel call it, it still doesn't work at all.
class Main_window(Tk):
# some_code_omitted...
def create_widgets():
# some_code_omitted...
self.tv1 = ttk.Treeview()
def a_foo(self, ):
self.tv1.item(ID, tags=(some_tag))
class A_Toplevel(Toplevel):
def __init__(self, parent):
self.parent = parent
# some_code_omitted...
def some_foo(self, event):
self.parent.a_foo()
What's wrong and how can I solve this problem?
Oh! Some progress.
Today, I found a way to solve it occasionally with threading module. Codes are as follows:
def a_foo_thr(self, ID, some_tag):
thr = threading.Thread(target=self.a_foo, args=(ID, some_tag))
thr.start()
def a_foo(self, ID, some_tag):
self.tv1.item(ID, some_tag)
But I have no idea why it succeeded, even whether it could make some unexpected problem.
Related
I'm working on an application with the tkinter graphical user interface. With the MVC pattern. I am partially using TDD - I realize that it is unnecessary for GUI build part. However, I assume that I need to automate the testing of controller duties. Most of which rely on passing data from view to model.
Passing process should be initialize by losing focus by entry widget, or by pressing Enter key. In my application there will be few different types of data to pass, so I decided to subclass widgets that have to initialize passing variables process.
Sidenote: I found a topic, Tkinter's event_generate command ignored where OP points in addendum that subclassing widgets is not a good idea. Nonetheless I don't know how to avoid it and still provide that many different widgets would have a standardized method to return different types of data.
Minimal reproducible example (main.py):
import tkinter as tk
class App():
def __init__(self):
self.model = Model()
self.view = View(self.pass_value_to_model)
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.view.quit()
del self
def run_gui(self):
self.view.mainloop()
def pass_value_to_model(self, value):
self.model.variable = value
print(f"changed model variable value to : {self.model.variable}")
class CustomEntry(tk.Entry):
def __init__(self, *args, **kwargs):
tk.Entry.__init__(self, *args, **kwargs)
self.parent = args[0]
def pass_value(self, event):
self.parent.passing_method(self.get())
class View(tk.Tk):
def __init__(self, passing_method):
tk.Tk.__init__(self)
self.passing_method = passing_method
self.frame = tk.Frame(self)
self.frame.pack()
self.variable = tk.StringVar(self.frame)
self.entry = CustomEntry(self.frame, textvariable=self.variable)
self.entry.pack(padx=100, pady=100)
self.entry.bind('<Return>', self.entry.pass_value)
self.entry.bind('<FocusOut>', self.entry.pass_value)
class Model():
def __init__(self):
self.variable = "init value"
if __name__ == '__main__':
app = App()
app.run_gui()
And this is working as I expect. Model variable is changing it's value properly. The problem is how to test this.
Here's what I wrote in pytest (test_module.py):
import main
def test_passing_value():
with main.App() as controller:
test_value = "test"
controller.view.variable.set(test_value)
controller.view.entry.event_generate("<Return>")
assert controller.model.variable == test_value
event_generate is not working. I suppose, that is because pytest don't start mainloop. If i code this - I can't automatically kill it. What I need to do to automate testing of this passing process?
python version is 3.7
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
I have a MainWindow that looks like this:
def __init__(self, parent = None):
QMainWindow.__init__(self, parent)
self.setupUi(self)
self.showMaximized()
menu=mainMenu.MainMenu()
classification=classificationMain.ClassificationMain()
self.stackedWidget.addWidget(menu)
self.stackedWidget.addWidget(classification)
self.stackedWidget.setCurrentWidget(menu)
self.stackedWidget.showFullScreen()
#connections
menu.pushButton.clicked.connect(self.showClassification)
classification.backButton.clicked.connect(self.showMainWindow)
def showClassification(self ):
self.stackedWidget.setCurrentIndex(3)
def showMainWindow(self):
self.stackedWidget.setCurrentIndex(2)
The MainWindows waits for signal from the rest of the dialogs. Now, the Classification dialog has another StackedWidget in it, since it works as a main window for an important part of the application. It looks like:
class ClassificationMain(QDialog, Ui_Dialog):
def __init__(self, parent = None):
QDialog.__init__(self, parent)
self.setupUi(self)
choose=choosePatient.ChoosePatient()
self.stackedWidget.addWidget(choose)
self.stackedWidget.setCurrentWidget(choose)
Now, I want to reload the data inside ChoosePatient every time the button "Show Classification" from MainMenu is clicked, but now the data is loaded only once in the line classification=classificationMain.ClassificationMain() of MainWindow.
I was thinking I had to connect a slot inside ChoosePatient with the click of "Show Classification" button inside MainMenu, but I would need an instance of MainMenu, which is not possible.
How can a method of ChoosePatient can be execute every time the button in the "parent" window is clicked? (also, please tell me if this is not the right way to work with pyqt windows)
You need to save references to your composed widgets, and also to expose some public methods to the parents:
class ClassificationMain(QDialog, Ui_Dialog):
def __init__(self, parent = None):
QDialog.__init__(self, parent)
self.setupUi(self)
self.chooseWidget=choosePatient.ChoosePatient()
self.stackedWidget.addWidget(self.chooseWidget)
self.stackedWidget.setCurrentWidget(self.chooseWidget)
def reloadPatients(self):
# whatever your operation should be on the ChoosePatient
self.chooseWidget.reload()
# MAIN WINDOW
def __init__(self, parent = None):
...
self.classification=classificationMain.ClassificationMain()
self.stackedWidget.addWidget(self.classification)
...
#connections
menu.pushButton.clicked.connect(self.showClassification)
def showClassification(self ):
self.stackedWidget.setCurrentIndex(3)
self.classification.reloadPatients()
You could also just skip the reloadPatients method and connect to the ChoosePatient directly if you want:
def showClassification(self ):
self.stackedWidget.setCurrentIndex(3)
self.classification.chooseWidget.reload()
My personal opinion is to make your custom classes wrap up the internal functionality nicely so that you only need to interface with it over the custom class, and not dig into its internals. That way you can change how it works inside without breaking the main window.
class GuiMaker(Frame):
#more code
def __init__(self, parent=None):
Frame.__init__(self, parent)
self.pack(expand=YES, fill=BOTH) # make frame stretchable
self.start() # for subclass: set menu/toolBar
self.makeMenuBar() # done here: build menu-bar
self.makeToolBar() # done here: build tool-bar
self.makeWidgets() # for subclass: add middle part
#more code
class TextEditor:
#more code
def start(self):
#more code
How come self.start() will call TextEditor's start if self refers to GuiMaker or else how come self refers to TextEditor?
Does GuiMaker inherit from TextEditor somewhere? In other words is Frame a descendant of TextEditor? That would cause TextEditor's start method to be called.
Other than that, I don't see any way for the code (as written) to have GuiMaker.start call TextEditor.start
I have a PyQt program used to visualize some python objects. I would like to do display multiple objects, each in its own window.
What is the best way to achieve multi-window applications in PyQt4?
Currently I have the following:
from PyQt4 import QtGui
class MainWindow(QtGui.QMainWindow):
windowList = []
def __init__(self, animal):
pass
def addwindow(self, animal)
win = MainWindow(animal)
windowList.append(win)
if __name__=="__main__":
import sys
app = QtGui.QApplication(sys.argv)
win = QMainWindow(dog)
win.addWindow(fish)
win.addWindow(cat)
app.exec_()
However, this approach is not satisfactory, as I am facing problems when I try to factor out the MultipleWindows part in its own class. For example:
class MultiWindows(QtGui.QMainWindow):
windowList = []
def __init__(self, param):
raise NotImplementedError()
def addwindow(self, param)
win = MainWindow(param) # How to call the initializer of the subclass from here?
windowList.append(win)
class PlanetApp(MultiWindows):
def __init__(self, planet):
pass
class AnimalApp(MultiWindows):
def __init__(self, planet):
pass
if __name__=="__main__":
import sys
app = QtGui.QApplication(sys.argv)
win = PlanetApp(mercury)
win.addWindow(venus)
win.addWindow(jupiter)
app.exec_()
The above code will call the initializer of the MainWindow class, rather than that of the appropriate subclass, and will thus throw an exception.
How can I call the initializer of the subclass? Is there a more elegant way to do this?
Why not using dialogs? In Qt you do not need to use the main window unless you want to use docks etc.. Using dialogs will have the same effect.
I can also see a problem in your logic regarding the fact that you want your super class to be calling the constructor of its children, which of course can be any type. I recommend you rewrite it like the following:
class MultiWindows(QtGui.QMainWindow):
def __init__(self, param):
self.__windows = []
def addwindow(self, window):
self.__windows.append(window)
def show():
for current_child_window in self.__windows:
current_child_window.exec_() # probably show will do the same trick
class PlanetApp(QtGui.QDialog):
def __init__(self, parent, planet):
QtGui.QDialog.__init__(self, parent)
# do cool stuff here
class AnimalApp(QtGui.QDialog):
def __init__(self, parent, animal):
QtGui.QDialog.__init__(self, parent)
# do cool stuff here
if __name__=="__main__":
import sys # really need this here??
app = QtGui.QApplication(sys.argv)
jupiter = PlanetApp(None, "jupiter")
venus = PlanetApp(None, "venus")
windows = MultiWindows()
windows.addWindow(jupiter)
windows.addWindow(venus)
windows.show()
app.exec_()
It is not a nice idea to expect the super class to know the parameter to be used in the init of its subclasses since it is really hard to ensure that all the constructor will be the same (maybe the animal dialog/window takes diff parameters).
Hope it helps.
In order to reference the subclass that is inheriting the super-class from inside the super-class, I am using self.__class__(), so the MultiWindows class now reads:
class MultiWindows(QtGui.QMainWindow):
windowList = []
def __init__(self, param):
raise NotImplementedError()
def addwindow(self, param)
win = self.__class__(param)
windowList.append(win)