Destroying widgets from a different subroutine in tkinter - python

So I'm using .place to set the location of my widgets at the moment.
def printresults():
SClabelspare=Label(cwindow, text ="Please enter the Customers ID Number:" ).place(x=10,y=560)
I'm looking to call another subroutine that will destroy these widgets. I believe there is something called .destroy() or .place_destroy? I'm not quite sure how these would work though and I have tried to create one that looked like this:
def destroy_widgets():
SClabelspare.destroy()
but it just produces an error code that says NameError: global name 'SClabelspare' is not defined
Any help will be appreciated!

First, place() returns None so SClabelspare==None not a Tkinter ID. Second it is local, so is garbage collected when the function exits. You have to keep a reference to the object which can be done in many ways. A Python tutorial would be a good idea to get the basics before you go further https://wiki.python.org/moin/BeginnersGuide/Programmers Also, programming a Tkinter app without using class structures is a frustrating experience, unless it is something very simple. Otherwise you get errors like yours and have to spend much time and effort trying to overcome them. This is an example that I already have and is meant to to give a general idea of the process.
from Tkinter import *
from functools import partial
class ButtonsTest:
def __init__(self):
self.top = Tk()
self.top.title("Click a button to remove")
Label(self.top, text="Click a button to remove it",
bg="lightyellow").grid(row=0)
self.top_frame = Frame(self.top, width =400, height=400)
self.button_dic = {}
self.buttons()
self.top_frame.grid(row=1, column=0)
Button(self.top_frame, text='Exit', bg="orange",
command=self.top.quit).grid(row=10,column=0, columnspan=5)
self.top.mainloop()
##-------------------------------------------------------------------
def buttons(self):
b_row=1
b_col=0
for but_num in range(1, 11):
## create a button and send the button's number to
## self.cb_handler when the button is pressed
b = Button(self.top_frame, text = str(but_num),
command=partial(self.cb_handler, but_num))
b.grid(row=b_row, column=b_col)
## dictionary key=button number --> button instance
self.button_dic[but_num] = b
b_col += 1
if b_col > 4:
b_col = 0
b_row += 1
##----------------------------------------------------------------
def cb_handler( self, cb_number ):
print "\ncb_handler", cb_number
self.button_dic[cb_number].grid_forget()
##===================================================================
BT=ButtonsTest()

Or, if this is supposed to be very simple, without a lot of hard-to-manage global variables, and if class structures would only introduce needless complexity, you might try something like this (it worked for me in the python3 interpreter from the command line):
from tkinter import *
root = Tk()
def victim():
global vic
vic = Toplevel(root)
vicblab = Label(vic, text='Please bump me off')
vicblab.grid()
def bumper():
global vic
bump = Toplevel(root)
bumpbutt = Button(bump, text='Bump off', command=vic.destroy)
bumpbutt.grid()
victim()
bumper()

Related

Tkinter changing entry state based on radiobutton

