python tkinter append list - python

I am an amateur python programer with 2 months of experience. I am trying to write a GUI to-do list through tkinter. The actual placement of the buttons are not important. I can play around with those after. I need some help with displaying the appended item to the list. In the program, it updates well on the digit, but it won't print onto the list. I double checked it on the console and it says "tkinter.StringVar object at 0x102fa4048" but didn't update the actual list. What I need help is how can I update the list Main_Q on my the label column? Much appreciate some direction and coding help. Thanks.
Main_Q =["read","clean dishes", "wash car"]
from tkinter import*
root=Tk(className="total tasks in the Q")
#formula
def update():
global Main_Q
a=len(Main_Q)
num.set(a)
def add2list():
Main_Q.append(name)
a=len(Main_Q)
num.set(a)
print (Main_Q)
#output
num=StringVar()
y=Label(root, textvariable=num).grid(row=0, column=1)
#input
name=StringVar()
b=Entry(root, textvariable=name).grid(row=7,column=0)
#buttons
z=Button(root, text="update", command=update).grid(row=7, column=2)
add2list=Button(root,text="add", command=add2list).grid(row=7,
column=1)
r = 0
for c in Main_Q:
Label(text=c, relief=RIDGE,width=15).grid(row=r,column=0)
r = r + 1
root.mainloop()

Your problem is that your for loop which build up your labels doesnt get called after each time you have entered a new "task". To easily fix this you can move this loop into your update function.
If you want to prevent of looping through widget everytime you can create a new list with all widgets which already have been created:
createdWidgets = []
widgetsQueue = []
In your update function you than have to iterate through the widgetsQueue (widgetsQueue.pop() for instance), create the widgets and append the widget to the createdWidgetes list.
def update():
global Main_Q
r = 0
for c in Main_Q:
Label(text=c, relief=RIDGE,width=15).grid(row=r,column=0)
r += 1 # shorthand for r = r + 1
Some addition notes:
for the entry it is easier to seperate the definition and placement:
b = Entry(root)
b.grid(row=7,column=0)
because than Entry() returns its instance and you can use it to get the text:
b.get()
if you go shopping do you throw everything into one bag ?
from tkinter import *
does axactly that(in this case the globals() variable would be the bag).If you want to read more about that Importing Python Modules. To prevent that and shorten the amount of letters to type:
import tkinter as t # or tk
root = t.Tk()
*But for sure, if you just want a small program its okay.

Design:
To resolve your problem, you need to design this simple solution:
retrieve the text of the Tkinter.Entry widget using get() method.
add the text you got in 1 to Main_Q using append() method.
bind the button that updates on click both Main_Q and your GUI using command method.
create a new Tkinter.Label widget, set its text to the value you got in 1 and increment its corresponding row in the GUI.
I prefer to organize your code within a class that contains a constructor where Main_Q is initialized so that we call initialize_user_interface() to initialize the GUI with its three elements:
def __init__(self, parent):
Tkinter.Frame.__init__(self, parent)
self.parent = parent
self.Main_Q = ["read", "clean dishes", "wash car"]
self.r = 0 # position of the row of each label
self.initialize_user_interface()
The method initialize_user_interface() does what its name says. We mainly bind the function update_gui() that inserts a new label with the text set to what the user types in Tkinter.Entry widget using command = self.update_gui
ef initialize_user_interface(self):
self.parent.title("Update GUI")
self.parent.grid_rowconfigure(0, weight = 1)
self.parent.grid_columnconfigure(0, weight = 1)
for e in self.Main_Q:
Tkinter.Label(self.parent, anchor = Tkinter.W, text = e).grid(row = self.r, sticky = Tkinter.W)
self.r+=1
self.entry_text = Tkinter.Entry(self.parent)
self.entry_text.grid(row = 0, column = 1)
self.button_update = Tkinter.Button(self.parent, text = "Update", command = self.update_gui).grid(row = 1, column = 1, sticky = Tkinter.E)
Finally, nothing is simpler than update_gui() function:
def update_gui(self):
self.r+=1 # increment the row reserved to the new label
self.Main_Q.append(self.entry_text.get())
Tkinter.Label(self.parent, anchor = Tkinter.W, text = self.entry_text.get()).grid(row = self.r, sticky = Tkinter.W)
Programming the application:
Here is the full program:
'''
Created on Mar 11, 2016
#author: Bill BEGUERADJ
'''
import Tkinter
class Begueradj(Tkinter.Frame):
def __init__(self, parent):
Tkinter.Frame.__init__(self, parent)
self.parent = parent
self.main_queue = ["read", "clean dishes", "wash car"]
self.r = 0
self.initialize_user_interface()
def initialize_user_interface(self):
self.parent.title("Update GUI")
self.parent.grid_rowconfigure(0, weight = 1)
self.parent.grid_columnconfigure(0, weight = 1)
for e in self.main_queue:
Tkinter.Label(self.parent, anchor = Tkinter.W, text = e).grid(row = self.r, sticky = Tkinter.W)
self.r+=1
self.entry_text = Tkinter.Entry(self.parent)
self.entry_text.grid(row = 0, column = 1)
self.button_update = Tkinter.Button(self.parent, text = "Update", command = self.update_gui).grid(row = 1, column = 1, sticky = Tkinter.E)
def update_gui(self):
self.r+=1
self.main_queue.append(self.entry_text.get())
Tkinter.Label(self.parent, anchor = Tkinter.W, text = self.entry_text.get()).grid(row = self.r, sticky = Tkinter.W)
def main():
root = Tkinter.Tk()
b = Begueradj(root)
root.mainloop()
if __name__ == "__main__":
main()
Demo:
Here is a screenshot of the running program:
Note:
I coded the previous program using Python 2.7, so if you want to test it, please change Tkinter to tkinter. Everything else remains the same.

