I was making a program for vocab test, and I needed to make each Entries to type in the answer. However, I found it out making every single Entries is very inefficient so I used for i in range(0,35) to make Entries. But now I am stuck with getting the value of each Entries. How can I make a button to collect all the .get() from each Entries that doesn't have names?
import tkinter
from tkinter import *
window = tkinter.Tk()
container = tkinter.Frame(window)
canvas = tkinter.Canvas(container)
window.title('Rescue word test')
window.geometry('640x480')
window.resizable(True, True)
scrollbar = tkinter.Scrollbar(container, orient="vertical", command=canvas.yview)
#scroll
main_frame = Frame(window)
main_frame.pack(fill=BOTH, expand=1)
my_canvas = Canvas(main_frame)
my_canvas.pack(side=LEFT, fill=BOTH, expand=1)
my_scrollbar = tkinter.Scrollbar(main_frame, orient=VERTICAL, command=my_canvas.yview)
my_scrollbar.pack(side=RIGHT, fill=Y)
my_canvas.configure(yscrollcommand=my_scrollbar.set)
my_scrollbar.bind('<Configure>', lambda e: my_canvas.configure(scrollregion= my_canvas.bbox("all")))
second_frame = Frame(my_canvas)
my_canvas.create_window((0,0), window= second_frame, anchor="nw")
def mouse_scroll(event):
my_canvas.yview_scroll(-1 * int((event.delta / 120)), "units")
my_canvas.bind_all("<MouseWheel>", mouse_scroll)
def getEntry( i ):
return list( second_frame.children.values() )[ i ]
Day21_eng = ['exquisite', 'acquisition', 'regulate', 'transportation', 'insight', 'straightforward', 'cultivate', 'innovation', 'preserve', 'odor', 'exception', 'munch', 'proclaim', 'slap', 'variability', 'investigate', 'flare', 'outpace', 'genuine', 'plead', 'fossilize', 'toil', 'drastic', 'withhold', 'inanimate', 'clockwise', 'amnesia', 'revive', 'theorize', 'culprit', 'limp', 'worn-out', 'indignity', 'span', 'bribe']
Day21_kor = [['우아한', '정교한', '절묘한'], ['취득', '획득', '습득'], ['규제하다', '통제하다'], ['운송', '운임', '추방'], ['통찰', '통찰력'], ['명확한', '솔직한'], ['경작하다', '기르다', '장려하다', '육성하다'], ['혁신'], ['보전', '보호지', '보호하다', '보존하다'], ['냄새', '악취', '기미', '낌새'], ['예외'], ['우적우적 먹다'], ['선언하다'], ['찰싹 때리다'], ['변화성', '가변성', '변용성'], ['조사하다'], ['불끈 성나게 하다', '이글거리다', '불꽃', '타오름'], ['앞지르다', '속도가 더 빠르다'], ['진짜의', '진품의'], ['탄원하다', '변호하다', '애원하다'], ['고착화하다', '화석화하다'], ['수고', '노고', '힘들게 일하다'], ['급격한', '극단적인'], ['보류하다', '유보하다'], ['생명 없는', '무생물의'], ['시계방향으로'], ['기억상실'], ['부활시키다', '되살아나게 하다'], ['이론화하다'], ['죄인', '범죄자', '장본인'], ['절뚝거리다', '느릿느릿 가다', '기운이 없는','축 처진'], ['닳아빠진', '진부한', '지친'], ['모욕', '무례', '치욕'], ['기간', '폭', '범위', '걸치다', '이르다'], ['뇌물을 주다', '뇌물']]
b = 0
for i in range(0,35):
lable = Label(second_frame, text= Day21_eng[b])
lable.grid(column=0, row=b)
#입력 값 35개
entry = tkinter.Entry(second_frame, width=30)
entry.grid(row=b, column=1, sticky='nsew')
# important to bind each one for access
entry.bind('<Return>', getEntry)
b += 1
b_check = Button(second_frame, text='grade')
b_check.grid(columnspan=2, row=36)
window.mainloop()
I want to make if I press the 'grade' button, that sends a command to check if the .get() of the each Entries are in the Day21_kor list.
You can use a list to store the Entry widgets and use this list inside the callback of grade.
...
def check():
for i, e in enumerate(entries):
value = e.get()
print(value, value in Day21_kor[i])
# or whatever you want to do
entries = []
for row, item in enumerate(Day21_eng):
lable = tkinter.Label(second_frame, text=item)
lable.grid(column=0, row=row)
#입력 값 35개
entry = tkinter.Entry(second_frame, width=30)
entry.grid(row=row, column=1, sticky='nsew')
# important to bind each one for access
entry.bind('<Return>', getEntry)
entries.append(entry) # save 'entry' into list
b_check = tkinter.Button(second_frame, text='grade', command=check)
b_check.grid(columnspan=2, row=36)
Note that there are other issues in your code:
unused widgets: container, canvas and scrollbar
should bind <Configure> on second_frame instead of my_scrollbar
exception when getEntry() is executed. What do you want to do inside this function actually?
A slight change to #acw1668 code will output results to shell and produce a dictionary of results - called result
result = dict()
def check():
global result
for i, e in enumerate(entries):
name = description[ i ][ 'text' ]
value = e.get()
print( name, value )
result[ name ] = value
entries = []
description = []
for row, item in enumerate(Day21_eng):
lable = tkinter.Label(second_frame, text=item)
lable.grid(column=0, row=row)
#입력 값 35개
entry = tkinter.Entry(second_frame, width=30)
entry.grid(row=row, column=1, sticky='nsew')
description.append( lable )
entries.append(entry)
b_check = tkinter.Button(second_frame, text='grade', command=check)
b_check.grid(columnspan=2, row=36)
I am trying to test a function that needs an interaction of the user. The question is how can I do it programmatically ?
Here is an example where we ask for the user to select an item in a list (main.py) :
import tkinter as tk
from tkinter import Button, OptionMenu, StringVar
def ask_for_item_in_list(lst, title, default_index=0):
root, item = tk.Tk(), None
WIDTH, HEIGHT = 300, 120
root.title(title)
root.maxsize(width=WIDTH, height=HEIGHT)
root.minsize(width=WIDTH, height=HEIGHT)
root.resizable(0, 0)
variable = StringVar(root)
variable.set(lst[default_index])
option_menu = OptionMenu(root, variable, *lst)
option_menu.pack(fill="none", expand=True)
def on_close():
# The window has been closed by the user
variable.set(None)
close()
def close():
# It quits mainloop()
root.quit()
# It closes the window
root.destroy()
button_ok = Button(root, text='OK', command=close)
button_ok.pack(fill='none', expand=True)
root.protocol('WM_DELETE_WINDOW', on_close)
# Execution stops here as long as the user has not closed the window or
# pressed ok
root.mainloop()
# We retrieve the selected item
item = variable.get()
if item == 'None':
item = None
return item
if __name__ == '__main__':
lst = ['Item 1', 'Item 2', 'Item 3']
title = 'Select an item'
default_selected_idx = lst.index('Item 2')
selected_item = ask_for_item_in_list(lst, title, default_selected_idx)
print(selected_item)
I used pytest to write all my tests since I can't use object oriented programming. Actually, the code must be maintainable by people who are not professional developpers.
As you can see, I can't test this function this way, since it will wait for the user input (test_main.py):
from main import ask_for_item_in_list
def test_ask_for_item_in_list():
lst = ['Item 1', 'Item 2', 'Item 3']
title = 'Select an item'
# Here TRY to test if changing the default selected index works
default_selected_idx = lst.index('Item 2')
# Code to simualte that the user as clicked on OK ?
# user.click_button('OK') ?
selected_item = ask_for_item_in_list(lst, title, default_selected_idx)
assert selected_item == 'Item 2'
Do I need to change the way I have coded this ?
Is it relevant to test this kind of function ?
I have faced this problem many times (whatever is the language used), I would like to know how this is supposed to be done in a clean way.
Thanks for reading ! :)
Usually one fills the user input beforehand with expected or special values and then calls the test function several times. Also you may simulate clicks with various tools.
In C++ you could do something like:
int number_of_tests = 10;
int tests_passed = 0;
tests_passed += my_test_function_int(0);
tests_passed += my_test_function_int(-1);
...
tests_passed += my_test_function_string("foo");
tests_passed += my_test_function_string("");
tests_passed += my_test_function_string(" ");
...
return (tests_passed == number_of_tests);
This is just an example how one can do it (in our company we do it this way).
Also it is not very hard to add new tests for non-programmers or new people.
I've been playing around with tkinter and loving what I can do. I'm a total noob, however, and wanting to make my own matrix multiplier I've run into numerous problems. My goal is to create two grids of tk entries and be able to call them back later with .get, when I call txt_ent_a.get() I get an error: list has no attribute 'get'.
I found these other questions that have helped me get this far:
List of lists changes reflected across sublists unexpectedly
How can I create a list of entries in tkinter
tkinter: Button instance has no attribute 'get'
And this one that is basically the same as my question, though I didn't really understand how to take the conceptual answer and convert it into code:
Saving variables in 'n' Entry widgets Tkinter interface
I know what I have created as "entry_list" is just an empty list of lists. My real question is: how do I loop through making entry widgets and key or objects (I'm shaky on the lingo) that I can loop through using .get() on later, which I can then make into a list of lists (a matrix) that I can use in a function?
Because I'm so new to Python, I'm not sure if I just need to add a StringVar somehow, or .pack something somehow... I believe .pack is a tkinter thing. I'll try and keep my code cleanish, thank you for any help.
from tkinter import *
import tkinter as tk
window = Tk()
lbl1 = Label(window, text="# of rows: 3").grid(column=0, row=0)
lbl2 = Label(window, text="# of cols: 2").grid(column=0, row=1)
window.geometry('250x250')
def clickedc(): #create matrix click event
row = (3) #In final version these will be the numbers I loop over when
col = (2) #making the entries in question.
lblA = Label(window, text="Input matrix:").grid(column=(int(col/2)), row=3)
entry_list = [] #This is what I want to be a list I can .get() from later
r = 1
for r in range(int(row)):
entry_list.append([])
c = 1
for c in range(int(col)):
entry_list[-1].append(tk.Entry(window, width=10).grid(column=c, row=4 + r))
def clickeds(): #Solving click event
r = 1
matrix_A = [] #The matrix I want to put the values into, for reference
for r in range(int(row)):
c = 1
for c in range(int(col)):
matrix_A[r - 1:c - 1] = entry_list.get("1.0",'end-1c') #Causes Error
#Here I'll: output a new matrix as a grid of labels.ex: #1 1
btn_s = Button(window, text="Solve", command=clickeds) #1 0
btn_s.grid(column=int(col) + 1, row=3) #0 1
btn_c = Button(window, text="Create", command=clickedc)
btn_c.grid(column=3, row=1)
window.mainloop()
I hope this code is clear enough. Any advice is welcome, I assume I code rather poorly.
Is there a way to achieve a similar result like in this post python ttk treeview sort numbers but without pressing on the heading? Best way would be right after an item is inserted.
If you are going to sort your contents every time you insert a new item, then a more efficient approach would be to insert the item in the right place rather than sorting the whole data for each insertion.
Here's one way to achieve this using the standard bisect module, whose bisect(a, x) function gives you the index at which x should be inserted in a to maintain order (a is assumed to be sorted). In my example below:
my whole interface is stored in some class GUI;
my treeview is called self.tree and features only one column;
my insert_item method inserts a new line below a certain category in the tree (pointed to by location), and items below each category must be sorted separately in my application, which is why I only retrieve that category's children .
from bisect import bisect
# ... class GUI contains the treeview self.tree ...
def insert_item(self, location, item_name, item_id):
"""Inserts a new item below the provided location in the treeview,
maintaining lexicographic order wrt names."""
contents = [
self.tree.item(child)["text"]
for child in self.tree.get_children(location)
]
self.tree.insert(
location, bisect(contents, item_name), item_id, text=item_name
)
To sort the Treeview after each new item insertion, just add an explicit call to the sorting function at the correct position in your code.
Example code:
import tkinter as tk
from tkinter import ttk
counter = 0
numbers = ['1', '10', '11', '2', '3', '4', '24', '12', '5']
def sort_treeview():
content = [(tv.set(child, column), child)
for child in tv.get_children('')]
try:
content.sort(key=lambda t: int(t[0]))
except:
content.sort()
for index, (val, child) in enumerate(content):
tv.move(child, '', index)
def add_item():
global counter
if counter < 8:
tv.insert('', 'end', values=numbers[counter])
counter += 1
# Sort the treeview after the new item was inserted
# -------------------------------------------------
sort_treeview()
root = tk.Tk()
column = 'number'
tv = ttk.Treeview(root, columns=column, show='headings')
tv.pack()
button = tk.Button(root, text='Add entry', command=add_item)
button.pack()
root.mainloop()
I am trying to add these panda columns to a Listbox, so they read like this:
New Zealand NZD
United States USD
ETC.
I am using pandas to get the data from a .csv, but when I try and use a for loop to add the items to the list box using insert I get the error
NameError: name 'END' is not defined or
NameError: name 'end' is not defined
Using this code:
def printCSV():
csv_file = ('testCUR.csv')
df = pd.read_csv(csv_file)
print (df[['COUNTRY','CODE']])
your_list = (df[['COUNTRY','CODE']])
for item in your_list:
listbox.insert(end, item)
You could turn the csv file into a dictionary, use the combined country and currency codes as the keys and just the codes as the values, and finally insert the keys into the Listbox. To get the code of the current selection, you can do this: currencies[listbox.selection_get()].
listbox.selection_get() returns the key which you then use to get the currency code in the currencies dict.
import csv
import tkinter as tk
root = tk.Tk()
currencies = {}
with open('testCUR.csv') as f:
next(f, None) # Skip the header.
reader = csv.reader(f, delimiter=',')
for country, code in reader:
currencies[f'{country} {code}'] = code
listbox = tk.Listbox(root)
for key in currencies:
listbox.insert('end', key)
listbox.grid(row=0, column=0)
listbox.bind('<Key-Return>', lambda event: print(currencies[listbox.selection_get()]))
tk.mainloop()