Passing variables between two windows Tkinter - python

I posted something about this earlier and how a response which I thought solved my issue but it didn't fully solve it. Basically, I'm looking for a way to pass StringVar() from my first method called 'main_frame' to my second method called 'result_frame' when the button in the 'main_frame' method is clicked.
from Tkinter import *
import Forecast
class Frames(object):
def __init__(self):
pass
def main_frame(self):
root = Tk()
root.title('WeatherMe')
root.geometry('300x100')
query = StringVar()
Label(root, text='Enter a city below').pack()
Entry(root, textvariable=query).pack()
Button(root, text="Submit", command=self.result_frame).pack()
root.mainloop()
def result_frame(self):
result = Toplevel()
result.title('City')
result.geometry('1600x150')
forecast = Forecast.GetWeather('City Here').fetch_weather()
Label(result, text='1-3 DAY: \n' + forecast[0]).pack()
Label(result, text='4-7 DAY: \n' + forecast[1]).pack()
Label(result, text='8-10 DAY: \n' + forecast[2]).pack()
Button(result, text="OK", command=result.destroy).pack()
As you can see one window will have an Entry box where you can type in a city. The entry will save it's contents to StringVar(). I want my button in the 'main_frame' method to call 'result_frame' and use the value located in my first window. If I add a parameter to the second method then I'd have to specify it in my button right away and that will of course immediately call it when the program is opened which I want to avoid. I apologize if this is an easy solution I'm just missing here, I've struggled with this for a while with no solution. I was told last time I posted this to only call one instance of Tk() which I have done here and used Toplevel() for my additional window but I'm still having issues passing the variable from one window to the other.

Your self (the object) is the same in both __init__() as well as result_frame() , so you can set the StringVar() as an instance variable, and then in the result_frame() method, you can get the StringVar() from that instance variable.
Also, I would like to suggest that you should not do root.mainloop() within the method of your class, that can cause the object to be never completely be available outside the class until you actually close the tkinter application (if called from within __init__() or so). Instead create root outside the class and pass it in as a variable. Example -
from Tkinter import *
import Forecast
class Frames(object):
def __init__(self):
pass
def main_frame(self, root):
root.title('WeatherMe')
root.geometry('300x100')
self.query = StringVar()
Label(root, text='Enter a city below').pack()
Entry(root, textvariable=self.query).pack()
Button(root, text="Submit", command=self.result_frame).pack()
def result_frame(self):
result = Toplevel()
result.title('City')
result.geometry('1600x150')
print(self.query.get()) #This would print the StringVar's value , use this in whatever way you want.
forecast = Forecast.GetWeather('City Here').fetch_weather()
Label(result, text='1-3 DAY: \n' + forecast[0]).pack()
Label(result, text='4-7 DAY: \n' + forecast[1]).pack()
Label(result, text='8-10 DAY: \n' + forecast[2]).pack()
Button(result, text="OK", command=result.destroy).pack()
root = Tk()
app = Frames()
app.main_frame(root)
root.mainloop()

Related

How can I display the current time left in a timer in a label?

I made a timer for part of a project I am making. I have the timer made, but I would like for the time left in the timer to be printed on to a label. ALso, if this wasn't expected, I would like for the time to be on the label then after a second it deletes itself and places the new time remaining(I do not want it to keep printing the time on a new line one after another).
One post I found that was pretty much what I wanted to do, but it did not work for me and I had to change some functions and add some new ones. I am not sure why this didn't work, but I would prefer it to be different because it has a preset time of 10 seconds and I would like for it to be the users choice. The link: Making a countdown timer with Python and Tkinter?
class Application(Frame):
def createWidgets(self):
# More code here
self.timeLeftLabel = Label(root, text='Time Left: ')
self.timeLeftLabel.pack()
def timeLeft(t):
time.sleep(1)
print(t)
def countdownInGUI():
countdown = Label(root, text=entryInt)
countdown.pack()
entryInt = IntVar()
t = Entry(root, textvariable=entryInt)
t.bind('<Return>', get)
t.pack(pady=5)
I am hoping that the time left will show in the label called countdown, but instead nothing shows up until the timer ends then it says "PY_VAR0" on a new line for each second (So its on 3 lines for 3 seconds, 4 lines for seconds, etc..)
In your func countdownInGUI, you created your Label widget by Label(root, text=entryInt), so tkinter will try to convert what you passed to a string. What you should do is to set entryInt as a textvariable instead.
On the other hand, you don't really need to set a textvariable for your Entry widget - you can retrieve the content directly by calling Entry.get().
Here is how everything could work base on your code:
import tkinter as tk
class Application(tk.Frame):
def __init__(self,master=None,**kwargs):
super().__init__(master,**kwargs)
self.master = master
self.timeLeftLabel = tk.Label(master, text='Time Left: ')
self.timeLeftLabel.pack()
self.entryInt = tk.StringVar()
self.countdown = tk.Label(master, textvariable=self.entryInt)
self.countdown.pack()
self.t = tk.Entry(master)
self.t.bind('<Return>', self.start_countdown)
self.t.pack(pady=5)
def start_countdown(self,event=None):
if self.t.get().isdigit():
self.time_left(int(self.t.get()))
def time_left(self, t):
self.entryInt.set(t)
t-=1
if t>=0:
self.master.after(1000,self.time_left, t)
else:
self.entryInt.set("Boom!")
root = tk.Tk()
frame = Application(root)
frame.pack()
root.mainloop()

