Arranging size of a frame in tkinter - python-3.x

I am making a simple toolbox for my app. I have used the class method, which inherits Frame as its super class. In my main file I import this class.
It will be a main window which all widgets will be in it. But there is a problem, here is the source code:
from tkinter import *
class ToolBox(Frame):
def __init__(self, master=None,
width=100, height=300):
Frame.__init__(self, master,
width=100, height=300)
self.pack()
Button(self, text="B").grid(row=0, sticky=(N,E,W,S))
Button(self, text="B").grid(row=0, column=1, sticky=(N,E,W,S))
Button(self, text="B").grid(row=1, column=0,sticky=(N,E,W,S))
Button(self, text="B").grid(row=1, column=1, sticky=(N,E,W,S))
Button(self, text="B").grid(row=2, column=0, sticky=(N,E,W,S))
Button(self, text="B").grid(row=2, column=1, sticky=(N,E,W,S))
I import this in here:
from tkinter import *
import toolbox as tl
root = Tk()
frame = Frame(root, width=400, height=400)
frame.pack()
tl.ToolBox(frame).pack()
root.mainloop()
Main window, which is the root who has the frame, must be 400 in widht and height. But it appears in dimensions of my toolbox. I want the toolbox to be in the main window. How can I solve this?

You can force the root window to have specific dimensions using the geometry method.
root = Tk()
root.geometry("400x400")
If you would also like the buttons to stretch evenly to fill the whole root window, you need to do two things:
Call rowconfigure and columnconfigure to set the weight of the root and each frame that is a parent of your buttons.
specify the sticky parameter for every button and frame that is a child of your root.
Here's an example. I removed your frame Frame, since it didn't seem to be doing anything. Toolbox is already a frame, after all, and there's not much point putting a frame inside a frame.
from tkinter import *
class ToolBox(Frame):
def __init__(self, master=None,
width=100, height=300):
Frame.__init__(self, master,
width=width, height=height)
for i in range(2):
self.grid_columnconfigure(i, weight=1)
for j in range(3):
self.grid_rowconfigure(j, weight=1)
Button(self, text="B").grid(row=0, sticky=(N,E,W,S))
Button(self, text="B").grid(row=0, column=1, sticky=(N,E,W,S))
Button(self, text="B").grid(row=1, column=0,sticky=(N,E,W,S))
Button(self, text="B").grid(row=1, column=1, sticky=(N,E,W,S))
Button(self, text="B").grid(row=2, column=0, sticky=(N,E,W,S))
Button(self, text="B").grid(row=2, column=1, sticky=(N,E,W,S))
root = Tk()
root.geometry("400x400")
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
ToolBox(root).grid(sticky="news")
root.mainloop()
Now your root is properly sized, and your buttons stretch to fill it.

Related

Trouble Binding a Button in a Class - Python (Tkinter)

