Reset tkinter radiobuttons created in for loop to default value - python-3.x

I am creating radiobuttons within a for loop. I want to be able to reset all their values with a single button (basically start the survey again from scratch), so I created 'self.info.buttons = []' and have appended each radiobutton to this list. However when I try and reset to the default value '-1' for unanswered, it results in the first & fourth option being selected and messes up the grouping.
def askQuestions(self):
file = open('questions.txt')
questionlist = file.readlines()
for number, question in enumerate(questionlist, 1):
self.var = tk.IntVar(value = -1)
width = 5
line = '{:5}'.format(number, fill=' ') + ' : ' + question.strip()
label = tk.Label(self, text=line)
label.grid(row=number, column = 0, sticky=tk.W)
options = ['?', 'No', 'Maybe', 'Yes']
for answer in range(-1,3):
button = tk.Radiobutton(self, borderwidth=10, variable = self.var, text=options[answer+1], width = 5, value = answer, indicatoron=0)
button.grid(row = number, column = answer+2)
self.info.buttons.append(button) # List containing radiobuttons
self.info.answers.append(self.var)
...function which resets values ....
Inside a class called Info() :
def resetFields(self):
self.name.set(value = '') #these work
self.dob.set(value = '')
for count, button in enumerate(self.buttons):
self.buttons[count].config(value = -1) # nothing I have tried works.
I have not been programming Python, or Tkinter long and it is probably a rudimentary mistake, but I have tried everything I can think of. The source is available here : https://github.com/inyoka/sand
Perhaps I should have just 'destroy'ed the form and recreated it? Resetting the variables seemed the simpler option when I started. Any help gratefully recieved.

In self.buttons you have Button() objects which don't keep answers. You have answers in IntVar() objects which are in self.answers. So you have to reset self.answers.
BTW: you don't have to use enumerate
def resetFields(self):
self.name.set(value='')
self.dob.set(value='')
for a in self.answers:
a.set(value=-1)
When you reset self.answers then buttons change state too.

Related

QComboBox only gives first item

