I am currently trying to make a GUI to an existing python program using Tkinter. The program gives the user two options from which the user must choose to either accept or decline. Before using Tkinter the options were placed in the terminal and awaited for a raw_input. (y/n). How can I make this so the canvas text updates with the new data and awaits for the users button click?
To make my question more specific: How can I run another programs code while the Tkinter mainloop is running and make these two interact?
Example code below.
from Tkinter import *
root = Tk()
root.resizable(width=False, height=False)
root.geometry('{}x{}'.format(500,550))
root.wm_title("Tkinter test")
BtnFrame = Frame (root)
BtnFrame.pack(side = BOTTOM)
BtnFrame.place(y=450, x=20)
canvas_1 = Canvas(root, width = "200", height ="300")
canvas_2 = Canvas(root, width = "250", height ="300")
canvas_1.pack(side = LEFT)
canvas_2.pack(side = RIGHT)
textfield_1 = canvas_1.create_text(100,50)
textfield_2 = canvas_2.create_text(100,50,)
def update_textfiel_1(text):
global textfield_1
canvas_1.delete(textfield_1)
textfield = canvas.create_text(100,50,text = text)
def update_textfiel_2(text):
global textfield_2
canvas_2.delete(textfield_2)
textfield1 = canvas1.create_text(100,50,text = text)
Accept = Button(BtnFrame, text="Accept", width=25)
Decline = Button(BtnFrame, text="Decline", width=25)
Accept.pack(side = LEFT)
Decline.pack(side = RIGHT)
root.mainloop()
First off you have some inconsistent variable names in your update_textfiel functions, you can greatly simplify it by using .itemconfigure (documentation for methods on canvas widget)
def update_textfiel_1(new_text):
canvas_1.itemconfigure(textfield_1, text=new_text)
def update_textfiel_2(new_text):
canvas_2.itemconfigure(textfield_2, text=new_text)
If I understand correctly you want a way to have a function that will simply wait for the user to press one of the buttons and then return the result, this is very easy with tkMessageBox:
question = """Do you accept {}?
if you say no you will instead get {}"""
#this message can GREATLY be improved
# But I really don't understand what you are using it for...
def user_confirmation(name1, name2):
response = tkMessageBox.askyesno("Accept or Decline",question.format(name1,name2))
print(response)
if response: # == True
return name1
else:
return name2
I have not yet found a way to make a blocking function that works with the window you have currently...
Related
I'm trying to create a series of tkinter buttons with a loop that are .grid'd to their own respective frames. I want every button to have a function that .tkraises the next frame in the list of frames that I create. Any idea how? Here's what I've got. The buttons/ frames are created I think but the .tkraise function doesn't work. Thanks
from tkinter import *
from PIL import ImageTk, Image
## Define root and geometry
root = Tk()
root.geometry('200x200')
# Define Frames
winlist = list()
winlist = Frame(root, bg='red'), Frame(root, bg='green'), Frame(root, bg='blue')
# Configure Rows
root.grid_rowconfigure(0, weight = 1)
root.grid_columnconfigure(0, weight = 1)
# Place Frames
for window in winlist:
window.grid(row=0, column = 0, sticky = 'news')
# Raises first window 'To the top'
winlist[0].tkraise()
# Function to raise 'window' to the top
def raise_frame(window):
window.tkraise()
d = {}
count = 0
for x in range(0, 3):
d["label{0}".format(x)] = Label(winlist[x], text = "label{0}".format(x))
if count <=1:
try:
d["button{0}".format(x)] = Button(winlist[x], text = "button{0}".format(x), command = raise_frame(winlist[x+1]))
d["button{0}".format(x)].pack(side=TOP)
except:
pass
else:
d["label{0}".format(x)].pack(side=TOP)
count += 1
root.mainloop()
The issue is on the command option of the line:
d["button{0}".format(x)] = Button(winlist[x], text = "button{0}".format(x), command = raise_frame(winlist[x+1]))
It will execute raise_frame(winlist[x+1]) immediately and then assign the result (which is None) to command option. Therefore, clicking the button later does nothing.
You need to use lambda instead:
d["button{0}".format(x)] = Button(winlist[x], text="button{0}".format(x),
command=lambda x=x: raise_frame(winlist[x+1]))
I answered my own question. instead of using frames I went back to creating Tk() objects. I made a loop that runs a function that creates Tk() objects and passed in a variable that carried the count of my loop. I used that count to change information on each Tk() object and instead made the 'command =' of each button include a Tk().destroy function. This creates all the windows I wanted all at once and I can perform an action and exit the window. It's progress. Thanks,
Tim,
I tried creating a program that will take in the symptoms of a person and return the disease they have. This is the GUI part of the project.
from tkinter import *
root = Tk()
root.title("Health GUI")
root.geometry("1000x625")
symptoms_list = []
def print_symptoms():
print(symptoms_list)
def typeSymptoms():
gap3 = Label(text="").pack()
symptoms_entry = Text(width=50, height=20)
symptoms_entry.pack()
symptoms_list.append(symptoms_entry.get(1.0, END))
done_symptoms = Button(text="I have written my symptoms", width=25, height=5, command=lol)
done_symptoms.pack()
gap1 = Label(text="").pack()
title = Label(text="HEALTH GUI", font=30).pack()
gap2 = Label(text="").pack()
start_button = Button(text="Click here to start", width=30, height=5, command=typeSymptoms, font=20).pack()
root.mainloop()
Just for simplicity, I tried printing out the symptoms given by the user to the console but it gives me a list with '\n'. Please help. Thanks!(PS: I lerned Tkinter day before yesterday so I don't know much)
At the moment, your variable symptoms_list just holds the contents of the newly created Text widget, since you append this content at startup.
If you want to add the symptoms to the list, you need to have your function lol() that you call when pressing the button.
This function should look something like:
def lol():
symptoms_text = symptoms_entry.get(1.0, END)
symptoms_list = symptoms_text.split('\n')
print_symptoms()
However, your widgets and the symptoms_list would have to be global variables in order for this program to work. It would probably be better, while you are getting acquainted with Tkinter, to learn how to create a dialog as Class with attributes. That makes sharing values between methods so much easier.
I'm very new at Python and need some help finishing the code. This is Tkiner related. I have an entry box, a button, and a lower frame for the output.
def loop_over_input(the_str=''):
master_list = []
for char in the_str:
tmp_char = passwordConversion[char]
master_list.append(tmp_char)
print("Master Pass List: ", master_list)
return master_list
This will work in command line with a couple of other lines. I'm not sure how tell it when I put text in the entry field and click the button to return the results in my lower frame. I have moved def loop_over_input to different parts of the code I think I may need to reference the test entry box and the button and the lower box.
I will post the complete code if requested to do so.
Firstly, you need to indent your code correctly. Everything that is in the function loop_over_input needs to be indented once more than the line def loop_over_input(the_str=''):
A few other notes. If you look up the documentation for the tkinter button, it will explain how to link a command to it. The piece of code you have supplied appears to be what you want the button command to be. Printing the list will do so in the shell, not in a frame below your entry field and button.
Here's some example code that should do what you want:
import tkinter as tk
# Creating tk window
window = tk.Tk()
# Master list
master_list = []
master_list_string = tk.StringVar()
# Frames
top_frame = tk.Frame(window)
top_frame.pack(expand = True, fill = 'x', pady = 10, padx = 10)
bottom_frame = tk.Frame(window)
bottom_frame.pack(expand = True, fill = 'both', padx = 10)
# Entry box
myEntry = tk.Entry(top_frame)
myEntry.pack(side = 'left',expand = True, fill = 'x', padx = 10)
# Label to display master list
myLabel = tk.Label(bottom_frame, textvariable = master_list_string)
myLabel.pack(expand = True, fill = 'both')
# Button to submit
def clicked():
master_list.append(myEntry.get())
myEntry.delete(0, 'end')
printed_list = ''
for password in master_list:
printed_list += "\n" + password
master_list_string.set(printed_list)
myButton = tk.Button(top_frame, text = "Submit", command = clicked)
myButton.pack(side = 'left', padx = 10)
# Mainloop
window.mainloop()
The two frames allow you to have the top section with your entry and button, while the bottom frame is for your output. However, you cannot just use a frame as your output, as your frame can't display text. Instead, use a Label widget linked to a StringVar which allows the text in the Label to update when the variable is changed.
The button command then takes the string entered into the entry, saves it to the master list and then sets the StringVar to the updated list, which automatically updates the Label.
I would highly recommend ready the documentation on Effbot, it's quite easy to understand with good examples. Link here
I've been trying to create a piece of code that would take a integer as a argument and create that number of tkinter entry fields. With a submit button at the end that would retrieve the data from the fields add these data to a list then close the window.
I have been able to get it working however I cant find a way to convert this to a callable function; a requirement to use it with the rest of my program.
This is the code I have produced so far, thanks:
import tkinter as tk
b = input("Enter: ")
b = int(b)
root = tk.Tk()
newdict = dict()
outputs = list()
for i in range(b):
newdict["entry" + str(i)] = tk.Entry(root)
newdict["entry" + str(i)].pack()
button1 = tk.Button(root, text="Submit", command=lambda: Get(newdict))
button1.pack()
def Get(newdict):
for j in range(b):
outputs.append(newdict["entry" + str(j)].get())
root.quit()
root.mainloop()
print(outputs)
The basic idea is to create a window, then use the wait_window method to wait for the window to be destroyed. Once it has been destroyed you can return some value.
The problem is that the values you want to fetch must not be attributes of the window, since it will have been destroyed by the time you are ready to fetch them. You need to set up your code to save the values before the window is destroyed.
A simple way is to provide an "OK" button which gets the values and then destroys the window. Another way would be to put a trace on variables associated with each entry, and save the values immediately as they are edited.
Which method you choose depends on what behavior you want when the user clicks the window control to close the window (eg: the red circle on OSX, the [x] button on windows, etc). Do you want to return what they had input, or do you treat that as a cancel action and return nothing?
Here's a simple example using an OK button. This example assumes that you aren't already running a GUI, and that this is to be run as part of a non-GUI application.
import tkinter as tk
class Dialog(object):
def show(self, num_fields):
self.num_fields = num_fields
self.root = tk.Tk()
self.entries = []
for i in range(num_fields):
entry = tk.Entry(self.root)
entry.pack(fill="x")
self.entries.append(entry)
ok = tk.Button(self.root, text="OK", command=self.ok)
ok.pack(side="bottom", anchor="e", pady=(10,0), padx=10)
# wait for the window to be destroyed, then
# return the values. If the user clicks the OK button
# the values will be set; if they cancel the dialog
# this will return None.
self.values = None
self.root.wait_window()
return self.values
def ok(self):
# save all the values, then destroy the window
self.values = []
for i in range(self.num_fields):
self.values.append(self.entries[i].get())
self.root.destroy()
Assuming you're running a non-gui program, here's an example of how you would use this class:
b = input("Enter: ")
b = int(b)
result = Dialog().show(b)
print("result:", result)
I made a very simple gui that has a button and shows an image(.gif). My goal is to output another .gif whenever you press the button. There are 2 .gif files in my file directory and the point is to keep switching between these two whenever you press the button.
#Using python2.7.2
import Tkinter
root = Tkinter.Tk()
try:
n
except:
n = 0
def showphoto(par):
if par%2 == 0:
try:
label2.destroy()
except:
pass
photo = Tkinter.PhotoImage(file="masc.gif")
label2 = Tkinter.Label(image=photo)
label2.image = photo
label2.pack()
else:
try:
label2.destroy()
except:
pass
photo = Tkinter.PhotoImage(file="123.gif")
label2 = Tkinter.Label(image=photo)
label2.image = photo
label2.pack()
myContainer1 = Tkinter.Frame(root, width = 100, height = 100)
myContainer1.pack()
def callback(event):
global n
showphoto(n)
n = n + 1
button1 = Tkinter.Button(myContainer1)
button1["text"]= "Next pic"
button1["background"] = "green"
button1.bind("<Button-1>", callback(n))
button1.pack()
root.mainloop()
The current code just outputs the first image (masc.gif) but when I press the button it doesn't switch to the other image(123.gif). What am I doing wrong?
This can achieved much easier with classes as the class holds all the data necessary without the use of global variables.
import Tkinter as tk
from collections import OrderedDict
class app(tk.Frame):
def __init__(self,master=None, **kwargs):
self.gifdict=OrderedDict()
for gif in ('masc.gif','123.gif'):
self.gifdict[gif]=tk.PhotoImage(file=gif)
tk.Frame.__init__(self,master,**kwargs)
self.label=tk.Label(self)
self.label.pack()
self.button=tk.Button(self,text="switch",command=self.switch)
self.button.pack()
self.switch()
def switch(self):
#Get first image in dict and add it to the end
img,photo=self.gifdict.popitem(last=False)
self.gifdict[img]=photo
#display the image we popped off the start of the dict.
self.label.config(image=photo)
if __name__ == "__main__":
A=tk.Tk()
B=app(master=A,width=100,height=100)
B.pack()
A.mainloop()
Of course, this could be done more generally ... (the list of images to cycle through could be passed in for example), and this will switch through all the images in self.gifs ...
This approach also removes the necessity to destroy and recreate a label each time, instead we just reuse the label we already have.
EDIT
Now I use an OrderedDict to store the files. (keys=filename,values=PhotoImages). Then we pop the first element out of the dictionary to plot. Of course, if you're using python2.6 or earlier, you can just keep a list in addition to the dictionary and use the list to get the keys.
button1 = Tkinter.Button(myContainer1)
button1["text"]= "Next pic"
button1["background"] = "green"
button1.bind("<Button-1>", callback(n))
First, you bind the <Button-1> event to None (that's what callback(n) evaluates to). You should bind it to callback (no parentheses a.k.a the call operator).
Second, I suggest you change callback to not accept any arguments, remove the bind call and create your button as:
button1 = Tkinter.Button(myContainer1, command=callback)