Using .get() to pull a Tkinter variable from another class - python

I am writing a Tkinter application that requires parts of the user display to exist in two different class, both imported from another file. I need to take a piece of user input and pass it from one class to another. In the toy example below, the user is supposed to type something into my_entry_input which later the class PullVariable is supposed to access.
Code is below. I had a few thoughts, such as somehow using globals or creating a function within the original class to get the variables and then pass them back. In all cases, I get:
AttributeError: type object 'Application' has no attribute 'my_entry'
The best solution I can think of is to create a function that responds to the binding, then pass the .get() to the other class from that function. My feeling is that tkinter doesn't like .get() in between classes.
Thanks to the community for your help.
MAIN
from import_test1 import *
root=Tk()
ui = Application(root)
ui.hello_world()
ui.entry()
pv = PullVariable()
if __name__ == '__main__':
root.mainloop()
IMPORTED CODE
from tkinter import *
from tkinter import ttk
class Application(Frame):
def __init__(self, parent, *args, **kwargs):
print('Application init')
Frame.__init__(self, parent, *args, **kwargs)
self.parent=parent
self.parent.grid()
def entry(self):
self.my_entry = StringVar(self.parent)
my_entry_input = Entry(self.parent, textvariable=self.my_entry,
width=16)
my_entry_input.bind('<FocusOut>', self.show_entry)
my_entry_input.grid(column=0, row=1)
self.show_label = Label(self.parent, text = '')
self.show_label.grid(column=0, row=2)
def hello_world(self):
print('hello world')
self.hw = Label(self.parent, text='Hello World!')
self.hw.grid(column=0, row=0)
def show_entry(self, event):
PullVariable(Application).find_entry()
class PullVariable:
def __init__(self, app):
self.app = app
print('Pull initiated')
def find_entry(self, event=None):
self.pulled_entry = self.app.my_entry.get()
self.app.show_label['text'] = self.pulled_entry

my_entry is not a attribute of Application class, so you can't do Application.my_entry, by it is an attribute of instance of Application class, so you can do Application().my_entry. You can probably add either instance of Application or my_entry to the __init__ method of PullVariable. I'll use the former.
# ...
class PullVariable:
def __init__(self, app): # add app here
self.pulled_entry = app.my_entry.get() # use app here
print(self.pulled_entry)
# and then
root=Tk()
ui = Application(root)
ui.hello_world()
ui.entry()
pv = PullVariable(ui) # supply PullVariable with instance of Application

Related

Tkinter: properly pass the instance for unit test

I am trying to bring the Tkinter script to the testing. The application would be tested without opening the window, just simulate the button click. What I've done seems to be inappropriate, so how could I pass the instance properly? Thanks.
Here is the prototype of my code:
# importing Tkinter and math
from tkinter import *
import math
class calc:
def __init__(self,master):
"""Constructor method"""
master.title('Calculator')
master.geometry()
self.master = master
self.btn_equal = Button(self.master,text="=",width=11,height=3, fg="red", bg="light green",command=lambda:self.equals()).grid(row=4, column=4,columnspan=2)
self.e.grid(row=0,column=0,columnspan=6,pady=3)
def start_application():
root = Tk()
app = calc(root)
# print(app)
root.bind_class("Button", "<Button-1>", app.callback)
return root
if __name__ == "__main__":
start_application().mainloop()
Here is the testing code:
import unittest
from tkinter import *
from calculator2 import start_application
class TestCalculator2(unittest.TestCase):
# start the application, test, and destroy at the end of the test
async def _start_app(self):
self.app.mainloop()
def setUp(self):
self.app = start_application()
self._start_app()
def tearDown(self):
self.app.destroy()
class TestCalculation(TestCalculator2):
def test_startup(self):
title = self.app.winfo_toplevel().title()
expected = "Calculator"
self.assertEqual(title, expected)
def test_addition(self):
self.btn_equal.invoke() # _Tkinter.tkapp has no attribute, since root is not calc object
The short answer is that the Button widget calls the grid, leading to the NoneType. The correct way of instantiating a button inside a class with position should be:
self.btn_equal = Button(self.master,text="=",width=11,height=3, fg="red", bg="light green",command=lambda:self.equals())
self.btn_equal.grid(row=4, column=4,columnspan=2)
On the other hand, the correct way to instantiate the calc class mentioned above and write a successful unit test with invoke(), which clicks the button. The code won't open the GUI, and the testing script works (with warning):
class TestCalculator2(unittest.TestCase):
# start the application, test, and destroy at the end of the test
async def _start_app(self):
self.app.mainloop() # the root in Tkinter activates the mainloop()
def setUp(self):
self.app = start_application() # activate the root
self.calc = calc(self.app) # instantiate the calculator
self._start_app()
def tearDown(self):
self.app.destroy()
class TestCalculation(TestCalculator2):
def test_startup(self):
title = self.app.winfo_toplevel().title()
expected = "Calculator"
self.assertEqual(title, expected)
def test_addition(self):
self.calc.btn_AC.invoke() # click AC to clear the place
self.calc.btn_7.invoke() # click button 7
self.calc.btn_plus.invoke() # click button +
self.calc.btn_5.invoke() # click button 5
result = self.calc.btn_equal.invoke() # click button equal, get the value
self.assertEqual(result, 12)
Note that the code snippet just gives a brief idea of how unit testing works on Tkinter, the calc class provided is incomplete.