how do I create entry fields and buttons to get the content from the entry field using iteration in tkinter for python?

I'm trying to make a tkinter GUI with a certain amount of buttons and entry fields that is specified by the user, so in this code below for example if the user specifies the variable number to be 3 I want there to be 3 entry boxes and , and I want to set it up so that if I type a value into the first field and click the button next to it, I want that button to read the value from the entry field next to it. I also need to assign each entry field to a variable that will be created through the same iterative loop. However, I'm having difficulty especially in regards to mapping the buttons to the entry fields, as I always seem to run up against an error with the text "AttributeError: 'NoneType' object has no attribute 'get'". Would anyone be able to either fix my code or help me find an alternative solution to the problem? Sorry if the description's a bit confusing.
This is different from just a problem of just getting the contents of the entry widget as I need it to create a certain amount of entry widgets and buttons using iteration, and the question that my question has been marked a duplicate of doesn't explain how to iteratively map each entry field to each button. For example, if I enter 3 as the variable, I need the program to create entry field 1, entry field 2 and entry field 3 and button 1, button 2 and button 3 and then map each button to its respective entry field using iteration.
I've tried using dictionaries, but this doesn't seem to help much.
import tkinter as tk
root = tk.Tk()
number = 3
d={}
def callBack():
print(d["E{0}".format(i)].get())
return
for i in range(0,number):
d["E{0}".format(i)] = tk.Entry(root)
d["E{0}".format(i)].grid(row=i, column=0)
d["B{0}".format(i)] = tk.Button(root, text="test", command=callBack)
d["B{0}".format(i)].grid(row=i, column=1)
The solution to "'NoneType' object has no attribute 'get'" has been asked probably a hundred times on this site, and the answer is always the same.
In python, when you do x = y().z(), x will be given the value of z(). In the case of x=tk.Entry(...).grid(...), x will be None because grid(...) always returns None. The solution is to always call grid or pack or place separate from when you create a widget.
You also claim you are having problems with dictionaries, but I don't see any problem in your code other than you are making it more difficult than necessary. You can directly use i as an index without having to build up a string for the index. If you need to keep track of both buttons and entries, I recommend two variables rather than one.
Part of the problem may also have to do with the fact you're trying to do something very odd in your command. You're trying to call the get method of the entry, but that's pointless since it simply returns a value that gets thrown away. In almost all cases, the correct solution is to write a proper function rather than trying to squeeze functionality into a lambda.
Example:
def handle_click(i):
entry = entries[i]
print("the value is {}".format(entry.get()))
buttons = {}
entries = {}
for i in range(0,number):
entry = tk.Entry(root)
button = tk.Button(root, text="test", command=lambda i=i: handle_click(i))
buttons[i] = button
entries[i] = entry
entry.grid(row=i, column=0)
button.grid(row=i, column=1)
You need to save the Entry and Button before calling grid:
import tkinter as tk
number = 3
root = tk.Tk()
def get_on_click(widget_dict, entry_name):
def on_click():
result = widget_dict[entry_name].get()
print("%s = %s" % (entry_name, result))
return result
return on_click
d = dict()
for i in range(0, number):
entry_name = "E{0}".format(i)
button_name = "B{0}".format(i)
print(entry_name, button_name)
d[entry_name] = tk.Entry(root)
d[entry_name].grid(row=i, column=0)
d[button_name] = tk.Button(root, text="test", command=get_on_click(d, entry_name))
d[button_name].grid(row=i, column=1)
root.mainloop()
This should help you get started.
In your comment, you ask how to save the value in the Entry. I would create a class to handle everything:
import tkinter as tk
number = 3
root = tk.Tk()
class EntryButton(object):
def __init__(self, root, number):
self.number = number
self.entry = tk.Entry(root)
self.button = tk.Button(root, text="test", command=self.on_click)
self.entry.grid(row=number, column=0)
self.button.grid(row=number, column=1)
self.value = None
def on_click(self):
self.value = self.entry.get()
storage = dict()
for i in range(0, number):
storage[i] = EntryButton(root, i)
root.mainloop()
for i in range(0, number):
value = storage[i].value
print(f"storage[{i}] = {value}")
As you can see, this eliminates a lot of extra work.
For get text from entry
Entry.get("1.0", "end-1c")
# 1.0 for get first line.
# end-1c for if last letter space, this deletes it.
More info