I have four radio buttons. Underneath these four button is an Entry widget. I am trying to make this Entry widget only become available to type into when the last radio button is selected. The gui is in a class, as you can see in the code below:
class Gui:
def __init__(self):
pass
def draw(self):
global root
if not root:
root = tk.Tk()
root.geometry('280x350')
self.type = tk.StringVar()
self.type_label = tk.Label(text="Game Mode")
self.name_entry = tk.Entry()
self.name_entry.configure(state="disabled")
self.name_entry.update()
self.type_entry_one = tk.Radiobutton(text="Garage", value="garage", variable=self.type, command=self.disable_entry(self.name_entry))
self.type_entry_two = tk.Radiobutton(text="Festival", value="festival", variable=self.type, command=self.disable_entry(self.name_entry))
self.type_entry_three = tk.Radiobutton(text="Studio", value="studio", variable=self.type, command=self.disable_entry(self.name_entry))
self.type_entry_four = tk.Radiobutton(text="Rockslam", value="rockslam", variable=self.type, command=self.enable_entry(self.name_entry))
self.type_label.pack()
self.type_entry_one.pack()
self.type_entry_two.pack()
self.type_entry_three.pack()
self.type_entry_four.pack()
self.name_entry.pack()
root.mainloop()
def enable_entry(self, entry):
entry.configure(state="normal")
entry.update()
def disable_entry(self, entry):
entry.configure(state="disabled")
entry.update()
if __name__ == '__main__':
root = None
gui = Gui()
gui.draw()
However, the the self.name_entry is always available to type into. What am I doing wrong. If you still don't understand what is happening then please run this code yourself and you will see.
Thank you very much for your time and I look forward to responses.
You have the right idea about using the RadioButton to enable/disable the entry widget. Mostly it is your class design that is flawed - it is halfway between OO code, and procedural code...
I fixed the class structure and made it a subclass of tk.Tk so it completely encapsulate your GUI. The name_entry is now enabled only when type_entry_four radio button is selected, and disabled otherwise. I've set that last button to be selected at launch, but you can easily change that; it results in the entry being enabled at launch.
Superfluous variable passing through methods was removed, as was the draw method and the calls to it; all widget creation is now conveniently found in GUI.__init__
import tkinter as tk
class Gui(tk.Tk):
def __init__(self):
super().__init__()
self.geometry('280x350')
self.select_type = tk.StringVar()
self.type_label = tk.Label(self, text='Game Mode')
self.name_entry = tk.Entry(self)
self.type_entry_one = tk.Radiobutton(self, text='Garage', value='garage', variable=self.select_type, command=self.disable_entry)
self.type_entry_two = tk.Radiobutton(self, text='Festival', value='festival', variable=self.select_type, command=self.disable_entry)
self.type_entry_three = tk.Radiobutton(self, text='Studio', value='studio', variable=self.select_type, command=self.disable_entry)
self.type_entry_four = tk.Radiobutton(self, text='Rockslam', value='rockslam', variable=self.select_type, command=self.enable_entry)
self.select_type.set('rockslam') # select the last radiobutton; also enables name_entry
self.type_label.pack()
self.type_entry_one.pack()
self.type_entry_two.pack()
self.type_entry_three.pack()
self.type_entry_four.pack()
self.name_entry.pack()
def enable_entry(self):
self.name_entry.configure(state='normal')
def disable_entry(self):
self.name_entry.configure(state='disabled')
if __name__ == '__main__':
Gui().mainloop()
The only problemS, I see, your facing here is because your not passing in the value "properly" into the function, when you use (..), your calling the function, so to get rid of that use lambda, like:
self.type_entry_one = tk.Radiobutton(text="Garage", value="garage", variable=self.type, command=lambda: self.disable_entry(self.name_entry))
self.type_entry_two = tk.Radiobutton(text="Festival", value="festival", variable=self.type, command=lambda:self.disable_entry(self.name_entry))
self.type_entry_three = tk.Radiobutton(text="Studio", value="studio", variable=self.type, command=lambda:self.disable_entry(self.name_entry))
self.type_entry_four = tk.Radiobutton(text="Rockslam", value="rockslam", variable=self.type, command=lambda:self.enable_entry(self.name_entry))
When using command=lambda:func(arg), this will get executed only when selecting a radiobutton. That is the point of using a radiobutton, right?
Also notice that when the initial code is run, the entire radiobuttons are selected, I think its probably because of tristate values, to get rid of that there are 2 ways I'm aware of:
Changing the declaration of self.type to:
self.type = tk.StringVar(value=' ')
Or, you could also go on adding an extra option to each radiobutton, tristatevalue=' ', like:
self.type_entry_one = tk.Radiobutton(text="Garage",..,tristatevalue=' ')
But make sure to do just one of the above solution. Take a read here about more on tristate values.
Also keep a note that your not passing in any master window to the widgets, its fine as long as your having just one window, when working with multiple windows, it may get confusing for where the widgets should appear.
Also side-note, if this is the complete code, then if nothing is being done on __init__(), its definition can be removed.

How to display input and output in same GUI for tkinter in Python