Related

Getting variables from a tkinter subwindow

I am currently writing a script in python that takes in user data at the beginning of the script which may need to be updated later.
The initial user data is input through a tkinter window which is then passed along to the lower functions. Later in the script, if the information is detected to be bad, I want to alert the user that the info was bad and prompt them to re-input the data without having to start the program from the beginning.
I was attempting to achieve this by adding in a sub window function that would be called whenever the data needed to be re-input, take the new user input, and then pass it up back up to the function that called it. The code below roughly shows what I'm trying to do:
import tkinter as tk
from tkinter import *
def gui():
window = tk.Tk()
window.geometry('300x200')
L1 = tk.Label(window, text = 'This is a test')
L1.grid(column = 1, row = 0)
L2 = tk.Label(window, text = 'Token')
L2.grid(column = 0, row = 1)
E1 = tk.Entry(window, width = 25)
E1.grid(column = 1, row = 1)
B1 = tk.ttk.Button(window, text = 'Run', command = lambda: shell(window, E1.get()))
B1.grid(column = 1, row = 2)
window.mainloop()
def shell(window, val):
print('Old Val:', val)
val = subwindow_test(window)
print('New Val:', val)
def subwindow_test(window):
def subwinfunc(window, val):
if val == None or val == '':
print('Enter something')
else:
window.sub_win.destroy()
return
window.sub_win = tk.Toplevel(window)
window.sub_win.geometry('300x200')
L1 = tk.Label(window.sub_win, text = 'this is a subwindow')
L1.grid(column = 1, row = 0)
L2 = tk.Label(window.sub_win, text = 'New Token')
L2.grid(column = 0, row = 1, sticky = 'E')
var = StringVar()
E1 = tk.Entry(window.sub_win, width = 25, textvariable = var)
E1.grid(column = 1, row = 1)
B1 = tk.ttk.Button(window.sub_win, text = 'Return', command = lambda: subwinfunc(window, var.get()))
B1.grid(column = 1, row = 2)
window.sub_win.mainloop()
return var.get()
gui()
The idea is to pass the window down to the subwindow_test function, spawn a sub window using tk.Toplevel, ask the user for new data, then destroy the sub window and pass the newly entered data back up to the calling function.
In theory, this would prevent me from having to restart the code from the beginning as this subwindow_test function could be run from anywhere in the code.
The issue is that after subwinfunc returns after destroying window.sub_win, the code hangs until the original window object (the one created in the gui function) is closed. Also, removing the return line from subwinfunc does not change this.
Is there a way to get around this issue?
I have tried using a separate window (An entirely different window, not a sub window of the one created in gui), but the same problem comes up.
It is also not possible, as far as I can tell, to pass the sub window object back up to the calling function and close it there, as subwindow_test cannot return until it breaks from window.sub_win.mainloop() (If the return comes before the mainloop(), the window will never appear) .
Additionally, the only way that I could find to get the value to return at all is to use a StringVar. I would rather try and avoid using global variables, and if I had to guess, I would say that the return val.get() is most likely the root of the problem. However because I can't find another way to pass variables up from this function, I'm stumped.
You should not be calling mainloop more than once. Tkinter provides the ability to wait for a window to be closed before continuing with the wait_window method.
Here is a very simple example that shows how to create a popup dialog that won't return until the user clicks the ok or cancel button.
def get_input():
value = None
def do_ok(event=None):
nonlocal value
value = entry.get()
top.destroy()
def do_cancel():
nonlocal value
value = None
top.destroy()
top = tk.Toplevel()
entry = tk.Entry(top)
ok = tk.Button(top, text="ok", command=do_ok)
cancel = tk.Button(top, text="cancel", command=do_cancel)
entry.bind("<Return>", do_ok)
entry.pack(side="top", fill="x")
ok.pack(side="right")
cancel.pack(side="left")
top.wait_window(top)
return value