tkinter python function takes 1 argument, given 2

I'm getting this error when I try to run my code:
File "./countdown.py", line 36, in <module>
app = Application(root)
File "./countdown.py", line 16, in __init__
self.create_buttons(self)
TypeError: create_buttons() takes exactly 1 argument (2 given)
Here's my code:
import Tkinter as tk
class Application(tk.Frame):
"""Countdown app - simple timer"""
def __init__(self, master):
"""initialize frame"""
tk.Frame.__init__(self, master)
#super(Application, self).__init__(master)
self.grid()
self.create_buttons(self)
def create_buttons(self):
self.startBttn = Button(app, text = "Start")
self.startBttn.grid()
self.stopBttn = Button(app, text = "Stop")
self.stopBttn.grid()
self.resetBttn = Button(app, text = "Reset")
self.resetBttn.grid()
### Main Code ###
# create the root window using Tk - an object of tkinter class
root = tk.Tk()
# modify the prog. window (set size, title, etc.)
root.title("Countdown")
root.geometry("200x100")
#instantiate Application
app = Application(root)
I've been looking for an answer to this for a while but haven't been able to apply other people's solutions to my code- any ideas? If I remove the tk. before Frame in the class Application declaration I get an error that says Frame not found. If I use super(Application, self).__init__(master) instead of the line above it, I get a type error must be class not class object.
Don't explicitly pass self when calling a bound method. Call it like this:
self.create_buttons()
By calling the method with self.create_buttons(self) the function receives two arguments: the implicit self that is passed when calling a bound method (Python does this automatically), and the explicit self that you pass in the method call.
There are also some other problems with create_buttons() which you can fix with this code:
def create_buttons(self):
self.startBttn = tk.Button(self, text = "Start")
self.startBttn.grid()
self.stopBttn = tk.Button(self, text = "Stop")
self.stopBttn.grid()
self.resetBttn = tk.Button(self, text = "Reset")
self.resetBttn.grid()
The changes are that you need to use tk.Button to reference the Button class, and to pass self to tk.Button which is a reference to the parent frame. Here self is the Application instance which is a subclass of tk.Frame - hence self is a frame.
Finally you need to add a call to mainloop():
#instantiate Application
app = Application(root)
root.mainloop()
Regarding the problem with super, the tkinter classes are of the "old-style" type and do not support super(). Therefore you must call the base class with tk.Frame.__init__(self, master).
There is a workaround by using multiple inheritance and including object as a base class. If you declare Application as :
class Application(tk.Frame, object):
def __init__(self, master):
"""initialize frame"""
super(Application, self).__init__(master)
then you can use super(), but it's hardly worth the effort.

Python Tkinter File Dialog event order, returning string from button click

