Label keeps on appearing - python-3.x

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.

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

How to ensure that the "bind" order is not skipped in tkinter?

I am trying to create a game using tkinter in which the players enter their names into an entry widget.
After entering their name, the user should press enter to call the function "player_names", which would ideally save the players name in a list, delete the text in the entry widget and then continue to the next loop (i.e player).
The script seems to be ignoring the bind and and moving straight to the line "self.name_entry.destroy()". How do I ensure that the script waits for the command before continuing?
def initialise_game(self, num_of_players):
self.players_list = []
for i in range(num_of_players):
player_num = i+1
self.name_label = tk.Label(self.bg_label, text='What is the name'
' of Player ' + str(player_num) + '?')
self.name_label.grid(row=0, padx=200, pady=120)
self.name_entry = tk.Entry(self.bg_label)
self.name_entry.grid(row=1, padx=200, pady=0)
self.name_entry.bind('<Return>', self.player_names)
self.name_entry.destroy()
def player_names(self, event):
self.players_list.append(self.name_entry.get())
self.name_entry.delete(0, 'end')
Entry doesn't work like input() it will not stop code and wait till you put text and press enter. GUI creates Entry and it executes code after Entry at once. You have bind to assign function which will be executed whey you press Enter and this function should get value from Entry, and replace widgets (or only text in Label). It also should remove Entry after last player so you have to count how many times function was executed (or how many players you already have - self.player_num)
I didn't test this code but it should works.
def initialise_game(self, num_of_players):
self.players_list = []
# remeber values in class variables, not local one
self.num_of_players = num_of_players
self.player_num = 0
# create only one Label - and change text in it
self.name_label = tk.Label(self.bg_label,
self.name_label.grid(row=0, padx=200, pady=120)
# create only one Entry and assign function `self.player_names`
self.name_entry = tk.Entry(self.bg_label)
self.name_entry.grid(row=1, padx=200, pady=0)
self.name_entry.bind('<Return>', self.player_names)
# set text for first player
self.player_num += 1
self.name_label["text"] = 'What is the name of Player {} ?'.format(player_num)
def player_names(self, event):
# get player's name from Entry
self.players_list.append(self.name_entry.get())
self.name_entry.delete(0, 'end')
# set text for next player or destroy Entry after last player
self.player_num += 1
if self.player_num <= self.num_of_players:
self.name_label["text"] = 'What is the name of Player {} ?'.format(player_num)
else:
self.name_label.destroy()
self.name_entry.destroy()
The same way it works in other GUIs (not only in tkinter) and other languages (not only in Python)
My idea of what are you trying to acomplish shuld be the same, to take an input from the player one by one until all player have added a name.
The ploblem with your code is the for, it needs to complite without an input from the player (tecnicly), insted i opted for the update mecanics that tkinter have, wen a value changes in a winget the winget will update itself and i bind to the button a change value from a increment.
If you want to reuse i , reference it a the start of the def the same wey
This example works, the input of initialise_game is the same as before, replacing the conde in the middle will work just fine, except if your code uses .grid() then replace ask_name.pack()
import tkinter as tk
root = tk.Tk()
root.bg_label = tk.Frame(root)
# Added code, top and bottom are for testing
#--------------------------------------------------------------------------------
root.players_list = [] # Global variabe witch can be accesd by all funcions
i = 1 # This global value is to keep a clear reference on the count of windows
def initialise_game(self, num_of_players):
ask_name = tk.Frame(self)
L = tk.Label(ask_name, text=('What is the name of Player ' + str(i) + '?'))
L.grid(row=0, padx=200, pady=120)
E = tk.Entry(ask_name)
def modify_entry(NULL):
root.players_list.append(E.get())
E.delete(0, 'end')
global i # Change the "search" of values to outside the funtion
i += 1
L.config(text=('What is the name of Player ' + str(i) + '?'))
if (i > num_of_players): # Close the Frame wen all were answered
root.players_list.append(E.get())
ask_name.destroy()
E.bind('<Return>', modify_entry)
E.grid(row=1, padx=200, pady=0)
ask_name.pack() # You can replace it with grin , this moves the Label + Entry were u want
#--------------------------------------------------------------------------------
initialise_game(root.bg_label, 3)
root.bg_label.pack()
root.mainloop()

Tkinter: displaying label with automatically updated variable

I know there are a lot of questions about this subject, but after long researches I didn't find any that could solve my problem.
I'm trying to display with a label (using tkinter) a variable that I get from the I²C bus. The variable is therefore updated very regularly and automatically. The rest of the window should stay available for the user.
For now, the only way I found to display the label with the updated variable and to keep the rest of the window available for the user is to do so:
window = tk.Tk()
window.title("Gestionnaire de périphériques")
window.minsize(1024,600)
labelValThermo = tk.Label(a_frame_in_the_main_window,text = "")
labelValThermo.grid(row = 1, column = 1)
while True:
if mcp.get_hot_junction_temperature() != 16.0625:
labelValThermo.configure(text = "Température thermocouple: {} °C".format(mcp.get_hot_junction_temperature()))
window.update()
time.sleep(0.75)
The variable that comes from the I²C and got updated is mcp.get_hot_junction_temperature
The fact is that I know it's not the best way to force the update in an infinite loop. This should be the role of the mainloop(). I found out that the after() method could solve my problem but I don't know how to run it. I tried the following code that didn't work:
def displayThermoTemp():
if mcp.get_hot_junction_temperature() != 16.0625:
labelValThermo.configure(text = "Température thermocouple: {} °C".format(mcp.get_hot_junction_temperature()))
labelValThermo.after(500,displayThermoTemp)
window = tk.Tk()
labelValThermo = tk.Label(thermoGraphFrame,text = "")
labelValThermo.after(500, displayThermoTemp)
labelValThermo.grid(row = 1, column = 1)
window.mainloop()
Does anyone have the right syntax ?
How To use after() ?
The after() calls the function callback after the given delay in ms. Just define it inside the given function and it'll run just like a while loop till you call after_cancel(id) .
Here is an example:
import tkinter as tk
Count = 1
root = tk.Tk()
label = tk.Label(root, text=Count, font = ('', 30))
label.pack()
def update():
global Count
Count += 1
label['text'] = Count
root.after(100, update)
update()
root.mainloop()
Update your function with this and call it once before mainloop().
def displayThermoTemp():
if mcp.get_hot_junction_temperature() != 16.0625:
labelValThermo.configure(text = "Température thermocouple: {} °C".format(mcp.get_hot_junction_temperature()))
labelValThermo.after(500,displayThermoTemp)
# 100ms = 0.1 secs
window(100, displayThermoTemp)
after has the following syntax:
after(delay_ms, callback=None, *args)
You need to place the command in your callback function and update the window on which the widget is (in your case the labelValThermo looks to be on the root window:
def displayThermoTemp():
if mcp.get_hot_junction_temperature() != 16.0625:
labelValThermo.configure(text = "Température thermocouple: {} °C".format(mcp.get_hot_junction_temperature()))
root.after(500,displayThermoTemp)

Reset tkinter radiobuttons created in for loop to default value

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.

Resources