Why can't I properly switch to a new frame using TKinter? - python

I am using a container class in TKinter to start with a Menu screen and be able to switch to one of two other screens when necessary. It isn't quite working correctly and I'm not sure why.
Here is what my main menu screen looks like:
http://i62.tinypic.com/2nhoju0.jpg (won't let me post images yet)
When I click New Game, it looks like this:
http://i57.tinypic.com/x3tglg.jpg (won't let me post images yet)
Clearly, the new game and continue buttons are not supposed to be there, and there should only be one quit button. There should also be a label at the top kinda like the main menu screen.
Can anyone tell me why this is happening?
Here is the relevant code:
class Battleship():
def __init__(self, root):
self.root = root
self.pages = {}
container = Frame(root)
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
for P in (MainMenu, ShipSelect, BattleScreen):
frame = P(container, self)
frame.grid(row=0, column=0, sticky="nsew")
self.pages[P] = frame
self.show_frame(MainMenu)
def show_frame(self, cont):
frame = self.pages[cont]
frame.tkraise()
class MainMenu(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
label = Label(self, text="Welcome to PyBattleship!", font=LARGE_FONT)
label.pack(pady=10,padx=10)
button1 = Button(root, text='New Game', width=13, command=lambda :controller.show_frame(ShipSelect))
button1.pack()
button2 = Button(root, text='Continue Game', width=13)
button2.pack()
button3 = Button(root, text='Quit', width=13, command=lambda controller=controller:controller.root.destroy())
button3.pack()
class ShipSelect(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
self.root = root
label = Label(self, text="Please place your ships", font=LARGE_FONT)
button1 = Button(self, text='Quit', width=13, command=lambda controller=controller:controller.root.destroy())
button1.pack()
root = Tk()
root.title('Py-Battleship')
sheet = Battleship(root)
root.mainloop()

I just realized that I was still using 'root' for the button widgets in MainMenu. When I changed the 'roots' to 'self' and added padding to the label in ShipSelect, it seems to work.
class MainMenu(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
label = Label(self, text="Welcome to PyBattleship!", font=LARGE_FONT)
label.pack(pady=10,padx=10)
button1 = Button(self, text='New Game', width=13, command=lambda controller=controller:controller.show_frame(ShipSelect))
button1.pack()
button2 = Button(self, text='Continue Game', width=13)
button2.pack()
button3 = Button(self, text='Quit', width=13, command=lambda controller=controller:controller.root.destroy())
button3.pack()
class ShipSelect(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
label = Label(self, text="Please place your ships", font=LARGE_FONT)
label.pack(pady=10,padx=10)
button1 = Button(self, text='Quit', width=13, command=lambda controller=controller:controller.root.destroy())
button1.pack()

Using a mixture of grid and pack in tkinter is never a good idea, which is one of the reasons your code might be having unpredictable results. Secondly, you are trying to pack the buttons to the root window, rather than one of the frames you have created.

Related

How to add background image in Tkinter using OOP concept?

I am new to object oriented python programming. Currently I am using Python 3. I want to create an app which has multiple pages. I am able to navigate between pages but find it difficult to add image in the background.
Please note I don't want to know how to background image in tkinter as I am already able to do it based on the following code.
bg = PhotoImage(file="images\\bg.png")
label_bgImage = Label(master, image=bg)
label_bgImage.place(x=0, y=0)
I want to know how to add background image to pages when you are defining each window as a class. I put the code to insert background image in the __init__() method of class ABCApp. When I tried to insert the code for adding background image to my existing code, it stopped showing the labels and buttons and now just shows the window.
The following code was my attempt to add an image as background.
import tkinter as tk
from tkinter import ttk
from tkinter import *
class ABCApp(tk.Tk):
def __init__(self,*args,**kwargs):
tk.Tk.__init__(self,*args,**kwargs)
self.geometry("1500x750")
main_frame = tk.Frame(self)
main_frame.pack(side = 'top',fill = 'both',expand ='True')
main_frame.grid_rowconfigure(0,weight=1)
main_frame.grid_columnconfigure(0,weight=1)
bg = PhotoImage(file="images\\bg.png")
label_bgImage = Label(self, image=bg)
label_bgImage.place(x=0, y=0)
self.frames = {}
for F in (HomePage,PageOne,PageTwo):
frame = F(main_frame, self)
self.frames[F] = frame
frame.grid(row=0,column=0,sticky='nsew')
self.show_frame(HomePage)
def show_frame(self,container):
frame = self.frames[container]
frame.tkraise()
class HomePage(tk.Frame):
def __init__(self,parent,controller):
tk.Frame.__init__(self,parent)
label = ttk.Label(self,text='Home Page',font =("Helvetica",20))
label.pack(padx=10,pady=10)
button1 = ttk.Button(self,text = "Page 1",command = lambda: controller.show_frame(PageOne))
button1.pack()
button6 = ttk.Button(self, text="Page 2", command=lambda: controller.show_frame(PageTwo))
button6.pack()
class PageOne(tk.Frame):
def __init__(self,parent,controller):
tk.Frame.__init__(self, parent)
label1 = ttk.Label(self, text='Page 1', font=("Helvetica", 20))
label1.pack(padx=10, pady=10)
button2 = ttk.Button(self, text="Back", command=lambda: controller.show_frame(HomePage))
button2.pack()
button5 = ttk.Button(self, text="Page 2", command=lambda: controller.show_frame(PageTwo))
button5.pack()
class PageTwo(tk.Frame):
def __init__(self,parent,controller):
tk.Frame.__init__(self, parent)
label2 = ttk.Label(self, text='Page 2', font=("Helvetica", 20))
label2.pack(padx=10, pady=10)
button3 = ttk.Button(self, text="Back", command=lambda: controller.show_frame(HomePage))
button3.pack()
button4 = ttk.Button(self, text="Page 1", command=lambda: controller.show_frame(PageOne))
button4.pack()
app = ABCApp()
app.mainloop()
I would like to have the same or different image to appear as background of each page. Either way I am satisfied.
Since the background image is the same on all the Page classes, an object-oriented way to do it would be to define a base class with the background image on it and then derive all of the concrete Page classes from that instead of a plain tk.Frame. Afterwards, each subclass will need to call its base class' __init__() method before adding whatever widgets are unique to it. In the code below, this new base class is the one named BasePage.
To avoid loading a separate copy of the image file for each BasePage subclass instance, it's only done once and saved as an attribute of the ABCApp class (which is the controller argument being passed to the constructor of each BasePage subclass). Because each page class completely covers-up all the others when it's made visible, each one does need to create its own Label with the background image on it.
Below shows what I mean. (Note I've made the code more PEP 8 - Style Guide for Python Code compliant than what's in your question).
import tkinter as tk
from tkinter.constants import *
from tkinter import ttk
class ABCApp(tk.Tk):
BKGR_IMAGE_PATH = '8-ball.png'
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.geometry("1500x750")
main_frame = tk.Frame(self)
main_frame.pack(side='top', fill='both', expand='True')
main_frame.grid_rowconfigure(0, weight=1)
main_frame.grid_columnconfigure(0, weight=1)
self.bkgr_image = tk.PhotoImage(file=self.BKGR_IMAGE_PATH)
self.frames = {}
for F in (HomePage, PageOne, PageTwo):
frame = F(main_frame, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky='nsew')
self.show_frame(HomePage)
def show_frame(self,container):
frame = self.frames[container]
frame.tkraise()
class BasePage(tk.Frame):
def __init__(self, parent, controller):
super().__init__(parent)
label_bkgr = tk.Label(self, image=controller.bkgr_image)
label_bkgr.place(relx=0.5, rely=0.5, anchor=CENTER) # Center label w/image.
class HomePage(BasePage):
def __init__(self, parent, controller):
super().__init__(parent, controller)
label = ttk.Label(self, text='Home Page', font =("Helvetica",20))
label.pack(padx=10, pady=10)
button1 = ttk.Button(self, text="Page 1",
command=lambda: controller.show_frame(PageOne))
button1.pack()
button6 = ttk.Button(self, text="Page 2",
command=lambda: controller.show_frame(PageTwo))
button6.pack()
class PageOne(BasePage):
def __init__(self,parent,controller):
super().__init__(parent, controller)
label1 = ttk.Label(self, text='Page 1', font=("Helvetica", 20))
label1.pack(padx=10, pady=10)
button2 = ttk.Button(self, text="Back",
command=lambda: controller.show_frame(HomePage))
button2.pack()
button5 = ttk.Button(self, text="Page 2",
command=lambda: controller.show_frame(PageTwo))
button5.pack()
class PageTwo(BasePage):
def __init__(self, parent, controller):
super().__init__(parent, controller)
label2 = ttk.Label(self, text='Page 2', font=("Helvetica", 20))
label2.pack(padx=10, pady=10)
button3 = ttk.Button(self, text="Back",
command=lambda: controller.show_frame(HomePage))
button3.pack()
button4 = ttk.Button(self, text="Page 1",
command=lambda: controller.show_frame(PageOne))
button4.pack()
app = ABCApp()
app.mainloop()
Also note that if you wanted each Page class to have a different background image, you would do things a little differently — i.e. each page would become responsible for loading its own image. To do that, the call to the BasePage constructor would need to be passed the name of the image file to be used (as an additional argument in the super().__init__() statement).

Issues with .grid in Tkinter

I am VERY new to coding/Python, but basically I am trying to move a button and label around using .grid, however, the button and label in the StartPage class just won't move to where I ask (or even at all).
Everything in the BMR class works fine (although the positions you see aren't the final positions, I was just checking).
What is the difference? Why do they not appear at the same position if I give the same details in both classes?
import tkinter as tk
class initials(tk.Tk):
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 F in (StartPage, BMR):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky='nsew')
self.show_frame(StartPage)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
class StartPage(tk.Frame): #GRID WON'T WORK HOW I WANT IT TO
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="Start Page")
label.grid(column=3, row=3, sticky='we')
button = tk.Button(self, text="Calculate BMR",
command=lambda: controller.show_frame(BMR))
button.grid(row=4, column=3, sticky='we')
class BMR(tk.Frame): #GRID WORKS PERFECTLY
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="BMR Calculator")
label.grid(column=1,row=1)
button1 = tk.Button(self, text="Back to Home",
command=lambda: controller.show_frame(StartPage))
button1.grid(column=2, row=2)
submit = tk.Button(self, text="Calculate")
submit.grid(column=3, row=3)
var1 = tk.IntVar()
tk.Checkbutton(self, text='Male', bg='white', variable=var1).grid(column=4, row=4)
var2= tk.IntVar()
tk.Checkbutton(self, text='Female', bg='white', variable=var2).grid(column=5, row=5)
height_inp = tk.Entry(self, width=20, bg="white").grid(column=6, row=6)
app = initials()
app.mainloop()
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="Start Page", width = 80)
# Added width property in the line above
# and changed sticky property to N
label.grid(row = 3, column=3, sticky = 'N')
label.width = 20
button = tk.Button(self, text="Calculate BMR",
command=lambda: controller.show_frame(BMR))
button.grid(row=4, column=3)
# Removed sticky property for the button
I understand this is how you wish to position the label and the button.
Pleaase see the comments. You can edit the value for the width property and make it suitable for your frame.

How to add a scrollbar that orientes its position on the top most widget

I have a tkinter app in which I have a main canvas with multiple pages (all of which are frames). I pull up the different pages by rasing them with the frame.tkraise() command. I now want to add a scrollbar to the whole thing. The scrollbar appears but without a slider and I am not sure if it cna recognize the change of page.
import tkinter as tk
from tkinter import ttk
class Economics(tk.Tk):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
tk.Tk.columnconfigure(self, 0, weight=1)
tk.Tk.rowconfigure(self, 0, weight=1)
self.container = tk.Canvas()
self.container.grid(row=0, column=0, sticky="nsew")
self.container.columnconfigure("all", weight=1)
self.container.rowconfigure("all", weight=1)
self.vscrollbar = tk.Scrollbar(orient="vertical", command=self.container.yview)
self.container.configure(yscrollcomman=self.vscrollbar.set)
self.vscrollbar.grid(row=0, column=1, sticky="ns")
self.frames = {}
for F in (StartPage, ExamplePage1, ExamplePage2): # TUPLE OF PAGES
frame = F(self.container, self)
self.frames[F] = frame
self.show_frame(StartPage)
def show_frame(self, cont):
self.container.delete("all")
frame = self.frames[cont]
self.container.create_window(0, 0, anchor="nw", window=frame)
class StartPage(tk.Frame):
def __init__(self, parent, controller):
super().__init__(parent)
button_1 = ttk.Button(self, text="Example Page 1",
command=lambda: controller.show_frame(ExamplePage1))
button_1.grid(row=0, column=0)
button_2 = ttk.Button(self, text="Example Page 2",
command=lambda: controller.show_frame(ExamplePage2))
button_2.grid(row=1, column=0)
class ExamplePage1(tk.Frame):
def __init__(self, parent, controller):
super().__init__(parent)
for i in range(50):
label = tk.Label(self, text="Button {} of 50".format(i+1))
label.grid(row=i, column=0)
button_back = ttk.Button(self, text="Back",
command=lambda: controller.show_frame(StartPage))
button_back.grid(row=0, column=1)
class ExamplePage2(tk.Frame):
def __init__(self, parent, controller):
super().__init__(parent)
for i in range(35):
label = tk.Label(self, text="Button {} of 35".format(i+1))
label.grid(row=i, column=0)
button_back = ttk.Button(self, text="Back",
command=lambda: controller.show_frame(StartPage))
button_back.grid(row=0, column=1)
app = Economics()
app.geometry("800x600")
app.resizable(True, True)
app.mainloop()
In this example file you can see the basic structure of my app with some example widgets and buttons. The scrollbar shows but without the slider. What do I have to change to get a working scrollbar for all pages.
Later on I'm planning to get a horizontal scrollbar as well.
You can't scroll items added to a canvas with pack, place, or grid. A canvas can only scroll items added via the canvas create_* functions, such as create_window.

How to change page through my menu bar methods in tkinter?

Basically what I'm trying to figure out is, how do I make my menu bar "buttons" change the page?
I've already made the dropdown menu, and the classes with each page. The thing is it works if I create a separate button to change the page, but not when i'm trying to do it through the menu bar?
every tutorial I find is through the buttons, and only the buttons..
I am using python 3.6, with tkinter.
import tkinter as tk
class MyApp(tk.Tk):
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 = {}
menu = tk.Menu(container)
betting = tk.Menu(menu, tearoff=0)
menu.add_cascade(menu=betting, label="Pages")
betting.add_command(label="PageOne",
command=lambda: controller.show_frame(PageOne))
betting.add_command(label="PageTwo",
command=lambda: controller.show_frame(PageTwo))
tk.Tk.config(self, menu=menu)
for F in (Startpage, PageOne, PageTwo):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(Startpage)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
class Startpage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="Home")
label.pack(pady=10, padx=10)
button1 = tk.Button(self, text="page 1",
command=lambda: controller.show_frame(PageOne))
button1.pack()
button2 = tk.Button(self, text="Page Two",
command=lambda: controller.show_frame(PageTwo))
button2.pack()
# ***** PAGES *****
class PageOne(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="Back to Home")
label.pack(pady=10, padx=10)
button1 = tk.Button(self, text="Back to Home",
command=lambda: controller.show_frame(Startpage))
button1.pack()
button2 = tk.Button(self, text="Page Two",
command=lambda: controller.show_frame(PageTwo))
button2.pack()
class PageTwo(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="Page Two")
label.pack(pady=10, padx=10)
button1 = tk.Button(self, text="Back to Home",
command=lambda: controller.show_frame(Startpage))
button1.pack()
button2 = tk.Button(self, text="Page One",
command=lambda: controller.show_frame(PageOne))
button2.pack()
app = MyApp()
app.geometry("1200x600")
app.mainloop()
Now I know, I haven't parsed the "controller & parent", right now, but I've tried and tried, and nothing works for me...
The controller is MyApp. Within the definition of MyApp that makes controller be self. Thus, you need to call self.show_frame rather than controller.show_frame.
betting.add_command(label="PageOne",
command=lambda: self.show_frame(PageOne))
betting.add_command(label="PageTwo",
command=lambda: self.show_frame(PageTwo))