I have some troubles with a QComboBox() when I want to read out the actual text from it.
The ComboBox is created dynamically, so I use the exec() function from python to create them.
I see the ComboBoxes in my layout - but so far so good - when I read the Data out from them, I only get returned the first value of the ComboBox, not the actual selected. I have tried it with .currentText() and .currentIndex()
As you see in my code snipped in the upper part - I create zu QComboBox with the exec function (the program is used to import data on a Database, so, I create QComboBoxes, as much as Database-Tableccolumns I have and so much entries, Import-Tablecolumns I have.
I create the ComboBox with startin CB_,and the I look for attributes starting with CB and run their function.
But I have no clue, what is wrong..
Here is my code:
self.layoutscroll = QVBoxLayout()
self.widgetscroll = QWidget()
self.widgetscroll.setLayout(self.layoutscroll)
self.nextbutton.setEnabled(True)
self.nextbutton.clicked.connect(self.uploaddatatodb)
self.scroll.setWidget(self.widgetscroll)
self.layoutbox.addWidget(self.scroll)
for col in dfdb.columns:
exec("self.groupbox_{0} = QGroupBox('Datenbank: {0}')".format(str(col)))
exec("self.groupbox_{0}_layout = QVBoxLayout()".format(str(col)))
exec("self.CB_{0} = QComboBox(self)".format(str(col)))
exec("for importcol in self.dfimport.columns:\n\tself.CB_{0}.addItem(str(importcol))".format(str(col)))
exec("self.groupbox_{0}_layout.addWidget(self.CB_{0})".format(str(col)))
exec("self.groupbox_{0}.setLayout(self.groupbox_{0}_layout)".format(str(col)))
exec("self.layoutscroll.addWidget(self.groupbox_{0})".format(str(col)))
def uploaddatatodb(self):
print("hallo")
self.nextbutton.setEnabled(False)
comparedf = pd.DataFrame()
dblist = [a for a in dir(self) if a.startswith('groupbox_') and not a.endswith('layout')]
importlist = [a for a in dir(self) if a.startswith('CB_')]
for idx,x in enumerate(importlist):
exec("str(x)")
exec("val=str(self."+str(x)+".currentText())")
exec("print(val)")
exec("importlist["+str(idx)+"]=val")
for i in range(len(dblist)):
comparedf = comparedf.append({"db":dblist[i][9:],"import":importlist[i]},ignore_index=True)
print(comparedf)
EDIT:
I have updated my code to get away from the exec() function - but I have the same error: When I want to read out the comboboxes - everytime I get only the first value of the combobox:
self.layoutscroll = QVBoxLayout()
self.widgetscroll = QWidget()
self.widgetscroll.setLayout(self.layoutscroll)
self.nextbutton.setEnabled(True)
self.nextbutton.clicked.connect(self.uploaddatatodb)
self.scroll.setWidget(self.widgetscroll)
self.layoutbox.addWidget(self.scroll)
for col in dfdb.columns:
combo = QComboBox()
combo.addItems(self.dfimport.columns)
groupbox_DB_lyt = QVBoxLayout()
groupbox_DB = QGroupBox(str("Datenbank: "+col))
groupbox_DB_lyt.addWidget(combo)
groupbox_DB.setLayout(groupbox_DB_lyt)
self.layoutscroll.addWidget(groupbox_DB)
def uploaddatatodb(self):
self.nextbutton.setEnabled(False)
for i in range(self.layoutscroll.count()):
dblist = self.layoutscroll.itemAt(i).widget().title()
importlist = self.layoutscroll.itemAt(i).widget().layout().itemAt(0).widget().currentText()
print(dblist,importlist)
With print I get over the iteration all Groupbox Titles, but only the first QComboBoxes first Text.

Mapping Tkinter entry box to variable in python 3.8

I am a complete beginner in python,I was hoping someone could help me figure out what I am trying to accomplish.
I built a small tkinter front end that will generate a string multiple times. I want the amount of times that the string is generated to be based off of an entry box. I have hit a wall.
Here is my Front End (help_FE.py)
from tkinter import *
import help_BE
def view_formula():
text1.delete('1.0',END)
text1.insert(END,help_BE.End_result)
pass
window = Tk()
window.wm_title=("Print:")
l1=Label(window,text = "Page to print:")
l1.grid(row=3, column=2)
e1 = Entry(window)
e1.grid(row=3, column=3)
text1=Text(window, height=20,width=35)
text1.grid(row=4,column=3, rowspan=10, columnspan=7, padx=5, pady=10)
sb1=Scrollbar(window)
sb1.grid(row=4,column=11,rowspan=10)
text1.configure(yscrollcommand=sb1.set)
sb1.configure(command=text1.yview)
text1.bind('<<TextboxSelect>>')
b1=Button(window, text = "Generate", width =10, command=view_formula)
b1.grid(row=3,column=6)
window.mainloop()
and my backend (help_BE.py)
currently, the generate button will print "Testing" 3 times, because i have set pages = 3 in the backend, but I want to be able to set pages to whatever is entered into the frontend entry box.
pages = 3
result=[]
def foo():
skip_zero = pages + 1
for x in range (skip_zero):
if x==0:
continue
result.append("Testing"+str(x))
listToStr = ''.join([str(element) for element in result])
full_formula = (listToStr)
return full_formula
End_result = foo()
The data inputted in the field can be accessed using the function Entry.get(), and you can convert the string to a number with the int function.
In order to get it to the backend, and in order to keep your value up to date, I would make sure that, as jasonharper mentioned, you call foo each time the button is pressed, passing the entry's value in as the argument pages. This means tweaking your code as such:
help_BE.py
def foo(pages):
skip_zero = pages + 1
for x in range (skip_zero):
if x==0:
continue
result.append("Testing"+str(x))
listToStr = ''.join([str(element) for element in result])
full_formula = (listToStr)
return full_formula
help_FE.py
def view_formula():
text1.delete('1.0',END)
text1.insert(END,help_BE.foo(int(e1.get())))

Python3 Tkinter display Listbox items in a Label when clicked

I have this listbox as part of a class:
def myListbox(self):
selection = Label(self, text="Please select Country").grid(row=0,column=0)
countries = Listbox(self, width = 20, height = 75)
countries.grid(row=0, column=1)
# I have a function that populates the country names from
# a text file and displays the names in the Listbox.
# I want to be able to select a country from the Listbox
# and have it displayed in a Label
country_display = Label(self, text = "").grid(row = 0, column = 9)
# this is where I'm not sure what code to use.
# my code is
countries.bind("<<ListboxSelect>>",country_display)
At the moment nothing is displaying. What am I missing here?
Thanks
First of all, when you perform the method grid on a widget, it will return None. This means that your variable now holds the value None, instead of the reference to the widget.
Secondly, the method bind, binds a function to an event. This function cannot be called yet. However, in your bind, you try to assign the Label (which is not a function) to the event; this is simply not possible.
In the solution below, a function is assigned to the event, which retrieves the country, and sets the label.
from tkinter import *
countries_list = ["Netherlands",
"America",
"Sweden",
"England"]
class MyClass(Tk):
def myListbox(self):
# better to structure it this way. The method grid simply returns None
# which meant that the variable hold None
# Now, the variable selection holds the widget
selection = Label(self, text="Please select Country")
selection.grid(row=0,column=0)
countries = Listbox(self, width = 20, height = len(countries_list))
countries.grid(row=0, column=1)
for country in countries_list:
countries.insert(END, country)
country_display = Label(self, text = country, width = 15)
country_display.grid(row = 0, column = 9)
def get_country(*x):
# gets which country is selected, and changes
# the label accordingly
country = countries_list[countries.curselection()[0]]
country_display.config(text = country)
countries.bind("<<ListboxSelect>>", get_country)
app = MyClass()
app.myListbox()
edit: for more information on Listbox see http://effbot.org/tkinterbook/listbox.htm
(although I get the feeling you might want to use a Combobox, as described in http://www.tkdocs.com/tutorial/widgets.html?)

Label keeps on appearing

SO I am using Python 3.4 and tkinter.
And when I call a function again n again which contains a label, the label keeps on appearing in window but previous label doesn't go away?
How can I remove any printed label from GUI window as soon as function is called and then display new one?
Here is the code:-
#def prestart():
#here I check if number of match is okay, if not, user is redirected to setting else, I call start()
def start():
#CPU Choice
cpu_choice = Label(historyframe, text = "CPU Choosed: {}".format(dict['cpu_choice']))
#Played Match
#played_num_of_match = Label(scoreframe, text = "Number of Matches Played: {}".format(int(dict['match_played'])))
#Display Status
status_disp = Label(scoreframe, text = "Current Status: {}".format(dict['status']))
if(int(dict['match_played']) < int(dict['num_of_match'])):
playframe.grid(row = 1, column = 0)
historyframe.grid(row = 2, column = 1)
status_disp.pack(fill=X)
elif(int(dict['match_played']) == int(dict['num_of_match'])):
playframe.grid(row = 1, column = 0)
historyframe.grid(row = 2, column = 1)
status_disp.pack(fill=X)
cp = dict['cpu_point']
up = dict['user_point']
result(cp, up)
cpu_choice.pack(fill = X)
scoreframe.grid(row = 2, column = 0)
This function just updates the display!
def send_value(x):
#Here I run logic of game and change value of key in dictionary and call start() at end of change.
Now, the choice buttons are not in any definition as they don't need to be called again n again. I just make playframe disappear n appear!
Here is the code for them:-
#Display Question
question = Label(playframe, text = "Rock? Paper? Scissor?")
#Rock
rock = Button(playframe, text = "Rock!", command = lambda: send_value("ROCK"))
#Paper
paper = Button(playframe, text = "Paper!", command = lambda: send_value("PAPER"))
#Scissor
scissor = Button(playframe, text = "Scissor!", command = lambda: send_value("SCISSOR"))
So when user clicks Rock/Paper/Scissor, I just change key value in dictionary! But if I keep the label outside function, it doesn't get auto updated!
Everything else is working perfectly. I'll kind of now start to make code cleaner.
Try something like this instead of creating a new label every time:
import Tkinter as tk
class Window():
def __init__(self, root):
self.frame = tk.Frame(root)
self.frame.pack()
self.i = 0
self.labelVar = tk.StringVar()
self.labelVar.set("This is the first text: %d" %self.i)
self.label = tk.Label(self.frame, text = self.labelVar.get(), textvariable = self.labelVar)
self.label.pack(side = tk.LEFT)
self.button = tk.Button(self.frame, text = "Update", command = self.updateLabel)
self.button.pack(side = tk.RIGHT)
def updateLabel(self):
self.i += 1
self.labelVar.set("This is new text: %d" %self.i)
root = tk.Tk()
window = Window(root)
root.mainloop()
Important points:
1) A class is used, as it is much easier to pass values around when all Tkinter objects and variables are member variables, accessible from all of your GUI functions.
2) updateLabel does not create a new Label. It simply updates the StringVar() object to hold new text every time you call the function. This is accomplished with the textvariable = self.labelVar keyword when creating my Label widget.
PS: This is done in Python 2.5 so for this code to work for you, change Tkinter to tkinter
EDIT 06/19/2015:
If you want to implement something similar to what I have with your code, without using a class, you'll need to pass around references to your variables.
1) Change start:
Your Labels cpu_choice, status_disp, etc. should be created outside of the function; likely in the same location as question, rock, paper, scissors, etc. You will also pack them outside of the function as well. Same with all the calls to .grid inside of start; you shouldn't need to call pack or grid more than once: right when you create the widget.
The following lines:
playframe.grid(row = 1, column = 0)
historyframe.grid(row = 2, column = 1)
status_disp.pack(fill=X)
Can be done outside of the function as well; you execute these 3 statements under both the if and the elif conditions. This means they aren't really conditional statements; they are done regardless of the validity of the condition.
2) Create a StringVar for both cpu_choice & status_disp & edit the Labels as follows (remember, outside of the function):
cpu_choice_text = StringVar()
cpu_choice_text.set("Set this to whatever is shown at the start of the game")
cpu_choice = Label(historyframe, text = cpu_choice_text.get(), textvariable = cpu_choice_text)
cpu_choice.pack(fill = X)
# And do this same thing for status_disp
3) When you call start, you will now pass it cpu_choice_text & status_disp_text (or whatever they are called). Instead of trying to change the text field of the Label frame, you may now use a set call on the StringVar which is connected to the Label & the Label will automatically update. Example:
def start(cpu_choice_text, status_disp_text):
cpu_choice.set(text = "CPU Choice: {}".format(dict['cpu_choice']))
...
Alternatively, wrap it all in a class and make it much easier for yourself by using self on every Tkinter variable & widget. In this way you won't need to pass variables to your functions, just access member variables directly as I have with self.i, self.labelVar in my example.
Each time you call start you create new labels and use grid to place them in the same spot as the old labels. The best solution is to only create the labels once, but if you insist on creating new labels each time start is called, you need to delete the old labels first.
You can use the destroy() method of a label to destroy it, though for that to work you must keep a global reference of the label.