I am trying to make the user input a string for my implemented DFA diagram, and I want the input box to be displayed along with its output right under it in the same GUI box. I'm only including one of my functions to show how I used tkinter's messagebox to display the output.
import tkinter as tk
from tkinter import simpledialog
from tkinter import messagebox
import tkinter
def q3(s, i) :
if (i == len(s)) :
tkinter.messagebox.showinfo('Answer',"REJECTED")
return;
if (s[i] == 'a') :
q3(s, i + 1);
elif (s[i] == 'b'):
q3(s, i + 1);
root = tk.Tk()
root.withdraw()
user_inp = simpledialog.askstring(title="DFA", prompt="Enter a string within the alphabet {a,b}* Suffix must be bb")
This code works to display the results correctly, like this.
AKA, when the user inputs the string and presses OK, it opens another GUI to display the results. Please help me so that I can have a GUI that displays both in the same box. It's even harder because all of my DFA functions (I have one for q0, q1, q2, and q3) have different results for the output as rejected/accepted. I'm not sure if I have to create a variable for the each of them. (The only one that is accepted is q2). I'm also not sure if I need to use any Labels. In your answer, if there's imports I must include, please include that as well.
Well I tried to understand what you really want, but couldnt get it, so I put together what I feel that you might want, with an example:
from tkinter import *
class Custombox:
def __init__(self, title, text):
self.title = title
self.text = text
def store():
self.new = self.entry.get() #storing data from entry box onto variable
if self.new == 'Hello World': #checking
a.change('ACCEPTED') #changing text
else:
a.change('REJECTED') #else, changing text
self.win = Toplevel()
self.win.title(self.title)
# self.win.geometry('400x150')
self.win.wm_attributes('-topmost', True)
self.label = Label(self.win, text=self.text)
self.label.grid(row=0, column=0, pady=(20, 10),columnspan=3,sticky='w',padx=10)
self.l = Label(self.win)
self.entry = Entry(self.win, width=50)
self.entry.grid(row=1, column=1,columnspan=2,padx=10)
self.b1 = Button(self.win, text='Ok', width=10,command=store)
self.b1.grid(row=3, column=1,pady=10)
self.b2 = Button(self.win, text='Cancel', width=10,command=self.win.destroy)
self.b2.grid(row=3, column=2,pady=10)
def __str__(self):
return str(self.new)
def change(self,ran_text):
self.l.config(text=ran_text,font=(0,12))
self.l.grid(row=2,column=1,columnspan=3,sticky='nsew',pady=5)
root = Tk()
root.withdraw()
a = Custombox('Custom box', 'Enter a string within the alphabet {a,b}*. Suffix must be bb.')
root.mainloop()
Over here im creating a window using basic tkinter properties and placements and all you have to understand is that store() inside that class is similar to your q3() as I couldnt understand what was going on there, I just made my own function. So you will have to replace store() with what works for you, but do not change the self.new = self.entry.get(). Yes this might seem a bit shady, but it was the quickest i could do, because im a beginner as well.
Anyways here, a has the value of whatever you type into the entry widget, BUT while using a, make sure to use str(a) or you wont get correct results as type(a) returns <class '__main__.Custombox'>. Do let me know if you face any difficulty in the implementation of this class. I know there are mistakes here, feel free to edit those mistakes out or let me know.

How to link multiple buttons to one text widget in tkinter?