Good afternoon all,
Rather than using the command line or hard coding a file name in Python, I'd like to have a separate class that I can use in any other Python program that allows a file to be chosen with the Tkinter file dialog box. Here is what I have so far:
import Tkinter as tk
import tkFileDialog
import tkFont
###################################################################################################
class MyFileDialog(tk.Frame):
# class level variables #######################################################################
my_form = tk.Tk() # declare form
my_button = tk.Button(my_form) # declare button
chosen_file = ""
# constructor #################################################################################
def __init__(self):
# create button event
self.my_button.configure(text = "press here", command = self.on_my_button_click)
self.my_button.pack() # add button to form
# make the button font bigger so I don't have to squint
updated_font = tkFont.Font(font = self.my_button['font'])
updated_font['size'] = int(float(updated_font['size']) * 1.6)
self.my_button.configure(font = updated_font)
###############################################################################################
def on_my_button_click(self):
self.chosen_file = tkFileDialog.askopenfilename() # get chosen file name
return
###############################################################################################
def get_chosen_file(self):
return self.chosen_file # return chosen file name
###################################################################################################
if __name__ == "__main__":
my_file_dialog = MyFileDialog()
my_file_dialog.my_form.mainloop()
# I need to get the chosen file here, when the file is chosen, NOT when the window is closed !!
# please help !!
print my_file_dialog.get_chosen_file() # this does not print until the window is closed !!!
The current problem with this code is (as the comment towards the end indicates) that the test code does not receive the chosen file name until the window is closed, however I need the test code to receive the file name when the file is chosen.
I should mention that in production use the test code would be in a different file altogether that would import and instantiate this class, therefore the modularity of the class should be maintained. Anybody out there have a solution? Please help !
In a nutshell, mainloop() blocks all other calls until the window is destroyed (more on this: Tkinter understanding mainloop). This is why your print statement waits until the window is closed.
if __name__ == "__main__":
my_file_dialog = MyFileDialog()
my_file_dialog.my_form.mainloop()
# this line will not run until the mainloop() is complete.
print my_file_dialog.get_chosen_file()
One simple solution might be to change your chosen_file variable to a tk.StringVar(), which you could then attach a callback method to.
import Tkinter as tk
import tkFileDialog
import tkFont
class MyFileDialog(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
self.master = master
# class variables
self.chosen_file = tk.StringVar()
# ui elements
self.my_button = tk.Button(self)
# configure button
self.my_button.configure(\
text = "press here", command = self.on_my_button_click)
# make button font bigger
# ...
# add button to frame
self.my_button.pack()
# attach a callback to chosen_file with trace
self.chosen_file.trace("w", self.callback)
# execute this method whenever chosen_file gets written
def callback(self, *args):
print("MyFileDialog: chosen_file is %s" % self.chosen_file.get())
def on_my_button_click(self):
f = tkFileDialog.askopenfilename()
# set chosen_file here to trigger callback (wherever it is)
self.chosen_file.set(f)
if __name__ == "__main__":
root = tk.Tk()
app = MyFileDialog(root).pack()
root.mainloop()
Note that you don't have to place the callback method in your MyFileDialog class: you could just as easily create the callback in another class once you have instantiated a MyFileDialog object.
class MyApp(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
self.master = master
self.mfd = MyFileDialog(master)
self.mfd.pack()
# attach callback to chosen_file from MyFileDialog
self.mfd.chosen_file.trace("w", self.callback)
def callback(self, *args):
print("MyApp: chosen_file is %s" % self.mfd.chosen_file.get())

Tkinter Toplevel in OOP script: how?

Goal of the script:
(3) different windows, each in its own class, with its own widgets and layout, are created via Toplevel and callbacks.
When a new (Toplevel) window is created, the previous one is destroyed. Thus, only one window is visible and active at a time.
Problem?
Basically, I've tried many things and failed, so I must understand too little of ["parent", "master", "root", "app", "..."] :(
Note on raising windows:
I have implemented a successful example of loading all frames on top of each other, and controlling their visibility via the .raise method.
For this problem, however, I don't want to load all the frames at once.
This is an abstracted version of a quiz program that will require quite a lot of frames with images, which makes me reluctant to load everything at once.
Script (not working; bugged):
#!/usr/bin/env python
from Tkinter import *
import tkMessageBox, tkFont, random, ttk
class First_Window(Frame):
"""The option menu which is shown at startup"""
def __init__(self, master):
Frame.__init__(self, master)
self.gotosecond = Button(text = "Start", command = self.goto_Second)
self.gotosecond.grid(row = 2, column = 3, sticky = W+E)
def goto_Second(self):
self.master.withdraw()
self.master.update_idletasks()
Second_Window = Toplevel(self)
class Second_Window(Toplevel):
"""The gamewindow with questions, timer and entrywidget"""
def __init__(self, *args):
Toplevel.__init__(self)
self.focus_set()
self.gotothird = Button(text = "gameover", command = self.goto_Third)
self.gotothird.grid(row = 2, column = 3, sticky = W+E)
def goto_Third(self):
Third_Window = Toplevel(self)
self.destroy()
class Third_Window(Toplevel):
"""Highscores are shown with buttons to Startmenu"""
def __init__(self, *args):
Toplevel.__init__(self)
self.focus_set()
self.master = First_Window
self.gotofirst = Button(text = "startover", command = self.goto_First)
self.gotofirst.grid(row = 2, column = 3, sticky = W+E)
def goto_First(self):
self.master.update()
self.master.deiconify()
self.destroy()
def main():
root = Tk()
root.title("Algebra game by PJK")
app = First_Window(root)
root.resizable(FALSE,FALSE)
app.mainloop()
main()
The problem is not really a Tkinter problem, but a basic problem with classes vs. instances. Actually, two similar but separate problems. You probably need to read through a tutorial on classes, like the one in the official Python tutorial.
First:
self.master = First_Window
First_Window is a class. You have an instance of that class (in the global variable named app), which represents the first window on the screen. You can call update and deiconify and so forth on that instance, because it represents that window. But First_Window itself isn't representing any particular window, it's just a class, a factory for creating instances that represent particular windows. So you can't call update or deiconify on the class.
What you probably want to do is pass the first window down through the chain of windows. (You could, alternatively, access the global, or do various other things, but this seems cleanest.) You're already trying to pass it to Second_Window, you just need to stash it and pass it again in the Second_Window (instead of passing self instance, which is useless—it's just a destroyed window object), and then stash it and use it in the Third_Window.
Second:
Second_Window = Toplevel(self)
Instead of creating an instance of the Second_Window class, you're just creating an instance of the generic Toplevel class, and giving it the local name Second_Window (which temporarily hides the class name… but since you never use that class, that doesn't really matter).
And you have the same problem when you try to create the third window.
So:
class First_Window(Frame):
# ...
def goto_Second(self):
# ...
second = Second_Window(self)
class Second_Window(Toplevel):
def __init__(self, first, *args):
Toplevel.__init__(self)
self.first = first
# ...
def goto_Third(self):
third = Third_Window(self.first)
self.destroy()
class Third_Window(Toplevel):
"""Highscores are shown with buttons to Startmenu"""
def __init__(self, first, *args):
Toplevel.__init__(self)
self.first = first
# ...
def goto_First(self):
self.first.update()
self.first.deiconify()
self.destroy()