Python3 Print Tuple in another class [duplicate]

I am trying to set the text of an Entry widget using a button in a GUI using the tkinter module.
This GUI is to help me classify thousands of words into five categories. Each of the categories has a button. I was hoping that using a button would significantly speed me up and I want to double check the words every time otherwise I would just use the button and have the GUI process the current word and bring the next word.
The command buttons for some reason are not behaving like I want them to. This is an example:
import tkinter as tk
from tkinter import ttk
win = tk.Tk()
v = tk.StringVar()
def setText(word):
v.set(word)
a = ttk.Button(win, text="plant", command=setText("plant"))
a.pack()
b = ttk.Button(win, text="animal", command=setText("animal"))
b.pack()
c = ttk.Entry(win, textvariable=v)
c.pack()
win.mainloop()
So far, when I am able to compile, the click does nothing.
You might want to use insert method. You can find the documentation for the Tkinter Entry Widget here.
This script inserts a text into Entry. The inserted text can be changed in command parameter of the Button.
from tkinter import *
def set_text(text):
e.delete(0,END)
e.insert(0,text)
return
win = Tk()
e = Entry(win,width=10)
e.pack()
b1 = Button(win,text="animal",command=lambda:set_text("animal"))
b1.pack()
b2 = Button(win,text="plant",command=lambda:set_text("plant"))
b2.pack()
win.mainloop()
If you use a "text variable" tk.StringVar(), you can just set() that.
No need to use the Entry delete and insert. Moreover, those functions don't work when the Entry is disabled or readonly! The text variable method, however, does work under those conditions as well.
import Tkinter as tk
...
entry_text = tk.StringVar()
entry = tk.Entry( master, textvariable=entry_text )
entry_text.set( "Hello World" )
You can choose between the following two methods to set the text of an Entry widget. For the examples, assume imported library import tkinter as tk and root window root = tk.Tk().
Method A: Use delete and insert
Widget Entry provides methods delete and insert which can be used to set its text to a new value. First, you'll have to remove any former, old text from Entry with delete which needs the positions where to start and end the deletion. Since we want to remove the full old text, we start at 0 and end at wherever the end currently is. We can access that value via END. Afterwards the Entry is empty and we can insert new_text at position 0.
entry = tk.Entry(root)
new_text = "Example text"
entry.delete(0, tk.END)
entry.insert(0, new_text)
Method B: Use StringVar
You have to create a new StringVar object called entry_text in the example. Also, your Entry widget has to be created with keyword argument textvariable. Afterwards, every time you change entry_text with set, the text will automatically show up in the Entry widget.
entry_text = tk.StringVar()
entry = tk.Entry(root, textvariable=entry_text)
new_text = "Example text"
entry_text.set(new_text)
Complete working example which contains both methods to set the text via Button:
This window
is generated by the following complete working example:
import tkinter as tk
def button_1_click():
# define new text (you can modify this to your needs!)
new_text = "Button 1 clicked!"
# delete content from position 0 to end
entry.delete(0, tk.END)
# insert new_text at position 0
entry.insert(0, new_text)
def button_2_click():
# define new text (you can modify this to your needs!)
new_text = "Button 2 clicked!"
# set connected text variable to new_text
entry_text.set(new_text)
root = tk.Tk()
entry_text = tk.StringVar()
entry = tk.Entry(root, textvariable=entry_text)
button_1 = tk.Button(root, text="Button 1", command=button_1_click)
button_2 = tk.Button(root, text="Button 2", command=button_2_click)
entry.pack(side=tk.TOP)
button_1.pack(side=tk.LEFT)
button_2.pack(side=tk.LEFT)
root.mainloop()
Your problem is that when you do this:
a = Button(win, text="plant", command=setText("plant"))
it tries to evaluate what to set for the command. So when instantiating the Button object, it actually calls setText("plant"). This is wrong, because you don't want to call the setText method yet. Then it takes the return value of this call (which is None), and sets that to the command of the button. That's why clicking the button does nothing, because there is no command set for it.
If you do as Milan Skála suggested and use a lambda expression instead, then your code will work (assuming you fix the indentation and the parentheses).
Instead of command=setText("plant"), which actually calls the function, you can set command=lambda:setText("plant") which specifies something which will call the function later, when you want to call it.
If you don't like lambdas, another (slightly more cumbersome) way would be to define a pair of functions to do what you want:
def set_to_plant():
set_text("plant")
def set_to_animal():
set_text("animal")
and then you can use command=set_to_plant and command=set_to_animal - these will evaluate to the corresponding functions, but are definitely not the same as command=set_to_plant() which would of course evaluate to None again.
One way would be to inherit a new class,EntryWithSet, and defining set method that makes use of delete and insert methods of the Entry class objects:
try: # In order to be able to import tkinter for
import tkinter as tk # either in python 2 or in python 3
except ImportError:
import Tkinter as tk
class EntryWithSet(tk.Entry):
"""
A subclass to Entry that has a set method for setting its text to
a given string, much like a Variable class.
"""
def __init__(self, master, *args, **kwargs):
tk.Entry.__init__(self, master, *args, **kwargs)
def set(self, text_string):
"""
Sets the object's text to text_string.
"""
self.delete('0', 'end')
self.insert('0', text_string)
def on_button_click():
import random, string
rand_str = ''.join(random.choice(string.ascii_letters) for _ in range(19))
entry.set(rand_str)
if __name__ == '__main__':
root = tk.Tk()
entry = EntryWithSet(root)
entry.pack()
tk.Button(root, text="Set", command=on_button_click).pack()
tk.mainloop()
e= StringVar()
def fileDialog():
filename = filedialog.askopenfilename(initialdir = "/",title = "Select A
File",filetype = (("jpeg","*.jpg"),("png","*.png"),("All Files","*.*")))
e.set(filename)
la = Entry(self,textvariable = e,width = 30).place(x=230,y=330)
butt=Button(self,text="Browse",width=7,command=fileDialog).place(x=430,y=328)

mutliple entry widgets and one button widget in tkinter

I am trying to make two entry boxes and an 'OK' button that processes both entries:
I don't understand the init() function or the reason 'self' has to be include in doing this.
I want to access the entries outside this class and mainloop. The entries should be string type.
from tkinter import *
root = Tk()
root.geometry("550x145+500+300")
class myDose:
def __init__(self):
l1 = Label(text="Enter the prescription dose to three (3) decimal places [cGy]").pack()
e1 = Entry(root)
e1.pack()
l2 = Label(text="Save new excel file as...").pack()
e2 = Entry(root)
e2.pack()
l3 = Label(text="click 'OK', and then close window.").pack()
b = Button(root, text="OK", command=submit)
b.pack()
def submit(self):
print(e1.get())
print(e2.get())
D = myDose()
root.mainloop()
#I want to access the entries, e1 and e2, outside the mainloop
print(D.e1)
print(D.e2)
The problem is that mainloop doesn't exit until the root window is destroyed. Once the root window is destroyed you can no longer access the widgets inside the root window. You can, however, access non-widget attributes of the object.
If that is what you want -- to destroy the window and have access to the values in the widget -- you need to save the values before destroying the window.
For example, in your submit you could save the values like this:
def submit(self):
self.data = {"e1": self.e1.get(), "e2": self.e2.get()}
Once the window is destroyed, you still have a reference to the application object, so you can still access the non-widget attributes of the class:
...
D = myDose(root)
root.mainloop()
print D.data["e1"]
print D.data["e2"]
Based on comments to the original question, you mention that later in your code you'll need to use askopenfilename. if that is the case, you should reconsider the need to run code after mainloop. Tkinter is designed to have a root window created exactly once, and for the program to exit immediately after mainloop exits.
You can write programs however you want, but I think your code will be easier to maintain and modify over the long run if you stick to normal design patterns. Move your "real" code into a method of the app class and that will eliminate all of the problems associated with widgets being destroyed.
Your approach is fundamentally flawed. mainloop() does what it says: starts a loop. The lines that follow it will not be executed until that loop is over - meaning, until your application has closed. Since you already have a class with Tkinter stuff in it, just finish the move to an OO approach:
from tkinter import *
class myDose:
def __init__(self, root):
root.geometry("550x145+500+300")
self.l1 = Label(root, text="Enter the prescription dose to three (3) decimal places [cGy]")
self.l1.pack()
self.e1 = Entry(root)
self.e1.pack()
self.l2 = Label(root, text="Save new excel file as...")
self.l2.pack()
self.e2 = Entry(root)
self.e2.pack()
self.l3 = Label(root, text="click 'OK', and then close window.")
self.l3.pack()
self.b = Button(root, text="OK", command=self.submit)
self.b.pack()
def submit(self):
print(self.e1.get())
print(self.e2.get())
root = Tk()
D = myDose(root)
root.mainloop()
I recommend looking more thoroughly into how fundamental concepts like objects and classes work before creating a full GUI application.
Here's a quick fix.
The problem with both your version and TigerhawkT3's modified version is that when you close the window the Entry widgets are no longer valid, so you need to save the data before closing the window. One way to do that is to attach Tkinter StringVars to the Entry widgets. The values in the StringVars will be available after the window is closed.
import tkinter as tk
class myDose:
def __init__(self, root):
root.geometry("550x145+500+300")
l = tk.Label(root, text="Enter the prescription dose to three (3) decimal places [cGy]")
l.pack()
self.dosage = tk.StringVar()
e = tk.Entry(root, textvariable=self.dosage)
e.pack()
l = tk.Label(root, text="Save new excel file as...")
l.pack()
self.savename = tk.StringVar()
e = tk.Entry(root, textvariable=self.savename)
e.pack()
l = tk.Label(root, text="Enter the data and then close window.")
l.pack()
root.mainloop()
D = myDose(tk.Tk())
print(D.dosage.get())
print(D.savename.get())
Note that I've changed the import statement. It's much cleaner to not do
from tkinter import *
as that clutters your namespace with over 100 names, leading to potential name collisions.
The __init__() function is a method of the myDose class. A class's __init__() method is called when an instance of that class is created. In your code that happens when you do
D = myDose()
In my version it's when this line is executed:
D = myDose(tk.Tk())
which first creates the Tkinter root window which is then passed to myDose; that root window is then available to my __init__() as its root argument.
The name self is used inside the class to refer to the current class instance. Outside the class we need to use the actual name we assigned the instance to, which in this case is D.

How can I concatenate a string with a Tkinter Variable?

I've got a program that counts clicks of a button and displays them on a label. Instead of just showing the number, how can I add "Number of clicks: " before the value displayed? Preferably within the Label's widget arguments/options where textvariable=Total is defined.
Total = IntVar()
def Clicked():
Total.set(Total.get() + 1)
total = Label(root, textvariable=Total).pack()
click = Button(root, command=Clicked).pack()
Use separate variables to keep track of the number of clicks, and the string that represents the number of clicks.
from Tkinter import *
def Clicked():
global amount
amount += 1
Total.set("Number of clicks: {}".format(amount))
root = Tk()
Total = StringVar()
amount = 0
Label(root, textvariable=Total).pack()
Button(root, command=Clicked).pack()
root.mainloop()
Incidentally, never pack a widget and assign it to something on the same line - your variable will always be None. See Tkinter: AttributeError: NoneType object has no attribute get for more information.
tkinter requires that you use its own variable types for textvariable, so I would use a StringVar as your textvariable (you can update the text property of some items directly, but different tkinter objects may have different methods for doing this, and it can get confusing). Here's how you can update a StringVar to show your counts.
root = tk.Tk()
tk.IntVar(value='1')
sv = tk.StringVar()
sv.set('clicks = ' + str(iv.get()))
>>> sv.get()
'clicks = 1'

Categories