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

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

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.

Python Tkinter: adding a scrollbar to frame within a canvas

I am quite new to Tkinter, but, nevertheless, I was asked to "create" a simple form where user could provide info about the status of their work (this is sort of a side project to my usual work).
Since I need to have quite a big number of text widget (where users are required to provide comments about status of documentation, or open issues and so far), I would like to have something "scrollable" (along the y-axis).
I browsed around looking for solutions and after some trial and error I found something that works quite fine.
Basically I create a canvas, and inside a canvas a have a scrollbar and a frame. Within the frame I have all the widgets that I need.
This is a snipet of the code (with just some of the actual widgets, in particular the text ones):
from Tkinter import *
## My frame for form
class simpleform_ap(Tk):
# constructor
def __init__(self,parent):
Tk.__init__(self,parent)
self.parent = parent
self.initialize()
#
def initialize(self):
#
self.grid_columnconfigure(0,weight=1)
self.grid_rowconfigure(0,weight=1)
#
self.canvas=Canvas(self.parent)
self.canvas.grid(row=0,column=0,sticky='nsew')
#
self.yscrollbar = Scrollbar(self,orient=VERTICAL)
self.yscrollbar.grid(column =4, sticky="ns")
#
self.yscrollbar.config(command=self.canvas.yview)
self.yscrollbar.pack(size=RIGTH,expand=FALSE)
#
self.canvas.config(yscrollcommand=self.yscrollbar.set)
self.canvas.pack(side=LEFT,expand=TRUE,fill=BOTH)
#
self.frame1 = Frame(self.canvas)
self.canvas.create_window(0,0,window=self.frame1,anchor='nw')
# Various Widget
# Block Part
# Label
self.labelVariableIP = StringVar() # Label variable
labelIP=Label(self.frame1,textvariable=self.labelVariableIP,
anchor="w",
fg="Black")
labelIP.grid(column=0,row=0,columnspan=1,sticky='EW')
self.labelVariableIP.set(u"IP: ")
# Entry: Single line of text!!!!
self.entryVariableIP =StringVar() # variable for entry field
self.entryIP =Entry(self.frame1,
textvariable=self.entryVariableIP,bg="White")
self.entryIP.grid(column = 1, row= 0, sticky='EW')
self.entryVariableIP.set(u"IP")
# Update Button or Enter
button1=Button(self.frame1, text=u"Update",
command=self.OnButtonClickIP)
button1.grid(column=2, row=0)
self.entryIP.bind("<Return>", self.OnPressEnterIP)
#...
# Other widget here
#
# Some Text
# Label
self.labelVariableText = StringVar() # Label variable
labelText=Label(self.frame1,textvariable=
self.labelVariableText,
anchor="nw",
fg="Black")
labelText.grid(column=0,row=curr_row,columnspan=1,sticky='EW')
self.labelVariableTexta.set(u"Insert some texts: ")
# Text
textState = TRUE
self.TextVar=StringVar()
self.mytext=Text(self.frame1,state=textState,
height = text_height, width = 10,
fg="black",bg="white")
#
self.mytext.grid(column=1, row=curr_row+4, columnspan=2, sticky='EW')
self.mytext.insert('1.0',"Insert your text")
#
# other text widget here
#
self.update()
self.geometry(self.geometry() )
self.frame1.update_idletasks()
self.canvas.config(scrollregion=(0,0,
self.frame1.winfo_width(),
self.frame1.winfo_height()))
#
def release_block(argv):
# Create Form
form = simpleform_ap(None)
form.title('Release Information')
#
form.mainloop()
#
if __name__ == "__main__":
release_block(sys.argv)
As I mentioned before, this scripts quite does the work, even if, it has a couple of small issue that are not "fundamental" but a little annoying.
When I launch it I got this (sorry for the bad screen-capture):
enter image description here
As it can be seen, it only shows up the first "column" of the grid, while I would like to have all them (in my case they should be 4)
To see all of the fields, I have to resize manually (with the mouse) the window.
What I would like to have is something like this (all 4 columns are there):
enter image description here
Moreover, the scrollbar does not extend all over the form, but it is just on the low, right corner of the windows.
While the latter issue (scrollbar) I can leave with it, the first one is a little more important, since I would like to have the final user to have a "picture" of what they should do without needing to resize the windows.
Does any have any idea on how I should proceed with this?
What am I missing?
Thanks in advance for your help
In the __init__ method of your class, you do not appear to have set the size of your main window. You should do that, or it will just set the window to a default size, which will only show whatever it can, and in your case, only 1 column. Therefore, in the __init__ method, try putting self.geometry(str(your_width) + "x" + str(your_height)) where your_width and your_height are whatever integers you choose that allow you to see what you need to in the window.
As for your scrollbar issue, all I had to do was change the way your scrollbar was added to the canvas to a .pack() and added the attributes fill = 'y' and side = RIGHT to it, like so:
self.yscrollbar.pack(side = 'right', fill = 'y')
Also, you don't need:
self.yscrollbar.config(command=self.canvas.yview)
self.yscrollbar.pack(size=RIGHT,expand=FALSE)
Just add the command option to the creation of the scrollbar, like so:
self.scrollbar = Scrollbar(self,orient=VERTICAL,command=self.canvas.yview)
In all, the following changes should make your code work as expected:
Add:
def __init__(self,parent):
Tk.__init__(self,parent)
self.parent = parent
self.initialize()
# Resize the window from the default size to make your widgets fit. Experiment to see what is best for you.
your_width = # An integer of your choosing
your_height = # An integer of your choosing
self.geometry(str(your_width) + "x" + str(your_height))
Add and Edit:
# Add `command=self.canvas.yview`
self.yscrollbar = Scrollbar(self,orient=VERTICAL,command=self.canvas.yview)
# Use `.pack` instead of `.grid`
self.yscrollbar.pack(side = 'right', fill = 'y')
Remove:
self.yscrollbar.config(command=self.canvas.yview)
self.yscrollbar.pack(size=RIGHT,expand=FALSE)