There is no attribute for one object

I learned the book "programming python' these days. When I run the examples, I met the problem. The shell showed me the error:
AttributeError: 'NoneType' object has no attribute 'pack'
However, I copy the exactly code from the book. I'm a freshman of Python. I try to fix it by myself, but I failed. So I hope anyone could kindly help me.
Thanks !!!!!!
CODE:
#File test.py
from tkinter import *
from tkinter.messagebox import showinfo
def MyGui(Frame):
def __init__(self, parent = None):
Frame.__init__(self, parent)
button = Button(self, text='press', command=reply)
button.pack()
def reply(self):
showinfo(title = 'popup',message ='Button pressed!')
if __name__ == '__main__':
window = MyGui()
window.pack()
window.mainloop()
#File test2.py
from tkinter import *
from test import MyGui
mainwin = Tk()
Label(mainwin,text = __name__).pack()
popup = Toplevel()
Label(popup,text = 'Attach').pack(side = LEFT)
MyGui(popup).pack(side=RIGHT)
mainwin.mainloop()
You can fix this with the following code:
#File test.py
from tkinter import *
from tkinter.messagebox import showinfo
class MyGui(Frame):
def __init__(self, parent = None):
Frame.__init__(self, parent)
button = Button(self, text='press', command=self.reply)
button.pack()
def reply(self):
showinfo(title = 'popup',message ='Button pressed!')
if __name__ == '__main__':
window = MyGui()
window.pack()
window.mainloop()
Basically two small syntax errors. First you were trying to make a class of MyGui, but you used keyword def which made a function instead (that returned None, hence the error you received.) It is syntaxically correct in python to define functions inside of functions so it was a little harder to catch. You have to use the keyword class to define a class.
Secondly when referencing the function reply you must use self.reply within the class itself.

Categories

Resources