I am trying to learn python, Tkinter and oop. Below is the code that I wrote after following tutorial on effbot.org
from Tkinter import Tk, Frame, Label
class Nexus(object):
"""Top level object which represents entire app"""
def __init__(self, main_window):
self.nexus_frame = Frame(main_window)
self.nexus_frame.pack()
self.label = Label(main_window, text="Tkinter")
self.label.pack()
def main():
main_window = Tk()
nexus_app = Nexus(main_window)
main_window.wm_title("Hello World Window")
width = main_window.winfo_screenwidth()
height = main_window.winfo_screenheight()
main_window.wm_minsize(width=width-100, height=height-100)
main_window.mainloop()
if __name__ == "__main__":
main()
Here a top level window is created first and it is passed as argument to Nexus class where I am adding a frame and a label to the frame. Then I am setting the size of top level window relative to current screen size back in the main function.
My question is why was the top level window create in main function?
Could it not be created inside __init__ of Nexus class itself?
What difference would it make if main_window was create inside __init__ of Nexus class and mainloop() was started therein?
Once Tk.mainloop is entered, no further code will be executed. Instead, the Tk event loop will take over (hence the name).
What that means is that if you, eg, did something like this:
def main():
...
main_window.mainloop()
print 'Hello world!'
then that print statement would never be executed (or, at least, not while the GUI is running).
So, with that in mind, why is there a problem with creating the root window and executing main loop within the constructor (the __init__ statement)? Well, two basic reasons:
It would mean that the constructor never returns, which is unexpected. If a programmer sees this:
def main():
Nexus()
print 'Hello world!'
then he or she will expect that print statement to be executed. As a rule, you don't expect creating an instance of a class to be the kind of thing which will cause an infinite loop (as the event loop is).
Related to that is the second reason: it would not be possible to create more than one instance of Nexus, because as soon as you create one, Tk.mainloop will take over. Again, that's unexpected: a class is a description of a type of object, and you would normally expect to be able to instantiate more than one object like that.
At the moment, if you write:
def main():
...
Nexus(main_window)
Nexus(main_window)
then you'll get two copies of your Nexus window on the screen. That's expected, and sensible. The alternative would not be.
So what's the take-away message?
When you're dealing with GUI programs, entering the event loop is the last thing you want to do. Your setup might involve creating one object (as now), or it might involve creating many objects (eg, a complex GUI app might have two or three windows).
Because we want to be able to write similar code in both cases, the usual approach is to create the root window (the Tk object) once, and then pass it in as a reference to any classes that need to know about it.
Related
I'm developing an application what have its functions set in different files.
The main file have a tkinter interface and the buttons, entrys and labels are in other file, like this:
Mainfile.py
from tkinter import *
class Program:
def __init__(self, root):
root.geometry('200x200')
self.main_frame = Frame(root)
self.main_frame.pack()
import Buttons
self.branch = Buttons.New_Button(self.main_frame)
#Here i wuold like to verify the hipotetic variable after the main_frame were destroyed
if self.branch.hipotetic_variable:
root.mainloop()
app = Program(Tk())
Buttons.py
from tkinter import *
import functools
class New_Button:
def __init__(self, using_frame):
self.button_1 = Button(using_frame, text = 'Button 1', command=functools.partial(self.Func, using_frame))
self.button_1.pack()
def Func(self, to_destroy):
to_destroy.destroy()
#Here is the hipotetic variable what i would like to verify with if statment
self.hipotetic_variable = True
The problem is that I want to keep managing the program in the main file calling the other functions and implementing it, but I cannot verify if it's time to update the screen because mainloop makes impossible to verify it using a while loop and an hipotetic variable that's created after user pressed button.
I wold like to know if there is an way to update an variable contained in the Buttons.py file on Mainfile.py to keep implementing all other canvas in this file.
Your if self.branch.hipotetic_variable: check in the Program.__init__() method is only going to be executed when the Program class instance gets created initially, which is before the button that could change the value of the variable could have been pressed. You also don't want to make the hipotetic_variable an attribute of the Button because that will be destroyed along with the Frame it is in when that's destroyed in the button callback function.
Tkinter applications are user-event driven, meaning that they're "run" by responding to events (that's what mainloop is all about). This type of programming paradigm is different from the procedural or imperative one you're probably used to.
Therefore to do what you want requires setting things up so an event that the program can respond to will be generated, which in this case to when the frame is destroyed. One way to do that is by taking advantage of tkinter Variable classes to hold this hipotetic variable you're interested in. It looks like a boolean, so I used a tkinter BooleanVar to hold its value. One interesting thing about Variables is that you can have changes to their values "traced" by defining functions to be called whenever that happens. That's what I have done in the code below, and the callback function in this case — check_hipotetic_variable() — updates a Label to display the new value of the variable when it's called.
Below is your code with the modifications necessary to use a tkinter BooleanVar and trace changes to its value.
Mainfile.py
from tkinter import *
import Buttons
class Program:
def __init__(self, root):
root.geometry('200x200')
self.main_frame = Frame(root)
self.main_frame.pack()
self.notice_lbl = Label(root, text='')
self.notice_lbl.pack(side=BOTTOM)
self.hipotetic_variable = BooleanVar(value=False)
# Set up a trace "write" callback for whenever its contents are changed.
self.hipotetic_variable.trace('w', self.check_hipotetic_variable)
self.branch = Buttons.New_Button(self.main_frame, self.hipotetic_variable)
root.mainloop()
def check_hipotetic_variable(self, *args):
"""Display value of the hipotetic variable."""
value = self.hipotetic_variable.get()
self.notice_lbl.config(text=f'hipotetic variable is: {value}')
app = Program(Tk())
Buttons.py
from tkinter import *
import functools
class New_Button:
def __init__(self, using_frame, variable):
self.button_1 = Button(using_frame, text = 'Button 1',
command=functools.partial(self.Func, using_frame))
self.button_1.pack()
self.variable = variable # Save for use in callback.
def Func(self, to_destroy):
to_destroy.destroy()
self.variable.set(True) # # Change value of the variable.
P.S. I noticed you're not following the PEP 8 - Style Guide for Python Code, which makes reading your code harder to read and follow that if you're were following them — for that reason I strongly suggest you read the guide and start following the suggestions, especially the Naming Conventions which apply to functions and variable names, as well as the names of script files.
I have been trying to represent tkinter window in an OO manner. There are few answers here, and I have managed to produce a working code, but I simply do not understand why it's working.
import tkinter as tk
class CurrConv:
def __init__(self, window, date):
self.window = window
window.title("Currency Converter")
window.geometry("350x150+300+300")
self.label = tk.Label(text="Date: {}\n".format(date))
self.label.pack()
self.text_box = tk.Text()
self.text_box.insert("2.0", "100 is: {}\n".format(100))
self.text_box.insert("3.0", "24 is: {}".format(24))
self.text_box.pack()
self.button = tk.Button(text="Quit", width=8, command=window.quit)
self.button.place(relx=0.5, rely=0.9, anchor="center",)
def main():
window = tk.Tk()
app = CurrConv(window, 1234)
window.mainloop()
if __name__ == "__main__":
main()
The thing I don't understand is the usage of "app" object. It is used nowhere, and usually when we create an object (in any programing language), we invoke certain actions on it. However here we do nothing with the app object. Class encapsulation appears to indirectly modify "window", which is confusing, to say the least.
Next, I don't understand how labels and text boxes are added to "window", when in the code I nowhere create those in "window", I create them on "self", which would be "app", which is no longer used.
Bottom line is, for the reasons above, I do not understand how the above code works.
Thanks in advance.
I hope I was clear enough.
Class encapsulation appears to indirectly modify "window", which is confusing, to say the least.
Yes, that is what the code is doing, and it's incorrect IMHO. The code inside of CurrConv.__init__ at least arguably should not be directly modifying the root window for some of the very reasons you put in your question.
Next, I don't understand how labels and text boxes are added to "window", when in the code I nowhere create those in "window", I create them on "self", which would be "app", which is no longer used.
Your terminology is a bit incorrect. "I nowhere create those in 'window'" isn't true. By creating the widgets without an explicit master they default to being created in the root window. The reference to the widget is created in the class (self.label, etc) but the widget itself is created in the root window. IMHO this is also not the proper way to write a tkinter application. Explicit is better than implicit.
For me, the proper way to write this class would be for CurrConv to inherit from a tkinter Frame (or some other widget). Everything it creates should go inside itself, and then the code that creates the instance of CurrConv is responsible for adding it to a window. This solves your problem of not being able to create multiple windows.
Also, the widgets inside of CurrConv should explicitly set the master of any child widgets it creates rather than relying on a default master.
Example:
class CurrConv(tk.Frame):
def __init__(self, window, date):
super().__init__(window)
self.label = tk.Label(self, ...)
self.text_box = tk.Text(self, ...)
self.button = tk.Button(self, ...)
...
...
With that, everything is nicely encapsulated inside the class, and you can create multiple instances inside multiple windows very easily:
root = tk.Tk()
conv1 = CurrConv(root)
conv1.pack(fill="both", expand=True)
top = tk.Toplevel(root)
conv2 = CurrConv(top)
conv2.pack(fill="both", expand=True)
In the above case, it's perfectly fine for the class to modify the window because it's part of the code that creates the window in the first place.
You can still use a class for the application as a whole which you can use to hold some global state. To further prove the usefulness of creating CurrConv as a subclass of Frame, the following example adds currency converters to a notebook instead of separate windows.
class CurrConvApp():
def __init__(self):
self.root = tk.Tk()
self.root.title("Currency Converter")
self.root.geometry("350x150+300+300")
self.notebook = ttk.Notebook(self.root)
cc1 = CurrConv(self.notebook)
cc2 = CurrConv(self.notebook)
self.notebook.add(cc1, text="USD to EUR")
self.notebook.add(cc2, text="EUR to USD")
...
def start(self):
self.root.mainloop()
if __name__ == "__main__":
app = CurrConvApp()
app.start()
For the first question, yes app is an object. But it is an instance of the class CurrConv. When you initialize a class, you call the class's __init__ method, and this case, by doing so, you execute the statements in that method: modifying the window (which you passed as a parameter whin you created app) and adding widgets to it. So although app is not directly used, it had the side effect of doing those things when it was created. And for that reason, since you only need the initialization method, assigning to a variable is not necessary, you can just make it like CurrConv(window, 1234).
For the second, yes, you didn't mention window when you created the widgets, but when the master of a new Tkinter widget is not specified, it takes the main master (the root, created using tk.Tk()) as it's master.
I have a code that works well for bulk data analysis and plotting. But now i'm trying to incorporate it into a larger data analysis GUI. I find that when i run my code on its own, all goes well. But when i call it from the main code and run it from a tkinter button, it's not the same. Everything looks the same and it runs smoothly, the only difference is that no files are saved.
i think maybe it's a problem with which window is defined with "____init____"? or something with how i create and destroy Tk() windows within the subcode?
**the stackoverflow text editor uses underscores to make text bold/itallic, so for all cases that double underscores are used to wrap "init" or "main" in python, i had to use four on each side here
my code (saved as SubCode.py):
def AnalysisFunction():
*does things*
main = Tk()
os.chdir(OutputFolder)
plt.savefig('image.png')
main.destroy()
if __name__ == '__main__':
AnalysisFuntion()
the code i want to add mine into:
import SubCode
class TopLevel(Frame):
def __init__(self, master):
Frame.__init__(self,master)
*Creates main GUI window*
MyButton = Button(root, command = self.CallSubCode)
def CallSubCode(self):
SubCode.AnalysisFunction()
root = Tk()
main_window = TopLevel(root)
root.mainloop()
Any ideas why the subcode alone can save figures but it cannot when called by the larger GUI? FYI, it still creates all variables correctly when running through the larger GUI.
I think you should just save the image in SubCode.py without creating a tkinter window. i.e
def AnalysisFunction():
*does things*
os.chdir(OutputFolder)
plt.savefig('image.png')
if __name__ == '__main__':
AnalysisFuntion()
i figured it out, I had to put the whole SubCode within a class structure, then call it as its own Toplevel app. I think otherwise the plt.savefig command doesn't know which Tkinter window it is working with, and tries to find data in the "host" window, not the one which is handling the data.
I am trying to bind events from a GUI file to use code from another file (effectively a "front end" and a "back end"). I can get the back end and front end working within the same file, but when I try to move them into separate files, I have issues getting the back end to see parts (labels, buttons, etc.) of the front end.
I. E. I need the back end code to change labels and do math and such, and it would need to affect the GUI.
I have provided a simple version of my program. Everything works with the exception of the error I get when I try to make the back end see the parts of the GUI.
mainfile.py
#!/usr/bin/python
# -*- coding: utf-8 -*-
import wx
import label_changer
class foopanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent, id=wx.ID_ANY)
box = wx.BoxSizer()
btn = wx.Button(self,1,"Press")
btn.Bind(wx.EVT_BUTTON,label_changer.change_label(self))
box.Add(btn)
self.lbl = wx.StaticText(self,1,"Foobar")
box.Add(self.lbl)
self.SetSizerAndFit(box)
class main_frame(wx.Frame):
"""Main Frame holding the main panel."""
def __init__(self,*args,**kwargs):
wx.Frame.__init__(self,*args,**kwargs)
sizer = wx.BoxSizer()
self.p = foopanel(self)
sizer.Add(self.p,1)
self.Show()
if __name__ == "__main__":
app = wx.App(False)
frame = main_frame(None,-1,)
app.MainLoop()
label_changer.py
def change_label(self):
self.p.lbl.SetLabel("barfoo")
All I want it to do is change the label of the GUI, but use an external file.
I am doing this mostly to keep my code separate and just as a learning experience.
Thanks in advance!
One solution is to modify change_label to accept an argument that identifies the label to change. For example:
def change_label(event, label):
label.SetLabel("barfoo")
Then, use lambda to create a callback that passes that argument in:
btn.Bind(wx.EVT_BUTTON, label_changer,
lambda event, label=self.p.lbl: label_changer.change_label(event, label))
Make sure you define self.lbl before you do the binding.
For more on passing arguments to callbacks see Passing Arguments to Callbacks on WxPyWiki
A common way to do this is the MVC Pattern and pubsub. See this Example.
This
btn.Bind(wx.EVT_BUTTON,label_changer.change_label(self))
needs to be
btn.Bind(wx.EVT_BUTTON,label_changer.change_label)
and this
def change_label(self):
self.p.lbl.SetLabel("barfoo")
needs to be
def change_label(event):
panel = event.GetEventObject().GetParent()
panel.lbl.SetLabel("barfoo")
To clarify, you need to pass a reference to a function to Bind that is to be called when the event occurs. wx will always pass one argument to these functions - the event. The self that you usually see in the callbacks is a byproduct of them being bound methods. Every bound method (to oversimplify, a function defined in a class) gets implicitly passed a first argument when called that is a reference to a class instance. So since you can't get to this instance the traditional way in an "external" function you have to get to it through the event object.
One more thing, you are not realy separating the gui from the logic this way. This is because the logic (label_changer in this case) needs to know about the gui and to manipulate it directly. There are ways to achieve much stronger separation (st2053 hinted at one of them) but for a relatively small program you don't need to bother if you don't want to right now, simply splitting the code in multiple files and focusing on getting the thing done is fine. You can worry about architecture later.
How do you invoke a tkinter event from a separate object?
I'm looking for something like wxWidgets wx.CallAfter. For example, If I create an object, and pass to it my Tk root instance, and then try to call a method of that root window from my object, my app locks up.
The best I can come up with is to use the the after method and check the status from my separate object, but that seems wasteful.
To answer your specific question of "How do you invoke a TkInter event from a separate object", use the event_generate command. It allows you to inject events into the event queue of the root window. Combined with Tk's powerful virtual event mechanism it becomes a handy message passing mechanism.
For example:
from tkinter import *
def doFoo(*args):
print("Hello, world")
root = Tk()
root.bind("<<Foo>>", doFoo)
# some time later, inject the "<<Foo>>" virtual event at the
# tail of the event queue
root.event_generate("<<Foo>>", when="tail")
Note that the event_generate call will return immediately. It's not clear if that's what you want or not. Generally speaking you don't want an event based program to block waiting for a response to a specific event because it will freeze the GUI.
I'm not sure if this solves your problem though; without seeing your code I'm not sure what your real problem is. I can, for example, access methods of root in the constructor of an object where the root is passed in without the app locking up. This tells me there's something else going on in your code.
Here's an example of successfully accessing methods on a root window from some other object:
from tkinter import *
class myClass:
def __init__(self, root):
print("root background is %s" % root.cget("background"))
root = Tk()
newObj = myClass(root)
Here below just some doc and link to better understand Bryan's answer above.
function description from New Mexico Tech :
w.event_generate(sequence, **kw)
This method causes an event to trigger without any external stimulus. The handling of the event is the same as if it had been triggered by an external stimulus. The sequence argument describes the event to be triggered. You can set values for selected fields in the Event object by providing keyword=value arguments, where the keyword specifies the name of a field in the Event object.
list and description of tcl/tk event attributes here