I'm trying to learn tkinter and I wanted to write a simple rock paper scissors game, where there is a window with 3 buttons and one text widget.
I'd like to be able to press any of the buttons and for the message to appear in the text field, then click a different button, the text field to clear and display a new message associated with the second button and so on.
From the tutorials I've watched, I know that I can pass the function housing text widget as an argument in button command parameter.I know I could make 3 functions with a text field, one for each button (displaying one at a time) but that's probably not the correct way. Here's what I have so far:
import tkinter as tk
root = tk.Tk()
root.title("Rock Paper Scissors")
root.geometry("420x200")
def Rock():
rockText = "Paper!"
return rockText
def Paper():
paperText = "Scissors!"
return paperText
def Scissors():
scissorsText = "Rock!"
return scissorsText
def display():
textDisplay = tk.Text(master = root, height = 10, width = 50)
textDisplay.grid(row = 1, columnspan = 5)
textDisplay.insert(tk.END, Rock())
buttonRock = tk.Button(text = "Rock", command = display).grid(row = 0, column = 1, padx = 10)
buttonPaper = tk.Button(text = "Paper").grid(row = 0, column = 2, padx = 10)
buttonScissors = tk.Button(text = "Scissors").grid(row = 0, column = 3, padx = 10)
root.mainloop()
Any help will be appreciated.
Edit: Second thought - I can imagine I'm complicating this for myself by trying to force the game to work this way. With the random module I'd be able to get away with one function for the computer choice with a list and saving the random pick in a parameter, then returning the value into the display function.
So if I got this right you just want to make a button click change the text in the Text-widget. For that you have two easy and quite similar options. First would be to define 3 functions, as you did, and let them change the text directly. The second option would be to make one function which changes the text according to whats given. Note that in the second case we will have to use lambda which works quite well in smaller projects but decreases the efficiency of your programs when they get bigger.
First option:
import tkinter as tk
class App:
def __init__(self):
root=tk.Tk()
root.title("Rock Paper Scissors")
root.geometry("420x200")
self.text=Text(root)
self.text.grid(row=1,columnspan=5)
tk.Button(root,text="Rock",command=self.Rock).grid(row=0,column=1,padx=10)
tk.Button(root,text="Paper",command=self.Paper).grid(row=0,column=2)
tk.Button(root,text="Scissors",command=self.Scissors).grid(row=0,column=3,padx=10)
root.mainloop()
def Rock(self):
text="Paper!"
self.text.delete(0,END) #delete everything from the Text
self.text.insert(0,text) #put the text in
def Paper(self):
text="Scissors!"
self.text.delete(0,END) #delete everything from the Text
self.text.insert(0,text) #put the text in
def Scissors(self):
text="Rock!"
self.text.delete(0,END) #delete everything from the Text
self.text.insert(0,text) #put the text in
if __name__=='__main__':
App()
Second option:
import tkinter as tk
class App:
def __init__(self):
root=tk.Tk()
root.title("Rock Paper Scissors")
root.geometry("420x200")
self.text=Text(root)
self.text.grid(row=1,columnspan=5)
tk.Button(root,text="Rock",command=lambda: self.updateText('Paper!')).grid(row=0,column=1,padx=10)
tk.Button(root,text="Paper",command=lambda: self.updateText('Scissors!')).grid(row=0,column=2)
tk.Button(root,text="Scissors",command=lambda: self.updateText('Rock!')).grid(row=0,column=3,padx=10)
root.mainloop()
def updateText(self,text):
self.text.delete(0,END) #delete everything from the Text
self.text.insert(0,text) #put the text in
if __name__=='__main__':
App()
Some little side notes from me here:
If you use grid, pack or place right on the widget itself you wont assign the widget to a variable but the return of the grid, pack or place function which is None. So rather first assign the widget to an variable and then use a geometry manager on it like I did for the Text-widget.
You don't have to extra set the title with the title function afterwards. You can set it with the className-argument in Tk.
If you're working with tkinter its fine to do it functionally but rather use a class to build up GUIs.
When creating new widgets always be sure to pass them the variable for the root window first. They will get it themselves too if you don't do that but that needs more unnecessary background activity and if you have more than one Tk-window open it will automatically chooses one which may not be the one you want it to take.
And one small tip in the end: If you want to learn more about all the tkinter widgets try http://effbot.org/tkinterbook/tkinter-index.htm#class-reference.
I hope its helpfull. Have fun programming!
EDIT:
I just saw your edit with the random module. In this case I would recommend the second option. Just remove the text-argument from updateText and replace lambda: self.updateText(...) with self.updateText(). In updateText itself you add that random of list thing you mentioned. :D

How do I link python tkinter widgets created in a for loop?

I want to create Button and Entry(state=disabled) widgets with a for loop. The number of widgets to be created will be a runtime argument. What I want is that every time I click the button, the corresponding entry will become enabled(state="normal"). The problem in my code is that any button I click, it only affects the last entry widget. Is there anyway to fix this.? Here is my code:
from tkinter import *
class practice:
def __init__(self,root):
for w in range(5):
button=Button(root,text="submit",
command=lambda:self.enabling(entry1))
button.grid(row=w,column=0)
entry1=Entry(root, state="disabled")
entry1.grid(row=w,column=1)
def enabling(self,entryy):
entryy.config(state="normal")
root = Tk()
a = practice(root)
root.mainloop()
Few issues in your code -
You should keep the buttons and entries you are creating and save them in an instance variable, most probably it would be good to store them in a list , then w would be the index for each button/entry in the list.
When you do lambda: something(some_param) - the function value of some_param() is not substituted, till when the function is actually called, and at that time, it is working on the latest value for entry1 , hence the issue. You should not depend on that and rather you should use functools.partial() and send in the index of Button/Entry to enable.
Example -
from tkinter import *
import functools
class practice:
def __init__(self,root):
self.button_list = []
self.entry_list = []
for w in range(5):
button = Button(root,text="submit",command=functools.partial(self.enabling, idx=w))
button.grid(row=w,column=0)
self.button_list.append(button)
entry1=Entry(root, state="disabled")
entry1.grid(row=w,column=1)
self.entry_list.append(entry1)
def enabling(self,idx):
self.entry_list[idx].config(state="normal")
root = Tk()
a = practice(root)
root.mainloop()
Whenever people have problem with a function created with a lambda expression instead of a def statement, I recommend rewriting the code with a def statement until it works right. Here is the simplest fix to your code: it reorders widget creation and binds each entry to a new function as a default arg.
from tkinter import *
class practice:
def __init__(self,root):
for w in range(5):
entry=Entry(root, state="disabled")
button=Button(root,text="submit",
command=lambda e=entry:self.enabling(e))
button.grid(row=w,column=0)
entry.grid(row=w,column=1)
def enabling(self,entry):
entry.config(state="normal")
root = Tk()
a = practice(root)
root.mainloop()

