Tkinter packing empty widgets if "if statement" is False - python

I am creating a search function in my tkinter program and I have a problem with empty widgets getting packed if the ifstatement where the widgets should get packed in is false.
I try to explain what I am trying to do:
I have a while loop which is looping through all files in a folder.
In the while loop is a if statement which checks if the filename contains the searched letters.
If yes, a Label with the filename will be packed. If no, nothing should happen.
But when the filename doesn´t contain the searched letters, a empty widget is been packed.
Here is a short example of my code:
whileLoop = True
search = Entry(root)
fileNameList = ["abc","def","ghi"]
whileLoopCounter = 0
while whileLoop == True:
if search in fileNameList[whileLoopCounter]:
Label(root, text=fileNameList[whileLoopCounter])
whileLoop = False
whileLoopCounter += 1
This is a very short version of my code, because the real code would be way to long. But I hope you understand what I am trying to do.
Thanks for your help.

search = Entry(root)
fileNameList = ["abc","def","ghi"]
for d in fileNameList:
if d in search.get():
Label(root, text=d)
try this! it uses "any". Also you need to do "in" on 'each' string not
on the whole list!

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()

How to delay blits being iterated from a list

I'm trying to create a typewriter effect for text being blitted. By typewriter effect, I simply mean that Im trying to avoid the entirety of the text being blitted on screen at once. Instead, im trying to have each letter appear individually, with a slight delay before the next character in the string appears.
The catch is that im not using pygame's font.render. Instead, i've made my own custom fonts, each letter being saved as a separate image file. Now each alphanumeric character has it's own variable to which it's image is attached and each is appended to a list.
e.g:
letter_IMGs = []
a = "a" == pygame.image.load("IMG/letter_a.gif)
letter_IMG.append(a)
Lower, I have something along these lines:
letter_pos_x = 0
text = "Hello"
for i, c in enumerate(text):
screen.blit(letter_IMGs[i], (letter_pos_x,0))
letter_pos_x += 20
scroll_wait #this is a clock.delay variable. It's value was set outside the loop. I'm just calling it here.
Now as you'd guess, the result with that code is that the entire line of text appears simultaneously after the delay. I've been trying to code it as needed from there, but most of what I come up with returns with a "cannot iterate through surface objects" error.
I'm pretty much at a loss on how I should proceed next. Note that, ive been learning a bit of code on my own, on and off, for the past year and that I don't really know what im doing yet. Any and all help will be much appreciated.
Thanks in advance for your time.
Without getting into the pygame specifices too much, you just need to change the iterator so it returns substrings rather than letters:
def iterate_text(text):
for r in range(len(text)):
yield text[:r + 1]
which will return the substring iteratively:
for t in iterate_text('hello'):
print t
# h
# he
# hel
# hell
# hello
use a separate function to draw the string:
def draw_text(x, y, text):
characters = [letter_IMGs[t] for t in text]
cursor = x
for char in characters:
screen.blit(char, cursor, y)
cursor += 20
in your main loop you can decide when to get the next character. You'll basically do something like:
typewriter = iter_text('hello world')
text_to_draw = None
advance_text = False
at a level outside the loop that survive from frame to frame. When you want to draw the next character, you set advance_text to True, in and in the main loop:
if typewriter and advance_text:
text_to_draw = typewriter.next()
advance_text = False # until you set it again
if text_to_draw :
draw_text(0,0, draw_text)
You can start over by resetting the typewriter with new text, and control the timing of the new character appearing by setting advance_text to True before the next frame

My program crashes(python.exe has stopped working)

So i've made a project for school, and it won't run and i have no idea why. Everytime i press the "start" button i made, the whole program freezes and stops working, and seeing as i've made a similar program earlier(this one is just much cleaner) i get really confused.
here is the code:
from tkinter import *
root=Tk()
root.minsize(width=3, height=100)
root.title("Counter")
list=[]
list.append(0)
def counter():
t = 10
c = 0
for row in list:
if (t < 60) or (c < 5):
l=Label(root, text=t).grid(row=1, column=c)
t=10+10
c=0+1
list.append(t)
print("") #This is just for debugging purpose
for row in list:
if (t < 110) or (c < 10):
c=0
ll=Label(root, text=t).grid(row=2, column=c)
c=0+1
t+10
list.append(t)
return
label=Label(root, text="...").grid(row=0, column=0)
b=Button(root, text="Start", command=counter).grid(row=0, column=1)
label=Label(root, text="...").grid(row=0, column=2)
root.mainloop()
Thanks for all the tips, figured it out due to you! :D
The problem is that you're modifying the list you're iterating over. Besides list isn't a good name as it shadows the builtin one, you always append an element to your list.
Additionally, none of the conditions will be met since t is always assigned to 20 and c to 1.
The reason it is freezing is because you are modifying list while looping over it:
list.append(t)
You did that twice. You can not do it in this manner and you should either use a separate list to carry out whatever you were trying to do or some sort of list iterator. Also as already noted,
t+10
has no affect and you should not be naming your list "list" anyways since it is a reserved word for a different purpose.

Entry Tkinter is not allowing to erase data if selected all and pressed delete or backspace button

I have written a code for entry widget which doesnot allow alphabets and limits the number of digits to 7. But i am not able to select all in the entry box and delete them using delete or backspace keys, could somebody help me on this.
My code snippet:
self.DelayLabel = ttk.Label(self)
self.DelayLabel["text"] = "timeout"
vcmd = (root.register(self.IntLength_Delay), '%P', '%S")
self.Delay = ttk.Entry(self, width = '5', validate = 'key', validatecommand = vcmd)
def IntLenght_Delay(self,value,text):
if text in '0123456789':
if len(value)<7:
return True
else:
return False
else:
return False
Answering an older question here, but I was having an extremely similar problem and wound up finding a decent solution. #Bryan Oakley's answer is useful, but doesn't provide an example, which is what I aim to do here.
After importing Tkinter or tkinter (depending on your Python version) as tk, first define the initial validation command:
val_cmd = (master.register(self.validate), '%P') # master is root in thie case.
Next, whenever you have an entry box needing validation, I found this to be a nice way to write it out:
self.entry = tk.Entry(your_frame_of_choice)
self.entry.insert('end', 100) # Inserts 100 as an initial value.
self.entry.config(validate='key', validatecommand=val_cmd)
This inserts a value before the validation begins. I originally had something like self.entry = tk.Entry(your_frame_of_choice, validate='key', validatecommand=val_cmd), but various iterations of the validation function that I toyed with rejected the insert code if the validation came before the insert. The one listed below doesn't care, but I've kept the entry code this way as I find it more aesthetically pleasing.
Finally, the actual function:
def validate(self, input_text):
if not input_text:
return True
try:
float(input_text)
return True
except ValueError:
return False
This checks for floats, but you could easily change it to ints, as you prefer. And, most importantly, it allows you to delete and write over any highlighted text in the entry box.
I'm sure the initial problem is long gone, but I hope someone in the future finds this to be helpful!
Follow the logic. let's say you've entered "987". You now select it and try to delete it. In your validation function text (the current value) will be "987". Your code isn't prepared for that so it will fail the first if statement. Since it fails, validation returns False, disallowing the edit.
You need to be prepared for what Tkinter passes to your function (a long string in the case of a deletion), and you need to explicitly allow an empty value after the edit.
Working in python 3.8.
%d == '0' -> delete text | %d == '-1' -> select text
import tkinter as tk
window = tk.Tk()
window.title('tkinter Entry')
def IntLength_Delay(action, key, content, value):
return True if not action == '1' or len(value) < 7 and (content != '' and key == '0' or key in '123456789') else False
vcmd=window.register(IntLength_Delay)
txt = tk.Entry(window,width=10,validate='key',validatecommand=(vcmd,'%d','%S','%s', '%P'))
txt.grid(column=1,row=1,sticky='news')
window.mainloop()

Limiting entry on a tk widget

I have trouble finding a way to limit the entry length of entry widgets, I would like to limit it to 20 characters, i.e. when I click on a sequence or the other I would like to be able to edit it but stay in the 20 characters limit. In or order to keep the code light , should I use a regex , a loop or check the entry with an event ?
Here is my code:
import Tkinter
from Tkinter import *
import tkFileDialog
root = Tkinter.Tk()
edit1 =StringVar()
edit2 =StringVar()
s = StringVar()
s = "GATACACGCGCGCGTATATATTACGCGCGCGATACA"
lb01=Label(root,text="sequence1")
lb01v=Entry(root,textvariable=edit1,width=20)
lb01v.delete(0, END)
lb01v.insert(0, s[6:20])
lb01.grid(sticky=W,row=1,column=1)
lb01v.grid(row=1,column=2)
lb02=Label(root,text="sequence2")
lb02v=Entry(root,textvariable=edit2,width=20)
lb02v.delete(0, END)
lb02v.insert(0, s[0:6])
lb02.grid(sticky=W,row=2,column=1)
lb02v.grid(row=2,column=2)
root.mainloop()
Ok I did try with the trace variable, on a short piece of test code , this is excactly what I was searching for !! I like the fact you can prototype so easily in Python ;)
def main():
pass
if __name__ == '__main__':
main()
from Tkinter import *
def callback(sv):
c = sv.get()[0:9]
print "c=" , c
sv.set(c)
root = Tk()
sv = StringVar()
sv.trace("w", lambda name, index, mode, sv=sv: callback(sv))
e = Entry(root, textvariable=sv)
e.pack()
root.mainloop()
I know its too late to add any answers to this, just found a simpler way to represent what Wabara had answered. This will help if you need multiple entry limits and each to a user-defined length limit. Here's a code working on Python 3.6.5:
def main():
pass
if __name__ == '__main__':
main()
from tkinter import *
def limit_entry(str_var,length):
def callback(str_var):
c = str_var.get()[0:length]
str_var.set(c)
str_var.trace("w", lambda name, index, mode, str_var=str_var: callback(str_var))
root = Tk()
abc = StringVar()
xyz = StringVar()
limit_entry(abc,3)
limit_entry(xyz,5)
e1 = Entry(root, textvariable=abc)
e2 = Entry(root, textvariable=xyz)
e1.pack()
e2.pack()
root.mainloop()
The simplest solution is to put a trace on the variable. When the trace fires, check the length of the value and then delete any characters that exceed the limit.
If you don't like that solution, Tkinter also has built-in facilities to do input validation on entry widgets. This is a somewhat under-documented feature of Tkinter. For an example, see my answer to the question Python/Tkinter: Interactively validating Entry widget content
I will start off by making an alphabet to measure from. The alphabet is a string and has 26 letters meaning its too long for our use. we want 20 letters only, so our output should be "A" thru "T" only.
I would define a function to make it happen and dump each string thru it that I would want cut to 20 characters or less.
I am making the below code in such a way that it takes as an input any string that is called it takes that input in and processes it to 20 characters in length only...
def twenty(z):
a = z[0:20]
return a
alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
so to execute our newly made code, we need only call the print twenty command with the variable we want cut to 20 characters in the parenthesis.
print twenty(alphabet)
-----------------------------------------------------------------
OUTPUT:
ABCDEFGHIJKLMNOPQRST
So you see, it worked, we input the entire alphabet into the program and it cut the string down to 20 letters only. now every time in your code you want to cut text down to 20 letters, just run the command
twenty(variable)
and it will make sure you have no more letters than that.
Explanation:
def twenty is to define a function with one input that you can call on over and over simply by typing twenty(variable)
the next line is a = z[0:20] Meaning call variable "a" to equal the input from position 0 to position 20 and dont worry about anything past that.
return command is how you get an output from the def function. anytime you create a def function, you should end it with a line.

Categories