Tkinter: creating an arbitrary number of buttons/widgets

So, I've got a list with entries that look like this:
Option1 Placeholder1 2 Placeholder2 0
Option2 Placeholder1 4
Option3 Placeholder1 2 Placeholder2 -2 Placeholder3 6
I have a listbox of the Options and a button that creates a new window with the values for the selected Option. What I want to do is to create n number of buttons when this new window is created, where n is the number of values of the selected Options (i.e. 2, 1 and 3 for Options 1 through 3, respectively). I want it to look something like this:
Option1
Placeholder1 [button1 containing value=2]
Placeholder2 [button2 containing value=0]
... which is of course quite simple if I just assign a button for the maximum number of n that I know will be present, but I'm wondering if there's a way to do it more arbitrarily. Obviously the same problem applies to the arbitrary number of Labels I would need to use for the value names (the 'PlaceholderX's) as well.
I've been trying to do some reading on this type of thing, variable variables, etc., and it seems it's a very big NO-NO most (if not all) of the time. Some advocate the use of dictionaries, but I don't really get how that's supposed to work (i.e. naming variables from entries/values in a dict).
Is this something that can (and should) be done, or am I better off just creating all the buttons manually?
[EDIT: added code]
from tkinter import *
import csv
root = Tk()
root.wm_title("RP")
listFrame = Frame(root, bd=5)
listFrame.grid(row=1, column=2)
listbox1 = Listbox(listFrame)
listbox1.insert(1, "Option1")
listbox1.insert(2, "Option2")
listbox1.insert(3, "Option3")
listbox1.pack()
infoFrame = Frame(root, bd=5)
infoFrame.grid(row=1, column=3)
info_message = Message(infoFrame, width=300)
info_message.pack()
# Read stats from file
stat_file = open('DiceTest.csv', 'rU')
all_stats = list(csv.reader(stat_file, delimiter=';'))
def list_selection(event):
# gets selection and info/stats for info_message
index = int(listbox1.curselection()[0])
stats = all_stats[index]
infotext = str(stats[0]) # just the name
for n in range(int((len(stats)-2)/2)): # rest of the stats
infotext += ('\n' + str(stats[n*2 + 2]) + '\t' + str(stats[n*2 + 3]))
info_message.config(text=infotext)
listbox1.bind('<ButtonRelease-1>', list_selection)
def load():
top = Toplevel()
top.geometry('300x100')
index = int(listbox1.curselection()[0])
stats = all_stats[index]
# some way to create arbitrary buttons/labels here (?)
load_button = Button(root, text='Load', command=load)
load_button.grid(row=2, column=2)
root.mainloop()
Oh, and every button should have the same command/function, which reduces whatever value currently is in the button by 2.
Figured it out! Creating the widgets dynamically with a dictionary worked just fine, but calling the correct widget on the various button presses was more difficult. This is what I had:
buttons = dict()
for k in range(len(info)):
buttons[k] = Button(top, text=info[k], command=lambda: my_function(buttons[k]))
... which would work, but all button presses would call the function with the last created button as the target. All that was needed was a few extra characters in the command part of the buttons:
buttons = dict()
for k in range(len(info)):
buttons[k] = Button(top, text=info[k], command=lambda a=k: my_function(buttons[a]))
... which I assume works because it somehow stores the value of k inside a rather than taking the last known value of k, i.e. equivalent to the last created button. Is this correct?
You can store Buttons in a list:
from tkinter import *
master = Tk()
buttons = []
n = 10
for i in range(n):
button = Button(master, text = str(i))
button.pack()
buttons.append(button)
master.mainloop()

Resources