Just started learning Tkinter and was hoping someone could help me. I've been trying to bind a keyboard character (Enter button) to a tk button following this example and not getting anywhere.
Say I take the button (Enter) and try bind it nothing happens:
Enter.bind('<Return>', lambda:self.retrieve_Input(t))
If I bind to self instead using Lambda nothing happens also. I can get it to trigger if I remove the lambda but that's not the desired outcome
self.bind('<Return>', lambda:self.retrieve_Input(t))
My Code:
import sys
import tkinter as tk
from tkinter import ttk
class windows(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.wm_title("Test Application")
self.lift() #Bringing the GUI to the front of the screen
main_frame = tk.Frame(self, height=400, width=600) #Creating a main Frame for all pages
main_frame.pack(side="top", fill="both", expand=True)
main_frame.grid_rowconfigure(0, weight=1) #Configuring the location of the main frame using grid
main_frame.grid_columnconfigure(0, weight=1)
# We will now create a dictionary of frames
self.frames = {}
for F in (MainPage, CompletionScreen): #Add the page components to the dictionary.
page = F(main_frame, self)
self.frames[F] = page #The windows class acts as the root window for the frames.
page.grid(row=0, column=0, sticky="nsew")
self.show_page(MainPage) #Method to switch Pages
def show_page(self, cont):
frame = self.frames[cont]
frame.tkraise()
##########################################################################
class MainPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
#switch_window_button = tk.Button(self, text="Go to the Side Page", command=lambda: controller.show_page(SidePage))
#switch_window_button.pack(side="bottom", fill=tk.X)
tk.Label(self, text="Project Python Search Engine", bg='white').pack()
tk.Label(self, text="", bg='white').pack()
tk.Label(self, text="Song", bg='white').pack()
tk.Label(self, text="", bg='white').pack()
t = tk.Entry(self, bg='white', width = 50)
t.pack()
tk.Label(self, text="", bg='white').pack()
Enter = tk.Button(self, text='Search', command= lambda:self.retrieve_Input(t))
Enter.pack()
tk.Button(self, text="Latest Popular Songs", command=lambda:self.Popular_Songs(t)).pack() #Line 210 onwards
Enter.bind('<Return>', lambda:self.retrieve_Input(t))
def retrieve_Input(self, t):
print ("work")
print (t)
class CompletionScreen(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="Completion Screen, we did it!")
label.pack(padx=10, pady=10)
switch_window_button = ttk.Button(
self, text="Return to menu", command=lambda: controller.show_page(MainPage)
)
switch_window_button.pack(side="bottom", fill=tk.X)
if __name__ == "__main__":
App = windows()
App.mainloop()
I'm not really sure what I'm missing
Answer: The button probably doesn't have the keyboard focus. When I run your code and then use the keyboard to move the focus to the button, your binding works. You probably want to bind to the entry widget rather than the button since that's what will have the keyboard focus. – Thanks Bryan Oakley

How to create a class to close GUI and exit Python

I have been trying to find a way to do this for a while to no avail. I would like to create a class to completely close my GUI in tkinter and I'm not having much luck. I've tried sys.exit and .destroy() a few different ways. I can manage to do what I want without using classes but I'm rather new to OOP. Here is my code:
import sys as system
import tkinter as tk
from tkinter import ttk
class headerFrame(ttk.Frame):
def __init__(self, container):
super().__init__(container)
#setup the grid layout manager
self.columnconfigure(0, weight=1)
self._create_widgets()
def _create_widgets(self):
#header bar
canvas = tk.Canvas(self, bg='#0066cc', highlightthickness=0, height=45, width=600)
canvas.grid(column=0, row=0, sticky=tk.W)
label = ttk.Label(self, text='Production Assistant', background='#0066cc', foreground='White', font=('calibri', 18, 'bold'))
label.grid(row=0, column=0)
class loginFrame(ttk.Frame):
def __init__(self, container):
super().__init__(container)
#setup the grid layout manager
self.columnconfigure(0, weight =1)
self.columnconfigure(0, weight=3)
self._create_widgets()
def _create_widgets(self):
#username
ttk.Label(self, text='Username: ', justify='right').grid(row=0, column=0, sticky=tk.E)
username = ttk.Entry(self, width=33)
username.focus()
username.grid(row=0, column=1, sticky=tk.W)
#password
ttk.Label(self, text='Password: ', justify='right').grid(row=1, column=0, sticky=tk.E)
password = ttk.Entry(self, width=33, show='*')
password.grid(row=1, column=1, sticky=tk.W)
#add padding
for widget in self.winfo_children():
widget.grid(padx=0, pady=5)
class loginButtonFrame(ttk.Frame):
def __init__(self, container):
super().__init__(container)
#setup the grid layout manager
self.columnconfigure(0, minsize=62)
self._create_widgets()
def _create_widgets(self):
#buttons
ttk.Button(self, text='Login', width=15).grid(row=0, column=1)
ttk.Button(self, text='Forgot Login', width=15).grid(row=0, column=2)
ttk.Button(self, text='Request Access', width=15).grid(row=1, column=1)
ttk.Button(self, text='Exit', width=15, command=exitButton).grid(row=1, column=2)
#add padding to buttons
for widget in self.winfo_children():
widget.grid(padx=3, pady=3)
class exitButton():
def exit():
#code to close gui and program
#create the main application
class mainLogin(tk.Tk):
def __init__(self):
super().__init__()
self.title('Login')
self.geometry('325x175')
self.resizable(0, 0)
self.configure(background='#444444')
#windows only (remove the minimize/maximize buttons)
self.attributes('-toolwindow', True)
#TCL to center the screen
self.eval('tk::PlaceWindow . center')
#layout on the root window
self.columnconfigure(0, weight=1)
self._create_Styles()
self._create_widgets()
def _create_Styles(self):
#create styles
s = ttk.Style()
s.configure('TFrame', background='#444444')
s.configure('TLabel', background='#444444', foreground='white')
s.configure('TButton', background='#878683', foreground='black')
def _create_widgets(self):
#create the header frame
_header_frame = headerFrame(self)
_header_frame.grid(column=0, row=0)
#create the login frame
_login_frame = loginFrame(self)
_login_frame.grid(column=0, row=1, sticky=tk.N)
#create the button frame
_login_button_frame = loginButtonFrame(self)
_login_button_frame.grid(column=0, row=2)
if __name__ == '__main__':
app = mainLogin()
app.mainloop()
class exitButton() is what I would like to call from multiple different pages in the application to close everything.
Any help is appreciated, I'm trying to learn as I build so if you have any suggested reading based around Python that would help with this I would appreciate it!

How am I suppose to get the width and height of a Frame in Python

In my python code, I am trying to make the width of my button the same as the width of the Frame it is in. The Frame's width changes when the window is resized. I tried Widget['width'] and Widget.winfo_width() but both of them give me errors.
My code:
from tkinter import *
root = Tk()
root.geometry('750x500')
root.minsize(750, 500)
# Frames
Screen = Frame(root, height=500, width=500, bg='pink').grid(row=0, column=0, sticky='nsew', rowspan=4)
root.grid_columnconfigure(0, weight=3)
Buttons = Frame(root, height=500, width=100, bg="blue").grid(row=0, column=1, sticky='nsew', rowspan=4, columnspan=2)
root.grid_columnconfigure(2, weight=1)
root.grid_rowconfigure(3, weight=1)
# Buttons
UpgradeBtn = Button(Buttons, text="Upgrades")
UpgradeBtn.grid(row=0, column=1, columnspan=2)
WallBreakBtn = Button(Buttons, text="Wall Breaking")
WallBreakBtn.grid(row=1, column=1, columnspan=2)
root.mainloop()
Could you help me?
The .grid method returns None so Screen and Buttons in your original code were both None, not widgets. Running .grid in a second statement means Screen or Buttons will have the winfo_XXX methods.
from tkinter import *
root = Tk()
root.geometry('750x500')
root.minsize(750, 500)
# Frames
Screen = Frame(root, height=500, width=500, bg='pink')
Screen.grid(row=0, column=0, sticky='nsew', rowspan=4) # Change
root.grid_columnconfigure(0, weight=3)
Buttons = Frame(root, height=500, width=100, bg="blue")
Buttons.grid(row=0, column=1, sticky='nsew', rowspan=4, columnspan=2) # Change
root.grid_columnconfigure(2, weight=1)
root.grid_rowconfigure(3, weight=1)
# New function
def on_upgrade():
print(Screen.winfo_width(), Screen.winfo_height())
# Buttons
UpgradeBtn = Button(Buttons, text=" w x h ", command = on_upgrade ) # Change
UpgradeBtn.grid(row=0, column=1, columnspan=2)
WallBreakBtn = Button(Buttons, text="Wall Breaking")
WallBreakBtn.grid(row=1, column=1, columnspan=2)
root.mainloop()
This prints the width and height of Screen to the console.

Destroying label frames

I am tinkering around with basic things in Tkinter/python. I am using the LabelFrame that corresponds with several buttons I click. I am struggling with trying to destroy the other frames and only displaying the frame I want visible when clicking on a certain button. When I run this code:
def lblframe1_view():
lblframe3.destroy()
lblframe2.destroy()
lblframe1 = LabelFrame(root, text="This is Frame One", height=370, width=370, bd=5, relief=FLAT).place(x=215, y=20)
btn1 = Button(lblframe1, text="this is button one").pack()
def lblframe2_view():
lblframe1.destroy()
lblframe3.destroy()
lblframe2 = LabelFrame(root, text="Frame Two", height=370, width=370, bd=5, relief=FLAT).place(x=215, y=20)
btn2 = Button(lblframe2, text="this is button two").pack()
def lblframe3_view():
lblframe1.destroy()
lblframe2.destroy
lblframe3 = LabelFrame(root, text="this is frame 3", height=370, width=370, bd=5, relief=FLAT).place(x=215, y=20)
def exit():
root.destroy()
manbtn1 = Button(root, text="Frame 1", bg="white", height=4, width=25, command=lblframe1).place(x=10, y=20)
mainbtn2 = Button(root, text="Frame 2, bg="white", height=4, width=25, command=lblframe2).place(x=10, y=120)
mainbtn3 = Button(root, text="Frame 3", bg="white", height=4, width=25, command=lblframe3).place(x=10, y=220)
exitbtn = Button(root, text="Exit", bg="white", height=4, width=25, command=exit).place(x=10, y=320)
When I run this I get the following error:
NameError: name 'lblframe3' is not defined
I have tried to put the functions after buttons and got the same error. I have done research on this
and haven't been able to figure out why this keeps happening. My hunch is python
Im not a experienced python programmer myself but there are a few things i noticed that might help you out.
Please make sure the code you post here is tested, it is a bit annoying if i have to debug the code before i can help you in the first place. (referring to the " in third line from the bottom as well as the indentation).
Button(command) takes a function as a argument. You provided a Object. So try to switch the command to command = lblframe1_view instead of lblframe1.
Frames in tkinter are objects, therefore they can not be accessed from a function where they were not defined in, unless u declare them global or pass it in as a argument. What that means is if lblframe1_view() is called, it does not know what lblframe3 or lblframe2 are.
I hope this helped you out.
EDIT:
I just realised that there might be one more problem with the structure of your programm that can not be solved by declaring the frames as globals.
In lblframe1_view u want to destroy an object that does not even exist yet, since it only will be created if lblframe2_view() and lblframe3_view() have been executed beforehand. But those two functions will also throw an error, since lblframe1 is not created yet.
I was able to do some research and found the answer I was looking for: I credit sentdex from Youtube for this. He basically stacks the frames on top of one another and a button click actually calls up the frame based on the command.
from tkinter import *
import tkinter as tk
from tkinter import ttk
LARGE_FONT = ("verdana", 12)
class tool(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
tk.Tk.iconbitmap(self, default="supporticon.ico")
self.geometry("400x400")
self.resizable(0,0)
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):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
frame.config(bg="darkblue")
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)
homeframe = LabelFrame(self, text="testing this out", width=200, height=350)
homeframe.config(background="darkblue", fg="white")
homeframe.pack()
label = Label(homeframe, text="Start Page", font=LARGE_FONT)
label.config(background="darkblue", fg="white", height=400, width=450)
label.place(x=10, y=20)
button1 = ttk.Button(homeframe, text="Vist Page 1",
command=lambda: controller.show_frame(PageOne))
button1.pack()
class PageOne(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="Page One", font=LARGE_FONT)
label.pack(pady=10, padx=10)
button1 = tk.Button(self, text="Back to Home",
command=lambda: controller.show_frame(StartPage))
button1.pack()
app = tool()
app.resizabe=(0,0)
app.geometry=("450x450")
app.mainloop()

Resize columns in Frame with Scrollbar

Based on the example from here: Scrollbar in Tkinter grid
I made a simplified version which is more or less what i need except i would like the columns to always fill the width of the frame as the window is beeing resized.
Without the scrollbar it was super easy, i just added grid_columnconfigure and that worked out of the box, but when I added the scrollbar i couldn't figure out how to get the columns to resize again.
Here is the example:
import tkinter as tk
row = 1
class ProgramWindow(tk.Frame):
def __init__(self):
self.canvas = tk.Canvas(root, borderwidth=0, background="#ffffff")
tk.Frame.__init__(self, self.canvas)
self.grid(column=0, row=0, sticky='ESW')
self.grid_columnconfigure(0, weight=1)
self.grid_columnconfigure(1, weight=1)
self.grid_columnconfigure(2, weight=1)
tk.Label(self, text="FirstCol", ).grid(row=0, column=0)
tk.Label(self, text="SecndCol", ).grid(row=0, column=1)
tk.Label(self, text="ThirdCol", ).grid(row=0, column=3)
self.vsb = tk.Scrollbar(root, orient="vertical", command=self.canvas.yview)
self.canvas.configure(yscrollcommand=self.vsb.set)
self.vsb.pack(side="right", fill="y")
self.canvas.pack(side="left", fill="both", expand=True)
self.canvas.create_window((4, 4), window=self)
self.bind("<Configure>", self.OnFrameConfigure)
def OnFrameConfigure(self, event):
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
def addrow(self, stuff, otherstuff):
global row
var = tk.StringVar(value=stuff)
entry = tk.Entry(self, textvariable=var)
entry.grid(row=row, column=0)
var = tk.StringVar(value=otherstuff)
entry = tk.Entry(self, textvariable=var)
entry.grid(row=row, column=1)
var = tk.StringVar(value="foobar")
entry = tk.Entry(self, textvariable=var)
entry.grid(row=row, column=3)
row += 1
def SomeProg():
for i in range(20):
stuff = "Stuff is " + str(i)
otherstuff = i * 4
win.addrow(stuff, otherstuff)
root = tk.Tk()
root.title("Stuff")
win = ProgramWindow()
SomeProg()
root.mainloop()
I've adapted Bryan Oakley's answer to Adding a scrollbar to a group of widgets in Tkinter so that the frame contained in the canvas fit the width of the canvas.
Canvas window objects have a width option. So, each time the canvas is resized, I pass the new canvas width to this width option using
self.canvas.itemconfigure(<frame tag>, width=self.canvas.winfo_width())
Here is the full code
import tkinter as tk
class Example(tk.Frame):
def __init__(self, root):
tk.Frame.__init__(self, root)
self.canvas = tk.Canvas(root, borderwidth=0, background="#ffffff")
self.frame = tk.Frame(self.canvas, background="#ffffff")
self.frame.columnconfigure(0, weight=1)
self.frame.columnconfigure(1, weight=1)
self.frame.columnconfigure(2, weight=1)
self.vsb = tk.Scrollbar(root, orient="vertical", command=self.canvas.yview)
self.canvas.configure(yscrollcommand=self.vsb.set)
self.vsb.pack(side="right", fill="y")
self.canvas.pack(side="left", fill="both", expand=True)
self.canvas.create_window((4,4), window=self.frame, anchor="nw",
tags="self.frame")
self.canvas.bind("<Configure>", self.onCanvasConfigure)
self.populate()
def populate(self):
'''Put in some fake data'''
for i in range(100):
tk.Entry(self.frame).grid(row=i, column=0, sticky='ew')
tk.Entry(self.frame).grid(row=i, column=1, sticky='ew')
tk.Entry(self.frame).grid(row=i, column=2, sticky='ew')
def onCanvasConfigure(self, event):
self.canvas.itemconfigure("self.frame", width=self.canvas.winfo_width())
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
if __name__ == "__main__":
root=tk.Tk()
Example(root).pack(side="top", fill="both", expand=True)
root.mainloop()

Resources