Python: Tkinter: Why is it root.mainloop() and not app.mainloop() - python

I'm a new member to Stack Overflow.
I found this thread, but was not allowed to comment or ask questions on it, so I thought I'd just reference it here: How can I make a in interactive list in Python's Tkinter complete with buttons that can edit those listings?
from tkinter import *
import os
import easygui as eg
class App:
def __init__(self, master):
frame = Frame(master)
frame.pack()
# character box
Label(frame, text = "Characters Editor").grid(row = 0, column = 0, rowspan = 1, columnspan = 2)
charbox = Listbox(frame)
for chars in []:
charbox.insert(END, chars)
charbox.grid(row = 1, column = 0, rowspan = 5)
charadd = Button(frame, text = " Add ", command = self.addchar).grid(row = 1, column = 1)
charremove = Button(frame, text = "Remove", command = self.removechar).grid(row = 2, column = 1)
charedit = Button(frame, text = " Edit ", command = self.editchar).grid(row = 3, column = 1)
def addchar(self):
print("not implemented yet")
def removechar(self):
print("not implemented yet")
def editchar(self):
print("not implemented yet")
root = Tk()
root.wm_title("IA Development Kit")
app = App(root)
root.mainloop()
Could somebody explain to me why the very last line is root.mainloop()?
Being a novice with Python, and coming from a background that's procedural-oriented with no object-orient experience, I would have thought it would have been app.mainloop().
In fact app = App(root) , app is never used again in the rest of the code! I'm having trouble understanding why root.mainloop() still works.

