For a small timer app I want to write a GTK interface where I can set the desired time. Here is a picture of the interface:
However, I am having trouble reading out the fields of the spin buttons. My envisaged procedure for this is the following:
Read out the buttons using methods for each button
Here is one of the methods that does this:
# Get the fields of the spinbuttons
def get_seconds(self, widget, spin):
self.rSeconds = spin.get_value_as_int()
It is then called like this:
button = gtk.Button("Start")
button.connect("clicked", self.get_seconds, spinnerS)
Create a timer object with the data from the buttons
This is planned to be accomplished using this method:
# Create the timer object ...
def prepare_timer(self, widget, hours, minutes, seconds, title, text):
self.timer = eggTimer(hours, minutes, seconds, title, text)
Which is called here:
button.connect("clicked", self.prepare_timer, self.rHours, self.rMinutes, self.rSeconds, "some title", "some text")
Unfortunately, when running the script I get the following error message:
Traceback (most recent call last):
File "GTKInterface.py", line 140, in <module>
SpinButtonExample()
File "GTKInterface.py", line 126, in __init__
button.connect("clicked", self.prepare_timer, self.rHours, self.rMinutes, self.rSeconds, "Title", "Text")
AttributeError: SpinButtonExample instance has no attribute 'rSeconds'
To check whether there really is no instance of that variable, I programmed a short method to print it:
def returnS(self, widget):
print self.rSeconds
And surprisingly this method can "see" self.rSeconds. This makes me wonder what determines the visibility of the variable. What am I doing wrong to read this out?
You try to pass the attribute self.rHours to the connect method, but at that point the attribute doesn't exist yet (the clicked handlers haven't executed yet).
Note that even if you fill in self.rHours before calling connect, it will pass the value at the time of connecting, not at the time of the handler executing.
You can solve this by passing self.rHours etc directly to eggTimer in prepare_timer.
But it would be even easier to just combine all the click handlers into one, and use local variables instead of self.rHours etc. There's no reason to split your code over many click handlers like this.
Edit: BTW, you can also use nested functions instead of methods:
...
def prepare_timer(widget):
self.timer = eggTimer(
spinnerH.get_value_as_int(),
spinnerM.get_value_as_int(),
spinnerS.get_value_as_int(),
"Title", "Text")
button.connect("clicked", prepare_timer)
...
Keep it simple!
Going off of adw's answer recommending one click handler, a simple addition to your pastebin code would be:
def read_and_prepare(self,spinnerS,spinnerM,spinnerH,title,text):
self.get_seconds(spinnerS)
self.get_minutes(spinnerM)
self.get_hours(spinnerH)
self.prepare_timer(elf.rHours, self.rMinutes, self.rSeconds, title, text)
and only have
button.connect("clicked", self.read_and_prepare,spinnerS,spinnerM,spinnerH,"Title","Text")
for the connection code.
You could also probably redesign a bit to avoid all the get_* calls and just read the values in the click handler.
Related
I am having trouble finding a way to accomplish what I want using buttons in tkinter. I am programming a multi-frame tkinter app, for myself, and each frame is a class, each class has buttons that I place on the screen in the __init__ method. I am having trouble trying to link the buttons and the command functions together.
Example:
class frameHome:
def __init__(self,parent, controller)
self.frame=tk.Frame(parent)
self.buttonOne=tk.Button(self.frame,text="Click Me") # I want to add the command here
self.buttonOne.pack()
def buttonOneClick():
print("You clicked me")
When I add the command in the tk.Button() call it says buttonOneClick not defined. One video I watched said to add the function at the top of the code. I would like to keep the function as a method of the class for organization, as well as keep init at the top of the class and was wondering if there is a way to create a reference to the function so I can have it as a method after the the init method and be able to call it in the init method, because I would like the init method to create the gui and link all widget commands for that class/frame in it.
Thank you for any time and help that you can offer
in your command did you write the command as command=buttonOneClick()? if that's the case then you should replace it with self.buttonOneClick()
The answer I needed was that I forgot the self param in the buttonOneClick(self, msg) method.
I need some help form all of you, so i have something like this
class LoginFrame(Frame):
def __init__(self, master):
super().__init__(master)
...
...
...
def _login_btn_clickked(self):
...
...
result = messagebox.askyesno("Wrong username or password.", "Do you want to try again?")
if result == True:
self.__init__(self)
So if the user select the option to try again i need the whole program start again, and show the login box. But when i execute the program the process fail.
But when i execute the program the process fail.
Actually it should. __init__ method is normally called once an instance is created. You need to redesign this to either recreate an instance (first call pack_forget on that frame and then make new one and pack on that place), or do it using methods, like show_loginbox etc
Usually, loginboxes are made using Toplevel with Entry as fields and some buttons to post a result. If done so, on error you could simply erase login or/and passwords entry box and show an error
What I mean is:
reference implementation of above is here
Are you using tkinter.Frame?
I should think that it wouldn't like being reconstructed through the use of
super().init()
While already being packed into a root window (I'm adsuming that is the setup you have).
I am creating a tkinter application using Python 3.4 that collects posts from an API, filter them and allow the user to review them and make a decision for each one (ignore, delete, share, etc.)
The user is expected to pick a date and some pages and then click on the 'Collect' button. The program then fetch the posts from the pages and stock them in 'wholeList'.
When the user clicks on the second button 'Review', the posts must be filtered and passed to the Reviewer.
My problem is that the Reviewer receives no posts at all, and neither does the Filterer. I have added some debugging print() statements at some places, notably to handlerCollect(), and the result baffled me, hence this post.
Instead of finishing the handlerCollect() callback method when I click on 'Collect', the program puts it on hold somewhere between "DEBUG->1" and "DEBUG->2". The main window does not freezes or anything, for I can click on 'Review' and have it print "DEBUG->4" and open up the Reviewer. When I close the main window, "DEBUG->0" "DEBUG->2" and "DEBUG->3" finaly print, along with the rest of the handlerCollect() method executing.
The same behavior happens with handlerChoosePage(), with "DEBUG->0" being delayed until the tkinter root (TK()) is destroyed. My knowledge of structural programming tells me it should be the very first one printed. Instead, it is the very last. My best conclusion is that I must not be ending my Toplevel mainloop()s correctly. I have to admit I have never encountered something like this before. I thought the proper way of ending mainloop()s on Toplevels was with destroy() and I am very confused as to why methods calling mainloop()s get put on hold until the Tk root is destroyed; not really practical.
from GUICollector import GUICollector as Collector
class Launcher(tk.Frame):
def __init__(self, *args, **kwargs):
...
self.allPagesCB = Checkbutton(self.dateFrame, text="Collect for all pages",
variable = self.allPagesVar, command=self.handlerChoosePage)
self.collectBtn = Button(self, text="Collect", command=self.handlerCollect)
self.reviewBtn = Button(self, text="Review", command=self.handlerReview)
def handlerChoosePage(self):
if self.allPagesVar.get() == 0:
child = tk.Toplevel(self)
selector = PageSelector(self.toCollect, child)
selector.pack(side="top", fill="both", expand=True)
selector.mainloop()
print("DEBUG->0")
def handlerCollect(self):
print("DEBUG->1")
self.collect()
print("DEBUG->4")
for post in self.collector.getPosts():
if post not in self.wholeList:
print("...")
self.wholeList.append(post.copy())
self.collector = None
print(len(self.wholeList), "posts in wholeList")
def collect(self):
window = tk.Toplevel(self)
self.collector = Collector(self.toCollect, self.sinceEpoch, window)
self.collector.grid(row=0,column=0)
self.collector.after(500, self.collector.start)
print("DEBUG->2")
self.collector.mainloop() # This is what seems to hang indefinetly
print("DEBUG->3")
def handlerReview(self):
print("DEBUG->5")
print(len(self.wholeList), "posts in wholeList")
filterer = Filterer(self.wholeList)
self.wholeList = filterer.done[:]
window = tk.Toplevel()
reviewer = Reviewer(self.wholeList[:], window)
reviewer.grid(row=0,column=0)
reviewer.mainloop()
The GUICollector module requires no interaction from the user at all.
This module seems to work perfectly: doing its job, displaying it is done and then closing after the specified delay.
Since the GuiCollector mainloop() seems to be the culprit of the hanging, here is how I end it:
class GUICollector(tk.Frame):
def __init__(self, pagesList, since, *args, **kwargs):
tk.Frame.__init__(self, *args, **kwargs)
def start(self, event=None):
if some_logic:
self.after(250,self.start)
else:
self.done() # Does get called.
def done(self):
# Some StringVar update to display we are done on screen
self.after(1250, self.validate)
def validate(self):
self.master.destroy()
The PageSelector module is destroyed with the same call on the press of a button: self.master.destroy()
Here is the revelant output of the program:
DEBUG->1
DEBUG->2
=> collected data of page [PageName]
=> Found 3 posts in page
DEBUG->5
0 posts in wholeList
[The main window (Launcher) is manually closed at this point]
DEBUG->3
DEBUG->4
...
...
...
3 posts in wholeList
DEBUG->0
The concept of mainloop assumes that you first create and initialize objects (well, at least these that are required at application start, i.e. not used dynamically), set event handlers (implement interface logic) and then go into infinite event handling (what User Interface essentially is), i.e. main loop. So, that is why you see it as it "hangs". This is called event-driven programming
And the important thing is that this event handling is done in one single place, like that:
class GUIApp(tk.Tk):
...
app = GUIApp()
app.mainloop()
So, the mainloop returns when the window dies.
Until I have some time to refactor my code, I solved the problem by adding the following line to my destroy() calls:
self.quit() # Ends mainloop
self.master.destroy() # Destroys master (window)
I understand this doesn't not solve the bad structure of my code, but it answers my specific question. destroy() doesn't end the mainloop of TopLevels, but quit() does. Adding this line makes my code execute in a predictable way.
I will be accepting #pmod 's answer as soon as I have refactored my code and verified his claim that the Tk() mainloop will cover all child TopLevels.
I have a program that at some point opens a new window (filled with buttons and gizmo's for the user to select and play around with) that is defined as follows:
def window(self,master):
def close(self):
# change some variables
self.destroy()
top = self.top = Toplevel()
# Several lines of buttons
top.lift()
top.protocol("WM_DELETE_WINDOW",close(self))
I initially had a close button there that would wrap everything up nicely but I noticed that if the user used the standard 'X' in the corner of the window, this function obviously would not be called and that would give a lot of problems later on. I found out about the 'WM_DELETE_WINDOW' suggestion from some other questions on this website but it gives me a rather strange error:
File "/usr/lib/python2.7/lib-tk/Tkinter.py", line 1630, in wm_protocol
'wm', 'protocol', self._w, name, command)
TclError: bad window path name ".33862072"
I assume that it somehow has gotten the wrong window ID and is unable to catch the event. My question is thus, is that true or not and secondly how should I continue to deal with this issue.
Let's examine this line of code:
top.protocol("WM_DELETE_WINDOW",close(self))
This line of code is saying "immediately call the function close(self), and assign the result to the protocol handler. See the problem? It's immediately calling close, likely before self has been fully constructed. You don't want the function to be called, you want to pass in a reference to the function.
Make close be a method of self (rather than an embedded function) and change the call to top.protocol to look like this (note the lack of trailing parenthesis):
top.protocol("WM_DELETE_WINDOW", self.close)
If you prefer to keep the nested function, you can use lambda:
top.protocol("WM_DELETE_WINDOW", lambda window=self: close(window))
I want to create a client frontend in pygtk for my Django project. My general idea is to have one main window, and everytime the user has an action that must change the screen to unload previous widgets and load the new ones. E.g if i have a login page, after user logs in he is presented with a customer screen. I want the new screen to be placed on the same main window, kinda like a page stack, but without the "back" functionality. My first thought was to create a function for every screen, a show_login, a show_customers_screen, etc. Is this a good choice or should i try a better one. And a second question, related to the first. Can i create callbacks inside a function?
e.g
This would be a method of MainWindow
def create_login(self):
....creating widgets here
#UnboundLocalError: local variable 'clear_clb' referenced before assignment
btnlogin.connect('clicked', clear_clb, data=None)
def clear_clb(widget, data=None):
..log in process
I know why i get the error. The thing is that the fields i want this func to clear are local in create_login. Is this the right approach?
Define the clear_clb symbol before referencing it:
def create_login(self):
# create widgets
def clear_clb(widget, data=None):
# log in process
btnlogin.connect('clicked', clear_clb, data=None)
However, the more usual, and in my opinion more readable, way of doing this is to save references to your widgets as attributes of self:
def create_login(self):
# create widgets
self.btnlogin = gtk.Button(...
self.btnlogin.connect('clicked', clear_clb)
def clear_clb(self, widget, data=None):
# log in process