Inserting a dynamic tkinter variable into a function arguments - python

How do you insert a dynamic tkinter variable into a function's arguments? I'm a newbie that just recently starting learning python.
Let's say I have a function with tkinter after() method that updates every second and that constantly change an integer variable value to my local time's second value. Then I want to put that variable into a function that does something like down here.
import tkinter as tk
import time as tm
wdw = tk.Tk()
wdw.geometry('500x500')
insert = tk.IntVar() #defined the variable that will inserted to function something
def clock():
scd= int(tm.strftime('%S')) #get the second local time
insert.set(scd) # set the insert variable with the local time
wdw.after(1000,clock) #update the variable scd every second
def something(val):
u=val #set the wanted letter from the text below
p=0
q=0
TXT= 'This is a text that will be rendered to the label below every 3 letter per second'
while u < val+3:
label=tk.Label(wdw,text=TXT[u],font="Courier",fg="red") #prints the needed letter
label.place(x=17+p,y=17+q)
p+=17 #translate by the x axis
q+=17 #translate by the y axis
u+=1 #cycle to the next letter
clock() #run the clock function to refresh the scd variable
something(insert.get()) #run the something function with the updated 'insert' variable
wdw.mainloop()
The problem with the code above is the variable insert is stuck and won't update every second, so I'm stuck with a window that only render a text that associated with the second I run the code.
How can I make the variable insert dynamic, so in a way that it would render different text depending with the second in my local time. Like it would render "Thi" during the first second, then render "his" during the second second, "is " during the third second, "s a" at fourth second, " a " at fifth second and so on.
Is there a function inside the tkinter that I wasn't aware of that could fix the problem, or is it the way python just can't update a variable dynamically? Any help will be appreciated

Just trigger the something function from the clock function. Then it will run every second. Like this:
import tkinter as tk
import time as tm
wdw = tk.Tk()
wdw.geometry('500x500')
insert = tk.IntVar() #defined the variable that will inserted to function something
def clock():
scd= int(tm.strftime('%S')) #get the second local time
insert.set(scd) # set the insert variable with the local time
wdw.after(1000,clock) #update the variable scd every second
something() #run the something function with the updated 'insert' variable
def something():
val=insert.get() #set the wanted letter from the text below
u=val #set the wanted letter from the text below
p=0
q=0
TXT= 'This is a text that will be rendered to the label below every 3 letter per second'
while u < val+3:
label=tk.Label(wdw,text=TXT[u],font="Courier",fg="red") #prints the needed letter
label.place(x=17+p,y=17+q)
p+=17 #translate by the x axis
q+=17 #translate by the y axis
u+=1 #cycle to the next letter
clock() #run the clock function to refresh the scd variable
wdw.mainloop()

Related

How do I only get the latest input from tkinter Text widget?

I need to get only the latest input from my text widget, and then append that character to a list.
I am using
Text.get(1.0,'end-1c')
, and it does not work because the loop constantly gets all the input, instead of only getting the latest input when there is a new latest input.
def main_screen():
start_time=time.time()
tk=Tk()
tk.title('Typing Test')
tk.geometry('800x500')
main_title=Label(tk,text='1 Minute Test',font=('Times New Roman',36))
main_title.pack(pady=5)
directions=Label(tk,text='Start Typing',font=('Times New Roman',14))
directions.pack()
base_text=Label(tk,text=randomizer(),bg='#E0E0EE',font=('Arial',14),wraplength=700,justify=LEFT)
base_text.pack(pady=10)
text_area=Text(tk,font=('Arial',14),width=63,height=7,wrap='word')
text_area.pack()
tk.update()
#WPM Calculation
target_text=randomizer()
typed_text=[]
wpm=0
errors=0
while True:
tk.update()
time_elapsed=max(time.time()-start_time,1)
wpm=round((len(typed_text)/60)/5)
if time_elapsed>=60:
break
#Problem Section
key=text_area.get(1.0,'end-1c')
typed_text.append(key)
for x in typed_text:
if x != target_text:
errors += 1
Alternatively, I tried using a variable in place of the 1.0 in .get, that would increase by one with each iteration of the loop. Next, I tried a try/except command, and put the #Problem Section into a function. I tried calling that function by binding the text area to
'<Key>'
'<KeyPress>'
'<KeyRelease>'
None of these attempts work. I used a print statement to see what those variables are with each iteration of the loop, and using the first method, it just keeps making a longer and longer string that repeats constantly, instead of updating with each new character. Trying the other ways I just got nothing, no output, but no error either. I am completely stuck, and don't know what else to try.
You can bind the text_area with a <KeyPress> event, but you need to pass the list typed_text as an argument so you can append the presses.
So you should do something like this:
text_area.bind("<KeyPress>", lambda _: getKey(_, typed_text))
while True:
tk.update()
time_elapsed = max(time.time() - start_time, 1)
wpm = round((len(typed_text) / 60) / 5)
if time_elapsed >= 60:
break
# Problem Section
for x in typed_text:
if x != target_text:
errors += 1
def getKey(event, list):
list.append(event.char)
print(list)
The text widget supports something called a "mark", which is like a bookmark. You can put a mark anywhere in the text and use it just like a normal index.
Assuming that data is only ever appended to the end of the widget, the simplest solution is to fetch a block of data and then move the mark to the end of the text that you fetched. The next time you fetch data, start at that mark.
Marks have something called "gravity" that defines which character the mark sticks to. For example, if the gravity is "left" and you set it to character "2.2", the mark will always stay adjacent to the character at index "2.2". If the gravity is "right", it will be stuck at the character following index "2.2" (eg: "2.3" or "3.0")
Here's a contrived example that will print only the latest additions to a text widget every five seconds, by tracking the last position that was used to fetch the data.
import tkinter as tk
def get_new_text():
data = text.get("last", "end-1c")
print(f"new data: >>>{data}<<<")
text.mark_set("last", "end-1c")
root.after(5000, get_new_text)
root = tk.Tk()
text = tk.Text(root, wrap="word")
text.pack(fill="both", expand=True)
text.mark_set("last", "1.0")
text.mark_gravity("last", "left")
root.after(5000, get_new_text)
root.mainloop()

