I'm trying to make a small application that displays words from a list one-by-one. Below is my code:
from Tkinter import *
master = Tk()
master.title('Serial Position Effect')
myArray = ['book','chair','door']
def cycle(myArray, k):
t.set(myArray[k])
t = StringVar()
w = Label(master, height=3, width=15, font=('Helvetica', 118), textvariable = t)
for n in range(0,3):
cycle(myArray, n)
w.pack()
master.mainloop()
I was expecting the label to show book, chair, and door, but it only showed door on the window. I tried to modify the for loop like:
for n in range(0,3):
for x in range(0,10000):
cycle(myArray, n)
Because I thought the problem was that the program was cycling through the words too quickly. But with this modified code, the application, again, only showed door after a short delay. (The delay was probably because it was counting up to 10000.)
Finally I approached this a little differently - a little less efficient but I thought by coding it like this I would be able to identify the problem in my original code:
from Tkinter import *
master = Tk()
master.title('Serial Position Effect')
#myArray = ['book','chair','door']
#def cycle(myArray, k):
# t.set(myArray[k])
t = StringVar()
w = Label(master, height=3, width=15, font=('Helvetica', 118), textvariable = t)
for n in range(0,10000)
t.set('book')
for n in range(0,10000)
t.set('chair')
for n in range(0,10000)
t.set('door')
w.pack()
master.mainloop()
Again, the window only displayed door.
I'm new to GUI programming with Python and Tkinter. I would really appreciate it if someone could help me out with this issue.
Thanks (=
The window won't even show up until you call mainloop, so calling set thousands of times won't have any visible effect, except for the very last call. You should use after to register callback functions that change the label some seconds in the future.
from Tkinter import *
master = Tk()
master.title('Serial Position Effect')
myArray = ['book','chair','door']
def cycle(myArray, k):
t.set(myArray[k])
t = StringVar()
w = Label(master, height=3, width=15, font=('Helvetica', 118), textvariable = t)
w.pack()
cycle(myArray,0)
master.after(1000, lambda: cycle(myArray, 1))
master.after(2000, lambda: cycle(myArray, 2))
master.mainloop()
You can also have the after-registered function call after itself, if you want the words to cycle forever.
from Tkinter import *
master = Tk()
master.title('Serial Position Effect')
myArray = ['book','chair','door']
cur_idx = -1
def cycle():
global cur_idx
cur_idx = (cur_idx + 1) % len(myArray)
t.set(myArray[cur_idx])
master.after(1000, cycle)
t = StringVar()
w = Label(master, height=3, width=15, font=('Helvetica', 118), textvariable = t)
w.pack()
cycle()
master.mainloop()
Related
I'm trying to create a GUI and I am having trouble getting read only entry fields to insert a value. I've tried using a text value and this didn't insert anything into the field.
here is the function:
def calculateFinalCMD():
global planetInfo
global getDestinationNum
displayfc1.grid(row=14, column=2)
displayfc1.insert(0, ((100 - int(crewInfo[0][1])) * planetInfo[getDestinationNum][1]))
displayfc2.grid(row=15, column=2)
displayfc2.insert(0, ((100 - int(crewInfo[1][1])) * planetInfo[getDestinationNum][1]))
displayfc3.grid(row=16, column=2)
displayfc3.insert(0, ((100 - int(crewInfo[2][1])) * planetInfo[getDestinationNum][1]))
displayfc4.grid(row=17, column=2)
displayfc4.insert(0, ((100 - int(crewInfo[3][1])) * planetInfo[getDestinationNum][1]))
displayms1.grid(row=18, column=2)
displayms1.insert(0, ((150 - int(crewInfo[4][1])) * planetInfo[getDestinationNum][1]))
displayms2.grid(row=19, column=2)
displayms2.insert(0, ((150 - int(crewInfo[5][1])) * planetInfo[getDestinationNum][1]))
Here are the entry fields which are separate from the function, getDestinationNum is updated by another entry field earlier in the code.
getDestinationNum = 0
displayfc1 = Entry(root, state="readonly")
displayfc2 = Entry(root, state="readonly")
displayfc3 = Entry(root, state="readonly")
displayfc4 = Entry(root, state="readonly")
displayms1 = Entry(root, state="readonly")
displayms2 = Entry(root, state="readonly")
Any ideas on how anything could be changed or if the code is wrong? thanks!
This is not a minimal reproducible example. Something like this would have better explained your problem.
from tkinter import Tk, Entry
root = Tk()
my_entry = Entry(root, state='readonly')
my_entry.grid(row=0, column=0)
my_entry.insert(0, 500) # does not work
root.mainloop()
The problem is that your Entry widget is read only and cannot be updated.
We can get around that by waiting until we have set a value and use the configure method to update the state after the fact.
from tkinter import Tk, Entry
root = Tk()
my_entry = Entry(root)
my_entry.grid(row=0, column=0)
my_entry.insert(0, 500)
my_entry.configure(state='readonly')
root.mainloop()
There are some other things that could be improved with your code like relegating your calculations to a helper function, having your entries in arrays, and setting your entries in a loop.
This is just an example as I don't have access to what's inside your variables.
# This makes it much easier to follow the logic
# of your calculation
def calculate(multiplier, crew, planet):
return (multiplier - int(crew[1])) * planet[1]
multipliers = [100, 100, 100, 100, 150, 150]
# you could just append your entries to an array when creating them
# rather than add them here
entries = [displayfc1,
displayfc2,
displayfc3,
displayfc4,
displayms1,
displayms2
]
# zipping them up allows you to iterate through all
# of them at once
for entry, mult, crew, row in zip(entries,
multipliers,
crewInfo,
range(14, 20)
):
entry.grid(row=row, column=2)
entry.insert(0, calculate(mult, crew, planetInfo[getDestinationNum]))
entry.configure(state='readonly')
What im trying to do is upon clicking the button, the label below it increases by one. Im honestly getting nowhere, as i change one thing and i get an error, i fix that error and i get another.
heres a sample of my code i've no idea what im doing wrong:
#!/usr/bin/env python3
from tkinter import *
from tkinter import ttk
count1=0
count2=0
count3=0
def vote(voting)
voting.set +=1
lbl1.set(text= count1)
lbl2.set(text=count2)
lbl3.set(text=count3)
root = Tk()
frame = ttk.Frame(root)
b1 = ttk.Button(frame, text="v1", command=vote(count1))
b1.grid(row=2,column=1)
b2 = ttk.Button(frame, text="v2", command=vote(count2))
b2.grid(row=2,column=2)
b3 = ttk.Button(frame, text="v3", command=vote(count3))
b3.grid(row=2,column=3)
lbl1 = ttk.Label(frame, text=count1)
lbl2 = ttk.Label(frame, text=count2)
lbl3 = ttk.Label(frame, text=count3)
lbl1.grid(row=3,column=1)
lbl2.grid(row=3,column=2)
lbl3.grid(row=3,column=3)
frame.grid(column = 0, row = 0, sticky = (N, W, E, S))
root.mainloop()
First, the command argument must be a function, not the result of a function, so it can't have the () on it. If you must pass a parameter you need to make a helper function. You can do that dynamically with functools.partial or lambda, but in this case I think it's easier to simply make 3 helper functions the normal way.
Second, it would be a lot easier to use a IntVar, so the Label stays updated automatically. I think you wanted to do this anyway, since set() is a Variable method, not a Label method.
#!/usr/bin/env python3
from tkinter import *
from tkinter import ttk
def vote1():
count1.set(count1.get() + 1)
def vote2():
count2.set(count2.get() + 1)
def vote3():
count3.set(count3.get() + 1)
root = Tk()
count1=IntVar()
count2=IntVar()
count3=IntVar()
frame = ttk.Frame(root)
b1 = ttk.Button(frame, text="v1", command=vote1)
b1.grid(row=2,column=1)
b2 = ttk.Button(frame, text="v2", command=vote2)
b2.grid(row=2,column=2)
b3 = ttk.Button(frame, text="v3", command=vote3)
b3.grid(row=2,column=3)
lbl1 = ttk.Label(frame, textvariable=count1)
lbl2 = ttk.Label(frame, textvariable=count2)
lbl3 = ttk.Label(frame, textvariable=count3)
lbl1.grid(row=3,column=1)
lbl2.grid(row=3,column=2)
lbl3.grid(row=3,column=3)
frame.grid(column = 0, row = 0, sticky = (N, W, E, S))
root.mainloop()
However this would be the ideal place for a subclass that bundles those things together in a new reusable widget.
#!/usr/bin/env python3
from tkinter import *
from tkinter import ttk
class Mick(Frame):
def __init__(self, master=None, text='', **kwargs):
Frame.__init__(self, master, **kwargs)
self.var = IntVar()
btn = ttk.Button(self, text=text, command=self.vote)
btn.grid(row=0,column=0)
lbl = ttk.Label(self, textvariable=self.var)
lbl.grid(row=1,column=0)
def vote(self):
self.var.set(self.var.get() + 1)
root = Tk()
frame = ttk.Frame(root)
for i in range(4): # set number of voting boxes here
b1 = Mick(frame, text='v'+str(i))
b1.grid(row=0, column=i)
frame.grid(column = 0, row = 0, sticky = (N, W, E, S))
root.mainloop()
Now you can easily scale it to make as many voting blocks as you want!
My little brother is just getting into programming, and for his Science Fair project, he's doing a simulation of a flock of birds in the sky. He's gotten most of his code written, and it works nicely, but the birds need to move every moment.
Tkinter, however, hogs the time for its own event loop, and so his code won't run. Doing root.mainloop() runs, runs, and keeps running, and the only thing it runs is the event handlers.
Is there a way to have his code run alongside the mainloop (without multithreading, it's confusing and this should be kept simple), and if so, what is it?
Right now, he came up with an ugly hack, tying his move() function to <b1-motion>, so that as long as he holds the button down and wiggles the mouse, it works. But there's got to be a better way.
Use the after method on the Tk object:
from tkinter import *
root = Tk()
def task():
print("hello")
root.after(2000, task) # reschedule event in 2 seconds
root.after(2000, task)
root.mainloop()
Here's the declaration and documentation for the after method:
def after(self, ms, func=None, *args):
"""Call function once after given time.
MS specifies the time in milliseconds. FUNC gives the
function which shall be called. Additional parameters
are given as parameters to the function call. Return
identifier to cancel scheduling with after_cancel."""
The solution posted by Bjorn results in a "RuntimeError: Calling Tcl from different appartment" message on my computer (RedHat Enterprise 5, python 2.6.1). Bjorn might not have gotten this message, since, according to one place I checked, mishandling threading with Tkinter is unpredictable and platform-dependent.
The problem seems to be that app.start() counts as a reference to Tk, since app contains Tk elements. I fixed this by replacing app.start() with a self.start() inside __init__. I also made it so that all Tk references are either inside the function that calls mainloop() or are inside functions that are called by the function that calls mainloop() (this is apparently critical to avoid the "different apartment" error).
Finally, I added a protocol handler with a callback, since without this the program exits with an error when the Tk window is closed by the user.
The revised code is as follows:
# Run tkinter code in another thread
import tkinter as tk
import threading
class App(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.start()
def callback(self):
self.root.quit()
def run(self):
self.root = tk.Tk()
self.root.protocol("WM_DELETE_WINDOW", self.callback)
label = tk.Label(self.root, text="Hello World")
label.pack()
self.root.mainloop()
app = App()
print('Now we can continue running code while mainloop runs!')
for i in range(100000):
print(i)
When writing your own loop, as in the simulation (I assume), you need to call the update function which does what the mainloop does: updates the window with your changes, but you do it in your loop.
def task():
# do something
root.update()
while 1:
task()
Another option is to let tkinter execute on a separate thread. One way of doing it is like this:
import Tkinter
import threading
class MyTkApp(threading.Thread):
def __init__(self):
self.root=Tkinter.Tk()
self.s = Tkinter.StringVar()
self.s.set('Foo')
l = Tkinter.Label(self.root,textvariable=self.s)
l.pack()
threading.Thread.__init__(self)
def run(self):
self.root.mainloop()
app = MyTkApp()
app.start()
# Now the app should be running and the value shown on the label
# can be changed by changing the member variable s.
# Like this:
# app.s.set('Bar')
Be careful though, multithreaded programming is hard and it is really easy to shoot your self in the foot. For example you have to be careful when you change member variables of the sample class above so you don't interrupt with the event loop of Tkinter.
This is the first working version of what will be a GPS reader and data presenter. tkinter is a very fragile thing with way too few error messages. It does not put stuff up and does not tell why much of the time. Very difficult coming from a good WYSIWYG form developer. Anyway, this runs a small routine 10 times a second and presents the information on a form. Took a while to make it happen. When I tried a timer value of 0, the form never came up. My head now hurts! 10 or more times per second is good enough for me. I hope it helps someone else. Mike Morrow
import tkinter as tk
import time
def GetDateTime():
# Get current date and time in ISO8601
# https://en.wikipedia.org/wiki/ISO_8601
# https://xkcd.com/1179/
return (time.strftime("%Y%m%d", time.gmtime()),
time.strftime("%H%M%S", time.gmtime()),
time.strftime("%Y%m%d", time.localtime()),
time.strftime("%H%M%S", time.localtime()))
class Application(tk.Frame):
def __init__(self, master):
fontsize = 12
textwidth = 9
tk.Frame.__init__(self, master)
self.pack()
tk.Label(self, font=('Helvetica', fontsize), bg = '#be004e', fg = 'white', width = textwidth,
text='Local Time').grid(row=0, column=0)
self.LocalDate = tk.StringVar()
self.LocalDate.set('waiting...')
tk.Label(self, font=('Helvetica', fontsize), bg = '#be004e', fg = 'white', width = textwidth,
textvariable=self.LocalDate).grid(row=0, column=1)
tk.Label(self, font=('Helvetica', fontsize), bg = '#be004e', fg = 'white', width = textwidth,
text='Local Date').grid(row=1, column=0)
self.LocalTime = tk.StringVar()
self.LocalTime.set('waiting...')
tk.Label(self, font=('Helvetica', fontsize), bg = '#be004e', fg = 'white', width = textwidth,
textvariable=self.LocalTime).grid(row=1, column=1)
tk.Label(self, font=('Helvetica', fontsize), bg = '#40CCC0', fg = 'white', width = textwidth,
text='GMT Time').grid(row=2, column=0)
self.nowGdate = tk.StringVar()
self.nowGdate.set('waiting...')
tk.Label(self, font=('Helvetica', fontsize), bg = '#40CCC0', fg = 'white', width = textwidth,
textvariable=self.nowGdate).grid(row=2, column=1)
tk.Label(self, font=('Helvetica', fontsize), bg = '#40CCC0', fg = 'white', width = textwidth,
text='GMT Date').grid(row=3, column=0)
self.nowGtime = tk.StringVar()
self.nowGtime.set('waiting...')
tk.Label(self, font=('Helvetica', fontsize), bg = '#40CCC0', fg = 'white', width = textwidth,
textvariable=self.nowGtime).grid(row=3, column=1)
tk.Button(self, text='Exit', width = 10, bg = '#FF8080', command=root.destroy).grid(row=4, columnspan=2)
self.gettime()
pass
def gettime(self):
gdt, gtm, ldt, ltm = GetDateTime()
gdt = gdt[0:4] + '/' + gdt[4:6] + '/' + gdt[6:8]
gtm = gtm[0:2] + ':' + gtm[2:4] + ':' + gtm[4:6] + ' Z'
ldt = ldt[0:4] + '/' + ldt[4:6] + '/' + ldt[6:8]
ltm = ltm[0:2] + ':' + ltm[2:4] + ':' + ltm[4:6]
self.nowGtime.set(gdt)
self.nowGdate.set(gtm)
self.LocalTime.set(ldt)
self.LocalDate.set(ltm)
self.after(100, self.gettime)
#print (ltm) # Prove it is running this and the external code, too.
pass
root = tk.Tk()
root.wm_title('Temp Converter')
app = Application(master=root)
w = 200 # width for the Tk root
h = 125 # height for the Tk root
# get display screen width and height
ws = root.winfo_screenwidth() # width of the screen
hs = root.winfo_screenheight() # height of the screen
# calculate x and y coordinates for positioning the Tk root window
#centered
#x = (ws/2) - (w/2)
#y = (hs/2) - (h/2)
#right bottom corner (misfires in Win10 putting it too low. OK in Ubuntu)
x = ws - w
y = hs - h - 35 # -35 fixes it, more or less, for Win10
#set the dimensions of the screen and where it is placed
root.geometry('%dx%d+%d+%d' % (w, h, x, y))
root.mainloop()
I am trying to populate my screen with some data that refreshes every given time interval. I am using Python3, themed tkinter. Every time my screen updates I see grey flickerings on screen for every label. Is there a way to avoid this?
P.S : I am calling the 'after' method to refresh data.
UPDATE: Here is some example code:
def button1Click(self):
self.top = Toplevel(width=600,height=400)
self.top.title("XYZ ORGANIZATION")
self.frame1 = Frame(self.top,bg='#009999')
self.frame1.pack()
self.noOfEmp = Label(self.frame1,text = "Number_Of_Employees : ", font =('Verdana',9, 'bold'),bg='#009999',fg = '#000000')
self.noOfEmp.grid(row=1,column=0,sticky=W,padx=0,pady=5)
self.TeamLabel = Label(self.frame1,text = "Team Name : ", font =('Verdana',9, 'bold'),bg='#009999',fg = '#000000')
self.TeamLabel.grid(row=2,column=0,sticky=W,padx=0,pady=5)
self.text = Text(self.frame1, bg='#009999')
self.text.grid(row=8,columnspan=17)
self.old_emp = 0
self.EFile = open('/abc','r').readlines()
for line in self.EFile:
if line.startswith('EmpTotal:'):
self.Tot_Emp = int(line.split()[1])
break
t1 = threading.Thread(target=self.__make_layout, args = ())
t1.daemon = True
t1.start()
t2 = threading.Thread(target=self.ProcEmp,args = ())
t2.daemon = True
t2.start()
def self.__make_layout:
self.CLabelVal = Label(self.frame1,text = CSpace, font=('Verdana',9),bg='#009999',fg = '#000000')
self.MLabelVal = Label(self.frame1,text = MSpace , font =('Verdana',9),bg='#009999',fg = '#000000')
self.Label1Val.grid(row=4,column=1,sticky=W+E+N+S,padx=5,pady=5)
self.Label2Val.grid(row=5,column=1,sticky=W+E+N+S,padx=5,pady=5)
self.frame1.after(5000,self.__make_layout)
Part of the problem is that you keep stacking more and more widgets on top of each other. You should create the labels exactly once, and then change what they display every five seconds, instead of creating new widgets every five seconds.
There's also the problem that you're creating the labels in a thread. Tkinter isn't thread safe. Any code that creates or modifies a widget needs to be in the main thread. To update the labels you don't need threads, though you can use a thread to change what actually gets displayed.
def __make_layout(self):
self.CLabelVal = Label(...,text = CSpace, ...)
self.MLabelVal = Label(...,text = MSpace, ...)
self.Label1Val.grid(...)
self.Label2Val.grid(...)
def __update_layout(self):
self.CLabelVal.configure(text=CSpace)
self.MLabelVal.configure(text=MSpace)
self.after(5000, self.__update_layout)
I made a small program based on what you provided.
This is what i got. I chose 500ms instead because i did not want to wait that long. I ran two internet videos at the same time and had no issues.
So my best guess you have a slow video card or over loaded computer.
from tkinter import *
class MyClass:
frame1 = Tk()
poll = 0
def __init__(self):
self.frame1.after(500, self.__make_layout)
def __make_layout(self):
self.poll += 1
CSpace = "Poll count = "*20
MSpace = str(self.poll)
self.CLabelVal = Label(self.frame1, text=CSpace, font=('Verdana', 9), bg='#009999', fg='#000000')
self.MLabelVal = Label(self.frame1, text=MSpace, font=('Verdana', 9), bg='#009999', fg='#000000')
self.CLabelVal.grid(row=4, column=1, sticky=W+E+N+S, padx=5, pady=5)
self.MLabelVal.grid(row=5, column=1, sticky=W+E+N+S, padx=5, pady=5)
print(CSpace, MSpace)
return self.frame1.after(500, self.__make_layout)
MyClass()
mainloop()
This doesn't create more labels and uses the "textvariable" update feature.
from tkinter import *
class MyClass:
frame1 = Tk()
poll = 0
textstg = StringVar()
CSpace = "Poll count"
def __init__(self):
self.frame1.after(500, self.__make_layout)
self.CLabelVal = Label(self.frame1, text=self.CSpace, font=('Verdana', 9), bg='#009999', fg='#000000')
self.MLabelVal = Label(self.frame1, textvariable=self.textstg, font=('Verdana', 9), bg='#009999', fg='#000000')
self.CLabelVal.grid(row=4, column=1, sticky=W+E+N+S, padx=5, pady=5)
self.MLabelVal.grid(row=5, column=1, sticky=W+E+N+S, padx=5, pady=5)
def __make_layout(self):
self.poll += 1
self.textstg.set(str(self.poll))
return self.frame1.after(50, self.__make_layout)
MyClass()
mainloop()
The following is the functional issue I'm dealing with. I'm not sure how long 'frank" will be since in the real program he's being created from a line of text. What's happening is 2 things. 1) in this small demonstration, it isn't waiting for the button press and it just prints empty values for what should reflect an entry. 2) When I use this function to print to a csv, I'm getting the entry box instance as a string. I assume that fixing one will fix the other. Thanks.
from Tkinter import *
import ttk
def getThat(top,bottom):
bottom = bottom.get()
print ('%s: "%s"' % (top, bottom))
root = Tk()
root.title("This space intentionally left blank")
root.minsize(900,200)
mainframe = ttk.Frame(root)
mainframe.grid(column = 1, row = 1)
frank = ["beany", "beans", "beens", "not beets"]
x=0
for f in frank:
ttk.Label(mainframe, text=frank[x]).grid(column=x+1, row=2)
addMore = StringVar()
moreBeans = ttk.Entry(mainframe, textvariable=addMore)
moreBeans.grid(column =x+1, row =3)
ttk.Button(mainframe, text = "Do It", command = getThat(frank[x], addMore)).grid(column=1, row=5, pady=5)
x+=1
root.mainloop()
I think
ttk.Button(mainframe, text = "Do It", command = lambda *a: getThat(frank[x], addMore)).grid(column=1, row=5, pady=5)
should fix it
the problem is its being called on creation ... by putting it in a lambda it wont ca;ll until the click
alternatively and probably better form would be to create a function
def OnClick(*args):
return getThat(frank[x], addMore)
ttk.Button(mainframe, text = "Do It", command =OnClick).grid(column=1, row=5, pady=5)
since you are in a loop you might need to do something like
def OnMyClick(x,addMore,*args):
return getThat(frank[x], addMore)
import functools
for x,f in enumerate(frank):
...
my_listener = functools.partial(OnMyClick,x,addMore)
ttk.Button(mainframe, text = "Do It", command = my_listener)
...
In case you were wondering what the fix was, I'll post it here
from Tkinter import *
import ttk
import nltk
import csv
frank = ["beany", "beans", "beens", "not beets"]
letters = ["a","s","d","f"]
newInputs=[]
allTheBeans=[]
def getThat(newInputs):
for inputs in newInputs:
allTheBeans.append((inputs[0], inputs[1].get()))
print allTheBeans
def makeBoxes (root, frank):
for x in range(0, len(frank)):
Label(mainframe, text=frank[x]).grid(column=x+1, row=2)
moreBeans = ttk.Entry(mainframe)
moreBeans.grid(column =x+1, row =3)
moreBeans.insert(0,letters[x])
newInputs.append((frank[x],moreBeans))
return newInputs
def clearItOut(clearList):
del clearList [:]
root = Tk()
root.title("This space intentionally left blank")
root.minsize(900,200)
mainframe = ttk.Frame(root)
mainframe.grid(column = 1, row = 1)
madeBoxes=makeBoxes(mainframe, frank)
ttk.Button(mainframe, text = "Do It", command = lambda *a: getThat(newInputs)).grid(column=1, row=5)
ttk.Button(mainframe, text = "Undo It", command = lambda *a: clearItOut(allTheBeans)).grid(column=1, row=6)
root.mainloop()
I added some other stuff to make sure I was understanding those principals as well.