Im attempting to insert an image on a canvas in my tkinter GUI. I'm able to insert an image usually but when doing it on a canvas I get the errors:
"pyimage does not exist" or "file directory not found"
In the class "PageMain" I have created a widget and followed the answer on this thread - How to insert an image in a canvas item?
However it still says the file cannot be found despite using the answer.
from Tkinter import *
import ttk
import sqlite3
import Tkinter as tk
import Tkinter
import sys
import StringIO
from PIL import Image, ImageTk
import os
class MyApp(Tk):
def __init__(self):
Tk.__init__(self)
container = ttk.Frame(self)
container.pack(side="top", fill="both", expand=True)
self.frames = {}
for F in (PageMain, PageOne, PageTwo):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky='NSEW')
self.show_frame(PageMain)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
class PageMain(ttk.Frame):
def __init__(self, parent, controller):
self.controller = controller
ttk.Frame.__init__(self, parent)
self.make_widget()
def make_widget(self):
canvas = Canvas(self, width="1000", height="600")
img = PhotoImage(file='D:\plogo\logo.png')
canvas.create_image(50, 10, image=img, anchor=NW)
# demo button to change page
btnChange = Button(canvas, text="Change", font="Arial 16",
command=lambda: self.controller.show_frame(PageOne),
bg="#a0ccda")
btnChange .place(x=600, y=550, width="100", height="50")
canvas.pack()
def change_page(self):
pass
if __name__ == '__main__':
app = MyApp()
app.title('Music Match')
app.mainloop()
you must keep a reference to the image, thanks Bryan, see below, especially to
w = tk.Canvas(self,)
w.img = tk.PhotoImage(file='logo.png')
#!/usr/bin/python3
import tkinter as tk
from tkinter import ttk
from tkinter import messagebox
class Main(ttk.Frame):
def __init__(self, parent):
super().__init__()
self.parent = parent
self.init_ui()
def init_ui(self):
w = tk.Canvas(self, )
w.img = tk.PhotoImage(file='logo.png')
w.create_image(50,50, anchor=tk.N+tk.W, image=w.img)
w.pack(expand=1)
def on_close(self):
self.parent.on_exit()
class App(tk.Tk):
"""Start here"""
def __init__(self):
super().__init__()
self.protocol("WM_DELETE_WINDOW", self.on_exit)
self.set_style()
self.set_title()
frame = Main(self,)
frame.pack(fill=tk.BOTH, expand=1)
def set_style(self):
self.style = ttk.Style()
#('winnative', 'clam', 'alt', 'default', 'classic', 'vista', 'xpnative')
self.style.theme_use("clam")
def set_title(self):
s = "{0}".format('Simple App')
self.title(s)
def on_exit(self):
"""Close all"""
if messagebox.askokcancel(self.title(), "Do you want to quit?", parent=self):
self.destroy()
if __name__ == '__main__':
app = App()
app.mainloop()
Related
I'm making a hold'em game, and I'm trying to create an in-game start login screen. I'm trying to center the name in a 1280*720 frame, but it's always at the top. Is there any way to solve it?
try:
import Tkinter as tk
except:
import tkinter as tk
class HoldemApp(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.geometry("1280x720")
self._frame = None
self.switch_frame(StartPage)
def switch_frame(self, frame_class):
new_frame = frame_class(self)
if self._frame is not None:
self._frame.destroy()
self.geometry("1280x720")
self._frame = new_frame
self._frame.pack()
class StartPage(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
label=tk.Label(self, text= "텍사스 홀덤에 오신것을 환영합니다.")
start=tk.Button(self, command=lambda: master.switch_frame(LoginPage), text="시작")
label.place(x=50, y=50)
start.place(relx=0.5, rely=0.55, anchor="center")
label.pack()
start.pack()
class LoginPage(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
player=tk.Entry(self, text="플레이어 수를 입력하시오")
cash=tk.Entry(self, text="각 플레이어가 사용할 캐시를 입력하시오.")
player.place(relx=0.5, rely=0.45, anchor="center")
cash.place(relx=0.5, rely=0.55, anchor="center")
player.pack()
cash.pack()
if __name__ == "__main__":
app = HoldemApp()
app.title("TEXAS HOLD 'EM")
app.geometry("1280x720+100+100")
app.resizable(False, False)
app.mainloop()
I've tried placing geometry("1280x720+100+100") in various places, but there is no response and only an error occurs.
I am working on a game called 'Flag Quiz' using tkinter. I have a script called mainmenu where I can choose between an easy mode and a hard mode. If I click on one of the buttons the recent mainmenu window disappears and a new tkinter window opens.
Here is my mainmenu script:
from tkinter import *
import tkinter as tk
from hardmode import HardApp
from easymode import EasyApp
class TitleScreen(tk.Tk):
def __init__(self):
super().__init__()
self.title('Flag Quiz')
self.geometry('600x600')
self.resizable(0,0)
self.make_widgets()
def make_widgets(self):
self.background = PhotoImage(file = './background.png')
self.label = Label(self, image=self.background)
self.label.place(x=0, y=0, relwidth=1, relheight=1)
self.easy = Button(self, text="Easy Mode", height=2, width=6, font=('default', 20), command=self.play_easy)
self.hard = Button(self, text="Hard Mode", height=2, width=6, font=('default', 20), command=self.play_hard)
self.easy.place(relx=0.5, rely=0.45, anchor=CENTER)
self.hard.place(relx=0.5, rely=0.55, anchor=CENTER)
def play_easy(self):
self.withdraw()
self.app = EasyApp()
#self.app.start()
def play_hard(self):
self.withdraw()
self.app = HardApp()
#self.app.start()
def start(self):
self.mainloop()
TitleScreen().start()
And here is my easy mode script:
import tkinter as tk
from tkinter import *
import random
import os
import json
class EasyApp(tk.Toplevel):
def __init__(self):
super().__init__()
self.title('Flag Quiz')
self.geometry('')
self.resizable(0,0)
self.score = 0
self.create_widgets()
def create_widgets(self):
# variables
self.user_guess = StringVar(self)
self.text = StringVar(self)
self.text.set(" ")
# initial image
self.scoretext = Label(self, text="Score: ").pack(side='top', fill='x')
self.scorevalue = Label(self, text=self.score).pack(side='top', fill='x')
self.file = random.choice(os.listdir('pngs'))
self.randimg = PhotoImage(file='pngs/{}'.format(self.file))
self.randimg = self.randimg.subsample(2, 2)
self.panel = Label(self, image=self.randimg)
self.panel.pack()
self.country, self.ext = self.file.split('.')
self.countries = self.load_lookup()
self.countryname = [country for country in self.countries if country['alpha2'] == self.country]
self.s = []
for i in range(0,3):
country = random.choice(self.countries)
self.s.append(country['de'])
self.s.append(self.countryname[0]['de'])
random.shuffle(self.s)
self.btndict = {}
for i in range(4):
self.btndict[self.s[i]] = Button(self, text=self.s[i], height=2, width=35, font=('default', 20), command=lambda j=self.s[i]: self.check_input(j))
self.btndict[self.s[i]].pack()
def check_input(self, d):
if d != self.countryname[0]['de']:
print("Falsch")
else:
self.score += 5
for widget in self.winfo_children():
widget.destroy()
self.create_widgets()
def load_lookup(self):
with open('lookup.json') as file:
self.obj = file.read()
self.countryobj = json.loads(self.obj)
return self.countryobj
# def start(self):
# self.mainloop()
After clicking the close button (the default button on windows/osx to close a window) the window from my easy mode app disappears but PyCharm says that my program is still running.
I made some investigations and removed the self.withdraw() function in the function play_easy(self) in my mainmenu script. So now the mainmenu is still open after I click on the easy mode button. If I'm closing both windows now, the program fully ends.
Replacing self.withdraw() with self.destroy() is not working. The main menu is closed, but a new empty window opens instead.
Any suggestions on how to handle this problem so that my program fully ends if I click the close button within the easy/hard mode window?
You have two windows - main window created with Tk and subwindow created with Toplevel. When you use close button to close main window then it should also close all subwindows but when you close subwindow then it doesn't close main window (parent window) but only own subwindows - because usually it can be useful to display again main window to select other options and open again subwindow.
One of the methods is to destroy first window and use Tk to create new window.
But in this method you can't use some button in second window to go back to first window - and sometimes it can be problem. Even if you create again first window then it will not remeber previous values (if you have some Entry or other widgets to set values)
# from tkinter import * # PEP8: `import *` is not preferred`
import tkinter as tk
class EasyApp(tk.Tk): # use `Tk` instead of `Toplevel`
def __init__(self):
super().__init__()
self.scoretext = tk.Label(self, text="EasyApp")
self.scoretext.pack()
#def start(self):
# self.mainloop()
class TitleScreen(tk.Tk):
def __init__(self):
super().__init__()
self.button = tk.Button(self, text="Easy Mode", command=self.play_easy)
self.button.pack()
def play_easy(self):
self.destroy() # destroy current window
self.app = EasyApp()
def start(self):
self.mainloop()
TitleScreen().start()
Other method is to use self.wm_protocol("WM_DELETE_WINDOW", self.on_close) to execute function on_close when you use close button and in this function destroy main window (master).
This way you can still use Button to go back to main window which will remember previous content.
# from tkinter import * # PEP8: `import *` is not preferred`
import tkinter as tk
class EasyApp(tk.Toplevel): # still use `Toplevel`
def __init__(self, master): # send main window as master/parent
super().__init__(master) # it will also set `self.master = master`
self.scoretext = tk.Label(self, text="EasyApp")
self.scoretext.pack()
self.button = tk.Button(self, text="Go Back", command=self.go_back)
self.button.pack()
# run `on_close` when used `close button`
#self.protocol("WM_DELETE_WINDOW", self.on_close)
self.wm_protocol("WM_DELETE_WINDOW", self.on_close)
def go_back(self):
self.destroy() # destroy only current window
self.master.deiconify() # show again main window
def on_close(self):
self.destroy() # destroy current window
self.master.destroy() # destroy main window
class TitleScreen(tk.Tk):
def __init__(self):
super().__init__()
self.entry = tk.Entry(self)
self.entry.pack()
self.entry.insert('end', "You can change text")
self.button = tk.Button(self, text="Easy Mode", command=self.play_easy)
self.button.pack()
def play_easy(self):
self.withdraw()
self.app = EasyApp(self) # send main window as argument
def start(self):
self.mainloop()
TitleScreen().start()
Here's a fairly simple architecture for doing what you want. An application class is derived from Tk() which hides the default "root" window it normally displays and all the windows it does display are subclasses of a custom Toplevel subclass I've named BaseWin.
This class is just a Toplevel with its protocol for being delete (closed) set to call an a method named on_close(). This additional method simply destroys the current window before quits the application's mainloop() causing it to terminate.
The first window—an instance of the TitleScreen class—is displayed automatically when an instance of the application class is created. This window has two Buttons one labelled Easy Mode and the other Hard Mode. When one of them is clicked, an instance of the appropriate Toplevel subclass is created after the current window is removed by it call its destroy() method.
mainmenu.py
import tkinter as tk
from tkinter.constants import *
from tkinter import font as tkfont
from basewin import BaseWin
from easymode import EasyApp
from hardmode import HardApp
class SampleApp(tk.Tk):
def __init__(self):
super().__init__()
self.title('Flag Quiz')
self.geometry('600x600')
self.resizable(FALSE, FALSE)
self.title_font = tkfont.Font(family='Helvetica', size=18, weight="bold")
self.withdraw() # Hide default root Tk window.
startpage = TitleScreen(self.master)
self.mainloop()
class TitleScreen(BaseWin):
def __init__(self, master):
super().__init__(master)
self.make_widgets()
def make_widgets(self):
label = tk.Label(self, text="This is the Start Page", font=self.master.title_font)
label.pack(side="top", fill="x", pady=10)
self.easy = tk.Button(self, text="Easy Mode", font=('default', 20),
command=self.play_easy)
self.hard = tk.Button(self, text="Easy Mode", font=('default', 20),
command=self.play_hard)
self.easy.pack()
self.hard.pack()
def play_easy(self):
self.destroy()
self.app = EasyApp(self.master)
def play_hard(self):
self.destroy()
self.app = HardApp(self.master)
if __name__ == '__main__':
SampleApp()
basewin.py
import tkinter as tk
from tkinter.constants import *
class BaseWin(tk.Toplevel):
def __init__(self, master):
super().__init__(master)
self.protocol("WM_DELETE_WINDOW", self.on_close)
def on_close(self):
self.destroy() # Destroy current window
self.master.quit() # Quit app.
easymode.py
import tkinter as tk
from tkinter.constants import *
from basewin import BaseWin
class EasyApp(BaseWin):
def __init__(self, master):
super().__init__(master)
self.title('Flag Quiz')
self.resizable(FALSE, FALSE)
self.make_widgets()
def make_widgets(self):
label = tk.Label(self, text="This is the Easy App", font=self.master.title_font)
label.pack(side="top", fill="x", pady=10)
hardmode.py
import tkinter as tk
from tkinter.constants import *
from basewin import BaseWin
class HardApp(BaseWin):
def __init__(self, master):
super().__init__(master)
self.title('Flag Quiz')
self.resizable(FALSE, FALSE)
self.make_widgets()
def make_widgets(self):
label = tk.Label(self, text="This is the Hard App", font=self.master.title_font)
label.pack(side="top", fill="x", pady=10)
If I create a Toplevel popup window in a tkinter application, when I close the main root window all sub windows are closed.
With the following code, if I open up two successive Toplevel windows, I can close "Window One" but "Window Two" stays open.
#!/usr/bin/env python3
import tkinter as tk
import tkinter.ttk as ttk
def main():
root = tk.Tk()
root.geometry("600x300")
root.title("Main Window")
app = Application(root)
app.mainloop()
def window_one():
window_one = WindowOne()
window_one.geometry("450x200")
window_one.title('Window One')
def window_two():
window_two = WindowTwo()
window_two.geometry("200x100")
window_two.title('Window Two')
class Application(ttk.Frame):
def __init__(self, master= None, *args, **kwargs):
super().__init__(master = master)
self.pack()
self.create_widgets()
def create_widgets(self):
button1 = ttk.Button(self, text = "Click Here", command = window_one)
button1.pack()
class WindowOne(tk.Toplevel):
def __init__(self, master= None, *args, **kwargs):
super().__init__(master = master)
self.create_widgets()
def create_widgets(self):
button1 = ttk.Button(self, text = "Click Here", command = window_two)
button1.pack()
class WindowTwo(tk.Toplevel):
def __init__(self, master= None, *args, **kwargs):
super().__init__(master = master)
self.create_widgets()
def create_widgets(self):
button1 = ttk.Button(self, text = "$$$")
button1.pack()
if __name__ == "__main__":
main()
How can I code this to make Window Two dependent on Window One so that if I close "Window One", "Window Two" also closes, mimicking the behaviour of the main root window?
You can make Window One the parent of Window Two:
def window_two(parent):
window_two = WindowTwo(parent) # pass parent to WindowTwo
window_two.geometry("200x100")
window_two.title('Window Two')
...
class WindowOne(tk.Toplevel):
def __init__(self, master= None, *args, **kwargs):
super().__init__(master = master)
self.create_widgets()
def create_widgets(self):
button1 = ttk.Button(self, text = "Click Here", command = lambda: window_two(self)) # pass self to `window_two()`
button1.pack()
Update: if you want to close Window One when Window Two is closed, you can use self.protocol("WM_DELETE_WINDOW", ...):
class WindowTwo(tk.Toplevel):
def __init__(self, master= None, *args, **kwargs):
super().__init__(master = master)
self.create_widgets()
self.protocol("WM_DELETE_WINDOW", self.on_destroy)
def create_widgets(self):
button1 = ttk.Button(self, text = "$$$")
button1.pack()
def on_destroy(self):
self.master.destroy() # close parent window
self.destroy() # close itself
or bind <Destroy> event to a callback to close the parent window:
class WindowTwo(tk.Toplevel):
def __init__(self, master= None, *args, **kwargs):
super().__init__(master = master)
self.create_widgets()
self.bind("<Destroy>", self.on_destroy)
def create_widgets(self):
button1 = ttk.Button(self, text = "$$$")
button1.pack()
def on_destroy(self, event):
self.master.destroy() # close parent window
I'm aware of how to create a basic Tkinter menu bar, but I'm not sure how to implement it such that the menu appears on every frame of a multi-frame GUI.
I will be using the menu bar to switch between frames. Therefore, I need to run the controller.show_frame command within the menu commands. I am currently using buttons to do this.
I'm unable to find a way to do this, as (as far as I am aware) the menu must be created in the frame class rather than the tk.Tk class, in order to allow me to run the function.
Here is the code:
""" Messing about with tkinter """
import tkinter as tk
LARGE_FONT = ("Verdana", 12)
class Window(tk.Tk):
""" Main class """
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
container = tk.Frame(self)
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
for frame in (Main, Checker):
current_frame = frame(container, self)
self.frames[frame] = current_frame
current_frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(Main)
def show_frame(self, cont):
""" Raises a particular frame, bringing it into view """
frame = self.frames[cont]
frame.tkraise()
def qprint(quick_print):
""" Function to print a string """
print(quick_print)
class Main(tk.Frame):
""" Main frame of program """
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="Main Menu", font=LARGE_FONT)
label.pack(pady=10, padx=10)
class Checker(tk.Frame):
""" Password Strength Checker """
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="Password Checker", font=LARGE_FONT)
label.pack(pady=10, padx=10)
APP = Window()
APP.geometry("350x200")
APP.mainloop()
"the menu must be created in the frame class rather than the tk.Tk class, in order to allow me to run the
function."
I don't think that's true, see below example that creates Menu for a Toplevel widget:
import tkinter as tk
if __name__ == '__main__':
root = tk.Tk()
root.withdraw()
toplevel = tk.Toplevel(root)
# create a toplevel menu
menubar = tk.Menu(toplevel)
menubar.add_command(label="Hello!")
menubar.add_command(label="Quit!", command=root.quit)
# display the menu
toplevel.config(menu=menubar)
root.mainloop()
Alternatively, you can create menu's in frames for their parents with the condition that their parent is Toplevel-like.
In below example when a menu item is selected Root's menu jumps between the Root's menu and its children FrameWithMenu object's menu:
import tkinter as tk
class Root(tk.Tk):
def __init__(self):
super().__init__()
self.title("The Root class with menu")
self.a_frame = FrameWithMenu(self)
self.create_menu()
def create_menu(self):
self.menubar = tk.Menu(self)
self.menubar.add_command(label="Root", command=self.a_frame.replace_menu)
self['menu'] = self.menubar
class FrameWithMenu(tk.Frame):
def __init__(self, master):
super().__init__(master)
def replace_menu(self):
""" Overwrite parent's menu if parent's class name is in _valid_cls_names.
"""
_parent_cls_name = type(self.master).__name__
_valid_cls_names = ("Tk", "Toplevel", "Root")
if _parent_cls_name in _valid_cls_names:
self.menubar = tk.Menu(self)
self.menubar.add_command(label="Frame", command=self.master.create_menu)
self.master['menu'] = self.menubar
if __name__ == '__main__':
root = Root()
root.mainloop()
I found an answer to my own question. All I needed to do was create the menu using the controller parameter, which references the tk.Tk class.
class Main(tk.Frame):
""" Main frame of program """
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="Main Menu", font=LARGE_FONT)
label.pack(pady=10, padx=10)
menubar = tk.Menu(controller)
menubar.add_command(label="Checker", command=lambda: controller.show_frame(Checker))
controller.config(menu=menubar)
I have a Tkinter app in which I would like to include some buttons in a frame, and then place this frame in the main window.
However running the code returns just an empty window. So I guess I miss completely how to build a Tkinter app with modular classes.. The atomic code is:
import Tkinter as tk
class MainApplication(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
self.navbar = NavBar(self)
self.navbar.grid(row=0, column=0)
class NavBar(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.parent = parent
self.fetchDataBtn = tk.Button(self, text='Fetch data')
self.filterDataBtn = tk.Button(self, text='Filter data')
self.fetchDataBtn.pack(padx=5, pady=10, side=tk.LEFT)
self.filterDataBtn.pack(padx=5, pady=20, side=tk.LEFT)
def main():
root = tk.Tk()
app = MainApplication(root)
root.mainloop()
if __name__ == '__main__':
main()
I thus wonder what I miss. I searched but cannot find duplicates ..(if they are some, you can point out and I will close the topic).
NB: I am using Python 2.7.10
The problem is that you don't pack (or grid or place) your MainApplication instance.
Since your MainApplication extends the tk.Frame class, its instances are widgets, and thus need to be packed into their master.
def main():
root = tk.Tk()
app = MainApplication(root) <--- here: where does it go in the root?
root.mainloop()
Pack it and it will work:
app.pack()
You must put the navbar in the parent frame, using pack, or grid:
import Tkinter as tk
class MainApplication(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
self.navbar = NavBar(self)
self.navbar.grid(row=0, column=0)
self.pack() # <-- here ---------
class NavBar(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.parent = parent
self.fetchDataBtn = tk.Button(self, text='Fetch data')
self.filterDataBtn = tk.Button(self, text='Filter data')
self.fetchDataBtn.pack(padx=5, pady=10, side=tk.LEFT)
self.filterDataBtn.pack(padx=5, pady=20, side=tk.LEFT)
def main():
root = tk.Tk()
app = MainApplication(root)
# app.pack() # <-- or here for a better control of the placement of several instances
root.mainloop()
if __name__ == '__main__':
main()
Credits to #RightLeg for pointing out an initial mistake.