How to functional test if tkinter bind works - python

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

Related

Why treeview's item method doesn't work in this situation?

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.

Destroying login page window and opening a new one

I'm trying to close a window while creating a new one. The following function runs once a login has been entered. Do you have any suggestions on how to close this window while the MenuPage(window) is opened?
from tkinter import *
class LoginPage():
def __init__(self):
window = Tk()
#UI to retrieve login details
#While loop when submit is clicked.
if UserLogins:
#Maybe here a close window function is ran
window = Toplevel()
start= MenuPage(window)
else:
print('Incorrect Login')
class MenuPage(object):
#Menu UI
if __name__ == '__main__':
LoginPage()
Hello and welcome to StackOverflow.
Your main issue is your class LoginPage.
You do initialize Tk() inside the __init__ of your class. As soon as this object is destroyed, the Tkinter Instance gets destroyed as well.
Consider using the following approach:
Create a Main Widget (e.g. based on Toplevel or Tk)
Create a LoginPage based on e.g. Frame inside your Main Widget
After login attempt, either destroy the LoginPage and create another Frame holding new data. (MenuPage)
Using this approach your Tkinter Main Instance Runs all the time, and you delete or add objects / widgets to it.
If you do not use your MainWidget (that should persist) as Tk-Instance, an "unnamed" Instance will be used for that. Alternatively use Tk() as Parent for your LoginPage, but do not embed it inside it.
Pseudo Code Example:
import tkinter as tk
class MainWidget(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__()
self.__load_widgets()
def __load_widgets(self):
self.page = LoginPage(self)
self.page.bind("&ltLoginEvent>", self.show_menu)
self.page.grid(sticky=tk.NW+tk.SE)
def show_menu(self, event=None):
self.page.destroy()
self.page=MenuPage(self)
self.page.grid(sticky=tk.NW+tk.SE)
class LoginPage(tk.Frame):
def __init__(self, *args, **kwargs):
tk.Frame.__init__(self, *args, **kwargs)
def raise_login_result(self):
""" some event may raise the event you can use from main widget to bind to """
# ...
raise("&ltLoginEvent>")
class MenuPage(tk.Frame):
def __init__(self, *args, **kwargs):
tk.Frame.__init__(self, *args, **kwargs)
if __name__ == "__main__":
app = MainWidget()
app.mainloop()
Edit
As your question expands into geometry managers, I hereby refer to one corresponding documentation on geometry managers(that is e.g. grid/pack/place). This documentation shows the corresponding interfaces.

Changed to options listed in PyQt4 combo_boxes defined in a different class not reflected

I am working on a small GUI project using PyQt4. I have defined one class (inside a separate file) defining the basic functionality of combo_boxes that I have to use and another class to use the functionality for all the combo_boxes.
The code looks something like
class core:
def __init__(self, default_value, window_name):
self.combo_box = QtGui.QComboBox(window_name)
self.combo_box.addItem(default_value)
self.combo_box.addItem("some other value")
self.combo_box.addItem("a third value")
self.combo_box.activated[str].connect(self.set_text)
self.text = default_value
def set_text(self, text):
print text
The main class is something like:
from file import *
class Window(QtGui.QMainWindow):
def __init__(self):
super(Window, self).__init__()
self.setGeometry(200, 100, 820, 700)
combo_box_one = core("first", self)
combo_box_two = core("second", self)
#some other methods follow defining the geometry for each combo_box and other functions
def main():
app = QtGui.QApplication(sys.argv)
gui = Window()
sys.exit(app.exec_())
main()
The GUI is working as expected. All the combo_boxes appear as per the defined geometry. However, when on selecting different options, nothing seems to happen. Ideally, I would expect the text on the option to be printed. In fact, when I return the combo_box object to the main class and set it connections there, the change in options is reflected. But when the same thing is done in the coreclass, the changes are not reflected as printed text. Is it a scope related thing? Please help me understand what's happening.
Slots can only be implemented in classes that inherit from QObject, a simple solution is that the core class inherits from QComboBox, since QComboBox inherits from QObject.
class core(QtGui.QComboBox):
def __init__(self, default_value, window_name):
QtGui.QComboBox.__init__(self, window_name)
self.addItem(default_value)
self.addItem("some other value")
self.addItem("a third value")
self.activated[str].connect(self.set_text)
def set_text(self, text):
print(text)

How to access a method in one inherited tkinter class from another inherited tkinter class

I've programmed using tkinter before, but usually did a long procedural GUI class that implemented other non GUI classes I've created. This time I wanted to do it using more OOP making it more modular.
I ran into a problem, I've searched for answers and haven't found any, which usually means it's either really easy or I'm really wrong. I created an inherited classes from tk.LabelFrame and created GUI widgets in them. I also have methods to manipulate the widgets in the classes but I can't figure out how to execute a function in another inherited class, partly because I can't figure out how to correctly instantiate an object from the other class (which have tkinter ('parent') objects as parameters).
Would I do this by overloading constructors? I've seen something about #classmethods and *args, **kwargs but haven't acted on them as I'm not sure if that's the right route either. There's some debate about the best/correct way to implement an overloaded constructor in python. I'm stumped as to what is the most apropos for what I'm trying to accomplish...
Thanks
#python 2.7 on win7
import Tkinter as tk
class Testing(tk.LabelFrame):
buttonwidth = 10
def __init__(self, parent):
self.parent=parent
#results = Results(???) #<-- Don't know how to instantiate Results object
tk.LabelFrame.__init__(self, self.parent,
text="Test Operations",
padx=10,
pady=10,
)
self.taskButton = tk.Button(
self,
text="Do A Task",
width=self.buttonWidth,
command=self.doATask,
)
self.taskButton.pack()
def doATask(self):
#want to execute function in Results.getResult() but don't know how
#results.getResults() #<--what I want to do
print("place holder")
class Results(tk.LabelFrame):
def __init__(self, parent):
self.parent = parent
tk.LabelFrame.__init__(self, self.parent, text="Visual Results")
self.resultLbl = tk.Label(self, text="Result")
self.resultLbl.pack()
def getResult(self):
self.resultLbl.configure(bg='yellow')
class Application(tk.Frame):
def __init__(self, parent):
self.parent = parent
tk.Frame.__init__(self, self.parent)
self.Testing = Testing(self.parent)
self.Results = Results(self.parent)
self.Testing.pack(fill=tk.X)
self.Results.pack(fill=tk.X)
if __name__ == "__main__":
root = tk.Tk()
root.title("Modular GUI App")
Application(root).pack()
root.mainloop()
I'd recommend sticking to instance variables, which are created for each individual object, unlike class variables which are shared among all of a class's instantiations - just prepend those variable names with self. (e.g. self.results). Also, stick to naming conventions so you don't have a Testing class and a Testing object of that class.
You instantiate objects according to their __init__. The Results class has an __init__ defined as def __init__(self, parent):, so it needs a parent. If you want it to have the same parent as the Testing object that created it, simply do results = Results(parent). However, you don't want to do this (see below).
A problem that I encountered after making the above change was that the Application class instantiated its own Results object, and that was what was actually being displayed, not the one created by the Testing object. Refer back to that object instead of creating a new one. Pass the Application object to each of these classes so they can refer to each other. Now, having said that, it's generally better to have each class know as little about other classes as possible, so that making a change in one class doesn't require any changes in other classes.
The following code will make the label yellow when you click the button.
import Tkinter as tk
class Testing(tk.LabelFrame):
def __init__(self, parent, main):
self.buttonWidth = 10
self.parent=parent
self.main = main # save the instantiating class
tk.LabelFrame.__init__(self, self.parent,
text="Test Operations",
padx=10,
pady=10
)
self.taskButton = tk.Button(
self,
text="Do A Task",
width=self.buttonWidth,
command=self.doATask,
)
self.taskButton.pack()
def doATask(self):
#want to execute function in Results.getResult() but don't know how
self.main.results.getResult() #<--what you can do
class Results(tk.LabelFrame):
def __init__(self, parent, main):
self.parent = parent
self.main = main # save the instantiating class
tk.LabelFrame.__init__(self, self.parent, text="Visual Results")
self.resultLbl = tk.Label(self, text="Result")
self.resultLbl.pack()
def getResult(self):
self.resultLbl.config(bg='yellow')
class Application(tk.Frame):
def __init__(self, parent):
self.parent = parent
tk.Frame.__init__(self, self.parent)
self.testing = Testing(self.parent, self)
self.results = Results(self.parent, self)
self.testing.pack(fill=tk.X)
self.results.pack(fill=tk.X)
if __name__ == "__main__":
root = tk.Tk()
root.title("Modular GUI App")
Application(root).pack()
root.mainloop()
This worked for me
Results(None,None).getResult()
goodluck!

Multiple Windows in PyQt4

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)

Categories