Inputting double digit numbers from keyboard on Tkinter, Python - python

So I'm using the following to enable single digit numbers to move the numbered tiles on my game board. However, I can't use the same method to input double digit numbers. Do you have any idea, how I could make Tkinter wait for me to input several digits as one number?
def key(event):
if event.char.isdigit():
for j, row in enumerate(board):
for i, char in enumerate(row):
if char.get() == event.char:
play(i,j)
return
root.bind('<Key>', key)

As I mention in previous question - you can put keys on list, wait a moment and check if there are two or more chars.
import tkinter as tk
''' catch two (or more) keys pressed in short time and tread it as one text '''
''' http://pastebin.com/WHQKcJkW '''
# --- functions ---
# keys buffer
keybuf = []
def test_after():
# check if buffer is not empty
if keybuf:
# get all keys in buffer as one text
text = ''.join(keybuf)
# clear buffer
keybuf.clear()
# run some function here
print('after:', text)
def get_key(event):
# save key in buffer
keybuf.append(event.char)
# check buffer after 500ms (0.5s)
root.after(500, test_after)
# --- main ---
root = tk.Tk()
root.bind('<Key>', get_key)
root.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()

Bubblesort visualization in tkinter

I can't figure out how can I finish one simple program written in Python. Program basically generates array of ten random numbers and then sorts them using bubblesort algorithm. Whole shorting process should be shown on screen - such as this one
My current code is this:
import tkinter
import random
canvas = tkinter.Canvas(bg='white',width='800',height='400')
canvas.pack()
c = []
for i in range(0,10):
c=c+[random.randrange(10)]
print(c)
print('Zoradenie...', c)
def sort(c):
x=300
for i in range(0,10):
for j in range(0,len(c)-1-1):
if c[j+1]<c[j]:
c[j+1],c[j]=c[j],c[j+1]
canvas.create_text(300,80,text=c[j],fill='Red')
x+=25
canvas.update()
canvas.after(1000)
print(c)
return c
sort(c)
But I can't figure out how to show numbers on screen. Any ideas?
To display the digits on the canvas, you must create a text item for each digit. See the end of my code. The harder part is moving the digits. One way is to delete and recreate; the other is to move. I choose the latter.
The hardest part, perhaps, is the time delays. If one uses mainloop, one should use after rather than time.sleep (which blocks the looping) and not use for-loops for animation. The problem is that the function (here sort) that naturally contains for-loops must be broken into pieces whose joint operation may be hard to understand. If one is running just one function and does not care about user interaction (for instance, a pause button), one can use time.sleep and update. I have done so here to make what is going on clearer.
from random import randrange
from time import sleep
import tkinter as tk
root = tk.Tk()
canvas = tk.Canvas(root, bg='white', width='800', height='400')
canvas.pack()
ndigits = 10
digits = [randrange(10) for _ in range(ndigits)]
tdelta1, tdelta2 = .8, .2
xstart = 300
xdelta = 25
y = 80
def color(i, swap):
"Temporarily color digits i and i+i according to swap needed."
x = xstart + xdelta * i
dcolor = 'Red' if swap else 'green'
canvas.itemconfigure(items[i], fill=dcolor)
canvas.itemconfigure(items[i+1],fill=dcolor)
canvas.update()
sleep(tdelta1)
canvas.itemconfigure(items[i], fill='Black')
canvas.itemconfigure(items[i+1], fill='Black')
canvas.update()
sleep(tdelta2)
def swap(i):
digits[i], digits[i+1] = digits[i+1], digits[i]
canvas.move(items[i], xdelta, 0)
canvas.move(items[i+1], -xdelta, 0)
items[i], items[i+1] = items[i+1], items[i]
def bubsort():
"Sort digits and animate."
for stop in reversed(range(1, ndigits)):
# stop = index of position whose entry will be determined.
for i in range(stop):
swap_needed = digits[i] > digits[i+1]
color(i, swap_needed)
if swap_needed:
swap(i)
color(i, False)
# Create display items and pause.
items = [canvas.create_text(xstart + xdelta*i, y, text=str(digit))
for i, digit in enumerate(digits)]
canvas.update()
sleep(tdelta1)
bubsort()
This code makes it fairly easy to replace the text digit display with, for instance, a colored bar display. To develop this further, I would define a class of items combining int values and display items as attributes. There would them be only one array of combined items. With comparison methods defines, the array could be passed to any sort function.

Beginner Python Keyboard GUI setup

