I'm creating a simple class named Dialog. I only want it to return when init method is already finished.
from tkinter import *
class Dialog:
def __set_data(self):
self.data = self.entry_.get()
self.top.destroy()
def __init__(self, prompt_text = "Prompt:", submit_text = "OK", text_size = 5, button_size = 5, main_window = Tk()):
main_window.withdraw()
top = self.top = Toplevel(main_window)
Label(top, text=prompt_text).pack()
self.entry_ = Entry(top)
self.entry_.pack(padx = text_size)
button = Button(top, text = submit_text, command = self.__set_data)
button.pack(pady=button_size)
def get_data(self):
return self.data
data = 0
a = Dialog();
print (a.get_data())
If you run this code, you will get the output 0. I want to the output only show up after the user input. How can I do this?
Firstly, I don't think that you really want to postpone the __init__ method returning. If you do that, your code will never get to tk.mainloop(), and will never actually appear on screen.
Instead, what you want to do is be notified when the data in the Entry widget changes. This is a pretty common thing to do in GUI toolkits; the way tkinter handles it is with Events and Bindings. You can read about them generally here.
One way of performing your particular task (showing the data in self.entry once it has been changed by the user) might be to use the method shown in this question.
Alternatively, you could add a command to your submit button (see here) and read the value of the entry in that method.
Related
I would like to repeatedly get the contents of a Text widget, so I can analyse it and gets stats about what's been entered. These stats would need to be updated in real time as the user types, hence why I need the variable currentContent to update every loop. What I'd like to do is something like this.
main = tk.Tk()
# Y'know, all the typical window setup stuff.
currentContent = inputBox.get(0.0,END)
textBlobContent = TextBlob(currentContent)
# Basically here I'd do a bunch of stuff using TextBlob.
main.mainloop()
However, that doesn't work. It gets the content once, as soon as the window loads, and then stops. Surely mainloop runs repeatedly, and it should keep getting the contents of the Text widget?
A simple solution that works most of the time would be to put a binding on <KeyRelease>. That will enable a function to be called whenever the user is typing. This won't trigger the callback whenever data is pasted with the mouse, or inserted via other means (such as a toolbar button).
A more robust solution is to set up a proxy for the widget, so that an event is generated whenever anything is inserted or deleted in the widget. This proxy can look at what is being done with the widget (insert, delete, selection changed, etc) and generate an event. You can then bind to this event to do whatever you want.
Here's an example of a custom text class that generates a <<TextModified>> event whenever data is inserted or deleted:
import tkinter as tk
class CustomText(tk.Text):
def __init__(self, *args, **kwargs):
"""A text widget that report on internal widget commands"""
tk.Text.__init__(self, *args, **kwargs)
# create a proxy for the underlying widget
self._orig = self._w + "_orig"
self.tk.call("rename", self._w, self._orig)
self.tk.createcommand(self._w, self._proxy)
def _proxy(self, command, *args):
cmd = (self._orig, command) + args
result = self.tk.call(cmd)
if command in ("insert", "delete", "replace"):
self.event_generate("<<TextModified>>")
return result
This proxy does four things:
First, it calls the actual widget command, passing in all of the arguments it received.
Next it generates an event for every insert and every delete
Then it then generates a virtual event
And finally it returns the results of the actual widget command
You can use this widget exactly like any other Text widget, with the added benefit that you can bind to <<TextModified>>.
For example, if you wanted to display the number of characters in the text widget you could do something like this:
import tkinter as tk
# ... import of definition of CustomText goes here ...
root = tk.Tk()
label = tk.Label(root, anchor="w")
text = CustomText(root, width=40, height=4)
label.pack(side="bottom", fill="x")
text.pack(side="top", fill="both", expand=True)
def onModification(event):
chars = len(event.widget.get("1.0", "end-1c"))
label.configure(text="%s chars" % chars)
text.bind("<<TextModified>>", onModification)
root.mainloop()
Goal of the script:
(3) different windows, each in its own class, with its own widgets and layout, are created via Toplevel and callbacks.
When a new (Toplevel) window is created, the previous one is destroyed. Thus, only one window is visible and active at a time.
Problem?
Basically, I've tried many things and failed, so I must understand too little of ["parent", "master", "root", "app", "..."] :(
Note on raising windows:
I have implemented a successful example of loading all frames on top of each other, and controlling their visibility via the .raise method.
For this problem, however, I don't want to load all the frames at once.
This is an abstracted version of a quiz program that will require quite a lot of frames with images, which makes me reluctant to load everything at once.
Script (not working; bugged):
#!/usr/bin/env python
from Tkinter import *
import tkMessageBox, tkFont, random, ttk
class First_Window(Frame):
"""The option menu which is shown at startup"""
def __init__(self, master):
Frame.__init__(self, master)
self.gotosecond = Button(text = "Start", command = self.goto_Second)
self.gotosecond.grid(row = 2, column = 3, sticky = W+E)
def goto_Second(self):
self.master.withdraw()
self.master.update_idletasks()
Second_Window = Toplevel(self)
class Second_Window(Toplevel):
"""The gamewindow with questions, timer and entrywidget"""
def __init__(self, *args):
Toplevel.__init__(self)
self.focus_set()
self.gotothird = Button(text = "gameover", command = self.goto_Third)
self.gotothird.grid(row = 2, column = 3, sticky = W+E)
def goto_Third(self):
Third_Window = Toplevel(self)
self.destroy()
class Third_Window(Toplevel):
"""Highscores are shown with buttons to Startmenu"""
def __init__(self, *args):
Toplevel.__init__(self)
self.focus_set()
self.master = First_Window
self.gotofirst = Button(text = "startover", command = self.goto_First)
self.gotofirst.grid(row = 2, column = 3, sticky = W+E)
def goto_First(self):
self.master.update()
self.master.deiconify()
self.destroy()
def main():
root = Tk()
root.title("Algebra game by PJK")
app = First_Window(root)
root.resizable(FALSE,FALSE)
app.mainloop()
main()
The problem is not really a Tkinter problem, but a basic problem with classes vs. instances. Actually, two similar but separate problems. You probably need to read through a tutorial on classes, like the one in the official Python tutorial.
First:
self.master = First_Window
First_Window is a class. You have an instance of that class (in the global variable named app), which represents the first window on the screen. You can call update and deiconify and so forth on that instance, because it represents that window. But First_Window itself isn't representing any particular window, it's just a class, a factory for creating instances that represent particular windows. So you can't call update or deiconify on the class.
What you probably want to do is pass the first window down through the chain of windows. (You could, alternatively, access the global, or do various other things, but this seems cleanest.) You're already trying to pass it to Second_Window, you just need to stash it and pass it again in the Second_Window (instead of passing self instance, which is useless—it's just a destroyed window object), and then stash it and use it in the Third_Window.
Second:
Second_Window = Toplevel(self)
Instead of creating an instance of the Second_Window class, you're just creating an instance of the generic Toplevel class, and giving it the local name Second_Window (which temporarily hides the class name… but since you never use that class, that doesn't really matter).
And you have the same problem when you try to create the third window.
So:
class First_Window(Frame):
# ...
def goto_Second(self):
# ...
second = Second_Window(self)
class Second_Window(Toplevel):
def __init__(self, first, *args):
Toplevel.__init__(self)
self.first = first
# ...
def goto_Third(self):
third = Third_Window(self.first)
self.destroy()
class Third_Window(Toplevel):
"""Highscores are shown with buttons to Startmenu"""
def __init__(self, first, *args):
Toplevel.__init__(self)
self.first = first
# ...
def goto_First(self):
self.first.update()
self.first.deiconify()
self.destroy()
I've got an interesting problem with the tk Entry widget. If I run the following test code,
from Tkinter import *
root =Tk()
def pfunc(self):
print Input.get()
f=Frame(root)
f.pack()
Input=Entry(f)
#Input.bind("<Return>",pfunc)
Input.pack()
root.mainloop()
I can properly enter into the widget and print to console; however the following code, as part of a larger GUI, does not allow me to click in the Entry boxes at all.
self.Tlabel = Label(self.TempFrame, text="Temp")
self.Tlabel.pack( side = LEFT)
self.Tenter = Entry(self.TempFrame,width=10, bd =5)
self.Tenter.bind("<Return>",self.getFlux)
self.Tenter.pack (side=RIGHT)
self.Flabel = Label(self.FluxFrame, text="Flux")
self.Flabel.pack( side = LEFT)
self.Fenter = Entry(self.FluxFrame, width=10, bd =5)
self.Fenter.bind("<Return>",self.getTemp)
self.Fenter.pack(side = RIGHT)
def getFlux(self):
for i in range(len(self.fit_tuples)):
if self.fit_tuples[i][0]==self.currentBFMdate and self.fit_tuples[i][1]==self.cell.get():
fit_data=self.fit_tuples[i][2]
self.Fenter.set(fit_data[0]*np.exp(fit_data[1]*int(self.Tenter.get())))
else:
self.Fenter.set("Invalid")
def getTemp(self):
for i in range(len(self.fit_tuples)):
if self.fit_tuples[i][0]==self.currentBFMdate and self.fit_tuples[i][1]==self.cell.get():
fit_data=self.fit_tuples[i][2]
self.Tenter.set(np.log(float(self.Fenter.get())/fit_data[0])/fit_data[1])
else:
self.Tenter.set("Invalid")
Furthermore, if I run both codes on a separate windows PC I have the same problem. The only difference I can possibly think of is that I am using instance variables within a class; but it seems that other widgets are bound and working properly.
Basically, the bind method is passing "Return" as a parameter to getTemp. As another user suggested, just add another parameter to the function.
If you use bind method, callback is called with an event object. You should add event parameter to the method/function. (See Events and Bindings - An Introduction to Tkinter )
So, rpelcae following lines:
def getFlux(self, event):
...
def getTemp(self, event):
...
The first program work unintentionally. Its parameter name should be event, not self.
Goal
I am trying to write a basic file which I can import in all other programs that will have a simple function that will take entry from the user and then return it.
Code
For that I have the following code:
class takeInput(object):
def __init__(self,requestMessage,parent):
self.string = ''
self.frame = Frame(parent)
self.frame.pack()
self.acceptInput(requestMessage)
def acceptInput(self,requestMessage):
r = self.frame
k = Label(r,text=requestMessage)
k.pack(side='left')
self.e = Entry(r,text='Name')
self.e.pack(side='left')
self.e.focus_set()
b = Button(r,text='okay',command=self.gettext)
b.pack(side='right')
def gettext(self):
self.string = self.e.get()
self.frame.destroy()
print self.string
def getString(self):
return self.string
def getText(requestMessage,parent):
global a
a = takeInput(requestMessage,parent)
return a.getString()
And I also added some script level code so as to test this:
root = Tk()
getText('enter your name',root)
var = a.getString()
print var
root.mainloop()
And what is really baffling me is that:
var does not have the value that I entered it has the empty string ''
a.string variable has the value that I entered and I checked this from the shell.
Also When I tried to assign the string returned from a.getString() to var in the shell, then it worked.
note I am new to Tkinter programming and dont fully understand how the mainloop() works. So maybe this is were the problem is. But I am not sure.
Specs
OS:Linux Mint 14
Python IDLE 2.7
Please help me out with this issue.
As other answers tell, you print var before entering the mainloop, that is, before your window is actually running, and your program is waiting for user input.
You could rely on tkSimpleDialog family to get your input:
import Tkinter
import tkSimpleDialog
root = Tkinter.Tk()
var = tkSimpleDialog.askstring("Name prompt", "enter your name")
print var
If you want to pursue your way, you could perform your print from the "ok" button callback (gettext in your case). You could also generate a virtual event when "ok" is pressed and bind to this event in your main program (http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/virtual-events.html)
The flow of your code goes like this:
the main scope calls getText.
getText creates a takeInput object a.
the takeInput object initializes itself, creating Labels & buttons etc.
getText returns a.getString(), which returns self.string, which still has its default value, the empty string.
the main scope prints var, which is empty.
So far, all of this has taken place within the span of a few nanoseconds. The user hasn't even seen the window yet.
the main scope then calls root.mainloop(), which finally gives the user the opportunity to interact with the window. But it's too late. var has already been printed.
If you want getText to not return until the user has submitted his text, then mainloop has to occur inside getText, not after it.
from Tkinter import *
class takeInput(object):
def __init__(self,requestMessage):
self.root = Tk()
self.string = ''
self.frame = Frame(self.root)
self.frame.pack()
self.acceptInput(requestMessage)
def acceptInput(self,requestMessage):
r = self.frame
k = Label(r,text=requestMessage)
k.pack(side='left')
self.e = Entry(r,text='Name')
self.e.pack(side='left')
self.e.focus_set()
b = Button(r,text='okay',command=self.gettext)
b.pack(side='right')
def gettext(self):
self.string = self.e.get()
self.root.destroy()
def getString(self):
return self.string
def waitForInput(self):
self.root.mainloop()
def getText(requestMessage):
msgBox = takeInput(requestMessage)
#loop until the user makes a decision and the window is destroyed
msgBox.waitForInput()
return msgBox.getString()
var = getText('enter your name')
print "Var:", var
The problem is that your test routine already prints out the value of var before the dialog has been shown, let alone text being entered. (You can easily validate this by adding some print statements to your test code.) This is because the call to mainloop() is at the very end. Instead, you should call mainloop after creating the frame, but before reading and returning the input, e.g. it might go to your getText method:
def getText(requestMessage,parent):
a = takeInput(requestMessage,parent)
parent.mainloop()
return a.getString()
This still does not work really well, as you have to close the dialog (click the [x]-button) even after clicking on 'okay', and I am not sure how to fix this.
However, note that there already is a module for this, tkSimpleDialog, providing methods such as askstring(title, prompt) that show just such an input dialog. So you might either use those, or look at the source code (found in /usr/lib/python2.7/lib-tk or the like) to find out how it's done.
Here's a quick snippet:
import tkinter
from tkinter import simpledialog
root = tkinter.Tk()
# withdraw() will make the parent window disappear.
root.withdraw()
# shows a dialogue with a string input field
youtube_url = simpledialog.askstring('YouTube URL', 'Enter the youtube URL of the video', parent=root)
if str(youtube_url).startswith('http'):
pass
else:
pass
This question already has answers here:
Why is my Button's command executed immediately when I create the Button, and not when I click it? [duplicate]
(5 answers)
Closed 6 months ago.
I'm trying to make a build retrieval form, and seem to have issues with the buttons... I'm a novice at Python/tkinter GUI programming (and GUI programming in general) and borrowed the skeleton of a Hello World app, and sorta built off that.
In the code below, I've set the "command" option of my Browse button to call my class's internal get_dir() function when it's clicked. However, as soon as I attempt to run the app, the get_dir() function is called and I'm prompted to choose a directory. Any ideas why this happens, and what I can do to make it behave properly?
from Tkinter import *
import tkFont
from tkFileDialog import askdirectory
class App:
def __init__(self, master):
fontHead = tkFont.Font(family="Arial", size=10, weight=tkFont.BOLD)
fontBold = tkFont.Font(family="Arial", size=8, weight=tkFont.BOLD)
fontReg = tkFont.Font(family="Arial", size=8)
frameN = Frame(master)
frameN.grid(row=0,padx=5,pady=5)
frameXBH = Frame(frameN)
frameXBH.grid(row=0,columnspan=5,padx=5)
Canvas(frameXBH,borderwidth=0,relief="flat",height=1,width=20,background="#cccccc").grid(row=0)
Label(frameXBH, text="Xbox 360",font=fontBold,width=9).grid(row=0,column=1)
Canvas(frameXBH,borderwidth=0,relief="flat",height=1,width=440,background="#cccccc").grid(row=0,column=2,sticky="WE")
Label(frameN, text="Destination Path:",font=fontReg).grid(row=1,sticky="W")
xbPath = Entry(frameN,width=30,font=fontReg)
xbPath.grid(row=1,column=1,sticky="W")
xbBrowse = Button(frameN,text="Browse...",font=fontReg,command=self.get_dir(xbPath))
xbBrowse.grid(row=1,column=2,sticky="W")
xbRel = Checkbutton(frameN,text="Release",font=fontReg)
xbRel.grid(row=1,column=3,sticky="W")
xbShip = Checkbutton(frameN,text="Ship",font=fontReg)
xbShip.grid(row=1,column=4,sticky="W")
Canvas(frameN,borderwidth=1,relief="groove",width=550,height=0).grid(row=2,columnspan=5,pady=10)
# SAVE AND CANCEL
btnSave = Button(frameN,text="Save",width=10)
btnSave.grid(row=3,column=3,sticky="E")
btnCancel = Button(frameN,text="Cancel",width=10)
btnCancel.grid(row=3,column=4,sticky="W")
def get_dir(self,box):
tmp = askdirectory(mustexist=1,title="Please select a destination")
tmp = tmp.replace("/","\\")
box.delete(0,END)
box.insert(0,tmp)
root = Tk()
root.resizable(0,0)
app = App(root)
root.mainloop()
Make your event handler a lambda function, which calls your get_dir() with whatever arguments you want:
xbBrowse = Button(frameN, text="Browse...", font=fontReg, command=lambda : self.get_dir(xbPath))
In the above code:
xbBrowse = Button(frameN,text="Browse...",font=fontReg,command=self.get_dir(xbPath))
You are invoking the function already, you should be simply passing the function:
xbBrowse = Button(frameN,text="Browse...",font=fontReg,command=self.get_dir)
You need to pass a reference of your get_dir method
so change
xbBrowse = Button(frameN,text="Browse...",font=fontReg,command=self.get_dir(xbPath))
to
xbBrowse = Button(frameN,text="Browse...",font=fontReg, command=self.get_dir)
Then make your Entry widget an instance variable so that you can access it in your get_dir method.
e.g.
self.xbPath = Entry(frameN,width=30,font=fontReg)
Then your get_dir() method will look like:
def get_dir(self):
tmp = askdirectory(mustexist=1,title="Please select a destination")
tmp = tmp.replace("/","\\")
self.xbPath.delete(0,END)
self.xbPath.insert(0,tmp)