How to show data in tkinter module dynamically-Python

I am new to GUI creation in python using Tkinter. I found a script in stack overflow which gives a GUI in which i can i add data manually. I tried to add data dynamically thus automating the whole process using following code:
def insert_data(self):
"""
Insertion method.
"""
for l in range(10):
time.sleep(3)
print(i)
self.treeview.insert('', 'end', text="Item_"+str(self.i)+str(l), values=(self.dose_entry.get()+" mg", self.modified_entry.get()))
# Increment counter
self.i = self.i + 1
This insert data snippet gives proper output and even shows data in the GUI. But data inserted in gui shows up after execution of for loop. TO be more elaborate .:
print(i) in the above for loop print 0 to 9 in IDLE but during that execution the data shown in the IDLE does not appear simultaneously in GUI. It prints data in IDLE until the loop is not finished and the GUI would not show any data until the loop is finished and then it shows all the numbers all at once.
I want to show data in GUI as it prints in IDLE i.e. for every iteration data should be visible in GUI the as it prints in IDLE. Example:
In first iteration for i = 0
print(i) will print '0'.I want to show it in GUI as it prints in IDLE. Following is my code:
import tkinter
from tkinter import ttk
import urllib
import requests
import time
class Begueradj(tkinter.Frame):
'''
classdocs
'''
def __init__(self, parent):
'''
Constructor
'''
tkinter.Frame.__init__(self, parent)
self.parent=parent
self.initialize_user_interface()
def initialize_user_interface(self):
"""Draw a user interface allowing the user to type
items and insert them into the treeview
"""
self.parent.title("Canvas Test")
self.parent.grid_rowconfigure(0,weight=1)
self.parent.grid_columnconfigure(0,weight=1)
self.parent.config(background="lavender")
# Define the different GUI widgets
self.dose_label = tkinter.Label(self.parent, text = "Dose:")
self.dose_entry = tkinter.Entry(self.parent)
self.dose_label.grid(row = 0, column = 0, sticky = tkinter.W)
self.dose_entry.grid(row = 0, column = 1)
self.modified_label = tkinter.Label(self.parent, text = "Date Modified:")
self.modified_entry = tkinter.Entry(self.parent)
self.modified_label.grid(row = 1, column = 0, sticky = tkinter.W)
self.modified_entry.grid(row = 1, column = 1)
self.submit_button = tkinter.Button(self.parent, text = "Insert", command = self.insert_data)
self.submit_button.grid(row = 2, column = 1, sticky = tkinter.W)
self.exit_button = tkinter.Button(self.parent, text = "Exit", command = self.parent.quit)
self.exit_button.grid(row = 0, column = 3)
# Set the treeview
self.tree = ttk.Treeview( self.parent, columns=('Dose', 'Modification date'))
self.tree.heading('#0', text='Item')
self.tree.heading('#1', text='Dose')
self.tree.heading('#2', text='Modification Date')
self.tree.column('#1', stretch=tkinter.YES)
self.tree.column('#2', stretch=tkinter.YES)
self.tree.column('#0', stretch=tkinter.YES)
self.tree.grid(row=4, columnspan=4, sticky='nsew')
self.treeview = self.tree
# Initialize the counter
self.i = 0
def insert_data(self):
"""
Insertion method.
"""
for l in range(10):
time.sleep(3)
print(l)
self.treeview.insert('', 'end', text="Item_"+str(l), values=(self.dose_entry.get()+" mg", self.modified_entry.get()))
# Increment counter
self.i = self.i + 1
def main():
root=tkinter.Tk()
d=Begueradj(root)
root.mainloop()
if __name__=="__main__":
main()
When you use time.sleep(3) the execution suspends, and also any updates to the GUI. The GUI update tasks are in a queue and they will be handled as soon as the application has time on its hands. And there is no time to spare until the loop ends.
To force the treeview to update you can insert the line:
self.treeview.update_idletasks() # Force widget update
after you have added the treeview item.

