Tkinter can't even invoke a simple "button" - python

Started with simple Tkinter lessons, I'm stuck in the case even that simple code doesn't work:
import tkinter as tk
root = tk.Tk()
b = tk.Button(root, text='button'); b.pack()
...
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/Users/../anaconda3/lib/python3.6/tkinter/__init__.py", line 2366, in __init__
Widget.__init__(self, master, 'button', cnf, kw)
File "/Users/../anaconda3/lib/python3.6/tkinter/__init__.py", line 2296, in __init__
(widgetName, self._w) + extra + self._options(cnf))
_tkinter.TclError: can't invoke "button" command: application has been destroyed
And can't find the reason why, considering that this code is from official documentation.
On the other hand, another code works:
import tkinter as tk
class Application(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.master = master
self.pack()
self.create_widgets()
def create_widgets(self):
self.hi_there = tk.Button(self)
self.hi_there["text"] = "Hello World\n(click me)"
self.hi_there["command"] = self.say_hi
self.hi_there.pack(side="top")
self.quit = tk.Button(self, text="QUIT", fg="red",
command=self.master.destroy)
self.quit.pack(side="bottom")
def say_hi(self):
print("hi there, everyone!")
root = tk.Tk()
app = Application(master=root)
app.mainloop()
I have tried to update tk from conda: conda install -c anaconda tk, but nothing change. Can't figure out why.

The only way I was able to reproduce your error is by building the code directly in the IDLE Shell and closing the root window that pops up before creating the button.
That said it is very odd to write a GUI in the Shell like this. If you do not close the tkinter window the code works fine. However GUI development should be done in the editor in a .py file and ran all at once. Simple fix is to not close the root window before everything else in the GUI has been added.
Proper fix is to build you GUI in a .py file and then run it.
I am not sure why you are saying that the editor is not working for you. When I copy your exact code it works fine on my end:
All that said you really do not need to build your code in the Python IDLE. It would be much better to use something like PyCharm or Eclipse/PyDev. Those are my Go to IDE tools.
One thing to note about Python's IDLE is it will not run code from the editor until you have saved the .py file.
Though not 100% required in the Python IDLE the mainloop() is a requirement for tkinter to work properly. Outside of Python's IDLE most other IDE environments requite the mainloop() so it is good practice to always include it.
import tkinter as tk
root = tk.Tk()
b = tk.Button(root, text='button')
b.pack()
root.mainloop()

I think you forgot to add root.mainloop() at the end.
import tkinter as tk
root = tk.Tk()
b = tk.Button(root, text='button'); b.pack()
root.mainloop()

Related

Tkinter - Linking buttons to different scripts

I am exploring GUI's at the moment. What I want is to have a GUI with a 2 buttons and I want each of the buttons to run a separate python script when clicked.
I have outlined my code below (the first button runs just fine but I am having issues with the second button.
Error Message when I choose the second button:
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\ProgramData\Anaconda3\lib\tkinter\__init__.py", line 1883, in __call__
return self.func(*args)
Code:
from tkinter import *
import tkinter as tk
master = Tk()
def First_Scriptcallback():
exec(open(r'Desktop\Automation\First_Script.py').read())
def second_Scriptcallback():
exec(open(r'Desktop\Automation\Second_Script.py').read())
#master.title("Test GUI")
#canvas = tk.Canvas(master, height=300, width = 400)
#canvas.pack()
firstButton = Button(master, text="Run first script", command=First_Scriptcallback)
firstButton.pack()
secondButton = Button(master, text="Run second script", command=second_Scriptcallback)
secondButton.pack()
mainloop()
Thanks
As #matiiss suggested importing the other scripts into your program can help and it can be done like this,
import First_Script as first
import Second_Script as second
from tkinter import *
import tkinter as tk
master = Tk()
def First_Scriptcallback():
first.function_name()#here you must create functions in first_script to call in this main script
def second_Scriptcallback():
second.function_name()
#master.title("Test GUI")
#canvas = tk.Canvas(master, height=300, width = 400)
#canvas.pack()
firstButton = Button(master, text="Run first script", command=First_Scriptcallback)
#command=first.function_name
#we can also directly call an function using above command,but sometimes there are problems related to this approch
firstButton.pack()
secondButton = Button(master, text="Run second script", command=second_Scriptcallback)
#command=second.function_name
secondButton.pack()
mainloop()
here for this example the scripts and the program must be in same directory.

How to fully close tkinter programm?

How would I go about writing a command that fully closes the currently open tkinter programm. There are already a ton of threads regarding this, but I have been unable to find a solution that reliably closes all open tkinter windows. In my scenario, I am adding menus to each window with the option to close the current window, as well as close the whole programm. While closing the current window always works, closing the whole programm only works sporadically.
Things I have tried:
using root.quit() frequently leads to the "Python not responding" windows error message.
using root.destroy() will close the root window, and all toplevel windows, if I have not clicked on anything in them. Once I do use a toplevel window, it will not be ended by the root.destroy() command anymore.
using mytoplevel.destroy() will reliably close the currently active toplevel window.
I have written a simplified version of the code I am using, however, the windows don't do anything here, so the destroy command will always work.
import tkinter as tk
class MainWindow(tk.Frame):
def __init__(self, master = None):
b = tk.Button(master, text="Type_1", command=self.window_type_1)
b.pack()
self.load_menu(root)
tk.Frame.__init__(self, master)
def close(self, parent):
parent.destroy()
def window_type_1(self):
top = tk.Toplevel()
top.minsize(width = 600, height = 500)
self.load_menu(top)
def load_menu(self, parent):
menubar = tk.Menu(parent, tearoff=False)
parent.config(menu=menubar)
start_menu = tk.Menu(menubar, tearoff=False)
if parent == root:
start_menu.add_command(label="close", command=lambda: self.close(parent))
menubar.add_cascade(label="File", menu=start_menu)
else:
start_menu.add_command(label="Window close", command=lambda: self.close(parent))
start_menu.add_command(label="Programm close", command=lambda: self.close(root))
menubar.add_cascade(label="File", menu=start_menu)
root = tk.Tk()
mainwindow = MainWindow(master = root)
mainwindow.mainloop()
Calling destroy on the root window is generally the right solution, and is specifically the right solution in your example program. Destroying the root window will cause mainloop to exit, so unless you have more code after the call to mainloop and/or more code running in a separate thread, the program must exit.

How do I bundle my tkinter based application with my custom icon?

I have a tkinter based application structured as follows:
import tkinter as tk
class App(tk.Frame):
def __init__(self, master):
self.master = master
tk.Frame.__init__(self, self.master)
self.configure_gui()
self.create_widgets()
def configure_gui(self):
self.master.iconbitmap("my_logo.ico")
self.master.title("Example")
self.master.minsize(250, 50)
def create_widgets(self):
self.label = tk.Label(self.master, text="hello world")
self.label.pack()
if __name__ == "__main__":
root = tk.Tk()
app = App(root)
root.mainloop()
When I run the .py file from the command line, my logo replaces the default tkinter feather logo in the applications main window as expected. I am even able to freeze and bundle my application with pyinstaller using the following command:
pyinstaller -i my_logo.ico my_application.py
Unfortunately, when I attempt to run the .exe file generated by this process I am met with the following error:
Traceback (most recent call last):
File "my_application.py", line 22, in <module>
File "my_application.py", line 7, in __init__
File "my_application.py", line 11, in configure_gui
File "tkinter\__init__.py", line 1865, in wm_iconbitmap
_tkinter.TclError: bitmap "my_logo.ico" not defined
[5200] Failed to execute script my_application
I have scoured this site and others in search of a solution that works in my case and have found none. Any direction would be greatly appreciated!
I've found it easier for me just to store the image as a .py module, then PyInstaller handles it like any other module and the basic command line command to make the exe work without anything special:
Script to make image.py file:
import base64
with open("my_logo.ico", "rb") as image:
b = base64.b64encode(image.read())
with open("image.py", "w") as write_file:
write_file.write("def icon(): return (" + str(b) + ")"
Then import the module as the image:
import tkinter as tk
import image
class App(tk.Frame):
def __init__(self, master):
self.master = master
tk.Frame.__init__(self, self.master)
self.configure_gui()
self.create_widgets()
def configure_gui(self):
self.master.tk.call('wm', 'iconphoto', self.master._w, tk.PhotoImage(data=image.icon()))
self.master.title("Example")
self.master.minsize(250, 50)
def create_widgets(self):
self.label = tk.Label(self.master, text="hello world")
self.label.pack()
if __name__ == "__main__":
root = tk.Tk()
app = App(root)
root.mainloop()
Otherwise you basically have to call out adding the .ico file in the build command, then in your script need to add lines to determine where the unpacked pyinstaller-packed script's directory is, then adjust your path to the packed .ico file.
Discussed here: Bundling data files with PyInstaller (--onefile)
But I find my way easier.

Using Python, how do you call a tkinter GUI from another GUI?

I created a couple of GUIs using tkinter. But now I am interested in combining them into one caller GUI. So the caller GUI would have buttons that, when clicked, would open the other GUIs. However, I cannot get it to work. I've done the imports correctly (I think), edited the main functions in the subGUIs, and added the command=GUI.main in my buttons. I get it to load but I get errors about missing files...but when I run a GUI by itself it works fine.
In my research, I read that there can only be one mainloop in a Tkinter program. Basically, I cannot use a Tkinter GUI to call another Tkinter GUI. Do you know what I can do different, for instance, can I create the caller GUI using wxPython and have it call all other GUIs that use Tkinter?
Thank you!
You can't "call" another GUI. If this other GUI creates its own root window and calls mainloop(), your only reasonable option is to spawn a new process. That's a simple solution that requires little work. The two GUIs will be completely independent of each other.
If you have control over the code in both GUIs and you want them to work together, you can make the base class of your GUI a frame rather than a root window, and then you can create as many windows as you want with as many GUIs as you want.
For example, let's start with a simple GUI. Copy the following and put it in a file named GUI1.py:
import tkinter as tk
class GUI(tk.Frame):
def __init__(self, window):
tk.Frame.__init__(self)
label = tk.Label(self, text="Hello from %s" % __file__)
label.pack(padx=20, pady=20)
if __name__ == "__main__":
root = tk.Tk()
gui = GUI(root)
gui.pack(fill="both", expand=True)
tk.mainloop()
You can run that GUI normally with something like python GUI1.py.
Now, make an exact copy of that file and name it GUI2.py. You can also run it in the same manner: python GUI2.py
If you want to make a single program that has both, you can create a third file that looks like this:
import tkinter as tk
import GUI1
import GUI2
# the first gui owns the root window
win1 = tk.Tk()
gui1 = GUI1.GUI(win1)
gui1.pack(fill="both", expand=True)
# the second GUI is in a Toplevel
win2 = tk.Toplevel(win1)
gui2 = GUI2.GUI(win2)
gui2.pack(fill="both", expand=True)
tk.mainloop()
Depending on your OS and window manager, one window might be right on top of the other, so you might need to move it to see both.
Thank you for the ideas. At first, your code wouldn't print the text on the toplevel window. So I edited it a little and it worked! Thank you. GUI1 and GUI2 look like:
import tkinter as tk
def GUI1(Frame):
label = tk.Label(Frame, text="Hello from %s" % __file__)
label.pack(padx=20, pady=20)
return
if __name__ == "__main__":
root = tk.Tk()
GUI1(root)
root.mainloop()
And then the caller looks like this:
from tkinter import *
import GUI1
import GUI2
def call_GUI1():
win1 = Toplevel(root)
GUI1.GUI1(win1)
return
def call_GUI2():
win2 = Toplevel(root)
GUI2.GUI2(win2)
return
# the first gui owns the root window
if __name__ == "__main__":
root = Tk()
root.title('Caller GUI')
root.minsize(720, 600)
button_1 = Button(root, text='Call GUI1', width='20', height='20', command=call_GUI1)
button_1.pack()
button_2 = Button(root, text='Call GUI2', width='20', height='20', command=call_GUI2)
button_2.pack()
root.mainloop()

Python Background Processes On Windows. Caps-lock app

I've been trying to write a small application (in python2.7) which wouldn't use much CPU memory or slow down the computer whilst running in the background (as a sub-process?). All it's suppose to do is display a message in the top right corner which states if it's "ON". And if it's not on, just close/don't display the message. Sounds pretty simple, but I can't seem to get it.
The end result I would like it to run when the computer starts up. Maybe use a .batch file which automatically runs the python script, however I think I will need to go into some processing coding for it to work efficiently.
This is the code I have so far, I recently added the while loop which makes it able to see the pop up window, however it doesn't close it when caps-lock is off. And I'm almost certain it could be done more 'neatly'?
Thanks for any help in advance!
The main code (capsLock.py);
import win32api, win32con
from Tkinter import *
class CapsLock(object):
def __init__(self, window):
window.resizable(FALSE,FALSE)
window.geometry('100x20-5+5')
# return 1 if CAPSLOCK is ON
if win32api.GetKeyState(win32con.VK_CAPITAL) == 1:
self.canvas = Canvas(window, bg ="white", height=20, width=20)
self.canvas.pack(expand=YES, fill=BOTH)
self.lock = Label(self.canvas, text="Caps Lock Is ON")
self.lock.pack()
window.attributes("-topmost", True)
else:
window.destroy()
def main():
while(1):
root = Tk()
app = CapsLock(root)
root.mainloop()
if __name__ == '__main__':
main()
Below is the code which I initially tried to run it as a sub-process (CapsStart.py);
import sys
import subprocess
proc = subprocess.Popen([sys.executable, "capsLock.py"])
print proc.returncode
Sorry for using the '_' to display the code snippets, not so familiar with stackoverflow.
Louis.
UPDATE
So I've added a delay as well as update to the code and moved the while loop. This seemed to work, but not for long. After running it with caps-lock being on and then switching it off works but when I switch capslock back on it throws a TclError. I tried catching it and just making it continue. But yeah It doesn't seem to work. Any further help would be appreciated. (I'd like to write my own caps-lock indicator instead of downloading someone elses). The error and new code is below;
Traceback (most recent call last):
File "C:\Users\Louis\Documents\PyGames\Learning\capsLock.py", line 42, in
main()
File "C:\Users\Louis\Documents\PyGames\Learning\capsLock.py", line 38, in main
app = CapsLock(root)
File "C:\Users\Louis\Documents\PyGames\Learning\capsLock.py", line 17, in init
self.canvas = Canvas(window, bg ="white", height=20, width=20)
File "C:\python27\lib\lib-tk\Tkinter.py", line 2195, in init
Widget.init(self, master, 'canvas', cnf, kw)
File "C:\python27\lib\lib-tk\Tkinter.py", line 2055, in init
(widgetName, self._w) + extra + self._options(cnf))
_tkinter.TclError: can't invoke "canvas" command: application has been destroyed
[Finished in 4.3s]
import win32api, win32con
from Tkinter import *
import time
class CapsLock(object):
def __init__(self, window):
window.resizable(FALSE,FALSE)
window.geometry('100x20-5+5')
while(1):
time.sleep(0.3)
# return 1 if CAPSLOCK is ON
if win32api.GetKeyState(win32con.VK_CAPITAL) == 1:
self.canvas = Canvas(window, bg ="white", height=20, width=20)
self.canvas.pack(expand=YES, fill=BOTH)
self.lock = Label(self.canvas, text="Caps-Lock Is ON")
self.lock.pack()
window.update()
window.attributes("-topmost", True)
else:
try :
window.destroy()
window.update()
except TclError:
continue
def main():
root = Tk()
app = CapsLock(root)
root.mainloop()
if __name__ == '__main__':
main()

Categories

Resources