Getting real time data in Tkinter

Consider the code below:
from tkinter import *
screen = Tk()
e =Entry()
e.pack()
screen.mainloop()
Now how to get to display the length of the characters entered in the e entry widget in real-time? It doesn't matter if the data is displayed in the GUI or Corresponding terminal
There are atleast 3 ways to do this here with one being better than the other:
Using trace from StringVar:
def func(*args):
print(len(var.get()))
var = StringVar()
e = Entry(screen,textvariable=var)
e.pack()
var.trace('w',func)
Every time the value of var is changed, func will be called.
Using bind to each key release:
def func(*args):
print(len(e.get()))
e.bind('<KeyRelease>',func)
Using after(ms,func) to keep repeating the function:
def func():
print(len(e.get()))
screen.after(500,func)
func()
As you can see, the first method is more efficient as it does not unnecessarily prints out values when you select all the items(with Ctrl+A) and so on. Using after() will be the most ridiculous method as it will keep printing the length always as there are no restrictions provided.

Making a script that will cycle through 3 different states in Maya Python

I want to create a script in Maya using Python and bind it on a hotkey. Every time I run the script I want to loop through 3 states, cube/ cylinder / plane. So for example first time I run the script it will create a cube, second time delete the cube and create a cylinder third time delete the cylinder and create a plane., fourth time delete the plane and create a cube etc... I want this to happen until the user decides what primitive he wants and end the loop. I tried using while loop but I failed miserably.
Ended up with this:
def createCube():
return "cube"
def createCylinder():
return "cylinder"
def createPlane():
return "plane"
def numbers_to_primitives(argument):
switcher = {
1: createCube,
2: createCylinder,
3: createPlane,
}
# Get the function from switcher dictionary
func = switcher.get(argument, lambda: "Invalid primitive")
# Execute the function
print func()
numbers_to_primitives(2)
This kinda seems to work. But I foresee issues when running the command over and over as I am creating more and more primitives instead of replacing the existing ones. Would also need to create a toggle button to cycle through these?
You have several questions problems to solve. First, you want to use the script in a hotkey what means it should produce a different result every time you call numbers_to_primitive() without any argument. So you first need to save and read the current state. You have several ways to do it. If you only need the state in the current maya session, you can use a global variable like this:
state = 0
def box():
print "Box"
def cyl():
print "Cylinder"
def sph():
print "Sphere"
def creator():
global state
print "current state", state
state = state + 1
if state > 2:
state = 0
creator()
This way the state variable cycles through values 0-2. Now you want to create geometry and replace it as soon as the function is called again. It works very similiar: Save the current object and delete it as soon as the function is called again like this:
import maya.cmds as cmds
state = 0
transformName = "" #again a global variable
def box():
print "Box"
global transformName #to use the global variable you have do use "global" keyword
transformName, shape = cmds.polyCube()
def cyl():
print "Cylinder"
global transformName
transformName, shape = cmds.polyCylinder()
def sph():
print "Sphere"
global transformName
transformName, shape = cmds.polySphere()
def creator():
global state
funcs = [box, cyl, sph]
print "current state", state
print "TransformName", transformName
if cmds.objExists(transformName):
cmds.delete(transformName)
funcs[state]()
state = state + 1
if state > 2:
state = 0
Now you can call the creator() function and every time it will delete the old object and create a new one.

Tkinter, xlwt, Trying to edit checkbuttons that have been created in a loop