Python Tkinter - Name of objects created by function

I am creating a set of buttons via this function:
from tkinter import *
from random import randint
window = Tk()
window.title("Test")
window.geometry('200x200')
color = ["red","blue","green","yellow","black","purple","orange"]
RandInt = 0
j = 0
h = 0
def ButtonDef(xvar = 0,yvar = 0):
btn = Button(command =lambda:[RandomColor()])
btn.grid()
btn.place(x = xvar*50, y = yvar*50, width = 50, height = 50)
def RandomColor():
RandInt = randint (0,6)
btn.configure(bg = color[RandInt])
while j in range (4):
i = 0
j += 1
while i in range (4):
ButtonDef(i,h)
i += 1
if i == 4:
h += 1
window.mainloop()
However, my RandomColor() function is changing only the color of the very button i have pressed - that is fun too, but i wonder how i can make it randomly change the color of all buttons. When being created by a function, i would have guessed that all buttons that were created are named "btn" since thats the only name i have given them.
How could i address all (or one specific) buttons, out of a group of created-by-function buttons?
Or to put it simply, what name do all of those buttons have? Do they share the name "btn"? Are they assigned a hidden ID?
The reason behind your problem:
The problem is that when this line is executed: btn = Button(command =lambda:[RandomColor()]) by the end of the loop, you get a reference only to the last button which is created. You loose reference to other buttons.
Solution:
You can overcome this problem if you rely on winfo_children().
You have to do 2 steps to fix your issue:
First of all, change: btn = Button(command =lambda:[RandomColor()]) to btn = Button(window, command=lambda:[RandomColor()]). That simply means we attach each created button to a parent widget called window.
Then all that you need to change is RandomColor() function body as follows:
def RandomColor():
for child in window.winfo_children():
child.configure(bg=color[randint(0,6)])
Demo:
This solves your problem but your code is not clean. You can ask for suggestions to improve your code on Code Review website.
EDIT:
Here is a solution for the scenario you described in the comment.
Note that I had to create code from scratch, cleaner one (I know you started only today, so I am not blaming you). In this code, I keep reference for every button I create:
import tkinter as tk
import random
class ChangeBottomRightColor(tk.Frame):
def __init__(self, master):
self.master = master
tk.Frame.__init__(self, self.master)
self.__colors = ["red","blue","green","yellow","black","purple","orange"]
self.configure_gui()
self.create_widgets()
def configure_gui(self):
pass
def create_widgets(self):
self.create_buttons()
def create_buttons(self):
self.buttons = {}
c = 0
for i in range(4):
for j in range(4):
self.buttons['button{}'.format(c)] = tk.Button(self.master)
self.buttons['button{}'.format(c)].grid(row=i, column=j)
self.buttons['button{}'.format(c)].config(width=3, height=3)
self.buttons['button{}'.format(c)].config(command=self.change_bottom_right_button_color)
c += 1
def get_random_color(self):
return random.choice(self.__colors)
def change_bottom_right_button_color(self):
self.buttons['button{}'.format(15)].config(bg=self.get_random_color())
if __name__ == '__main__':
root = tk.Tk()
main_app = ChangeBottomRightColor(root)
root.mainloop()
Demo:
Let try
btn = []
for i in range(16):
btn.append(Button(window))
it will create an array of button. So you can access by btn[i].configure(command=lambda:[RandomColor()]) or something else.