I am beginning GUI in Python 3.5, and I am trying to setup a simple qwerty keyboard. Based on the examples, I tried the following code
from tkinter import Tk, Label, RAISED, Button, Entry
self.window = Tk()
#Keyboard
labels = [['q','w','e','r','t','y','u','i','o','p'],
['a','s','d','f','g','h','j','k','l'],
['z','x','c','v','b','n','m','<']]
n = 10
for r in range(3):
for c in range(n):
n -= 1
label = Label(self.window,
relief=RAISED,
text=labels[r][c])
label.grid(row=r,column=c)
continue
This gives me the first row, but it does not return anything else. I tried simply using 10 as the range, which created the first two rows of the keyboard, but it still did not continue onto the last row.
Your issue is in the line n -= 1. Every time a label is created, you make n one less- after the first whole row, n==0, and thus the range is 0>0, and ranges never include the high bound- for c in range(0) will just drop from the loop (as it has looped through all the nonexistent contents).
A better solution involves iterating through the lists instead of through the indexes- for loops take any iterable (list, dictionary, range, generator, set, &c.);
for lyst in labels:
# lyst is each list in labels
for char in lyst:
# char is the character in that list
label = Label(... text=char) # everything else in the Label() looks good.
label.grid(...) # You could use counters for this or use ennumerate()-ask if you need.
# The continue here was entirely irrelevant.
Is this what you want it to do? Let me know if you need me to explain it further but basically what I'm doing is first filling the columns in each row. So row remains 0 and then as I loop through the column (the inner list) I fill in each of the keys, then on to the next row and etc.
from tkinter import Tk, Label, RAISED, Button, Entry
window = Tk()
#Keyboard
labels = [['q','w','e','r','t','y','u','i','o','p'],
['a','s','d','f','g','h','j','k','l'],
['z','x','c','v','b','n','m','<']]
for r in labels:
for c in r:
label = Label(window, relief=RAISED, text=str(c))
label.grid(row=labels.index(r), column=r.index(c))
window.mainloop()

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)

Tkinter Listbox not deleting items correctly

I have a method that is suppose to take a search parameter and remove everything from the list that does not meet the parameter. But when it runs it removes list items at almost random. I've debugged it and it correctly determines if an item needs to be removed but it doesn't remove the right one. I think it has something to do with when I remove one item it messes up the indexes of the rest of the list, which doesn't with with my method of tracking the index.
I posted the whole class but the relevant code is towards the bottom
class StudentFinderWindow(Tkinter.Toplevel):
def __init__(self):
Tkinter.Toplevel.__init__(self) # Create Window
##### window attributes
self.title('Edit Students') #sets window title
##### puts stuff into the window
# text
editStudentInfoLabel = Tkinter.Label(self,text='Select the student from the list below or search for one in the search box provided')
editStudentInfoLabel.grid(row=0, column=0)
# entry box
self.searchRepositoryEntry = Tkinter.Entry(self)
self.searchRepositoryEntry.grid(row=1, column=0)
# list box
self.searchResults = Tkinter.Listbox(self)
self.searchResults.grid(row=2, column=0)
# search results initial updater
self.getStudentList()
for student in self.studentList:
self.searchResults.insert(Tkinter.END, student)
##### event handler
self.searchRepositoryEntry.bind('<KeyRelease>', self.updateSearch)
This is the relevant code
def updateSearch(self, event):
parameters = self.searchRepositoryEntry.get()
int = 0
currentList = self.searchResults.get(0, Tkinter.END)
length = len(parameters)
print(parameters)
print(length)
for i in currentList:
if not i[0:length] == parameters:
self.searchResults.delete(int)
print(i[0:length] == parameters)
print(i[0:length])
print(int)
int += 1
def getStudentList(self):
global fileDirectory # gets the directory that all the files are in
fileList = listdir(fileDirectory) # makes a list of files from the directory
self.studentList = [] # makes a new list
for file in fileList: # for loop that adds each item from the file list to the student list
self.studentList.append(file[:-4])
When you delete an item, everything below it moves up causing the index of all following items to change. The simplest solution to this sort of a problem (it's also common when deleting words from a text widget) is to delete backwards, starting at the end.
I think you already know the problem. When you delete an item, the index for the rest of the items change. For example, if you delete the 4th item, then the 5th item becomes the "new" 4th item. So you don't want to increment int whenever you delete an item. You can implement that with continue:
for i in currentList:
if not i[0:length] == parameters:
self.searchResults.delete(int)
continue # <-- Use continue so `int` does not increment.
int += 1
PS. It's not good coding style to use int as a variable name -- in Python it masks the built-in function of the same name.

Categories