Python tkinter, making a quiz with randomly generated questions that reset after each submit

My problem is that I'm trying to make a math quiz in tkinter which asks for a name, and then picks a question using the randint function. The person inputs an answer, clicks submit and the app responds to whether it was right or wrong.
The problem arises in that I then want the program to after it has been submitted clear the question and answer and come up with new ones 3 times over (each time adding to a score which is then shown at the end), however I can't seem to find a way to easily do this; I've currently been trying a while loop but it isn't working.
So my question is how would I make that part of the code loop 3 times over asking a different question each time?
My code thus far:
from tkinter import*;from random import randint
class Tk_app(Frame):
def __init__(self, root):
super(Tk_app, self).__init__(root);self.grid();self.createElements()
def nameElements(self):
self.NmLbl = Label(self, text="Name:").grid(row=0)
self.Name = Entry(self);self.Name.grid(row=0, column=1);self.score = int(0)
def createElements(self):
Frame.grid_forget(self)
self.QNum = randint(1, 2)
self.QEnt = Entry(self);self.QEnt.grid(row=1, column=1)
if(self.QNum == 1):
self.QLbl = Label(self, text="What is the air speed velocity of a flying swallow?").grid(row=1)
self.a = "African or European?"
elif(self.QNum == 2):
self.QLbl = Label(self, text="What is your quest?").grid(row=1)
self.a = "To find the holy grail."
else:
self.QLbl = Label(self, text="What is your favourite colour?").grid(row=1)
self.a = "Green"
def submit(self):
FinNam = self.Name.get()
Ans = self.QEnt.get()
if(Ans == self.a):
AnsLbl = Label(self, text = "Well done you got it right, "+FinNam).grid(row=2, column=1)
self.score+=1
else:
AnsLbl = Label(self, text = "Sorry not this time, "+FinNam+" The answer was " + self.a).grid(row=2, column=1)
self.SBut = Button(self, text="submit", command=lambda:submit(self)).grid(row=2)
root = Tk();root.title("Monty Questions")
app = Tk_app.nameElements(root)
fin = int(0)
while(fin<3):
fin+=1
app2 = Tk_app.createElements(root)
root.mainloop()
You don't want to have a while loop outside of your app class. When the program is running, it should have called root.mainloop() before the user interacts with it at all, and stay that way until it is finished. The general structure of this code is not correct.
In Tkinter I would only have this outside of the class definition:
root = Tk()
root.title("Monty Questions")
app = Tk_app()
root.mainloop()
And then you set up all of your tk widgets and whatnot in init:
class Tk_app(Frame):
def __init__(self, root):
Frame.__init__(root);
self.grid();
self.createElements()
self.nameElements()
etc.
Finally, if you just define submit() as a member function of Tk_app instead of as a nested function definition like you have it, you don't need to use a lambda function to pass self. Just do:
class Tk_app():
... __init__ and other things...
def createElements(self):
... some code ...
self.SBut = Button(self, text="submit", command=self.submit ).grid(row=2)
def submit(self, Event):
... submit code ...
The Event is necessary because not only will submit be passed self, as all member functions are, it also gets passed the event that triggered its call.
This might not get you all the way but will hopefully help structure your code in a way that will allow Tkinter to work properly. Check out examples, especially this one, to see how to structure your code. Explore that site and you should get an idea of the vibe of Tkinter.

Categories