storing data from input box in tkinter

I got the assignment to build a program of a store.Now, the customers have to register to be able to buy, I made a main window that has buttons for each action I need to perform. When the user tries to register another window with the data needed to complete the registration appears. Now how do I store the data from the input-box into a list with a button?
Here's an example of how I'm setting each box that the user needs to fill:
var1 = StringVar()
var1.set("ID:")
label1 = Label(registerwindow,textvariable=var1,height = 2)
label1.grid(row=0,column=1)
ID=tkinter.StringVar()
box1=Entry(registerwindow,bd=4,textvariable=ID)
box.grid(row=0,column=2)
botonA= Button(registerwindow, text = "accept",command=get_data, width=5)
botonA.grid(row=6,column=2)
I tried setting the button to run a function that gets the input, but I't now working. Here's what I did
def get_data():
print (box1.get())
A few problems:
Unless you do import tkinter AND from tkinter import * - which you shouldn't; just choose one - your program will choke on either var1 = StringVar() or on ID=tkinter.StringVar().
Define the get_data() function before binding it to a Button.
You assigned box1 but then gridded box.
The following sample will get the box's contents, add it to a list, and print the list to the console every time you click "Accept." Replace the names of parent windows, grid locations of each widget, and so on to suit your program.
from tkinter import *
root = Tk()
root.wm_title("Your program")
mylist = []
def get_data(l):
l.append(box1.get())
print(l)
var1 = StringVar()
var1.set("ID:")
label1 = Label(root,textvariable=var1,height = 2)
label1.grid(row=0,column=0)
ID=StringVar()
box1=Entry(root,bd=4,textvariable=ID)
box1.grid(row=0,column=1)
botonA= Button(root, text = "accept",command=lambda: get_data(mylist), width=5)
botonA.grid(row=0,column=2)
root.mainloop()
to retrieve the value, you need to access the variable it is attached to, not the entry field on the screen:
def get_data():
print (ID.get())

Creating a circle that changes in size depending on score in Tkinker (Python)

I really don't know Tkinter very well, I'm not even sure this is possible to do. But basically I want a visual representation of the score the user gets in the game i've programmed. As it works currently, the user gets to choose between "study" and "party", and depending on how he answers the tamaguchi either increases in size or decreases. The idea is that the tamaguchi is represented in Tkinter by a circle that corresponds with the score the user gets. I was thinking maybe I can have this in the root.mainloop()? So each time it goes through the loop, it deletes the last circle, and creates a new one with the updated score. This is what i've written so far:
class Application(Frame):
def __init__(self,master):
super(Application,self).__init__(master)
self.grid()
self.create_widgets()
self.circle()
def circle(self):
circle1.destroy()
r = int(tamaguchin.size)
self.circle1 = circle(r^2*3.14)
self.circle1.grid()
def create_widgets(self):
Label(self,
text = "Välkommen till spelet!"
).grid(row = 0, column = 6, sticky = W)
self.btn1 = Button(self, text = "study", command = lambda:self.update_text('plugga'))
self.btn1.grid(row=1,column=0)
self.btn2 = Button(self, text = "party", command = lambda:self.update_text('festa'))
self.btn2.grid(row=2,column=0)
self.btn3 = Button(self, text = "exit", command = self.exit)
self.btn5.grid()
def update_text(self,value):
message = "Your choice was",value,"which brings your last 3 choices to:"
print(message)
lista.append(value)
lista.remove(lista[0])
print(lista[0],'-',lista[1],'-',lista[2])
if lista in PositivLista:
tamaguchin.increasesize()
elif lista in NegativLista:
tamaguchin.decreasesize()
elif lista in HalveringsLista:
tamaguchin.halfsize()
else:
tamaguchin.samesize()
return lista
def exit(self):
print('You have chosen to exit the game')
root.destroy()
root = Tk()
root.title("Tamaguchi-game")
root.geometry("500x500")
app = Application(root)
app.grid()
root.mainloop()
I don't know if there's some sort of in-built function that can help me with this, but I haven't been able to find anything yet on my own. Of course my idea could be (and likely is) not very good so if anyone has a better idea on how to approach it, i'm all open; I'm a pretty big noob =] Thanks a lot for any help, i'm really stuck with this!
There are two ways to handle this issue.
1) Update the circle size on every change of the variable tamaguchin.size
2) Update the circle using a timer. Tkinker uses the after function to do this.
I am going to provide an example of the second approach:
def circle(self):
circle1.destroy()
r = int(tamaguchin.size)
self.circle1 = circle(r^2*3.14)
self.circle1.grid()
self.root.after(1000, self.circle)
This will update the circle every second. You can change the 1000 number to the update frequency (in milliseconds) that you want.

Categories