This is part of my code for a register in tkinter which also uses xlwt,xlutils and xlrd (Excel modules). It takes the names from a file and creates a list of these names with 'present' checkbuttons next to them:
https://imgur.com/a/hTrhWFy
I want to save the checkbuttons' on and off values so that i can put them in the excel spreadsheet but because i've created them in a loop the variable will be the same for all of them and therefore if i add that and click on them, they all switch on and off.
Does anyone know how to fix this?
x=0
y=1
for line in open(register,"r"):
with open(register,"r") as file:
all_lines=file.readlines()
Label(registersuccess,text=all_lines[x].strip("\n")).grid(column=0,row=x)
Checkbutton(registersuccess,text="Present").grid(column=1,row=x)
listeesheet.write(x,0,all_lines[x].strip("\n"))
entrysheet.write(y,0,all_lines[x].strip("\n"))
entrywb.save(filename)
x=x+1
y=y+1
If you were to save a the value of a checkbutton outside of a loop it would look like so:
present = tk.IntVar()
tk.Checkbutton(root, text="Present", variable=present)
This creates a new Tkinter Variable, which when the checkbutton is pressed it is equal to 1, and when it is unchecked it is equal to 0 (these can be changed with the parameters offvalue and onvalue.
If we want to do the same within a loop we will place all the checkbutton variables into a dictionary, like so:
import tkinter as tk # recommended over the use of "from tkinter import *"
root = tk.Tk()
def get_present():
for name, present in register_dict.items(): # searches through the dictionary, and puts the persons name into the variable name and the checkbutton variable into the present variable
print(name, present.get()) # present.get() gets the current value of a tkinter variable
register = "names.txt" # Use your own text file
with open(register,"r") as file: # moved outside of the loop to prevent the system reloading the file every time - saves time
all_lines=file.readlines()
registersuccess = tk.Frame(root) # Use your own registersuccess Frame/Window
registersuccess.grid(row=0, column=0)
x=0
y=1
register_dict = {} # creates an empty dictionary
for line in open(register,"r"): # for each line in the register
name = all_lines[x].strip("\n") # get the current name and remove the enter key
tk.Label(registersuccess,text=name).grid(column=0,row=x) # Add a label with the name
register_dict[name] = tk.IntVar() # places into the register dictionary the key: name, and a new integer
tk.Checkbutton(registersuccess,text="Present", variable=register_dict[name]).grid(column=1,row=x) # on a checkbutton change the variable will be updated accordingly
x += 1 # A more python way to write x = x + 1
y += 1
tk.Button(registersuccess, command=get_present, text="Print Present").grid(column=0, row=x, rowspan=2) # Prints the values (can be removed for your project)
root.mainloop()
This code can be further optimised by, instead of reopening the file, use the list all lines:
for line in all_lines:
name = line.strip("\n")
The timings on my machine after this look like this:
$ python3 optimized.py
real 0m0.312s
user 0m0.073s
sys 0m0.013s
$ python3 unoptimized.py
real 0m0.318s
user 0m0.059s
sys 0m0.030s
The import numbers to look at in this are the length of time it takes for the user to see the effect and the amount of sys time (the optimised code being nearly 3x faster)

Python: change entry colour dynamically with Tkinter

I am getting problems with Tkinter after() method.
Actually, what I want to do is to change the background colour of some entry boxes as soon as times passes. Let's take this piece of code (which is different from the script I'm working on, but the situation described is the same):
import Tkinter as tk
root = tk.Tk()
root.option_add("*Entry.Font","Arial 32 bold")
emptyLabel=tk.Label()
emptyLabel.grid(row=4) #Empty label for geometry purpose
entryList=[]
for x in range(4):
entryList.append([])
for y in range(4):
entryList[x].append('')
entryList[x][y]=tk.Entry(root, bg="white",width=2,justify="center",
takefocus=True,insertofftime=True)
entryList[x][y].grid(row=x,column=y)
solvebt=tk.Button(root,text='Solve').grid(row=5,column=2)
newgamebt=tk.Button(root,text='New').grid(row=5,column=1)
#BROKEN PART STARTS HERE
def changebg(x,y):
entryList[x][y]['bg']='yellow'
for x in range(4):
for y in range(4):
entryList[x][y].after(300,changebg(x,y))
#Same result with root.after(300,changebg(x,y))
root.mainloop()
The problem is that when I start the program, I would expect it to show me as it "paints", one at time, all of the entry boxes in yellow. What happens, instead, is that the program freezes for (300*16) milliseconds and then, all of a sudded, every entry boxes is yellow!
The problem is here:
def changebg(x,y):
entryList[x][y]['bg']='yellow'
for x in range(4):
for y in range(4):
entryList[x][y].after(300,changebg(x,y))
#Same result with root.after(300,changebg(x,y))
You're calling changebg to immediately in the double for loop -- You're then passing the return value (None) to root.after. This won't lead to the delay that you describe. Perhaps your actual code looks like:
for x in range(4):
for y in range(4):
entryList[x][y].after(300,lambda x=x,y=y : changebg(x,y))
That will lead to the behavior you actually describe. Ultimately, what you need is to flatten your list of widgets and then pass then one at a time -- registering the next one if it exists:
import itertools
all_entries = itertools.chain.from_iterable(entryList)
def changebg(ientries):
ientries = iter(ientries) #allow passing a list in as well ...
entry = next(ientries,None)
if entry is not None:
entry['bg'] = 'yellow' #change the color of this widget
root.after(300,lambda : changebg(ientries)) #wait 300ms and change color of next one.
changebg(all_entries)

Categories