Python Tkinter Calculator with shift function and Classes

I am somewhat new to Python, and extremely new to class structures. I am trying to build a calculator GUI with buttons that insert a value into an Entry display. My calculator works without using class structures (the functional code is below), but I'm trying to make the same code work while incorporating the classes. My reason for doing this is to add a "shift" function that changes the values of text on the buttons, and what values (same as the text) they insert into the display. The values as you can see below are class variables, and in order to change the text on the buttons, the shift button is pressed which changes the value of the variables (I have gotten this to work in a non-tkinter file without buttons, etc). The problem is I am getting the GUI with an Entry and two Buttons (good) and '1' in the Entry. (not good) The buttons also don't do anything. Here is my code: (The one that works first)
from tkinter import *
import parser
import math
root = Tk()
root.title('Calculator')
displayb = Entry(root)
displayb.grid(row = 3, columnspan = 6, ipady = 7, ipadx = 100)
def display(e):
global i
i = len(displayb.get())
displayb.insert(i,e)
one = Button(root, text = '1', width = 10, height = 1, command = lambda : display(1))
one.grid(row = 4, column = 0)
root.mainloop()
New Code:
from tkinter import *
import parser
class Calc:
text1 = '1'
shft = 'shft1'
def __init__(self,master):
self.master = master
self.displaya = Entry(master)
self.displaya.grid(row = 1, columnspan = 6)
self.one = Button(master, text = Calc.text1, command = self.display((Calc.text1)))
self.one.grid(row = 4, column = 1)
self.update = Button(master, text = Calc.shft, command = self.update((Calc.shft[4:5])))
self.update.grid(row = 0, column = 0)
def update(self, mode):
if mode == 1:
Calc.text1 = 'A'
Calc.shft = 'shft2'
else:
Calc.text1 = '1'
Calc.shft = 'shft1'
return
def display(self,e):
i = len(self.displaya.get())
self.displaya.insert(i,e)
return
root = Tk()
calc = Calc(root)
root.mainloop()
Can you help me find a solution that makes my code work properly? Thanks
Edit: I attempted using StringVar() and .set, but on the interface, the shift button, instead of saying shft1 or even shft2, said "PY_VAR1". The one button said 1, but inserted "PY_VAR0" to the display. When pressed, the shift button raised this error:
Exception in Tkinter callback
Traceback (most recent call last):
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/tkinter/__init__.py", line 1699, in __call__
return self.func(*args)
File "/Users/ryanflynn/Classesmod.py", line 13, in <lambda>
self.update_button = Button(master, text = self.shft, command = lambda: self.update(int(self.shft[4:5]))) # use int()
TypeError: 'StringVar' object is not subscriptable
Your code has few errors in it, here is a list of all of them and a modified version of your code:
1st: Your button has the same name as one of your function, change it to a unique name.
2nd: Don't use the class's namespace unless it's necessary, use self instead.
3rd: You only need one set of brackets when calling a function, else it's redundant.
4th: Use lambda or functools.partial to set a command that has arguments.
5th: You can use END(since you did from tkinter import *, otherwise tkinter.END) to specify the ending instead of doing len(self.displaya.get()).
6th: Calc.shft[4:5] returns a string not an int. So in order to get your result, use int(Calc.shft[4:5]).
7th: I didn't see you using parser, so I don't see why you need it.
8th: Don't add a return at the end of a function that returns nothing, it's redundant. Since that's the default for all functions.
This is a modified(working) version of your code:
from tkinter import *
class Calc:
def __init__(self,master):
self.text1 = '1' # make them self's not Calc's
self.shft = 'shft1'
self.master = master
self.displaya = Entry(master)
self.displaya.grid(row = 1, columnspan = 6)
self.one = Button(master, text = self.text1, command = lambda: self.display(self.text1)) # use lambda and single brackets
self.one.grid(row = 4, column = 1)
self.update_button = Button(master, text = self.shft, command = lambda: self.update(int(self.shft[4:5]))) # use int()
self.update_button.grid(row = 0, column = 0)
def update(self, mode):
if mode == 1:
self.text1 = 'A'
self.shft = 'shft2'
else:
self.text1 = '1'
self.shft = 'shft1'
def display(self,e):
self.displaya.insert(END,e) # use END instead
root = Tk()
calc = Calc(root)
root.mainloop()
This code does work but the button text shouldn't change
EDIT:
If you want to use string variable to make the text change, you will need a StringVar as well:
from tkinter import *
class Calc:
def __init__(self,master):
self.master = master
self.displaya = Entry(master)
self.displaya.grid(row = 1, columnspan = 6)
self.text1 = StringVar(value = '1') # change to this
self.shft = StringVar(value = 'shft1')
self.one = Button(master, textvariable = self.text1, command = lambda: self.display(self.text1.get())) # Use .get()
self.one.grid(row = 4, column = 1)
self.update_button = Button(master, textvariable = self.shft, command = lambda: self.update(int(self.shft.get()[4:5])))
self.update_button.grid(row = 0, column = 0)
def update(self, mode):
if mode == 1:
self.text1.set('A') # set the variable
self.shft.set('shft2')
else:
self.text1.set('1')
self.shft.set('shft1')
def display(self,e):
self.displaya.insert(END,e) # use END instead
root = Tk()
calc = Calc(root)
root.mainloop()
When you provide a function to the command argument, you need to provide the actual function, and not the result of calling the function. In other words, you can't have () on the end. One hacky way of handling this is to use lambda as you did earlier:
self.one = Button(master, text = Calc.text1, command = lambda: self.display((Calc.text1)))
A slightly less hacky way is to use functools.partial:
from functools import partial
# ...
self.one = Button(master, text = Calc.text1, command = partial(self.display, Calc.text1))
IMO the best way is to make a real method to use.
self.one = Button(master, text = Calc.text1, command = self.one_pushed)
# ...
def one_pushed(self):
self.display(Calc.text1)
As a side note: tkinter will automatically calculate the end point for you if you pass 'end' as the position:
def display(self,e):
self.displaya.insert('end',e)

