I am trying to show the options for quiz questions in a listbox. The value will later be retrieved with a button. They show as one line instead of different listed items. I think it has something to do with the list and how it is formatted, but I am not sure how to fix this. How can I fix this?
from tkinter import *
from random import randint
import random
from string import ascii_lowercase
QuizFrame = Tk()
NUM_QUESTIONS_PER_QUIZ = 4
QUESTIONS = {
"What day of the week is it?": [
"Monday",
"Tuesday",
"Wednesday",
"Thursday"
],
"What is the first state?": [
"Delaware",
"Maryland",
"Texas",
"Maine"
],
}
num_questions = min(NUM_QUESTIONS_PER_QUIZ, len(QUESTIONS))
questions = random.sample(list(QUESTIONS.items()), k=num_questions)
Question_Label = Label(QuizFrame, text="", font=("Helvetica", 20))
Question_Label.grid(row=3, column=1)
for num, (question, alternatives) in enumerate(questions, start=1):
Question_Label.config(text=question)
correct_answer = alternatives[0]
labeled_alternatives = dict(
zip(ascii_lowercase, random.sample(alternatives, k=len(alternatives)))
)
my_listbox=Listbox(QuizFrame)
my_listbox.grid(row=6, column=1)
my_list=[labeled_alternatives]
for item in my_list:
my_listbox.insert(END,item)
QuizFrame.mainloop()
my_list only contains one item, labeled_alternative, so when you iterate it in the for loop, the entire dictionary gets inserted. Instead, you want to iterate labeled_alternatives.items() to get the keys and values for each item in your dictionary. Change the for loop to
for k, v in labeled_alternatives.items():
my_listbox.insert(END, k + ": " + v)
Related
I'm retrieving a large list of people from a database, then looping through each person to get their note/s and phone number/s. then displaying one person at a time with PySimpleGUI.
My issue is that it I'm doing multiple sql queries rather than just one (I don't know if this is an issue), and I have to repeatedly Finalize() and close() the screen, which creates a new window each time, rather than just refreshing the current page.
Am I able to refactor this so I don't have to close and create a new window each time?
Thank you.
# get all people
people = conn.execute('''SELECT * FROM people''')
people_tuple = people.fetchall()
# loop through all people
for index, person in enumerate(people_tuple):
# get persons notes
notes = conn.execute('''SELECT * FROM notes WHERE person_id = ?''', (person[0],))
notes_list = events.fetchall()
# get persons phone number/s
phone_numbers = conn.execute('''SELECT * FROM phone_numbers WHERE person_id = ?''', (person[0],))
# redacted #
window = main_window_layout(person, phone_num_list, notes_list).Finalize()
while True:
event, values = window.read()
# many if statements checking user input #
window.close()
def main_window_layout(person, phone_num_list, notes_list):
top_left_frame_1 = [[sg.Text("ID: " + str(person[0]), key='id', pad=(2,4))],
[sg.Text("Name: " + person[1] + " " + person[2], key='name', pad=(2,4))]]
frame_2 = [
[sg.Text(note[4], key='note_date', pad=(3, (12, 3))), sg.Text(note[6], key='note_info', pad=(3, (12, 3)))]
for note in reversed(notes_list)
]
Try to create the layout of the window, all elements with key to update the content later.
Demo code
import PySimpleGUI as sg
person = [
[1, 'Ronald', 'Reagan'],
[2, 'Abraham', 'Lincoln'],
[3, 'George', 'Washington'],
[4, 'Andrew', 'Jackson'],
[5, 'Thomas', 'Jefferson'],
[6, 'Harry', 'Truman'],
]
size, index = 10, 0
total = len(person)
keys = ['ID', 'First Name', 'Last Name']
layout = [
[sg.Text(key),
sg.Push(),
sg.Text(str(person[index][i]), size=size, background_color='blue', key=key)]
for i, key in enumerate(keys)] + [
#[sg.Button('< Prev'), sg.Push(), sg.Button('Next >')],
]
window = sg.Window('Title', layout)
while True:
event, values = window.read(timeout=500)
if event == sg.WIN_CLOSED:
break
elif event == sg.TIMEOUT_EVENT:
index = (index + 1) % total
for i, key in enumerate(keys):
window[key].update(value=str(person[index][i]))
""" button events to show previous/next record
elif event in ('< Prev', 'Next >'):
delta = -1 if event == '< Prev' else 1
index = (index + delta) % total
for i, key in enumerate(keys):
window[key].update(value=str(person[index][i]))
"""
window.close()
I creating GUI interacting with treeview and I want to get all the value inside the list and show it in treeview window. I have a add button and once I click it, it will work fine. But on the second time, it shows an error Item 1 already exists. It also does not added to the list.
This is my code:
from tkinter import *
from tkinter import ttk
root = Tk()
def add1():
global count
for i in tree_table.get_children():
tree_table.delete(i)
get_name = txt_name.get()
get_ref = txt_ref.get()
get_age = txt_age.get()
get_email = txt_email.get()
data_list.append((get_name, get_ref, get_age, get_email))
for item in data_list:
tree_table.insert(parent='', index='end', iid=count, text=f'{count + 1}', values=(item))
txt_name.delete(0, END)
txt_ref.delete(0, END)
txt_age.delete(0, END)
txt_email.delete(0, END)
count += 1
print(data_list)
tree_table = ttk.Treeview(root)
global count
count = 0
data_list = []
tree_table['columns'] = ("Name", "Reference No.", "Age", "Email Address")
tree_table.column("#0", width=30)
tree_table.column("Name", width=120, anchor=W)
tree_table.column("Reference No.", width=120, anchor=W)
tree_table.column("Age", width=120, anchor=W)
tree_table.column("Email Address", width=120, anchor=W)
headings = ["#0", "Name", "Reference No.", "Age", "Email Address"]
txt_headings = ["No.", "Name", "Reference No.", "Age", "Email Address"]
for i in range(len(headings)):
tree_table.heading(headings[i], text=txt_headings[i], anchor=W)
txt_name = Entry(root, width=20)
txt_name.pack()
txt_ref = Entry(root, width=20)
txt_ref.pack()
txt_age = Entry(root, width=20)
txt_age.pack()
txt_email = Entry(root, width=20)
txt_email.pack()
btn_enter = Button(root, text="Add", width=20, command=add1)
btn_enter.pack()
tree_table.pack()
root.mainloop()
Traceback:
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Python\Python385\lib\tkinter\__init__.py", line 1883, in __call__
return self.func(*args)
File "d:/Code/Cpet5/new.py", line 32, in add1
tree_table.insert(parent='', index='end', iid=count, text=f'{count + 1}', values=(item))
File "C:\Python\Python385\lib\tkinter\ttk.py", line 1366, in insert
res = self.tk.call(self._w, "insert", parent, index,
_tkinter.TclError: Item 1 already exists
How do I refresh the treeview to reflect the changes made in the list?
Quick fix:
Get rid of the iid argument:
tree_table.insert(parent='', index='end', text=f'{count + 1}', values=(item))
But that will create an issue of the No. column getting same value always.
Actual fix:
So the actual problem is your list contains alot of values, you should get rid of those values once you insert it, so the code should be something like:
def add1():
global count
get_name = txt_name.get()
get_ref = txt_ref.get()
get_age = txt_age.get()
get_email = txt_email.get()
data_list.clear() #clear the list
data_list.append((get_name, get_ref, get_age, get_email)) #append to the empty list
for item in data_list:
tree_table.insert(parent='', index='end',iid=count, text=f'{count + 1}', values=(item))
txt_name.delete(0, END)
txt_ref.delete(0, END)
txt_age.delete(0, END)
txt_email.delete(0, END)
count += 1
print(data_list)
This way your clearing whats inside of the list each time and appending new values to list.
With your older code you can see that first time the the list is [('1', '1', '1', '1')] and when the next set of values is appended it becomes [('1', '1', '1', '1'), ('2', '2', '2', '2')] so there is not enough space accommodate all this value in? Anyway this fixes it. Or you could also make a tuple of those inputs and then pass that tuple as the values for treeview without looping, like acw1668 did.
You don't need to clear the treeview before adding new row to it:
def add1():
global count
get_name = txt_name.get()
get_ref = txt_ref.get()
get_age = txt_age.get()
get_email = txt_email.get()
row = (get_name, get_ref, get_age, get_email)
data_list.append(row)
count += 1
tree_table.insert(parent='', index='end', iid=count, text=f'{count}', values=row)
txt_name.delete(0, END)
txt_ref.delete(0, END)
txt_age.delete(0, END)
txt_email.delete(0, END)
print(data_list)
I'm working on a script that needs a phone number queried from an tex field or int field for maya. I using python and I can't seem to find anything that works. Can you help?
Thanks
TED
Ok This is the mess that I came up with.
def makeTui():
if(cmds.window('window2',q=1,ex=1)):cmds.deleteUI('window2')
cmds.window('window2',menuBar=1)
cmds.formLayout('formLayout1')
cmds.text(label='Phone Number')
num = cmds.intField('textField4',width=100,height=20,changeCommand = num_callback)
cmds.text('text95',label='Service Provider')
cmds.optionMenu('optionMenu1')
cmds.menuItem(label='AT&T')
cmds.menuItem(label='Verizon')
cmds.menuItem(label='Sprint')
cmds.menuItem(label='Cricket')
cmds.menuItem(label='Tmobil')
cmds.iconTextButton('iconTextButton45',parent='formLayout1',image='render.png',command='num_callback()')
cmds.formLayout('formLayout1',e=1,attachForm=[['textField4', 'left', 100], ['text95', 'top', 30], ['optionMenu1', 'top', 30], ['optionMenu1', 'left', 100], ['iconTextButton45', 'left', 100], ['iconTextButton45', 'top', 60]])
cmds.showWindow('window2')
makeTui()
def num_callback():
print cmds.intField(num, q=True, value=True)
You want to define your callback function in the same scope as the UI items - that saves a lot of work in trying to remember the names of the widgets that you need to work on.
def create_ui():
window = cmds.window()
column = cmds.columnLayout(adj=True)
# three items arranged horizontally
row = cmds.rowLayout(nc = 3)
numfield = cmds.textFieldGrp(label = 'phone number')
cmds.text("provider")
provider = cmds.optionMenu()
for p in ('AT&T', 'Verizon', 'Sprint', 'T-Mobile'):
cmds.menuItem(label = p)
cmds.setParent("..")
# define this function here so it knows the widgets for
# the text field and the option menu
def render_button():
phoneno = cmds.textFieldGrp(numfield, q=True, text=True)
# remove any punctuation:
digits =[int(c) for c in phoneno if c.isdigit()]
providername = cmds.optionMenu(provider, q=True, v=True)
print digits, providername
cmds.iconTextButton(image='render.png', c = render_button)
cmds.showWindow(window)
Here the callback function knows the 'names' of the field for entering numbers and the optionmenu for picking providers. The actual work would go into the render_buttons() function. I used a text field, incidentally, since many people will expect to type things like 1(555)111-2222 and an intField wont' allow it.
The function below creates a nicely formatted list of all attributes of an OptionMenu widget. However a kludge is needed because OptionMenu does not having a "Menu size"' attribute (that holds the number of elements inside the dropdown list).
How do I extract this value from the widget so I can eliminate the kludge (and show the attributes of EVERY entry in the drop-down menu) ?
BTW, The function does not list the contents of the (non-standard) Optionmenu "command" option. For info on this option see the accepted answer to tkinter OptionMenu issue (bug?): GUI and program values not kept in lockstep (python 3.x) )
def OptionMenuConfigLister( OptionMenuWidget ) :
'''
Uncomment this block to see ALL attributes of the widget
#
# Gets the main attributes of the widget (EXCLUDING the attributes for
# the dropdown list and the values in the list)
#
print( " \nOptionMenuWidget.config()\n" )
for i, j in enumerate( sorted( OptionMenuWidget.config().items() ) ) :
print( " {0:<19} |{1}|".format( j[ 0 ], j[ 1 ][ -1 ] ), flush = True )
#
# Gets the attributes of the list portion of the widget (but NOT the entries)
#
print( "\nOptionMenuWidget[ 'menu' ].config().items() :\n" )
for i, j in enumerate( sorted( OptionMenuWidget[ 'menu' ].config().items() ) ) :
print( " {0:<18} |{1}|".format( j[ 0 ], j[ 1 ][ -1 ] ), flush = True )
'''
#
# Get the attributes of each/every entry in the dropdown list
#
# TODO: Determine how to get # of items in list
#
for i in range( 0, 1 ) : ''' <======== KLUDGE '''
print( "\nOptionMenuWidget[ 'menu' ].entryconfig(" + str( i ) + ").items()) :\n" )
for _, j in enumerate( sorted(
OptionMenuWidget[ 'menu' ].entryconfig( i ).items() ) ) :
print( " {0:<16} |{1}|".format( j[ 0 ], j[ 1 ][ -1 ] ), flush = True )
print()
return
EDIT 20180117: Here's the fix, based on the answer by #nae - replace the kludge line with:
ElementCount = OptionMenuWidget[ 'menu' ].index( 'end' ) + 1
for i in range( 0, ElementCount ) :
And as per comment by #furas, sample code now uses [ -1 ] in the formatted print statements.
Based on Menu total index counts and from the source code, the fact that OptionMenu's *values are stored as Menu items:
class OptionMenu(Menubutton):
"""OptionMenu which allows the user to select a value from a menu."""
def __init__(self, master, variable, value, *values, **kwargs):
...
menu.add_command(label=value,
command=_setit(variable, value, callback))
for v in values:
menu.add_command(label=v,
command=_setit(variable, v, callback))
self["menu"] = menu
One can extract the 'Menu Size' using .index('end') for OptionMenu's menu option like the following:
import tkinter as tk
root = tk.Tk()
opt_var = tk.IntVar()
opt = tk.OptionMenu(root, opt_var, 3, 2, 3, 5, 4)
opt.pack()
print(opt['menu'].index('end') + 1) # basically len(*values) + len(value)
root.mainloop()
I have Tkinter program that has to add a significant amount of data to the window so I tried to write a for loop to take care of it but since I have to use a string variable for the name of the object that Tkinter is running .insert() on the object. I didn't explain it very well here is the method
def fillWindow(self):
global fileDirectory
location = os.path.join(fileDirectory, family + '.txt')
file = open(location, 'r')
ordersDict = {}
for line in file:
(key, value) = line.split(':', 1)
ordersDict[key] = value
for key in ordersDict:
ordersDict[key] = ordersDict[key][:-2]
for item in ordersDict:
if item[0] == '#':
if item[1] == 'o':
name = 'ordered%s' %item[2:]
right here is the problem line because I have the variable that matches the name of the entry object already created but 'name' is actually a string variable so it gives me the error "AttributeError: 'str' object has no attribute 'insert'"
name.insert(0,ordersDict[item])
here is the entire class. It makes a Tkinter window and fills it with a sort of shipping screen so all the entries are for how many orders of a certain thing are needed. I'm also very new so I know that I do things the long way a lot.
class EditShippingWindow(Tkinter.Toplevel):
def __init__(self, student):
Tkinter.Toplevel.__init__(self)
self.title('Orders')
family = student
## Window Filling
ageGroupLabel = Tkinter.Label(self,text='Age Group')
ageGroupLabel.grid(row=0,column=0)
itemColumnLabel = Tkinter.Label(self,text='Item')
itemColumnLabel.grid(row=0, column=1)
costColumnLabel = Tkinter.Label(self,text='Cost')
costColumnLabel.grid(row=0, column=2)
orderedColumnLabel = Tkinter.Label(self,text='Ordered')
orderedColumnLabel.grid(row=0, column=3)
paidColumnLabel = Tkinter.Label(self,text='Paid')
paidColumnLabel.grid(row=0, column=4)
receivedColumnLabel = Tkinter.Label(self,text='Received')
receivedColumnLabel.grid(row=0, column=5)
#Item Filling
column1list = ['T-Shirt (2T):$9.00', 'T-Shirt (3T):$9.00', 'T-Shirt (4T):$9.00',
'Praise Music CD:$10.00', ':', 'Vest L(Size 6):$10.00', 'Vest XL(Size 8):$10.00',
'Hand Book (KJ/NIV):$8.75', 'Handbook Bag:$6.00', 'Memory CD (KJ/NIV):$10.00',
':', 'Vest L(size 10):$10.00', 'Vest XL(Size 12):$10.00', 'Hand Glider (KJ/NIV/NKJ):$10.00',
'Wing Runner (KJ/NIV/NKJ):$10.00', 'Sky Stormer (KJ/NIV/NKJ):$10.00', 'Handbook Bag:$5.00',
'Memory CD (S/H/C):$10.00', 'Hand Glider Freq. Flyer:$8.00', 'Wing Runner Freq. Flyer:$8.00',
'Sky Stormer Handbook:$8.00' , ':', 'Uniform T-Shirt Size (10/12/14):$13.00',
'Uniform T-Shirt Size(10/12/14):$13.00', 'Uniform T-Shirt(Adult S / M / L / XL):$13.00',
'3rd & 4th Gr. Book 1 (KJ / NIV / NKJ):$8.75', '3rd & 4th Gr. Book 2 (KJ / NIV / NKJ):$8.75',
'4th & 5th Gr. Book 1 (KJ / NIV / NKJ):$8.75', '4th & 5th Gr. Book 2 (KJ / NIV / NKJ):$8.75',
'Memory CD 3rd & 4th Gr. Book (1/2):$10.00', 'Drawstring Backpack:$5.50']
column1num = 1
for item in column1list:
num = str(column1num)
(title, price) = item.split(':')
objectName1 = 'column1row' + num
objectName1 = Tkinter.Label(self,text=title)
objectName1.grid(row=column1num, column=1)
objectName2 = 'column1row' + num
objectName2 = Tkinter.Label(self,text=price)
objectName2.grid(row=column1num, column=2)
column1num += 1
#Ordered Paid Recieved Filler
for i in range(32):
if i == 11 or i == 22 or i == 0 or i == 5:
pass
else:
width = 10
# First Column
title1 = 'ordered' + str(i)
self.title1 = Tkinter.Entry(self,width=width)
self.title1.grid(row=i,column=3)
#self.title1.insert(0, title1)
#Second
title2 = 'paid' + str(i)
self.title2 = Tkinter.Entry(self,width=width)
self.title2.grid(row=i,column=4)
#self.title2.insert(0, title2)
#Third
title3 = 'received' + str(i)
self.title3 = Tkinter.Entry(self,width=width)
self.title3.grid(row=i,column=5)
#self.title3.insert(0, title3)
## Methods
def fillWindow(self):
global fileDirectory
location = os.path.join(fileDirectory, family + '.txt')
file = open(location, 'r')
ordersDict = {}
for line in file:
(key, value) = line.split(':', 1)
ordersDict[key] = value
for key in ordersDict:
ordersDict[key] = ordersDict[key][:-2]
for item in ordersDict:
if item[0] == '#':
if item[1] == 'o':
self.name = 'ordered%s' %item[2:]
self.name.insert(0,ordersDict[item])
fillWindow(self)
It looks like you have a conceptual error there: inside this method, the variable "name" does not exist up to the last line on the first listing. Then it is created, and points to an ordinary Python string -- if you are using a "name" variable elsewhere on your class that variable does not exist inside this method.
For an easy fix of your existing code, try calling the variable as "self.name" instead of just name where it is created, and on your last line in this method use:
self.name.insert(0,ordersDict[item]) instead.
The self. prefix will turn your variable into an instance variable, which is shared across methods on the same instance of the class.
On a side note, you don' t need even the dictionary much less three consecutive for loops on this method, just insert the relevant values you extract from "line" in your text variable.