I'm not sure if you'll find this answer satisfying, but you call root.mainloop() because root is the object that has the mainloop method. In the code you've given, App has no mainloop function.
In simpler terms, this is just how tkinter works -- you always end your script by calling the mainloop method of the root window. When that method returns, your program will exit.
Let's start at the beginning. The simplest, non-OO Tkinter program is going to look like the following example. Note that this is a python 2.x example, and I do not use a global import since I think global imports are bad.
import Tkinter as tk
root = tk.Tk()
<your widgets go here>
root.mainloop()
Many people find that a pure procedural style is not an effective way to write code, so they might choose to write this in an object-oriented style. It's natural to think of "the app" as a singleton object. There are many ways to do this -- the one in your question is, unfortunately, not one of the clearer ways to do it.
A slightly better way, IMO, would be to structure the code like this:
class App(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
<your widgets go here>
app = App()
app.mainloop()
In this case, mainloop is still being called, though now it's a method of App since App inherits from Tk. It is conceptually the same as root.mainloop() since in this case, app is the root window even though it goes by a different name.
So, in both cases, mainloop is a method that belongs to the root window. And in both cases, it must be called for the GUI to function properly.
There is a third variation which is what the code you picked is using. And with this variation, there are several ways to implement it. The variation is your question uses a class to define the GUI, but does not inherit from Tk. This is perfectly fine, but you still must call mainloop at some point. Since you don't create (or inherit) a mainloop function in your class, you must call the one associated with the root window. The variations I speak of are how and where the instance of App is added to the root window, and how mainloop is ultimately called.
Personally I prefer that App inherits from Frame, and that you pack the app outside the definition of the app. The template I use looks like this:
class App(tk.Frame):
def __init__(self, *args, **kwargs):
tk.Frame.__init__(self, *args, **kwargs)
<your widgets go here>
if __name__ == "__main__":
root = tk.Tk()
app = App(root)
app.pack(fill="both", expand=True)
root.mainloop()
In this final example, app and root are two completely different objects. app represents a frame that exists inside the root window. Frames are commonly used this way, as a container for groups of other widgets.
So, in all cases, mainloop must be called. where it is called, and how, depends a bit on your coding style. Some people prefer to inherit from the root window, some don't. In either case, you must call the mainloop function of the root window.

I tested both like you see:
One is written with "app." + ".pack()" and one calls "mainframe." + ".grid()"
#-*- coding: utf-8 -*-
#THIS IS THE "MAINFRAME." - PART
from Tkinter import *
import ttk
def show():
p = password.get() #get password from entry
print(p)
root = Tk()
root.title("Ingos first program")
mainframe = ttk.Frame(root, padding="30 30 60 12")
mainframe.grid(column=0, row=0, sticky=(N, W, E, S))
mainframe.columnconfigure(0, weight=1)
mainframe.rowconfigure(0, weight=1)
password = StringVar() #Password variable
passEntry = Entry(mainframe, textvariable=password, show='*').grid(column=3, row=3, sticky=S)
submit = Button(mainframe, text='Show Console',command=show).grid(column=3, row=4, sticky=S)
root.mainloop()
def show():
p = password.get() #get password from entry
print(p)
#THIS IS THE "APP."-PART. BOTH WORKS FINE.
app = Tk()
app.title("Ingos first program")
password = StringVar() #Password variable
passEntry = Entry(app, textvariable=password, show='#').pack()
submit = Button(app, text='Show Console',command=show).pack()
app.mainloop()
This instance works fine with python 2.7. In that test even app. can handle "mainloop()"
That script opens 2 windows, one after another (if you close the first one) and the first programm is formatted, didn't tryed to write the colum=3... stuff in the pack() clamps.
I still started the Tkinter so don't fight me, just trying.. Hope I could help to answer your question.
All the best, Ingo

The App object is just your app code, and the reason you call App(root) is to make an instance with your class, which then has access to your root window.
It receives this reference in the __init__ method:
def __init__(self, master):
# master refers to the root window now
...
You can see the entire definition of the App object (given by the block beginning with class App:), and it doesn't even have a mainloop method, so to start the main Tkinter loop, you have to call it on the root window.
In the example in the Python2 documentation, they do call it as you suspected should be done, but note that their example class subclasses the Tk object Frame. In your example code, App is an old-style class that doesn't inherit anything.

Related

Tkinter OOP - Class Instance Management With Multiple Top Level Windows

My initial approach is based on the following example:
Best way to structure a tkinter application?
I am trying to create a more complex application involving multiple sub applications each displayed in a separate top level window. All sub applications need to be able to exchange information among each other. Hence, I intend to use class variables in the main application in order to accomplish this requirement.
At the moment I am stuck at one point:
I do not know how to properly implement the destruction of the sub application class instance once it has been created.
The "app_one" window can be opened exactly one time and then it can be properly closed. Of course, if the window is opened a second time an error occurs since only the "Toplevel" window instance has been destroyed, but not the sub application class instance itself.
How do I properly implement the destruction of the sub application class instance upon closing the "Toplevel" window ?
Besides that, I am also very grateful for any other advise on how to improve the structure of my template below in terms of following "Python" as well as "tkinter" best practice paradigms:
import tkinter
import tkinter.filedialog
class main_app(tkinter.Tk):
root = None
var_one = None
sub_app_one_instance = None
sub_app_two_instance = None
sub_app_thr_instance = None
def __init__(self):
super().__init__()
main_app.root = self # <<< in order to be able to refer to root window in other classes
main_app.var_one = 99 # <<< class variables in order to exchange data between main app and sub apps
main_app.sub_app_one_instance = None
main_app.sub_app_two_instance = None
main_app.sub_app_thr_instance = None
self.create_tkinter_interface_main_app()
def create_tkinter_button(self, button_destination, button_text, button_width):
tkinter_button = tkinter.Button(button_destination, text=button_text, width=button_width)
return tkinter_button
def create_tkinter_label(self, label_destination, label_text):
tkinter_label = tkinter.Label(label_destination, text=label_text)
return tkinter_label
def create_tkinter_interface_main_app(self):
frame_main = tkinter.Frame(self)
frame_sub_one = tkinter.Frame(frame_main, bg="red")
frame_sub_two = tkinter.Frame(frame_main, bg="green")
frame_sub_thr = tkinter.Frame(frame_main, bg="blue")
label_app = self.create_tkinter_label(frame_main, "application")
label_one = self.create_tkinter_label(frame_sub_one, "menu one")
label_two = self.create_tkinter_label(frame_sub_two, "menu two")
label_thr = self.create_tkinter_label(frame_sub_thr, "menu thr")
button_one = self.create_tkinter_button(frame_sub_one, "app_one", 20)
button_one.configure(command=self.sub_app_one_open)
button_two = self.create_tkinter_button(frame_sub_one, "app_two", 20)
label_app.pack(side=tkinter.TOP, fill=tkinter.X, expand=tkinter.NO)
frame_sub_one.pack(side=tkinter.LEFT, fill=tkinter.BOTH, expand=tkinter.YES)
frame_sub_two.pack(side=tkinter.LEFT, fill=tkinter.BOTH, expand=tkinter.YES)
frame_sub_thr.pack(side=tkinter.LEFT, fill=tkinter.BOTH, expand=tkinter.YES)
label_one.pack(side=tkinter.TOP, fill=tkinter.NONE, expand=tkinter.NO, padx=10, pady=10)
button_one.pack(side=tkinter.TOP, fill=tkinter.NONE, expand=tkinter.NO, padx=10, pady=10)
button_two.pack(side=tkinter.TOP, fill=tkinter.NONE, expand=tkinter.NO, padx=10, pady=10)
label_two.pack(side=tkinter.TOP, fill=tkinter.NONE, expand=tkinter.NO, padx=10, pady=10)
label_thr.pack(side=tkinter.TOP, fill=tkinter.NONE, expand=tkinter.NO, padx=10, pady=10)
frame_main.pack(fill=tkinter.BOTH, expand=tkinter.TRUE)
def sub_app_one_open(self):
if not main_app.sub_app_one_instance == None:
main_app.sub_app_one_instance.sub_app_one_move_to_front()
return None
else:
main_app.sub_app_one_instance = sub_app_one()
class sub_app_one(main_app): # <<< inherit from main application in order to be able to access class variables
def __init__(self):
self.sub_app_one_toplevel_instance = tkinter.Toplevel(main_app.root)
self.sub_app_one_toplevel_instance.protocol("WM_DELETE_WINDOW", self.sub_app_one_close_toplevel)
print(main_app.var_one) # <<< access information from main app
def sub_app_one_move_to_front(self):
self.sub_app_one_toplevel_instance.attributes("-topmost", 0x01) # <<< set window state
self.sub_app_one_toplevel_instance.attributes("-topmost", 0x00) # <<< reset window state
def sub_app_one_close_toplevel(self):
self.sub_app_one_toplevel_instance.destroy()
self.sub_app_one_toplevel_instance = None
if __name__ == "__main__":
main_app_instance = main_app()
main_app_instance.mainloop()
# <<< inherit from main application in order to be able to access class variables
This is not how inheritance works. If sub_app_one needs access to variables in the main application, it should get them via the instance of main_app. You should definitely not be inheriting from main_app.
When you use inheritance, you are saying that the subclass is a main class. Meaning, you end up with two main classes. One is the original, and one is exactly like the original, with some modifications. That means you end up with two instances of tk.Tk which is not how tkinter is designed to be used. You can do it, but the behavior is not intuitive, and is rarely the right thing to do.
What you should do instead is pass in the instance of main_app into your sub_app_one when creating the instance. It would look something like this:
class main_app(tkinter.Tk):
...
def sub_app_one_open(self):
...
main_app.sub_app_one_instance = sub_app_one(main_app=self)
...
class sub_app_one():
def __init__(self, main_app):
self.main_app = main_app
...
As for the handling of deleting the window, the simplest solution is to make your child classes subclasses of tk.Toplevel. With that, you can use tkinter's built-in event handling to know when the widget has been destroyed.
Here's a working example:
import tkinter as tk
class Main(tk.Tk):
def __init__(self):
super().__init__()
self.sub_app_one = None
b1 = tk.Button(self, text="Open Sub App One", command=self.sub_app_one_open)
b1.pack(padx=20, pady=20)
def sub_app_one_open(self):
if self.sub_app_one is None:
self.sub_app_one = Sub_app_one(self)
else:
self.sub_app_one.deiconify()
self.sub_app_one.bind("<Destroy>", self._child_destroyed)
def _child_destroyed(self, event):
if event.widget == self.sub_app_one:
print("sub_app_one has been destroyed")
self.sub_app_one = None
class Sub_app_one(tk.Toplevel):
def __init__(self, main_app):
self.main_app = main_app
super().__init__(main_app)
label = tk.Label(self, text="This is sub app one")
label.pack(padx=50, pady=50)
main = Main()
main.mainloop()
Here is some advice that I can give after having worked on a application like this for the past few weeks. I will split my answer into two parts:
Some examples from my own code
Advice that will be helpful if this is a long-term project
Examples from my code
Managing your GUI
I would advise you to have one main class that manages your application. This class puts all the frames/pages of your applicaton on top of each other and raises the one you need to the top. I have a separate file for every frame, since the code for each of them can get very long. This is the 'Main.py' file:
import tkinter as tk
#importing frames that we will create soon!
from Frame1 import Frame1
frome Frame2 import Frame2
frome Frame3 import Frame3
class Main(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
#Sets the screen size of the GUI
self.width= self.winfo_screenwidth()
self.height= self.winfo_screenheight()
self.geometry(str(self.width) + 'x' + str(self.height) + '+0+0')
self.title('project_title')
#The container is a frame that contains the projects's frames
container = tk.Frame(self,
height=self.height,
width=self.width)
#Pack the container to the root
container.pack(side="top", fill="both", expand=True)
#Fixes pack location of the container using grid
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
#Create empty dictionary of frames
self.frames = {}
#Use container as master frame for imported frames
for Frame in (Frame1, Frame2, Frame3):
self.frames[Frame] = Frame(container)
self.frames[Frame].grid(row=0, column=0, sticky="nsew")
#Define buttons to switch GUI pages
self.frame1_btn = tk.Button(self,
text='Frame1',
width=15,
font=('Arial', 10),
command=self.show_frame1)
self.frame1_btn.place(relx=0.75,
rely=0.025,
anchor='center')
self.frame2_btn = tk.Button(self,
text='Frame2',
width=15,
font=('Arial', 10),
command=self.show_frame2)
self.frame2_btn.place(relx=0.85,
rely=0.025,
anchor='center')
self.frame3_btn = tk.Button(self,
text='Frame3',
width=15,
font=('Arial', 10),
command=self.show_frame3)
self.frame3_btn.place(relx=0.95,
rely=0.025,
anchor='center')
#The start page is raised to the top at the beginning.
self.show_frame1()
#Raises the selected frame to the top.
def show_frame1(self):
frame = self.frames[Frame1]
frame.tkraise()
def show_frame2(self):
frame = self.frames[Frame2]
frame.tkraise()
def show_frame3(self):
frame = self.frames[Frame3]
frame.tkraise()
def main():
app = Main()
app.mainloop()
if __name__ == '__main__':
main()
You have a 'main' file that manages the GUI. Now you have to create the frames that will later be the pages of your application. Best practice: keep them each in a separate file and import them in Main.py! This is an example Frame.py file:
import tkinter as tk
class Frame1(tk.Frame):
def __init__(self, ParentFrame):
tk.Frame.__init__(self, ParentFrame)
#The label acts as a title for each main frame.
self.label = tk.Label(self,
text='App_Name: Frame1',
font=('Arial', 20))
self.label.pack(padx=10, pady=10, anchor='w')
Repeat this for as many frames as you need. You should then have the following files in your directory:
Main.py, Frame1.py, Frame2.py, Frame3.py, etc.
Try running the Main.py file! The GUI still looks boring right now, but you will fill each frame with your own code.
Destroying frames
Let's say you have a start page that you only want to use once. It should be destroyed after its use. Let our 'Frame1.py' file be that start page. The code for it should look like this:
import tkinter as tk
class Frame1(tk.Frame):
def __init__(self, ParentFrame):
tk.Frame.__init__(self, ParentFrame)
#The label acts as a title for each main frame.
self.label = tk.Label(self,
text='App_Name: Frame1',
font=('Arial', 20))
self.label.pack(padx=10, pady=10, anchor='w')
self.selfdestruct_btn = tk.Button(self,
text='selfdestruct',
command=self.selfdestruct)
self.selfdestruct_btn.pack(pady=20)
def selfdestruct(self):
self.destroy()
If you click the 'selfdestruct' button on the start page, this frame will be properly destroyed. However, you can still see the 'Frame 1' button that you defined in 'Main.py', even if clicking it produces no result. Go back to the 'Main.py' file and delete the part where you defined this button. You don't need it anyway, since the start page will be raised to the top by the line 'self.show_frame1()' when you start your application.
I hope that helps you with your question. Here are some other tips I would like to give you:
Further advice
File scructure
A good file structure helps you to have an overview of your code and will make fixing errors much easier. You might want to have a directory that is similar to this:
Main.py file
Folder for Frame1
Folder for Frame2
Folder for Frame3
possibly a database
any other major components of your application
Why use folders for each frame of your application? When you write the code for each of these frames, you will probably start filling each frames with subframes. Each subframe has its own function and may need lots of code. I would therefore define a class for every major subframe you use that is described by lots of code. If you have multiple of those subframe classes, you might even want to have a 'subframes' folder within the frame folder. You can also put those files in the frame folder that are used only by this frame. Lets say you have to perform some complex calculation and define a class that does just that. You can keep this class in a separate file in the frame folder.
Fast GUI loadup
Try not making your computer load a bunch of unncessary stuff when it starts your program. The best case scenario would be that it only has to pack all of your pages/frames and their buttons. Don't make it load huge picture files or anything of that sort. Make it do these thing by the click of a button instead.
Pretty won't win any prizes
...at least that's probably not the case here. I wouldn't spend too much time formatting each button perfectly before moving on, since you are likely to move things around all the time anyway. Once you have all elements that you need packed to a frame, you can think about formatting.
That's all I'm going to share for now. Feel free to ask if you have any follow up questions!
Best,
Ansel

Why doesn't Tkinter Entry's get function return anything?

I'm trying to use two dialogs to get manual input, and then work with that data.
All source I've found claim I should use the get() function, but I wrote a simple mini program yet, and I can't make the second dialog work.
I hope someone can tell me what I'm doing wrong. Here's a file:
from tkinter import *
from tkinter.filedialog import askdirectory
from tkinter import messagebox
def getpath():
def selectPath():
path_ = askdirectory()
path.set(path_)
root = Tk()
root.title('select path')
path = StringVar()
def close():
if(path.get()==""):
messagebox.showinfo("","nothing")
else:
root.withdraw()
root.quit()
Label(root,text="path:").grid(row=0,column=0)
Entry(root,textvariable = path).grid(row=0,column=1)
Button(root,text="select",command=selectPath).grid(row=0,column=2)
Button(root,text="enter",command=close).grid(row=0,column=3)
root.mainloop()
return path.get()
def getname():
def get_need_name():
name = need_name.get()
print('hereherehere'+name) #does not work
root = Tk()
root.title('select name')
need_name = StringVar()
Label(root,text="name:").grid(row=0,column=0)
entry = Entry(root,bd=10,textvariable=need_name)
entry.grid(row=0,column=1)
Button(root,text="enter", font=16, bg="silver", relief='groove', command=get_need_name).grid(row=0,column=2)
root.mainloop()
return name.get()
def main():
path = getpath()
print("mypath:"+path)
print('******************')
print('done!')
name = getname()
print("myname:"+name)
if __name__ == '__main__':
main()
This give me two dialogs I can type in, but only the first dialog works.
The reason is that you are creating multiple instances of Tk, and you don't destroy the instances when you are done with them. This causes two problems. First is a memory leak. Each time you call one of these functions you create a new window and a new tcl interpreter.
The second problem is that the first root window becomes the default window when a root isn't specified. When you create a StringVar in the second function, because you didn't specify which root window it belongs to it will be assigned to the first root window. When you use it as the target of textvariable in a second instance of Tk, tkinter thinks the variable doesn't exist so it creates a new one for the second window. However, your reference is still to the one created in the first root window and is never updated by user input in the second window.
Confusing? Yes, which is why you typically shouldn't be creating more than one instance of Tk.
To make your code work with as few changes as possible and to remove the memory leak caused by not destroying the windows, you can change the last couple of lines in your method to look like the following. This destroys the root window when you are done with it, removing the memory leak and the side effect of having more than one root window.
root = Tk()
...
root.mainloop()
value = path.get()
root.destroy()
return value
The second dialog should look similar:
root = Tk()
...
root.mainloop()
value = name.get()
root.destroy()
return value
This retrieves the value after mainloop exits but before the underlying tcl interpreter is deleted, and then destroys the window and its tcl interpreter.
The next time you create an instance of Tk, that instance will become the new default root, and any new instance of StringVar will go to that root.
Another solution would be to specify the master for the StringVar instance, but that leaves the memory leak in place so it's only half of a solution.
Arguably a better solution is to create a single root window, and either reuse it or create instances of Toplevel rather than Tk. Effbot has some decent documentation on how to create a modal window with wait_window.
After some testing, and googling the root.quit() is the problem
here is a working example for you to look at and mess with.
from tkinter import *
from tkinter.filedialog import askdirectory
from tkinter import messagebox
root = Tk()
path = StringVar()
def select_path():
#uses the return value to set no need to create an additional variable
path.set(askdirectory())
def close():
if path.get() == "":
messagebox.showinfo("","Please select path")
else:
get_name_frame.tkraise()
def get_name():
print("hereherehere", name.get())
get_path_frame = Frame(root)
get_path_frame.grid(row=0, column=0, sticky="nsew")
Label(get_path_frame,text="path:").grid(row=0,column=0)
Entry(get_path_frame,textvariable = path).grid(row=0,column=1)
Button(get_path_frame,text="select",command=select_path).grid(row=0,column=2)
Button(get_path_frame,text="enter",command=close).grid(row=0,column=3)
get_name_frame = Frame(root)
get_name_frame.grid(row=0, column=0,sticky="nsew")
Label(get_name_frame, text="name: ").grid(row=0, column=0)
name = StringVar()
entry = Entry(get_name_frame, bd=10, textvariable = name)
entry.grid(row=0, column=1)
Button(get_name_frame,text="enter", font=16, bg="silver", relief='groove', command=get_name).grid(row=0,column=2)
get_path_frame.tkraise()
root.mainloop()

How do I get the Tkinter event-listener to work?

I'm using Tkinter for the GUI of a little tool I wrote with Python. Basically I just want a callback-method to be executed as soon as the contents of an entry widget have changed. This can be done with Tkinter's own variable classes (StringVar, BooleanVar, etc. - see documentation for details: http://effbot.org/tkinterbook/variable.htm).
So I couldn't get the mechanism to work and I found a snippet online, where it works perfectly fine. Now I'm trying to figure out why my version does not work.
As you can see in the two code examples the only difference is, that I'm using the event-listening functionality inside a class, whereas the snippet I found online only demonstrates it in a straight top-to-bottom manner.
Here's what I've already tried:
I instantiated the Tk instance directly in the constructor of my GUI class - same behaviour.
I inherited directly from the Tk class (instead of Frame) - same behaviour.
I placed the callback outside of the class - same behaviour.
The only idea I have is that the problem might be scope related, which I tried to verify.
Working code snippet:
from tkinter import *
import tkinter as tk
def text_changed(*args):
print("Text changed.")
top = tk.Tk()
string_listener = StringVar()
string_listener.set("Init Text")
string_listener.trace("w", text_changed)
entry_widget = tk.Entry(top, textvariable = string_listener)
entry_widget.pack()
top.mainloop()
Not working code snippet
from tkinter import *
import tkinter as tk
root = tk.Tk()
class GUI(tk.Frame):
def __init__(self, master=root):
super(GUI, self).__init__(master)
string_listener = StringVar()
string_listener.set("Init Text")
string_listener.trace("w", self.text_changed_callback)
entry_widget = tk.Entry(master, textvariable=string_listener)
entry_widget.pack()
def text_changed_callback(self, *args):
print("Text changed.")
gui = GUI()
gui.mainloop()
Like in the working example, my code ought to print Text changed., everytime a character is either deleted from or appended to the string in the extry-widget.
The problem is that string_listener is a local variable, and python is destroying the variable when __init__ finishes running. This doesn't happen in your original code since the variable is created in the global scope.
A simple solution is to save a reference as an attribute of the class:
import tkinter as tk
root = tk.Tk()
class GUI(tk.Frame):
def __init__(self, master=root):
super(GUI, self).__init__(master)
self.string_listener = tk.StringVar()
self.string_listener.set("Init Text")
self.string_listener.trace("w", self.text_changed_callback)
entry_widget = tk.Entry(master, textvariable=self.string_listener)
entry_widget.pack()
def text_changed_callback(self, *args):
print("Text changed.")
gui = GUI()
gui.mainloop()
note: I also changed StringVar to tk.StringVar so that I could remove the redundant wildcard import of tkinter.

I can't get an image to load on the canvas in Tkinter python

I am having a big issue. The Canvas loads perfectly but the image does not display.
I started Python 1 week ago and I have no clue why does is not working. Can anyone please show me the way to solve the issue of the image not loading on the canvas?
from Tkinter import *
from PIL import ImageTk
from PIL import Image
class Fake_Virus:
def __init__(self, master):
self.master = master
master.title("Totally not a virus!")
b = Button(master, text="Help", command=self.prank)
b.pack(padx=10, pady=10, side=LEFT)
quit = Button(master, text="Close", command=self.close_window)
quit.pack(padx=10, pady=10, side=RIGHT)
photo = PhotoImage("eh.gif")
label = Label(image=photo)
label.image = photo # keep a reference!
label.pack()
f = Frame(master, height=150, width=150)
f.pack_propagate(0) # don't shrink
f.pack()
def prank(self):
print "work"
return
def close_window(self):
root.destroy()
return
root = Tk()
my_gui = Fake_Virus(root)
root.mainloop()
You should use the file option to initialize the photo image object.
This means you need to change photo = PhotoImage("eh.gif") to photo = PhotoImage(file="eh.gif")
Now your code will work. But a working code is not necessarily a good code. There are other issues with your code. Let me go through them quickly:
It is better to code import Tkinter as Tk than from Tkinter import *
Why that hyphen in your class name? Follow PEP8 so that, in the futur, people will find it easy to review and understand your code.
Good that you have written self.master = master (read complete code to know why) but then you have never used it. This means you made a good decision and you render it useless.
You set the title of the window within the initializer. It is better if you do that in a separate function so that whenever you want to add additional settings to your GUI (such as the size, font or whatever) you will only add code to that function instead of vomiting lot of trash inside the initializer which rather needs to be clean.
None of the widgets you created is 'selfed' (you may read Why explicit self has to stay)
It is better you create the widgets in a separate function otherwise your __init__() will be dirty.
Why do you use return in prank() and close_window()? By default, Python functions that do not return something return None anyway so it is useless to code that.
Why did you pack one button to left and the other one to right and then no pack siding for the label? Read about the pack() geometry manager.
Why you did not attach the label to a parent widget as you did for the 2 other buttons? All Tkinter widgets need to be clung into a parent widget. The grand parent of those widgets is an instance of Tkinter.Tk()
Why did you create that frame and then you never used it? You are not doing anything with it, so ..?
Given these remarks, I want to provide you an improved -but not perfect- version of your program. You can then follow this 'philosophy' to add or modifying existing widgets:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import Tkinter as Tk
from PIL import ImageTk
class FakeVirus:
def __init__(self, master):
self.master = master
self.configure_gui()
self.create_widgets()
def configure_gui(self):
self.master.title('Totally not a virus!')
def create_widgets(self):
self.create_buttons()
self.create_label_for_image()
def create_buttons(self):
self.help = Tk.Button(self.master, text='Help', command=self.prank)
self.help.pack(side=Tk.LEFT)
self.quit = Tk.Button(self.master, text='Close', command=self.close_window)
self.quit.pack(side=Tk.LEFT)
def create_label_for_image(self):
self.image_label = Tk.Label(self.master)
self.image_label.pack(side=Tk.LEFT)
self.load_image_to_label()
def load_image_to_label(self):
self.photo = ImageTk.PhotoImage(file='eh.gif')
self.image_label.image = self.photo
self.image_label.config(image=self.photo)
def prank(self):
print "work"
def close_window(self):
root.destroy()
if __name__ == '__main__':
root = Tk.Tk()
my_gui = FakeVirus(root)
root.mainloop()
The output of the above program is:

Scale function not outputting number to variable?

So for some reason the scale function in tkinter doesn't want to output the number on the scale. All i receive is either 0.0 or nothing. It seems to be to do with the GUI and calling functions through the GUI. Written and run in python 3.4.
from tkinter import *
from tkinter import ttk
from tkinter import messagebox
iterations=30
def settings():
global itervar, iterscale
sGui = Tk()
itervar = DoubleVar()
iterscale = Scale(sGui, orient="horizontal", from_=1, to=1000, variable=itervar)
iterscale.pack()
iterbutton = Button(sGui, text="Save Settings", command=saveSettings)
iterbutton.pack()
sGui.mainloop()
def saveSettings():
global iterations
iterations = itervar.get()
print(iterations)
def doNothing():
pass
def main():
global root, version
root= Tk()
menu = Menu(root)
root.config(menu=menu)
fileMenu = Menu(menu)
menu.add_cascade(label="File", menu=fileMenu)
fileMenu.add_command(label="Quit", command=quit)
benchmarkMenu = Menu(menu)
menu.add_cascade(label="Benchmark", menu=benchmarkMenu)
benchmarkMenu.add_command(label="Run [All]", command=doNothing)
benchmarkMenu.add_separator()
benchmarkMenu.add_command(label="Settings", command=settings)
root.mainloop()
#Main
main()
I have tried the functions settings and saveSettings on their own and they work fine, but when i call it through the GUI it doesn't seem to work.
Any ideas on the problem, my only solution would be have the settings function and saveSettings function in a different file and then run that file externally with os.startfile("etc...")
Minimal fix: change this
itervar = DoubleVar()
to this:
itervar = DoubleVar(sGui)
Because you have two root applications (root and sGui are both instances of Tk) the implied parent widget for itervar is the first one created, being root so tkinter gets confused when you specify it as the variable for a completely different application.
But I would highly recommend using a Toplevel instance to keep the windows a part of the same program:
sGui = Toplevel(root)
...
#sGui.mainloop() #no longer need this
although if you want to be able to run the setting window without the main one you might consider making all your visible windows Toplevels and make the actual root hidden:
# I'm not sure if you want to call it this
abs_root = Tk() # but you are already using root
abs_root.withdraw() #hide the window
Then make root = Toplevel(abs_root)
You coud phase out the variable all together by using .geting the scale directly:
iterations = iterscale.get()

Categories