Assigning values to buttons in Tkinter and appending them in a list (Python)

Beginner here, so sorry in advance for any stupid questions =] I have huge problems making the program i'm working with graphical though. The player gets to choose between 4 options, "study, "exam","sleep" and "party". Depending on what he chooses, I want the choice to be appended to the list that keeps track of all choices. This is no problem if I don't make it graphical, but when I try it graphical I'm just lost. I've tried creating 4 buttons, and then just try to attatch a value to each button (the value would be "sleep","study","exam", etc) and then use the StringVar()-function, but doesn't seem to work for me :) This is what i've been writing so far (it's probably really bad :s):
from tkinter import *
list1 = ['sleep','party','sleep']
root = Tk()
root.title("test!")
root.geometry("500x200")
class Application(Frame):
def __init__(self,master):
super(Application,self).__init__(master)
self.grid()
self.create_widgets()
def create_widgets(self):
Label(self,
text = "Welcome to the game!"
).grid(row = 0, column = 0, sticky = W)
self.favorite = StringVar()
self.favorite.set(None)
self.btn1 = Button(self, text = "study", command =self.update_text, value = "study")
self.btn1.grid()
self.btn2 = Button(self, text = "party", command = self.update_text, value = "party")
self.btn2.grid()
self.btn3 = Button(self, text = "exam", command = self.update_text, value = "exam")
self.btn3.grid()
self.btn4 = Button(self, text = "sleep", command = self.update_text, value = "sleep")
self.btn4.grid()
def update_text(self):
message = "Your choice was "
message += value
print(message)
lista1.append(value)
app = Application(root)
app.grid()
root.mainloop()
Thanks a lot and sorry if this is really basic
Send the value as argument. Try this & then tell if you encounter any problem

Categories