Object Orientated Tkinter Functions - How would I put this in?

I am coding a program which will need functions to change labels and enter text into text boxes however I do not know how to do this with a new style of programming which I am using (Object Orientated). I have done the program before however I generated the frames using this code:
f = [Frame(root) for i in range(0,5)]
for i in f:
i.place(relx=0,rely=0,relwidth=1,relheight=1)
and then I put it all in one class which I ran however that was bad form so I am redoing it. My code so far is as follows:
import tkinter as tk
import datetime,time,os,sys
import sqlite3 as lite
from datetime import date
Title_Font= ("Times", 18, "underline italic")
unix = time.time()
time_curr = str(datetime.datetime.fromtimestamp(unix).strftime('%H:%M'))
date1 = str(datetime.datetime.fromtimestamp(unix).strftime('%d-%m-%Y'))
class Creating_Stuff(tk.Tk):
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 F in (StartPage, PageOne, PageTwo):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(StartPage)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self,parent)
self.grid_columnconfigure(0, weight=1)
self.grid_columnconfigure(3, weight=1)
self.option_add( "*font", "Times 12" )
self.tk_setPalette(background='#bbfff0', foreground='black',
activeBackground='#d9d9d9', activeForeground='#ff9933')
label = tk.Label(self, text="Laptop Booking System", font=Title_Font)
label.grid(row=0, column=1, pady = 30)
time = tk.Label(self, text="Time: " + time_curr + "\nDate: " + date1, font="Times 10")
time.grid(row=0, column=2,columnspan=2)
Booking_1 = tk.Button(self, text="Booking",
command=lambda: controller.show_frame(PageOne),bg='#f2f2f2',width=20)
Booking_1.grid(row=2, column=1, pady=10)
Tbl_q1 = tk.Button(self, text="Table Querying",
command=lambda: controller.show_frame(PageTwo),bg='#f2f2f2',width=20)
Tbl_q1.grid(row=3, column=1, pady=10)
Exit = tk.Button(self, text ="Exit",command=lambda:destroy(),bg='#f2f2f2')
Exit.grid(row=5, column=1)
class PageOne(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.grid_columnconfigure(0, weight=1)
self.grid_columnconfigure(3, weight=1)
label = tk.Label(self, text="Page One!!!", font=Title_Font)
label.grid(row=1, column=1)
bk2_menu = tk.Button(self, text="Back to Home",
command=lambda: controller.show_frame(StartPage))
bk2_menu.grid(row=3, column=1)
class PageTwo(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.grid_columnconfigure(0, weight=1)
self.grid_columnconfigure(3, weight=1)
label = tk.Label(self, text="Page Two!!!", font=Title_Font)
label.grid(row=1, column=1)
bk2_Menu2 = tk.Button(self, text="Back to menu",
command=lambda: controller.show_frame(StartPage))
bk2_Menu2.grid(row=3, column=1)
app = Creating_Stuff()
def destroy():
app.destroy()
app.title("Laptop Booking System")
app.geometry("700x400")
app.mainloop()
If you try it out it works however it just has 3 different frames. How can I get a button in a frame to make a label say "Hello" in the frame after it is pressed?
I've modified one of your page classes to illustrate how it could be done. It involved adding a Label to hold the message, a Button to control it, and a function, called simply handler(), to call when the latter is pressed. It saves the widgets by making them attributes of the containing Frame subclass instance, self, so they can be easily referenced in the handler() function (without resorting to global variables).
class PageOne(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.grid_columnconfigure(0, weight=1)
self.grid_columnconfigure(3, weight=1)
label = tk.Label(self, text="Page One!!!", font=Title_Font)
label.grid(row=1, column=1)
self.msg_label = tk.Label(self, text="")
self.msg_label.grid(row=2, column=1)
self.msg_button = tk.Button(self, text='Show Message',
command=self.handler)
self.msg_button.grid(row=3, column=1)
self.msg_toggle = False
bk2_menu = tk.Button(self, text="Back to Home",
command=lambda: controller.show_frame(StartPage))
bk2_menu.grid(row=5, column=1)
def handler(self):
self.msg_toggle = not self.msg_toggle
if self.msg_toggle:
self.msg_label.config(text='Hello')
self.msg_button.config(text='Clear Message')
else:
self.msg_label.config(text='')
self.msg_button.config(text='Show Message')
Screenshots
Before button is pressed:
After button is pressed:

Categories

Resources